Date: Thu, 12 Mar 2026 09:08:18 +0300
Subject: [PATCH 035/226] why the fuck is this here
---
index.html | 7 -------
1 file changed, 7 deletions(-)
diff --git a/index.html b/index.html
index b14849223..a26c4a040 100644
--- a/index.html
+++ b/index.html
@@ -5544,13 +5544,6 @@ Sign Up / Sign In
Sync your library across devices
-
We only store music data and a randomized ID to find out which Google/Email account is
which.
From 6ff010d098e1c89f6efc3ba7483ff0d0f6b1cc36 Mon Sep 17 00:00:00 2001
From: Samidy
Date: Thu, 12 Mar 2026 09:13:20 +0300
Subject: [PATCH 036/226] consistency shi ykwim
---
index.html | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/index.html b/index.html
index a26c4a040..fb24770f6 100644
--- a/index.html
+++ b/index.html
@@ -1756,7 +1756,7 @@ Support Monochrome
- An alternative way to support us is by starring us on GitHub, and it's completely free!
+ If you cannot financially support us, please consider starring the project on GitHub and sharing with friends!
Date: Thu, 12 Mar 2026 09:16:58 +0300
Subject: [PATCH 037/226] we dont need to pre-connect to any hifi API
---
index.html | 1 -
1 file changed, 1 deletion(-)
diff --git a/index.html b/index.html
index fb24770f6..9ec2385d2 100644
--- a/index.html
+++ b/index.html
@@ -16,7 +16,6 @@
-
From 401c2687fb1ee709e18b1e184aaf3bb4bdc382be Mon Sep 17 00:00:00 2001
From: Samidy
Date: Thu, 12 Mar 2026 09:17:22 +0300
Subject: [PATCH 038/226] both arent needed anymore and practically worse for
privacy
---
index.html | 2 --
1 file changed, 2 deletions(-)
diff --git a/index.html b/index.html
index 9ec2385d2..2a9c31d8b 100644
--- a/index.html
+++ b/index.html
@@ -14,8 +14,6 @@
-
-
From 5dca0421c020ae8d0a255c5c75a84f140e5c71f3 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Thu, 12 Mar 2026 06:18:39 +0000
Subject: [PATCH 039/226] chore(deps): bump tar and npm
Removes [tar](https://github.com/isaacs/node-tar). It's no longer used after updating ancestor dependency [npm](https://github.com/npm/cli). These dependencies need to be updated together.
Removes `tar`
Updates `npm` from 11.11.0 to 11.11.1
- [Release notes](https://github.com/npm/cli/releases)
- [Changelog](https://github.com/npm/cli/blob/latest/CHANGELOG.md)
- [Commits](https://github.com/npm/cli/compare/v11.11.0...v11.11.1)
---
updated-dependencies:
- dependency-name: tar
dependency-version:
dependency-type: indirect
- dependency-name: npm
dependency-version: 11.11.1
dependency-type: direct:production
...
Signed-off-by: dependabot[bot]
---
package-lock.json | 61 +++++++++++++++++++++++------------------------
package.json | 2 +-
2 files changed, 31 insertions(+), 32 deletions(-)
diff --git a/package-lock.json b/package-lock.json
index 1ab271955..fb9706bfd 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -23,7 +23,7 @@
"fuse.js": "^7.1.0",
"hls.js": "^1.6.15",
"jose": "^6.2.0",
- "npm": "^11.6.0",
+ "npm": "^11.11.1",
"pocketbase": "^0.26.8",
"taglib-wasm": "^1.0.5",
"uuid": "^13.0.0"
@@ -7789,9 +7789,9 @@
}
},
"node_modules/npm": {
- "version": "11.11.0",
- "resolved": "https://registry.npmjs.org/npm/-/npm-11.11.0.tgz",
- "integrity": "sha512-82gRxKrh/eY5UnNorkTFcdBQAGpgjWehkfGVqAGlJjejEtJZGGJUqjo3mbBTNbc5BTnPKGVtGPBZGhElujX5cw==",
+ "version": "11.11.1",
+ "resolved": "https://registry.npmjs.org/npm/-/npm-11.11.1.tgz",
+ "integrity": "sha512-asazCodkFdz1ReQzukyzS/DD77uGCIqUFeRG3gtaT8b9UR0ne1m9QOBuMgT72ij1rt7TRrOox4A1WzntMWIuEg==",
"bundleDependencies": [
"@isaacs/string-locale-compare",
"@npmcli/arborist",
@@ -7869,7 +7869,7 @@
],
"dependencies": {
"@isaacs/string-locale-compare": "^1.1.0",
- "@npmcli/arborist": "^9.4.0",
+ "@npmcli/arborist": "^9.4.1",
"@npmcli/config": "^10.7.1",
"@npmcli/fs": "^5.0.0",
"@npmcli/map-workspaces": "^5.0.3",
@@ -7877,7 +7877,7 @@
"@npmcli/package-json": "^7.0.5",
"@npmcli/promise-spawn": "^9.0.1",
"@npmcli/redact": "^4.0.0",
- "@npmcli/run-script": "^10.0.3",
+ "@npmcli/run-script": "^10.0.4",
"@sigstore/tuf": "^4.0.1",
"abbrev": "^4.0.0",
"archy": "~1.0.0",
@@ -7894,17 +7894,17 @@
"is-cidr": "^6.0.3",
"json-parse-even-better-errors": "^5.0.0",
"libnpmaccess": "^10.0.3",
- "libnpmdiff": "^8.1.3",
- "libnpmexec": "^10.2.3",
- "libnpmfund": "^7.0.17",
+ "libnpmdiff": "^8.1.4",
+ "libnpmexec": "^10.2.4",
+ "libnpmfund": "^7.0.18",
"libnpmorg": "^8.0.1",
- "libnpmpack": "^9.1.3",
+ "libnpmpack": "^9.1.4",
"libnpmpublish": "^11.1.3",
"libnpmsearch": "^9.0.1",
"libnpmteam": "^8.0.2",
"libnpmversion": "^8.0.3",
"make-fetch-happen": "^15.0.4",
- "minimatch": "^10.2.2",
+ "minimatch": "^10.2.4",
"minipass": "^7.1.3",
"minipass-pipeline": "^1.2.4",
"ms": "^2.1.2",
@@ -7918,7 +7918,7 @@
"npm-registry-fetch": "^19.1.1",
"npm-user-validate": "^4.0.0",
"p-map": "^7.0.4",
- "pacote": "^21.4.0",
+ "pacote": "^21.5.0",
"parse-conflict-json": "^5.0.1",
"proc-log": "^6.1.0",
"qrcode-terminal": "^0.12.0",
@@ -7927,7 +7927,7 @@
"spdx-expression-parse": "^4.0.0",
"ssri": "^13.0.1",
"supports-color": "^10.2.2",
- "tar": "^7.5.9",
+ "tar": "^7.5.11",
"text-table": "~0.2.0",
"tiny-relative-date": "^2.0.2",
"treeverse": "^3.0.0",
@@ -7993,10 +7993,11 @@
}
},
"node_modules/npm/node_modules/@npmcli/arborist": {
- "version": "9.4.0",
+ "version": "9.4.1",
"inBundle": true,
"license": "ISC",
"dependencies": {
+ "@gar/promise-retry": "^1.0.0",
"@isaacs/string-locale-compare": "^1.1.0",
"@npmcli/fs": "^5.0.0",
"@npmcli/installed-package-contents": "^4.0.0",
@@ -8193,7 +8194,7 @@
}
},
"node_modules/npm/node_modules/@npmcli/run-script": {
- "version": "10.0.3",
+ "version": "10.0.4",
"inBundle": true,
"license": "ISC",
"dependencies": {
@@ -8201,8 +8202,7 @@
"@npmcli/package-json": "^7.0.0",
"@npmcli/promise-spawn": "^9.0.0",
"node-gyp": "^12.1.0",
- "proc-log": "^6.0.0",
- "which": "^6.0.0"
+ "proc-log": "^6.0.0"
},
"engines": {
"node": "^20.17.0 || >=22.9.0"
@@ -8357,7 +8357,7 @@
}
},
"node_modules/npm/node_modules/brace-expansion": {
- "version": "5.0.3",
+ "version": "5.0.4",
"inBundle": true,
"license": "MIT",
"dependencies": {
@@ -8711,11 +8711,11 @@
}
},
"node_modules/npm/node_modules/libnpmdiff": {
- "version": "8.1.3",
+ "version": "8.1.4",
"inBundle": true,
"license": "ISC",
"dependencies": {
- "@npmcli/arborist": "^9.4.0",
+ "@npmcli/arborist": "^9.4.1",
"@npmcli/installed-package-contents": "^4.0.0",
"binary-extensions": "^3.0.0",
"diff": "^8.0.2",
@@ -8729,12 +8729,12 @@
}
},
"node_modules/npm/node_modules/libnpmexec": {
- "version": "10.2.3",
+ "version": "10.2.4",
"inBundle": true,
"license": "ISC",
"dependencies": {
"@gar/promise-retry": "^1.0.0",
- "@npmcli/arborist": "^9.4.0",
+ "@npmcli/arborist": "^9.4.1",
"@npmcli/package-json": "^7.0.0",
"@npmcli/run-script": "^10.0.0",
"ci-info": "^4.0.0",
@@ -8751,11 +8751,11 @@
}
},
"node_modules/npm/node_modules/libnpmfund": {
- "version": "7.0.17",
+ "version": "7.0.18",
"inBundle": true,
"license": "ISC",
"dependencies": {
- "@npmcli/arborist": "^9.4.0"
+ "@npmcli/arborist": "^9.4.1"
},
"engines": {
"node": "^20.17.0 || >=22.9.0"
@@ -8774,11 +8774,11 @@
}
},
"node_modules/npm/node_modules/libnpmpack": {
- "version": "9.1.3",
+ "version": "9.1.4",
"inBundle": true,
"license": "ISC",
"dependencies": {
- "@npmcli/arborist": "^9.4.0",
+ "@npmcli/arborist": "^9.4.1",
"@npmcli/run-script": "^10.0.0",
"npm-package-arg": "^13.0.0",
"pacote": "^21.0.2"
@@ -8873,7 +8873,7 @@
}
},
"node_modules/npm/node_modules/minimatch": {
- "version": "10.2.2",
+ "version": "10.2.4",
"inBundle": true,
"license": "BlueOak-1.0.0",
"dependencies": {
@@ -9183,7 +9183,7 @@
}
},
"node_modules/npm/node_modules/pacote": {
- "version": "21.4.0",
+ "version": "21.5.0",
"inBundle": true,
"license": "ISC",
"dependencies": {
@@ -9462,7 +9462,7 @@
}
},
"node_modules/npm/node_modules/tar": {
- "version": "7.5.9",
+ "version": "7.5.11",
"inBundle": true,
"license": "BlueOak-1.0.0",
"dependencies": {
@@ -9607,11 +9607,10 @@
}
},
"node_modules/npm/node_modules/write-file-atomic": {
- "version": "7.0.0",
+ "version": "7.0.1",
"inBundle": true,
"license": "ISC",
"dependencies": {
- "imurmurhash": "^0.1.4",
"signal-exit": "^4.0.1"
},
"engines": {
diff --git a/package.json b/package.json
index 554fb77ee..32dd856bf 100644
--- a/package.json
+++ b/package.json
@@ -66,7 +66,7 @@
"fuse.js": "^7.1.0",
"hls.js": "^1.6.15",
"jose": "^6.2.0",
- "npm": "^11.6.0",
+ "npm": "^11.11.1",
"pocketbase": "^0.26.8",
"taglib-wasm": "^1.0.5",
"uuid": "^13.0.0"
From b7c57a06fe884aa9609d81bae85f3d682d8e5948 Mon Sep 17 00:00:00 2001
From: Samidy
Date: Thu, 12 Mar 2026 09:27:13 +0300
Subject: [PATCH 040/226] outdated
---
package.json | 1 -
1 file changed, 1 deletion(-)
diff --git a/package.json b/package.json
index 32dd856bf..4f9201003 100644
--- a/package.json
+++ b/package.json
@@ -3,7 +3,6 @@
"type": "module",
"version": "2.5.0",
"description": "[ ](https://monochrome.samidy.com)",
- "main": "sw.js",
"scripts": {
"dev": "vite",
"dev:desktop": "start npm run dev & node scripts/dev-runner.js",
From dfa6e4038500bfa4e3322f6b90db8c5345813fc6 Mon Sep 17 00:00:00 2001
From: Samidy
Date: Thu, 12 Mar 2026 09:27:27 +0300
Subject: [PATCH 041/226] we dont even have tests
---
package.json | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/package.json b/package.json
index 4f9201003..7496fa531 100644
--- a/package.json
+++ b/package.json
@@ -13,8 +13,7 @@
"lint:css": "stylelint \"**/*.css\"",
"lint:html": "htmlhint \"**/*.html\" --ignore=\"dist/**,legacy/**,node_modules/**\"",
"lint": "npm run lint:js && npm run lint:css && npm run lint:html",
- "format": "prettier --write .",
- "test": "echo \"Error: no test specified\" && exit 1"
+ "format": "prettier --write ."
},
"repository": {
"type": "git",
From 46d09af6d7657a9501e5c3a3e6d4ea9683252c11 Mon Sep 17 00:00:00 2001
From: Samidy
Date: Thu, 12 Mar 2026 09:27:58 +0300
Subject: [PATCH 042/226] outdated github links
---
package.json | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/package.json b/package.json
index 7496fa531..075c10f6c 100644
--- a/package.json
+++ b/package.json
@@ -17,15 +17,15 @@
},
"repository": {
"type": "git",
- "url": "git+https://github.com/SamidyFR/monochrome.git"
+ "url": "git+https://github.com/monochrome-music/monochrome.git"
},
"keywords": [],
"author": "",
"license": "ISC",
"bugs": {
- "url": "https://github.com/SamidyFR/monochrome/issues"
+ "url": "https://github.com/monochrome-music/monochrome/issues"
},
- "homepage": "https://github.com/SamidyFR/monochrome#readme",
+ "homepage": "https://github.com/monochrome-music/monochrome#readme",
"devDependencies": {
"@neutralinojs/neu": "^11.7.0",
"@types/node": "^25.3.5",
From cb7141341ad73e8da93b8543ff2b35fb26175152 Mon Sep 17 00:00:00 2001
From: Samidy
Date: Thu, 12 Mar 2026 09:31:02 +0300
Subject: [PATCH 043/226] use bun for everything
---
package.json | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/package.json b/package.json
index 075c10f6c..43b3214a9 100644
--- a/package.json
+++ b/package.json
@@ -5,14 +5,14 @@
"description": "[ ](https://monochrome.samidy.com)",
"scripts": {
"dev": "vite",
- "dev:desktop": "start npm run dev & node scripts/dev-runner.js",
+ "dev:desktop": "start bun run dev & node scripts/dev-runner.js",
"build": "vite build --mode neutralino && bun x neu build",
"postbuild": "node -e \"const fs = require('fs'); const path = require('path'); const src = 'extensions'; const dest = path.join('dist', 'Monochrome', 'extensions'); if (fs.existsSync(src)) { fs.mkdirSync(dest, { recursive: true }); fs.cpSync(src, dest, { recursive: true }); console.log('Extensions manually copied to ' + dest); }\"",
"preview": "vite preview",
"lint:js": "eslint .",
"lint:css": "stylelint \"**/*.css\"",
"lint:html": "htmlhint \"**/*.html\" --ignore=\"dist/**,legacy/**,node_modules/**\"",
- "lint": "npm run lint:js && npm run lint:css && npm run lint:html",
+ "lint": "bun run lint:js && bun run lint:css && bun run lint:html",
"format": "prettier --write ."
},
"repository": {
From c165e979f1dfb8e86c7d74389a6d694a9482df7f Mon Sep 17 00:00:00 2001
From: Samidy
Date: Thu, 12 Mar 2026 09:37:35 +0300
Subject: [PATCH 044/226] shitty ai slop ass dashes
---
.env.example | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/.env.example b/.env.example
index da95031de..47a579be9 100644
--- a/.env.example
+++ b/.env.example
@@ -1,11 +1,11 @@
# Monochrome Docker Configuration
# Copy to .env and edit: cp .env.example .env
-# --- Monochrome ---
+# Monochrome
MONOCHROME_PORT=3000
MONOCHROME_DEV_PORT=5173
-# --- Auth Gate (server-side authentication) ---
+# Auth Gate (server-side authentication)
# Set AUTH_ENABLED=true to enable the auth gate entirely (login required)
AUTH_ENABLED=false
AUTH_SECRET=change-me-to-a-random-string
@@ -18,7 +18,7 @@ APPWRITE_PROJECT_ID=auth-for-monochrome
# POCKETBASE_URL=https://monodb.samidy.com
# SESSION_MAX_AGE=604800000 # 7 days in ms (default)
-# --- PocketBase (only used with --profile pocketbase) ---
+# PocketBase (only used with --profile pocketbase)
POCKETBASE_PORT=8090
PB_ADMIN_EMAIL=admin@example.com
PB_ADMIN_PASSWORD=changeme
From edffc9566a45ae9610d3d161f7e5a686df6794ea Mon Sep 17 00:00:00 2001
From: Samidy
Date: Thu, 12 Mar 2026 09:37:54 +0300
Subject: [PATCH 045/226] update example
---
.env.example | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.env.example b/.env.example
index 47a579be9..1c0b4570b 100644
--- a/.env.example
+++ b/.env.example
@@ -15,7 +15,7 @@ APPWRITE_PROJECT_ID=auth-for-monochrome
# AUTH_GOOGLE_ENABLED=true
# AUTH_EMAIL_ENABLED=true
# Optional: set PocketBase URL (hides the field in settings when set)
-# POCKETBASE_URL=https://monodb.samidy.com
+# POCKETBASE_URL=https://data.samidy.xyz
# SESSION_MAX_AGE=604800000 # 7 days in ms (default)
# PocketBase (only used with --profile pocketbase)
From 898c9c62e5badf117c8ab3a354d8d8ce11678f2c Mon Sep 17 00:00:00 2001
From: Samidy
Date: Thu, 12 Mar 2026 09:40:21 +0300
Subject: [PATCH 046/226] no need for .vscode
---
.gitignore | 1 +
.vscode/tasks.json | 23 -----------------------
2 files changed, 1 insertion(+), 23 deletions(-)
delete mode 100644 .vscode/tasks.json
diff --git a/.gitignore b/.gitignore
index 5c3c0678c..15e834060 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,6 +3,7 @@ dist
.DS_Store
*.local
.vite
+.vscode
# Docker
.env
diff --git a/.vscode/tasks.json b/.vscode/tasks.json
deleted file mode 100644
index 693875458..000000000
--- a/.vscode/tasks.json
+++ /dev/null
@@ -1,23 +0,0 @@
-{
- "version": "2.0.0",
- "tasks": [
- {
- "type": "npm",
- "script": "build",
- "group": {
- "kind": "build",
- "isDefault": true
- },
- "problemMatcher": [],
- "label": "npm: build",
- "detail": "vite build"
- },
- {
- "type": "npm",
- "script": "dev",
- "problemMatcher": [],
- "label": "npm: dev",
- "detail": "vite"
- }
- ]
-}
From b04019f2823489fef0d17d3098f2e457c951791a Mon Sep 17 00:00:00 2001
From: Daniel <790119+DanTheMan827@users.noreply.github.com>
Date: Thu, 12 Mar 2026 05:57:42 +0000
Subject: [PATCH 047/226] fix(downloads): mp4 files with flac audio are now
tagged
This is resolved by using ffmpeg to copy the audio data into a new mp4 container file before passing it to taglib.
---
bun.lock | 2 +-
js/download-utils.ts | 3 ++-
js/downloads.js | 2 --
js/ffmpegFormats.ts | 22 ++++++++++------------
js/metadata.js | 11 ++---------
js/storage.js | 3 +--
6 files changed, 16 insertions(+), 27 deletions(-)
diff --git a/bun.lock b/bun.lock
index 7a7217b85..8223c9999 100644
--- a/bun.lock
+++ b/bun.lock
@@ -18,7 +18,7 @@
"fuse.js": "^7.1.0",
"hls.js": "^1.6.15",
"jose": "^6.2.0",
- "npm": "^11.11.0",
+ "npm": "^11.6.0",
"pocketbase": "^0.26.8",
"taglib-wasm": "^1.0.5",
"uuid": "^13.0.0",
diff --git a/js/download-utils.ts b/js/download-utils.ts
index 1bf62fcdd..763a78c36 100644
--- a/js/download-utils.ts
+++ b/js/download-utils.ts
@@ -9,6 +9,7 @@ import {
getContainerFormat,
transcodeWithContainerFormat,
} from './ffmpegFormats';
+import { ffmpeg } from './ffmpeg';
/**
* Triggers a browser file download for the given blob.
@@ -62,7 +63,7 @@ export async function applyAudioPostProcessing(
if (containerFmt) {
if (await containerFmt.needsTranscode(blob)) {
blob = await transcodeWithContainerFormat(blob, containerFmt, onProgress, signal);
- } else if ((await getExtensionFromBlob(blob)) == 'flac') {
+ } else if ((await getExtensionFromBlob(blob)) === 'flac') {
blob = await rebuildFlacWithoutMetadata(blob);
}
}
diff --git a/js/downloads.js b/js/downloads.js
index 56cf9c9a3..29821fe05 100644
--- a/js/downloads.js
+++ b/js/downloads.js
@@ -549,7 +549,6 @@ async function bulkDownloadToZip(
const discNumber = discLayout.resolveDiscNumber(i);
const discPath = separateByDisc ? `${getDiscFolderName(discNumber)}/${filename}` : filename;
- console.log(`[Playlist] Track ${i + 1}: ${discPath}`);
trackPaths.push(discPath);
yield {
@@ -852,7 +851,6 @@ export async function downloadDiscography(artist, selectedReleases, api, quality
const discNumber = discLayout.resolveDiscNumber(i);
const discPath = separateByDisc ? `${getDiscFolderName(discNumber)}/${filename}` : filename;
- console.log(`[Playlist] Track ${i + 1}: ${discPath}`);
trackPaths.push(discPath);
yield {
diff --git a/js/ffmpegFormats.ts b/js/ffmpegFormats.ts
index ea94e15bf..049399350 100644
--- a/js/ffmpegFormats.ts
+++ b/js/ffmpegFormats.ts
@@ -149,24 +149,13 @@ export const containerFormats: ContainerFormat[] = [
{
displayName: 'FLAC',
internalName: 'flac',
- ffmpegArgs: ['-vn', '-map_metadata', '-1', '-map', '0:a', '-c:a', 'flac', '-compression_level', '12'],
+ ffmpegArgs: ['-vn', '-map_metadata', '-1', '-map', '0:a', '-c:a', 'flac'],
outputFilename: 'output.flac',
outputMime: 'audio/flac',
extension: 'flac',
// Only transcode when the source is NOT already a FLAC file.
needsTranscode: async (blob) => (await getExtensionFromBlob(blob)) !== 'flac',
},
- {
- displayName: 'FLAC - Max Compression',
- internalName: 'flac_max',
- // `-compression_level 12` is the highest FLAC compression level; audio
- // data is bit-identical to the source — only the compressed size changes.
- ffmpegArgs: ['-vn', '-map_metadata', '-1', '-map', '0:a', '-c:a', 'flac', '-compression_level', '12'],
- outputFilename: 'output.flac',
- outputMime: 'audio/flac',
- extension: 'flac',
- needsTranscode: async () => true,
- },
{
displayName: 'Apple Lossless',
internalName: 'alac',
@@ -176,6 +165,15 @@ export const containerFormats: ContainerFormat[] = [
extension: 'm4a',
needsTranscode: async () => true,
},
+ {
+ displayName: "Don't change",
+ internalName: 'nochange',
+ ffmpegArgs: ['-c:a', 'copy', '-strict', '-2'],
+ outputFilename: 'output.mp4',
+ outputMime: 'audio/mp4',
+ extension: 'mp4',
+ needsTranscode: async (blob) => (await getExtensionFromBlob(blob)) == 'm4a',
+ },
];
/** Returns true if the quality string identifies a known custom ffmpeg-transcoded format */
diff --git a/js/metadata.js b/js/metadata.js
index 29d6d46d3..330aa3009 100644
--- a/js/metadata.js
+++ b/js/metadata.js
@@ -47,14 +47,6 @@ export async function addMetadataToAudio(audioBlob, track, api, _quality, prefet
*/
const data = {};
- const detectedExt = await getExtensionFromBlob(audioBlob);
- const isM4A = detectedExt === 'm4a' || detectedExt === 'mp4';
-
- if (isM4A) {
- console.log('Skipping TagLib for M4A (handled by FFmpeg)');
- return audioBlob;
- }
-
const audioBuffer = await doTimedAsync('Get audio array buffer', () => audioBlob.arrayBuffer());
try {
@@ -64,7 +56,8 @@ export async function addMetadataToAudio(audioBlob, track, api, _quality, prefet
data.albumArtist = track.album?.artist?.name || track.artist?.name;
data.trackNumber = track.trackNumber;
data.discNumber = track.volumeNumber ?? track.discNumber;
- data.totalTracks = track.album.numberOfTracks;
+ data.totalTracks = track.album.numberOfTracksOnDisc ?? track.album.numberOfTracks;
+ data.totalDiscs = track.album.totalDiscs;
data.copyright = track.copyright;
data.isrc = track.isrc;
data.explicit = Boolean(track.explicit);
diff --git a/js/storage.js b/js/storage.js
index db28d429e..e03b18d4f 100644
--- a/js/storage.js
+++ b/js/storage.js
@@ -560,8 +560,7 @@ export const losslessContainerSettings = {
getContainer() {
try {
const stored = localStorage.getItem(this.STORAGE_KEY) || 'flac';
- // 'nochange' was removed as an option; fall back to FLAC
- return stored === 'nochange' ? 'flac' : stored;
+ return stored;
} catch {
return 'flac';
}
From 9ccd6cf514a854627e3332a4ce6d802270c53537 Mon Sep 17 00:00:00 2001
From: Samidy
Date: Thu, 12 Mar 2026 09:44:46 +0300
Subject: [PATCH 048/226] this section doesnt exist anymore
---
CONTRIBUTING.md | 1 -
1 file changed, 1 deletion(-)
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 83417b1dc..e3fe8780f 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -13,7 +13,6 @@ Thank you for your interest in contributing to Monochrome! This guide will help
- [Contributing Workflow](#contributing-workflow)
- [Commit Message Guidelines](#commit-message-guidelines)
- [Deployment](#deployment)
-- [Questions?](#questions)
---
From 079198c59e9a26fd61e8b5edc12831a6ff1c904e Mon Sep 17 00:00:00 2001
From: Daniel <790119+DanTheMan827@users.noreply.github.com>
Date: Thu, 12 Mar 2026 06:48:05 +0000
Subject: [PATCH 049/226] fix: improve discNumber formatting in formatTemplate
function
---
js/utils.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/js/utils.js b/js/utils.js
index 5c55dfa0c..75588d79b 100644
--- a/js/utils.js
+++ b/js/utils.js
@@ -373,7 +373,7 @@ export const getTrackArtistsHTML = (track = {}, { fallback = 'Unknown Artist' }
export const formatTemplate = (template, data) => {
let result = template;
- result = result.replace(/\{discNumber\}/g, data.discNumber ? String(data.discNumber).padStart(2, '0') : '01');
+ result = result.replace(/\{discNumber\}/g, String(Number(data.discNumber || 1)));
result = result.replace(/\{trackNumber\}/g, data.trackNumber ? String(data.trackNumber).padStart(2, '0') : '00');
result = result.replace(/\{artist\}/g, sanitizeForFilename(data.artist || 'Unknown Artist'));
result = result.replace(/\{title\}/g, sanitizeForFilename(data.title || 'Unknown Title'));
From ea46d7c0217d26fd7ce6fd391d1b7db0d3a0ec60 Mon Sep 17 00:00:00 2001
From: Samidy
Date: Thu, 12 Mar 2026 09:49:33 +0300
Subject: [PATCH 050/226] woopsie daisys
---
index.html | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/index.html b/index.html
index 2a9c31d8b..0df789b6a 100644
--- a/index.html
+++ b/index.html
@@ -2046,7 +2046,7 @@
From fe7a9e54979f625d9a859f089c3c03099038874a Mon Sep 17 00:00:00 2001
From: Samidy
Date: Thu, 12 Mar 2026 10:10:50 +0300
Subject: [PATCH 053/226] replace discord link here
---
index.html | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/index.html b/index.html
index 697a3f46f..0b01b4d83 100644
--- a/index.html
+++ b/index.html
@@ -5651,7 +5651,7 @@ Monochrome Official App
The App is still in Beta. Please report any issues in our
- Discord server.
From df7ba22fa1a05b6a8f9abd80bc451921e98465d4 Mon Sep 17 00:00:00 2001
From: Samidy
Date: Thu, 12 Mar 2026 10:12:06 +0300
Subject: [PATCH 054/226] didnt see these
---
package.json | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/package.json b/package.json
index 43b3214a9..3c9fa509d 100644
--- a/package.json
+++ b/package.json
@@ -2,7 +2,7 @@
"name": "monochrome",
"type": "module",
"version": "2.5.0",
- "description": "[ ](https://monochrome.samidy.com)",
+ "description": "[ ](https://monochrome.tf)",
"scripts": {
"dev": "vite",
"dev:desktop": "start bun run dev & node scripts/dev-runner.js",
From 3fc74738a50356bda46ce78b552343ccc582c56c Mon Sep 17 00:00:00 2001
From: Samidy
Date: Thu, 12 Mar 2026 10:16:38 +0300
Subject: [PATCH 055/226] this barely ever worked anyways lets just remove
smooth scrolling
---
index.html | 12 -----
js/app.js | 1 -
js/settings.js | 12 -----
js/smooth-scrolling.js | 100 -----------------------------------------
js/storage.js | 16 -------
5 files changed, 141 deletions(-)
delete mode 100644 js/smooth-scrolling.js
diff --git a/index.html b/index.html
index 0b01b4d83..72614e6d0 100644
--- a/index.html
+++ b/index.html
@@ -3746,18 +3746,6 @@ Custom Theme
-
-
- Smooth Scrolling
- Provides a smoother scrolling experience with Lenis (Experimental)
-
-
-
-
-
-
Album Cover Background
diff --git a/js/app.js b/js/app.js
index 480c3339a..742f33c4f 100644
--- a/js/app.js
+++ b/js/app.js
@@ -25,7 +25,6 @@ import { showNotification } from './downloads.js';
import { syncManager } from './accounts/pocketbase.js';
import { authManager } from './accounts/auth.js';
import { registerSW } from 'virtual:pwa-register';
-import './smooth-scrolling.js';
import { openEditProfile } from './profile.js';
import { ThemeStore } from './themeStore.js';
import './commandPalette.js';
diff --git a/js/settings.js b/js/settings.js
index 22a48ba59..24a1cf381 100644
--- a/js/settings.js
+++ b/js/settings.js
@@ -10,7 +10,6 @@ import {
cardSettings,
waveformSettings,
replayGainSettings,
- smoothScrollingSettings,
downloadQualitySettings,
losslessContainerSettings,
coverArtSizeSettings,
@@ -2170,17 +2169,6 @@ export function initializeSettings(scrobbler, player, api, ui) {
});
}
- // Smooth Scrolling Toggle
- const smoothScrollingToggle = document.getElementById('smooth-scrolling-toggle');
- if (smoothScrollingToggle) {
- smoothScrollingToggle.checked = smoothScrollingSettings.isEnabled();
- smoothScrollingToggle.addEventListener('change', (e) => {
- smoothScrollingSettings.setEnabled(e.target.checked);
-
- window.dispatchEvent(new CustomEvent('smooth-scrolling-toggle', { detail: { enabled: e.target.checked } }));
- });
- }
-
// Visualizer Sensitivity
const visualizerSensitivitySlider = document.getElementById('visualizer-sensitivity-slider');
const visualizerSensitivityValue = document.getElementById('visualizer-sensitivity-value');
diff --git a/js/smooth-scrolling.js b/js/smooth-scrolling.js
deleted file mode 100644
index 8407c7830..000000000
--- a/js/smooth-scrolling.js
+++ /dev/null
@@ -1,100 +0,0 @@
-//js/smooth-scrolling.js
-import { smoothScrollingSettings } from './storage.js';
-
-let lenis = null;
-let lenisLoaded = false;
-let lenisLoading = false;
-
-async function loadLenisScript() {
- if (lenisLoaded) return true;
- if (lenisLoading) {
- return new Promise((resolve) => {
- const checkLoaded = setInterval(() => {
- if (!lenisLoading) {
- clearInterval(checkLoaded);
- resolve(lenisLoaded);
- }
- }, 100);
- });
- }
-
- lenisLoading = true;
-
- try {
- await new Promise((resolve, reject) => {
- const script = document.createElement('script');
- script.src = 'https://unpkg.com/@studio-freight/lenis';
- script.onload = resolve;
- script.onerror = reject;
- document.head.appendChild(script);
- });
-
- lenisLoaded = true;
- lenisLoading = false;
- console.log('✓ Lenis loaded successfully');
- return true;
- } catch (error) {
- console.error('✗ Failed to load Lenis:', error);
- lenisLoaded = false;
- lenisLoading = false;
- return false;
- }
-}
-
-async function initializeSmoothScrolling() {
- if (lenis) return; // Already initialized
-
- const loaded = await loadLenisScript();
- if (!loaded) return;
-
- lenis = new window.Lenis({
- wrapper: document.querySelector('.main-content'),
- content: document.querySelector('.main-content'),
- lerp: 0.1,
- smoothWheel: true,
- smoothTouch: false,
- normalizeWheel: true,
- wheelMultiplier: 0.8,
- });
-
- function raf(time) {
- if (lenis) {
- lenis.raf(time);
- requestAnimationFrame(raf);
- }
- }
-
- requestAnimationFrame(raf);
-}
-
-function destroySmoothScrolling() {
- if (lenis) {
- lenis.destroy();
- lenis = null;
- }
-}
-
-async function setupSmoothScrolling() {
- // Check if smooth scrolling is enabled
- const smoothScrollingEnabled = smoothScrollingSettings.isEnabled();
-
- if (smoothScrollingEnabled) {
- await initializeSmoothScrolling();
- }
-
- // Listen for toggle changes
- window.addEventListener('smooth-scrolling-toggle', async function (e) {
- if (e.detail.enabled) {
- await initializeSmoothScrolling();
- } else {
- destroySmoothScrolling();
- }
- });
-}
-
-// Initialize when DOM is ready
-if (document.readyState === 'loading') {
- document.addEventListener('DOMContentLoaded', setupSmoothScrolling);
-} else {
- setupSmoothScrolling();
-}
diff --git a/js/storage.js b/js/storage.js
index de7c4709c..556be078f 100644
--- a/js/storage.js
+++ b/js/storage.js
@@ -599,22 +599,6 @@ export const waveformSettings = {
},
};
-export const smoothScrollingSettings = {
- STORAGE_KEY: 'smooth-scrolling-enabled',
-
- isEnabled() {
- try {
- return localStorage.getItem(this.STORAGE_KEY) === 'true';
- } catch {
- return false;
- }
- },
-
- setEnabled(enabled) {
- localStorage.setItem(this.STORAGE_KEY, enabled ? 'true' : 'false');
- },
-};
-
export const qualityBadgeSettings = {
STORAGE_KEY: 'show-quality-badges',
From 3b088d169221b1ebb608bd84c7da69706a848c15 Mon Sep 17 00:00:00 2001
From: Samidy
Date: Thu, 12 Mar 2026 10:24:08 +0300
Subject: [PATCH 056/226] unecessary comments
---
index.html | 4 ----
1 file changed, 4 deletions(-)
diff --git a/index.html b/index.html
index 72614e6d0..98da4e6dd 100644
--- a/index.html
+++ b/index.html
@@ -3876,7 +3876,6 @@ Custom Theme
>
-
Cycle Presets
@@ -4747,7 +4746,6 @@
Custom Theme
-
Playback Speed
@@ -4783,7 +4781,6 @@
Custom Theme
-
Preserve Pitch
@@ -4795,7 +4792,6 @@
Custom Theme
-
Equalizer
From 9226515bcf9a8303813b4327072e3e9f589efae3 Mon Sep 17 00:00:00 2001
From: Daniel <790119+DanTheMan827@users.noreply.github.com>
Date: Thu, 12 Mar 2026 14:46:52 +0000
Subject: [PATCH 057/226] feat: update client-zip to version 2.5.0 and adjust
import in bulk-download-writer.ts
---
bun.lock | 3 +++
js/bulk-download-writer.ts | 2 +-
package.json | 1 +
3 files changed, 5 insertions(+), 1 deletion(-)
diff --git a/bun.lock b/bun.lock
index 8223c9999..da73ceda3 100644
--- a/bun.lock
+++ b/bun.lock
@@ -13,6 +13,7 @@
"appwrite": "^23.0.0",
"butterchurn": "^2.6.7",
"butterchurn-presets": "^2.4.7",
+ "client-zip": "^2.5.0",
"cookie-session": "^2.1.1",
"dashjs": "^5.1.1",
"fuse.js": "^7.1.0",
@@ -623,6 +624,8 @@
"chokidar": ["chokidar@4.0.3", "", { "dependencies": { "readdirp": "^4.0.1" } }, "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA=="],
+ "client-zip": ["client-zip@2.5.0", "", {}, "sha512-ydG4nDZesbFurnNq0VVCp/yyomIBh+X/1fZPI/P24zbnG4dtC4tQAfI5uQsomigsUMeiRO2wiTPizLWQh+IAyQ=="],
+
"codem-isoboxer": ["codem-isoboxer@0.3.10", "", {}, "sha512-eNk3TRV+xQMJ1PEj0FQGY8KD4m0GPxT487XJ+Iftm7mVa9WpPFDMWqPt+46buiP5j5Wzqe5oMIhqBcAeKfygSA=="],
"color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="],
diff --git a/js/bulk-download-writer.ts b/js/bulk-download-writer.ts
index 418002c7c..49d84135a 100644
--- a/js/bulk-download-writer.ts
+++ b/js/bulk-download-writer.ts
@@ -25,7 +25,7 @@ interface NeutralinoBridge {
async function loadClientZip() {
try {
- return await import('https://cdn.jsdelivr.net/npm/client-zip@2.4.5/+esm');
+ return await import('client-zip');
} catch (error) {
console.error('Failed to load client-zip:', error);
throw new Error('Failed to load ZIP library');
diff --git a/package.json b/package.json
index 1ebbdfe44..eca55e0a4 100644
--- a/package.json
+++ b/package.json
@@ -60,6 +60,7 @@
"appwrite": "^23.0.0",
"butterchurn": "^2.6.7",
"butterchurn-presets": "^2.4.7",
+ "client-zip": "^2.5.0",
"cookie-session": "^2.1.1",
"dashjs": "^5.1.1",
"fuse.js": "^7.1.0",
From 14817a3314921c6b531e7e407da9e8ecbcf0a1c0 Mon Sep 17 00:00:00 2001
From: Daniel <790119+DanTheMan827@users.noreply.github.com>
Date: Thu, 12 Mar 2026 14:57:57 +0000
Subject: [PATCH 058/226] fix: update ffmpeg imports to use URL loader for core
JS and WASM
---
js/ffmpeg.js | 5 ++---
1 file changed, 2 insertions(+), 3 deletions(-)
diff --git a/js/ffmpeg.js b/js/ffmpeg.js
index 93cd1ab72..d62e71310 100644
--- a/js/ffmpeg.js
+++ b/js/ffmpeg.js
@@ -1,8 +1,7 @@
import { fetchBlobURL } from './utils';
import FfmpegWorker from './ffmpeg.worker.js?worker';
-const ffmpegBase = 'https://unpkg.com/@ffmpeg/core/dist/esm';
-const coreJs = `${ffmpegBase}/ffmpeg-core.js`;
-const coreWasm = `${ffmpegBase}/ffmpeg-core.wasm`;
+import coreJs from '!/@ffmpeg/core/dist/esm/ffmpeg-core.js?url';
+import coreWasm from '!/@ffmpeg/core/dist/esm/ffmpeg-core.wasm?url';
class FfmpegError extends Error {
constructor(message) {
From c865b21bf5fa0df74cd90cedf18495f042336c88 Mon Sep 17 00:00:00 2001
From: Daniel <790119+DanTheMan827@users.noreply.github.com>
Date: Thu, 12 Mar 2026 15:24:54 +0000
Subject: [PATCH 059/226] refactor: ffmpeg customFormats/containerFormats are
now an object
---
js/customFormats.ts | 13 -------------
js/ffmpegFormats.ts | 44 ++++++++++++++++----------------------------
js/settings.js | 6 +++---
3 files changed, 19 insertions(+), 44 deletions(-)
delete mode 100644 js/customFormats.ts
diff --git a/js/customFormats.ts b/js/customFormats.ts
deleted file mode 100644
index f8d5c2e9f..000000000
--- a/js/customFormats.ts
+++ /dev/null
@@ -1,13 +0,0 @@
-// Re-exports for backwards compatibility – canonical source is ffmpegFormats.ts
-export {
- type ProgressEvent,
- type CustomFormat,
- type ContainerFormat,
- customFormats,
- containerFormats,
- isCustomFormat,
- getCustomFormat,
- getContainerFormat,
- transcodeWithCustomFormat,
- transcodeWithContainerFormat,
-} from './ffmpegFormats';
diff --git a/js/ffmpegFormats.ts b/js/ffmpegFormats.ts
index 049399350..5fa25b535 100644
--- a/js/ffmpegFormats.ts
+++ b/js/ffmpegFormats.ts
@@ -12,8 +12,6 @@ export interface ProgressEvent {
export interface CustomFormat {
/** Human-readable label shown in the UI */
displayName: string;
- /** Internal identifier, must start with `FFMPEG_` */
- internalName: string;
/** Arguments passed to ffmpeg (excluding input/output file args) */
ffmpegArgs: string[];
/** Output filename used when calling ffmpeg */
@@ -40,37 +38,33 @@ export interface ContainerFormat extends Omit {
needsTranscode: (blob: Blob) => Promise;
}
-export const customFormats: CustomFormat[] = [
- {
+export const customFormats: Record = {
+ FFMPEG_MP3_320: {
displayName: 'MP3 320kbps',
- internalName: 'FFMPEG_MP3_320',
ffmpegArgs: ['-map_metadata', '-1', '-c:a', 'libmp3lame', '-b:a', '320k', '-ar', '44100'],
outputFilename: 'output.mp3',
outputMime: 'audio/mpeg',
extension: 'mp3',
category: 'MP3',
},
- {
+ FFMPEG_MP3_256: {
displayName: 'MP3 256kbps',
- internalName: 'FFMPEG_MP3_256',
ffmpegArgs: ['-map_metadata', '-1', '-c:a', 'libmp3lame', '-b:a', '256k', '-ar', '44100'],
outputFilename: 'output.mp3',
outputMime: 'audio/mpeg',
extension: 'mp3',
category: 'MP3',
},
- {
+ FFMPEG_MP3_128: {
displayName: 'MP3 128kbps',
- internalName: 'FFMPEG_MP3_128',
ffmpegArgs: ['-map_metadata', '-1', '-c:a', 'libmp3lame', '-b:a', '128k', '-ar', '44100'],
outputFilename: 'output.mp3',
outputMime: 'audio/mpeg',
extension: 'mp3',
category: 'MP3',
},
- {
+ FFMPEG_OGG_320: {
displayName: 'OGG 320kbps',
- internalName: 'FFMPEG_OGG_320',
ffmpegArgs: [
'-map_metadata',
'-1',
@@ -88,9 +82,8 @@ export const customFormats: CustomFormat[] = [
extension: 'ogg',
category: 'OGG',
},
- {
+ FFMPEG_OGG_256: {
displayName: 'OGG 256kbps',
- internalName: 'FFMPEG_OGG_256',
ffmpegArgs: [
'-map_metadata',
'-1',
@@ -108,9 +101,8 @@ export const customFormats: CustomFormat[] = [
extension: 'ogg',
category: 'OGG',
},
- {
+ FFMPEG_OGG_128: {
displayName: 'OGG 128kbps',
- internalName: 'FFMPEG_OGG_128',
ffmpegArgs: [
'-map_metadata',
'-1',
@@ -128,16 +120,15 @@ export const customFormats: CustomFormat[] = [
extension: 'ogg',
category: 'OGG',
},
- {
+ FFMPEG_AAC_256: {
displayName: 'AAC 256kbps',
- internalName: 'FFMPEG_AAC_256',
ffmpegArgs: ['-map_metadata', '-1', '-c:a', 'aac', '-b:a', '256k'],
outputFilename: 'output.m4a',
outputMime: 'audio/mp4',
extension: 'm4a',
category: 'AAC',
},
-];
+};
/**
* Container format definitions for lossless re-muxing. Each entry describes
@@ -145,10 +136,9 @@ export const customFormats: CustomFormat[] = [
* `needsTranscode` predicate so callers can skip the ffmpeg step when the
* source is already in the correct container.
*/
-export const containerFormats: ContainerFormat[] = [
- {
+export const containerFormats: Record = {
+ flac: {
displayName: 'FLAC',
- internalName: 'flac',
ffmpegArgs: ['-vn', '-map_metadata', '-1', '-map', '0:a', '-c:a', 'flac'],
outputFilename: 'output.flac',
outputMime: 'audio/flac',
@@ -156,25 +146,23 @@ export const containerFormats: ContainerFormat[] = [
// Only transcode when the source is NOT already a FLAC file.
needsTranscode: async (blob) => (await getExtensionFromBlob(blob)) !== 'flac',
},
- {
+ alac: {
displayName: 'Apple Lossless',
- internalName: 'alac',
ffmpegArgs: ['-c:a', 'alac'],
outputFilename: 'output.m4a',
outputMime: 'audio/mp4',
extension: 'm4a',
needsTranscode: async () => true,
},
- {
+ nochange: {
displayName: "Don't change",
- internalName: 'nochange',
ffmpegArgs: ['-c:a', 'copy', '-strict', '-2'],
outputFilename: 'output.mp4',
outputMime: 'audio/mp4',
extension: 'mp4',
needsTranscode: async (blob) => (await getExtensionFromBlob(blob)) == 'm4a',
},
-];
+};
/** Returns true if the quality string identifies a known custom ffmpeg-transcoded format */
export function isCustomFormat(quality: string): boolean {
@@ -183,12 +171,12 @@ export function isCustomFormat(quality: string): boolean {
/** Looks up a custom format by its internal name, or returns undefined */
export function getCustomFormat(internalName: string): CustomFormat | undefined {
- return customFormats.find((f) => f.internalName === internalName);
+ return customFormats[internalName];
}
/** Looks up a container format by its internal name, or returns undefined */
export function getContainerFormat(internalName: string): ContainerFormat | undefined {
- return containerFormats.find((f) => f.internalName === internalName);
+ return containerFormats[internalName];
}
/**
diff --git a/js/settings.js b/js/settings.js
index 8d7b1b020..8cbe24070 100644
--- a/js/settings.js
+++ b/js/settings.js
@@ -816,8 +816,8 @@ export function initializeSettings(scrobbler, player, api, ui) {
}));
// Append custom (ffmpeg-transcoded) format options
- for (const fmt of customFormats) {
- allOptions.push({ value: fmt.internalName, text: fmt.displayName, category: fmt.category });
+ for (const [key, fmt] of Object.entries(customFormats)) {
+ allOptions.push({ value: key, text: fmt.displayName, category: fmt.category });
}
// Sort by category order first, then by bitrate descending within each category
@@ -877,7 +877,7 @@ export function initializeSettings(scrobbler, player, api, ui) {
}
if (losslessContainerSetting) {
- for (const { internalName, displayName } of containerFormats) {
+ for (const [internalName, { displayName }] of Object.entries(containerFormats)) {
const option = document.createElement('option');
option.value = internalName;
option.textContent = displayName;
From 37a74ad7555aadc5c416feb334d3655869a717b2 Mon Sep 17 00:00:00 2001
From: Daniel <790119+DanTheMan827@users.noreply.github.com>
Date: Thu, 12 Mar 2026 16:02:44 +0000
Subject: [PATCH 060/226] refactor(downloads/ffmpeg): refactor ffmpeg usage and
add additional logging for ffmpeg
---
index.html | 4 +++-
js/download-utils.ts | 24 ++++++++++++++++--------
js/ffmpeg.js | 26 +++++++++++++++++++++++---
js/ffmpeg.worker.js | 12 +++++++++---
js/ffmpegFormats.ts | 12 ++----------
js/settings.js | 5 +++++
6 files changed, 58 insertions(+), 25 deletions(-)
diff --git a/index.html b/index.html
index dfafb6da1..4db618468 100644
--- a/index.html
+++ b/index.html
@@ -5139,7 +5139,9 @@ Custom Theme
Lossless Container
Container format for lossless downloads
-
+
+ Don't change
+
diff --git a/js/download-utils.ts b/js/download-utils.ts
index 763a78c36..c5b17da3e 100644
--- a/js/download-utils.ts
+++ b/js/download-utils.ts
@@ -1,6 +1,6 @@
import { losslessContainerSettings } from './storage';
-import { rebuildFlacWithoutMetadata } from './metadata.flac';
import { getExtensionFromBlob } from './utils';
+import { rebuildFlacWithoutMetadata } from './metadata.flac.js';
import {
type ProgressEvent,
isCustomFormat,
@@ -9,7 +9,7 @@ import {
getContainerFormat,
transcodeWithContainerFormat,
} from './ffmpegFormats';
-import { ffmpeg } from './ffmpeg';
+import { ffmpegNewContainer } from './ffmpeg';
/**
* Triggers a browser file download for the given blob.
@@ -60,12 +60,20 @@ export async function applyAudioPostProcessing(
if (quality.endsWith('LOSSLESS')) {
try {
const containerFmt = getContainerFormat(losslessContainerSettings.getContainer());
- if (containerFmt) {
- if (await containerFmt.needsTranscode(blob)) {
- blob = await transcodeWithContainerFormat(blob, containerFmt, onProgress, signal);
- } else if ((await getExtensionFromBlob(blob)) === 'flac') {
- blob = await rebuildFlacWithoutMetadata(blob);
- }
+ const extension = await getExtensionFromBlob(blob);
+
+ if (await containerFmt?.needsTranscode(blob)) {
+ blob = await transcodeWithContainerFormat(blob, containerFmt, onProgress, signal);
+ } else if (extension == 'flac') {
+ blob = await rebuildFlacWithoutMetadata(blob);
+ } else {
+ blob = await ffmpegNewContainer(
+ blob,
+ extension == 'm4a' ? 'mp4' : extension,
+ blob.type,
+ onProgress,
+ signal
+ );
}
} catch (error) {
if ((error as Error)?.name === 'AbortError') {
diff --git a/js/ffmpeg.js b/js/ffmpeg.js
index d62e71310..28193fd50 100644
--- a/js/ffmpeg.js
+++ b/js/ffmpeg.js
@@ -27,7 +27,7 @@ export function loadFfmpeg() {
async function ffmpegWorker(
audioBlob,
- args = {},
+ args = [],
outputName = 'output',
outputMime = 'application/octet-stream',
onProgress = null,
@@ -93,7 +93,7 @@ async function ffmpegWorker(
{
audioData,
extraFiles,
- ...args,
+ args,
output: {
name: outputName,
mime: outputMime,
@@ -108,7 +108,7 @@ async function ffmpegWorker(
export async function ffmpeg(
audioBlob,
- args = {},
+ args = [],
outputName = 'output',
outputMime = 'application/octet-stream',
onProgress = null,
@@ -128,4 +128,24 @@ export async function ffmpeg(
}
}
+/**
+ * Creates a new FFmpeg container with copied codec and stripped metadata.
+ * @param {Blob} audioBlob - The audio blob to process
+ * @param {string} outputExtension - The extension for the output file
+ * @param {string} outputMime - The MIME type for the output blob
+ * @param {Function} onProgress - Callback function to track conversion progress
+ * @param {AbortSignal} signal - AbortSignal for cancelling the operation
+ * @returns {Promise
} A promise that resolves to the processed data blob
+ */
+export async function ffmpegNewContainer(audioBlob, outputExtension, outputMime, onProgress, signal) {
+ return await ffmpeg(
+ audioBlob,
+ ['-map_metadata', '-1', '-c', 'copy', '-strict', '-2'],
+ `output.${outputExtension}`,
+ outputMime,
+ onProgress,
+ signal
+ );
+}
+
export { FfmpegError };
diff --git a/js/ffmpeg.worker.js b/js/ffmpeg.worker.js
index b90082d31..e331ec6d1 100644
--- a/js/ffmpeg.worker.js
+++ b/js/ffmpeg.worker.js
@@ -141,15 +141,21 @@ self.onmessage = async (e) => {
} finally {
try {
if (audioData) await ffmpeg.deleteFile('input');
- } catch {}
+ } catch {
+ self.postMessage({ type: 'log', message: 'Failed to delete input file from FFmpeg FS.' });
+ }
for (const file of extraFiles) {
try {
await ffmpeg.deleteFile(file.name);
- } catch {}
+ } catch {
+ self.postMessage({ type: 'log', message: `Failed to delete ${file.name} from FFmpeg FS.` });
+ }
}
try {
await ffmpeg.deleteFile(output.name);
- } catch {}
+ } catch {
+ self.postMessage({ type: 'log', message: `Failed to delete ${output.name} from FFmpeg FS.` });
+ }
}
} catch (error) {
self.postMessage({ type: 'error', message: error.message });
diff --git a/js/ffmpegFormats.ts b/js/ffmpegFormats.ts
index 5fa25b535..ef6927728 100644
--- a/js/ffmpegFormats.ts
+++ b/js/ffmpegFormats.ts
@@ -154,14 +154,6 @@ export const containerFormats: Record = {
extension: 'm4a',
needsTranscode: async () => true,
},
- nochange: {
- displayName: "Don't change",
- ffmpegArgs: ['-c:a', 'copy', '-strict', '-2'],
- outputFilename: 'output.mp4',
- outputMime: 'audio/mp4',
- extension: 'mp4',
- needsTranscode: async (blob) => (await getExtensionFromBlob(blob)) == 'm4a',
- },
};
/** Returns true if the quality string identifies a known custom ffmpeg-transcoded format */
@@ -192,7 +184,7 @@ export async function transcodeWithCustomFormat(
): Promise {
return ffmpeg(
audioBlob,
- { args: format.ffmpegArgs },
+ format.ffmpegArgs,
format.outputFilename,
format.outputMime,
onProgress,
@@ -214,7 +206,7 @@ export async function transcodeWithContainerFormat(
): Promise {
return ffmpeg(
audioBlob,
- { args: format.ffmpegArgs },
+ format.ffmpegArgs,
format.outputFilename,
format.outputMime,
onProgress,
diff --git a/js/settings.js b/js/settings.js
index 8cbe24070..191ea414f 100644
--- a/js/settings.js
+++ b/js/settings.js
@@ -877,6 +877,9 @@ export function initializeSettings(scrobbler, player, api, ui) {
}
if (losslessContainerSetting) {
+ const noChangeOption = losslessContainerSetting.querySelector('option:last-child');
+ noChangeOption.remove();
+
for (const [internalName, { displayName }] of Object.entries(containerFormats)) {
const option = document.createElement('option');
option.value = internalName;
@@ -884,6 +887,8 @@ export function initializeSettings(scrobbler, player, api, ui) {
losslessContainerSetting.appendChild(option);
}
+ losslessContainerSetting.append(noChangeOption);
+
losslessContainerSetting.value = losslessContainerSettings.getContainer();
losslessContainerSetting.addEventListener('change', (e) => {
From b31be7dc8094bf9ffee254b80da8d3564ec7c319 Mon Sep 17 00:00:00 2001
From: edideaur
Date: Thu, 12 Mar 2026 19:35:23 +0000
Subject: [PATCH 061/226] Fix bulk download edge cases and improve robustness
- FolderPickerWriter: throw AbortError on cancel instead of returning null
- FolderPickerWriter: add try/catch with abort() to release file locks on failure
- ZipNeutralinoWriter: move writeBinaryFile after response.body validation
- bulkDownloadSettings: migrate legacy key and validate stored values
- download-utils: catch ffmpeg cancellation via signal.aborted
- downloads.js: use consistent Neutralino detection with bridge module
- download-utils: use strict equality for flac extension check
---
js/bulk-download-writer.ts | 53 ++++++++++++++++++++++----------------
js/download-utils.ts | 4 +--
js/downloads.js | 14 +++++++---
js/storage.js | 14 +++++++++-
4 files changed, 57 insertions(+), 28 deletions(-)
diff --git a/js/bulk-download-writer.ts b/js/bulk-download-writer.ts
index 49d84135a..24a236773 100644
--- a/js/bulk-download-writer.ts
+++ b/js/bulk-download-writer.ts
@@ -98,11 +98,11 @@ export class ZipNeutralinoWriter implements IBulkDownloadWriter {
}
const { downloadZip } = await loadClientZip();
- await bridge.filesystem.writeBinaryFile(savePath, new ArrayBuffer(0));
-
const response = downloadZip(files);
if (!response.body) throw new Error('ZIP response body is null');
+ await bridge.filesystem.writeBinaryFile(savePath, new ArrayBuffer(0));
+
const reader = response.body.getReader();
let receivedLength = 0;
@@ -138,10 +138,17 @@ export class FolderPickerWriter implements IBulkDownloadWriter {
static async create(): Promise {
// showDirectoryPicker is part of the File System Access API (not yet in all TS DOM libs)
// eslint-disable-next-line @typescript-eslint/no-explicit-any
- const dirHandle: FileSystemDirectoryHandle = await (window as any).showDirectoryPicker({
- mode: 'readwrite',
- });
- return new FolderPickerWriter(dirHandle);
+ try {
+ const dirHandle: FileSystemDirectoryHandle = await (window as any).showDirectoryPicker({
+ mode: 'readwrite',
+ });
+ return new FolderPickerWriter(dirHandle);
+ } catch (error) {
+ if (error instanceof DOMException && error.name === 'AbortError') {
+ throw error;
+ }
+ throw new DOMException('User cancelled directory picker', 'AbortError');
+ }
}
async write(files: AsyncIterable): Promise {
@@ -158,23 +165,25 @@ export class FolderPickerWriter implements IBulkDownloadWriter {
const fileHandle = await currentDir.getFileHandle(filename, { create: true });
const writable = await fileHandle.createWritable();
- const { input } = file;
- if (input instanceof Blob) {
- await writable.write(input);
- } else if (typeof input === 'string') {
- await writable.write(new Blob([input], { type: 'text/plain' }));
- } else {
- // ArrayBuffer or Uint8Array – wrap in a Blob to guarantee strict typing.
- // Use byteOffset/byteLength so only the view's range is included, not the
- // whole backing ArrayBuffer (which may be larger due to pooling).
- const buf =
- input instanceof Uint8Array
- ? input.buffer.slice(input.byteOffset, input.byteOffset + input.byteLength)
- : input;
- await writable.write(new Blob([buf as ArrayBuffer]));
+ try {
+ const { input } = file;
+ if (input instanceof Blob) {
+ await writable.write(input);
+ } else if (typeof input === 'string') {
+ await writable.write(new Blob([input], { type: 'text/plain' }));
+ } else {
+ const buf =
+ input instanceof Uint8Array
+ ? input.buffer.slice(input.byteOffset, input.byteOffset + input.byteLength)
+ : input;
+ await writable.write(new Blob([buf as ArrayBuffer]));
+ }
+
+ await writable.close();
+ } catch (error) {
+ await writable.abort();
+ throw error;
}
-
- await writable.close();
}
}
}
diff --git a/js/download-utils.ts b/js/download-utils.ts
index c5b17da3e..61c7b6f36 100644
--- a/js/download-utils.ts
+++ b/js/download-utils.ts
@@ -64,7 +64,7 @@ export async function applyAudioPostProcessing(
if (await containerFmt?.needsTranscode(blob)) {
blob = await transcodeWithContainerFormat(blob, containerFmt, onProgress, signal);
- } else if (extension == 'flac') {
+ } else if (extension === 'flac') {
blob = await rebuildFlacWithoutMetadata(blob);
} else {
blob = await ffmpegNewContainer(
@@ -76,7 +76,7 @@ export async function applyAudioPostProcessing(
);
}
} catch (error) {
- if ((error as Error)?.name === 'AbortError') {
+ if ((error as Error)?.name === 'AbortError' || signal?.aborted) {
throw error;
}
diff --git a/js/downloads.js b/js/downloads.js
index 29821fe05..5b178def0 100644
--- a/js/downloads.js
+++ b/js/downloads.js
@@ -669,7 +669,9 @@ async function bulkDownloadToZip(
* or null when individual sequential downloads should be used.
*/
async function createBulkWriter(folderName) {
- const isNeutralino = window.NL_MODE === true;
+ const isNeutralino =
+ typeof window !== 'undefined' &&
+ (window.NL_MODE || window.location.search.includes('mode=neutralino') || window.parent !== window);
const method = bulkDownloadSettings.getMethod();
const forceZipBlob = bulkDownloadSettings.shouldForceZipBlob();
const hasFileSystemAccess = 'showSaveFilePicker' in window && 'createWritable' in FileSystemFileHandle.prototype;
@@ -679,8 +681,14 @@ async function createBulkWriter(folderName) {
return new ZipNeutralinoWriter(folderName);
}
if (method === 'folder' && hasFolderPicker) {
- // FolderPickerWriter.create() throws AbortError if the user cancels
- return await FolderPickerWriter.create();
+ try {
+ return await FolderPickerWriter.create();
+ } catch (error) {
+ if (error instanceof DOMException && error.name === 'AbortError') {
+ throw error;
+ }
+ return null;
+ }
}
if (method === 'individual') {
return null;
diff --git a/js/storage.js b/js/storage.js
index 62e594cf2..abcca0a9c 100644
--- a/js/storage.js
+++ b/js/storage.js
@@ -637,11 +637,23 @@ export const trackDateSettings = {
export const bulkDownloadSettings = {
METHOD_KEY: 'bulk-download-method',
FORCE_ZIP_BLOB_KEY: 'bulk-download-force-zip-blob',
+ LEGACY_INDIVIDUAL_KEY: 'force-individual-downloads',
+ VALID_METHODS: ['zip', 'folder', 'individual'],
/** Returns the selected bulk download method: 'zip' | 'folder' | 'individual' */
getMethod() {
try {
- return localStorage.getItem(this.METHOD_KEY) || 'zip';
+ const stored = localStorage.getItem(this.METHOD_KEY);
+ if (stored && this.VALID_METHODS.includes(stored)) {
+ return stored;
+ }
+ const legacy = localStorage.getItem(this.LEGACY_INDIVIDUAL_KEY);
+ if (legacy === 'true') {
+ localStorage.setItem(this.METHOD_KEY, 'individual');
+ localStorage.removeItem(this.LEGACY_INDIVIDUAL_KEY);
+ return 'individual';
+ }
+ return 'zip';
} catch {
return 'zip';
}
From ed6753d15e52f1a84ab84c21b259330673826c07 Mon Sep 17 00:00:00 2001
From: edideaur
Date: Thu, 12 Mar 2026 19:37:27 +0000
Subject: [PATCH 062/226] Update lockfile
---
bun.lock | 24 +++++++++++++++---------
1 file changed, 15 insertions(+), 9 deletions(-)
diff --git a/bun.lock b/bun.lock
index 0701e6896..7df198d39 100644
--- a/bun.lock
+++ b/bun.lock
@@ -20,7 +20,7 @@
"fuse.js": "^7.1.0",
"hls.js": "^1.6.15",
"jose": "^6.2.0",
- "npm": "^11.6.0",
+ "npm": "^11.11.1",
"pocketbase": "^0.26.8",
"taglib-wasm": "^1.0.5",
"uuid": "^13.0.0",
@@ -1095,7 +1095,7 @@
"normalize-path": ["normalize-path@3.0.0", "", {}, "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA=="],
- "npm": ["npm@11.11.0", "", { "dependencies": { "@isaacs/string-locale-compare": "^1.1.0", "@npmcli/arborist": "^9.4.0", "@npmcli/config": "^10.7.1", "@npmcli/fs": "^5.0.0", "@npmcli/map-workspaces": "^5.0.3", "@npmcli/metavuln-calculator": "^9.0.3", "@npmcli/package-json": "^7.0.5", "@npmcli/promise-spawn": "^9.0.1", "@npmcli/redact": "^4.0.0", "@npmcli/run-script": "^10.0.3", "@sigstore/tuf": "^4.0.1", "abbrev": "^4.0.0", "archy": "~1.0.0", "cacache": "^20.0.3", "chalk": "^5.6.2", "ci-info": "^4.4.0", "fastest-levenshtein": "^1.0.16", "fs-minipass": "^3.0.3", "glob": "^13.0.6", "graceful-fs": "^4.2.11", "hosted-git-info": "^9.0.2", "ini": "^6.0.0", "init-package-json": "^8.2.5", "is-cidr": "^6.0.3", "json-parse-even-better-errors": "^5.0.0", "libnpmaccess": "^10.0.3", "libnpmdiff": "^8.1.3", "libnpmexec": "^10.2.3", "libnpmfund": "^7.0.17", "libnpmorg": "^8.0.1", "libnpmpack": "^9.1.3", "libnpmpublish": "^11.1.3", "libnpmsearch": "^9.0.1", "libnpmteam": "^8.0.2", "libnpmversion": "^8.0.3", "make-fetch-happen": "^15.0.4", "minimatch": "^10.2.2", "minipass": "^7.1.3", "minipass-pipeline": "^1.2.4", "ms": "^2.1.2", "node-gyp": "^12.2.0", "nopt": "^9.0.0", "npm-audit-report": "^7.0.0", "npm-install-checks": "^8.0.0", "npm-package-arg": "^13.0.2", "npm-pick-manifest": "^11.0.3", "npm-profile": "^12.0.1", "npm-registry-fetch": "^19.1.1", "npm-user-validate": "^4.0.0", "p-map": "^7.0.4", "pacote": "^21.4.0", "parse-conflict-json": "^5.0.1", "proc-log": "^6.1.0", "qrcode-terminal": "^0.12.0", "read": "^5.0.1", "semver": "^7.7.4", "spdx-expression-parse": "^4.0.0", "ssri": "^13.0.1", "supports-color": "^10.2.2", "tar": "^7.5.9", "text-table": "~0.2.0", "tiny-relative-date": "^2.0.2", "treeverse": "^3.0.0", "validate-npm-package-name": "^7.0.2", "which": "^6.0.1" }, "bin": { "npm": "bin/npm-cli.js", "npx": "bin/npx-cli.js" } }, "sha512-82gRxKrh/eY5UnNorkTFcdBQAGpgjWehkfGVqAGlJjejEtJZGGJUqjo3mbBTNbc5BTnPKGVtGPBZGhElujX5cw=="],
+ "npm": ["npm@11.11.1", "", { "dependencies": { "@isaacs/string-locale-compare": "^1.1.0", "@npmcli/arborist": "^9.4.1", "@npmcli/config": "^10.7.1", "@npmcli/fs": "^5.0.0", "@npmcli/map-workspaces": "^5.0.3", "@npmcli/metavuln-calculator": "^9.0.3", "@npmcli/package-json": "^7.0.5", "@npmcli/promise-spawn": "^9.0.1", "@npmcli/redact": "^4.0.0", "@npmcli/run-script": "^10.0.4", "@sigstore/tuf": "^4.0.1", "abbrev": "^4.0.0", "archy": "~1.0.0", "cacache": "^20.0.3", "chalk": "^5.6.2", "ci-info": "^4.4.0", "fastest-levenshtein": "^1.0.16", "fs-minipass": "^3.0.3", "glob": "^13.0.6", "graceful-fs": "^4.2.11", "hosted-git-info": "^9.0.2", "ini": "^6.0.0", "init-package-json": "^8.2.5", "is-cidr": "^6.0.3", "json-parse-even-better-errors": "^5.0.0", "libnpmaccess": "^10.0.3", "libnpmdiff": "^8.1.4", "libnpmexec": "^10.2.4", "libnpmfund": "^7.0.18", "libnpmorg": "^8.0.1", "libnpmpack": "^9.1.4", "libnpmpublish": "^11.1.3", "libnpmsearch": "^9.0.1", "libnpmteam": "^8.0.2", "libnpmversion": "^8.0.3", "make-fetch-happen": "^15.0.4", "minimatch": "^10.2.4", "minipass": "^7.1.3", "minipass-pipeline": "^1.2.4", "ms": "^2.1.2", "node-gyp": "^12.2.0", "nopt": "^9.0.0", "npm-audit-report": "^7.0.0", "npm-install-checks": "^8.0.0", "npm-package-arg": "^13.0.2", "npm-pick-manifest": "^11.0.3", "npm-profile": "^12.0.1", "npm-registry-fetch": "^19.1.1", "npm-user-validate": "^4.0.0", "p-map": "^7.0.4", "pacote": "^21.5.0", "parse-conflict-json": "^5.0.1", "proc-log": "^6.1.0", "qrcode-terminal": "^0.12.0", "read": "^5.0.1", "semver": "^7.7.4", "spdx-expression-parse": "^4.0.0", "ssri": "^13.0.1", "supports-color": "^10.2.2", "tar": "^7.5.11", "text-table": "~0.2.0", "tiny-relative-date": "^2.0.2", "treeverse": "^3.0.0", "validate-npm-package-name": "^7.0.2", "which": "^6.0.1" }, "bin": { "npm": "bin/npm-cli.js", "npx": "bin/npx-cli.js" } }, "sha512-asazCodkFdz1ReQzukyzS/DD77uGCIqUFeRG3gtaT8b9UR0ne1m9QOBuMgT72ij1rt7TRrOox4A1WzntMWIuEg=="],
"object-inspect": ["object-inspect@1.13.4", "", {}, "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew=="],
@@ -1547,7 +1547,7 @@
"npm/@npmcli/agent": ["@npmcli/agent@4.0.0", "", { "dependencies": { "agent-base": "^7.1.0", "http-proxy-agent": "^7.0.0", "https-proxy-agent": "^7.0.1", "lru-cache": "^11.2.1", "socks-proxy-agent": "^8.0.3" } }, "sha512-kAQTcEN9E8ERLVg5AsGwLNoFb+oEG6engbqAU2P43gD4JEIkNGMHdVQ096FsOAAYpZPB0RSt0zgInKIAS1l5QA=="],
- "npm/@npmcli/arborist": ["@npmcli/arborist@9.4.0", "", { "dependencies": { "@isaacs/string-locale-compare": "^1.1.0", "@npmcli/fs": "^5.0.0", "@npmcli/installed-package-contents": "^4.0.0", "@npmcli/map-workspaces": "^5.0.0", "@npmcli/metavuln-calculator": "^9.0.2", "@npmcli/name-from-folder": "^4.0.0", "@npmcli/node-gyp": "^5.0.0", "@npmcli/package-json": "^7.0.0", "@npmcli/query": "^5.0.0", "@npmcli/redact": "^4.0.0", "@npmcli/run-script": "^10.0.0", "bin-links": "^6.0.0", "cacache": "^20.0.1", "common-ancestor-path": "^2.0.0", "hosted-git-info": "^9.0.0", "json-stringify-nice": "^1.1.4", "lru-cache": "^11.2.1", "minimatch": "^10.0.3", "nopt": "^9.0.0", "npm-install-checks": "^8.0.0", "npm-package-arg": "^13.0.0", "npm-pick-manifest": "^11.0.1", "npm-registry-fetch": "^19.0.0", "pacote": "^21.0.2", "parse-conflict-json": "^5.0.1", "proc-log": "^6.0.0", "proggy": "^4.0.0", "promise-all-reject-late": "^1.0.0", "promise-call-limit": "^3.0.1", "semver": "^7.3.7", "ssri": "^13.0.0", "treeverse": "^3.0.0", "walk-up-path": "^4.0.0" }, "bundled": true, "bin": { "arborist": "bin/index.js" } }, "sha512-4Bm8hNixJG/sii1PMnag0V9i/sGOX9VRzFrUiZMSBJpGlLR38f+Btl85d07G9GL56xO0l0OZjvrGNYsDYp0xKA=="],
+ "npm/@npmcli/arborist": ["@npmcli/arborist@9.4.1", "", { "dependencies": { "@gar/promise-retry": "^1.0.0", "@isaacs/string-locale-compare": "^1.1.0", "@npmcli/fs": "^5.0.0", "@npmcli/installed-package-contents": "^4.0.0", "@npmcli/map-workspaces": "^5.0.0", "@npmcli/metavuln-calculator": "^9.0.2", "@npmcli/name-from-folder": "^4.0.0", "@npmcli/node-gyp": "^5.0.0", "@npmcli/package-json": "^7.0.0", "@npmcli/query": "^5.0.0", "@npmcli/redact": "^4.0.0", "@npmcli/run-script": "^10.0.0", "bin-links": "^6.0.0", "cacache": "^20.0.1", "common-ancestor-path": "^2.0.0", "hosted-git-info": "^9.0.0", "json-stringify-nice": "^1.1.4", "lru-cache": "^11.2.1", "minimatch": "^10.0.3", "nopt": "^9.0.0", "npm-install-checks": "^8.0.0", "npm-package-arg": "^13.0.0", "npm-pick-manifest": "^11.0.1", "npm-registry-fetch": "^19.0.0", "pacote": "^21.0.2", "parse-conflict-json": "^5.0.1", "proc-log": "^6.0.0", "proggy": "^4.0.0", "promise-all-reject-late": "^1.0.0", "promise-call-limit": "^3.0.1", "semver": "^7.3.7", "ssri": "^13.0.0", "treeverse": "^3.0.0", "walk-up-path": "^4.0.0" }, "bundled": true, "bin": { "arborist": "bin/index.js" } }, "sha512-SaXiFtYcAbzPI+VmuI+O6hii9fEVe36vm6XRAu0QcvCR9YphHfNF8PIDeDapVkE+LJ0c7BN7uPGd3plbh9zbrw=="],
"npm/@npmcli/config": ["@npmcli/config@10.7.1", "", { "dependencies": { "@npmcli/map-workspaces": "^5.0.0", "@npmcli/package-json": "^7.0.0", "ci-info": "^4.0.0", "ini": "^6.0.0", "nopt": "^9.0.0", "proc-log": "^6.0.0", "semver": "^7.3.5", "walk-up-path": "^4.0.0" }, "bundled": true }, "sha512-lh0eZYOknIpIKYKxbQKX7xFmb4FbmrOHUD25+0iEo3djRQP6YleHwBFgjH3X7QvUVM4t+Xm7rGsjDwJp63WkAg=="],
@@ -1679,15 +1679,15 @@
"npm/libnpmaccess": ["libnpmaccess@10.0.3", "", { "dependencies": { "npm-package-arg": "^13.0.0", "npm-registry-fetch": "^19.0.0" }, "bundled": true }, "sha512-JPHTfWJxIK+NVPdNMNGnkz4XGX56iijPbe0qFWbdt68HL+kIvSzh+euBL8npLZvl2fpaxo+1eZSdoG15f5YdIQ=="],
- "npm/libnpmdiff": ["libnpmdiff@8.1.3", "", { "dependencies": { "@npmcli/arborist": "^9.4.0", "@npmcli/installed-package-contents": "^4.0.0", "binary-extensions": "^3.0.0", "diff": "^8.0.2", "minimatch": "^10.0.3", "npm-package-arg": "^13.0.0", "pacote": "^21.0.2", "tar": "^7.5.1" }, "bundled": true }, "sha512-QZ9rpchNXSzvxTRHzEqxCfYBK2h+6j4J7IbBViBGy3xSJDBl026BCMhmlZQ0a69GeQkjkbM9X1hzRV9N5cdQog=="],
+ "npm/libnpmdiff": ["libnpmdiff@8.1.4", "", { "dependencies": { "@npmcli/arborist": "^9.4.1", "@npmcli/installed-package-contents": "^4.0.0", "binary-extensions": "^3.0.0", "diff": "^8.0.2", "minimatch": "^10.0.3", "npm-package-arg": "^13.0.0", "pacote": "^21.0.2", "tar": "^7.5.1" }, "bundled": true }, "sha512-AvkBTAHfW4C/YzdZYtQ9qDXCpHrIz4EISUOlOwX+IjZguuhj/uniFg1K6m7kCfp9EsFK/K1U2mhzw2VyNfJ3Vg=="],
- "npm/libnpmexec": ["libnpmexec@10.2.3", "", { "dependencies": { "@gar/promise-retry": "^1.0.0", "@npmcli/arborist": "^9.4.0", "@npmcli/package-json": "^7.0.0", "@npmcli/run-script": "^10.0.0", "ci-info": "^4.0.0", "npm-package-arg": "^13.0.0", "pacote": "^21.0.2", "proc-log": "^6.0.0", "read": "^5.0.1", "semver": "^7.3.7", "signal-exit": "^4.1.0", "walk-up-path": "^4.0.0" }, "bundled": true }, "sha512-tCeneLdUhmn8GTORbui7QZrr1Rv8Y2/mQRwMjUeyY8IrhCjv29RkoH3gFz+1CCPGGMp26eT8KI977G74+rXMpw=="],
+ "npm/libnpmexec": ["libnpmexec@10.2.4", "", { "dependencies": { "@gar/promise-retry": "^1.0.0", "@npmcli/arborist": "^9.4.1", "@npmcli/package-json": "^7.0.0", "@npmcli/run-script": "^10.0.0", "ci-info": "^4.0.0", "npm-package-arg": "^13.0.0", "pacote": "^21.0.2", "proc-log": "^6.0.0", "read": "^5.0.1", "semver": "^7.3.7", "signal-exit": "^4.1.0", "walk-up-path": "^4.0.0" }, "bundled": true }, "sha512-kDfvrMN1aN7RTOKsRFtF83rhr5H0Frxp5ayMWWhZf6SMBv5wmyTZNRHomsKgcib/AsTUdmla8zIOtwpwcT+Suw=="],
- "npm/libnpmfund": ["libnpmfund@7.0.17", "", { "dependencies": { "@npmcli/arborist": "^9.4.0" }, "bundled": true }, "sha512-0VRPO+Bs21kneI3J01QqnuxiNnHn1lErTqLIbI3zGM9LvsPtc2q2/xhjACuXbkcejuHVm3T9mWaky0IjM9gQeQ=="],
+ "npm/libnpmfund": ["libnpmfund@7.0.18", "", { "dependencies": { "@npmcli/arborist": "^9.4.1" }, "bundled": true }, "sha512-eYwOtxFOgqHyPcNvp1UEHEFJdc7fj/s5C51R2E46bPYVya+KjV4pDIuQ1m9xJNBYKWPGNKkURLbuyp7BPm5E/w=="],
"npm/libnpmorg": ["libnpmorg@8.0.1", "", { "dependencies": { "aproba": "^2.0.0", "npm-registry-fetch": "^19.0.0" }, "bundled": true }, "sha512-/QeyXXg4hqMw0ESM7pERjIT2wbR29qtFOWIOug/xO4fRjS3jJJhoAPQNsnHtdwnCqgBdFpGQ45aIdFFZx2YhTA=="],
- "npm/libnpmpack": ["libnpmpack@9.1.3", "", { "dependencies": { "@npmcli/arborist": "^9.4.0", "@npmcli/run-script": "^10.0.0", "npm-package-arg": "^13.0.0", "pacote": "^21.0.2" }, "bundled": true }, "sha512-7Uvo0mDIidFCOGwZJghTuk9glaR6Es9FxmLWJobOS857/cb5SO5YPqgYLlC1TZB6L0c2jtu8XB1GfxKRf4W4GA=="],
+ "npm/libnpmpack": ["libnpmpack@9.1.4", "", { "dependencies": { "@npmcli/arborist": "^9.4.1", "@npmcli/run-script": "^10.0.0", "npm-package-arg": "^13.0.0", "pacote": "^21.0.2" }, "bundled": true }, "sha512-WJ4KArsfQ3h1rNIVw+1uRRzbp0oZS+k/E9M9Uw4g1SZz67SInp74TWYTknLepgahma/OJHDxlT2LinnHvtDk8g=="],
"npm/libnpmpublish": ["libnpmpublish@11.1.3", "", { "dependencies": { "@npmcli/package-json": "^7.0.0", "ci-info": "^4.0.0", "npm-package-arg": "^13.0.0", "npm-registry-fetch": "^19.0.0", "proc-log": "^6.0.0", "semver": "^7.3.7", "sigstore": "^4.0.0", "ssri": "^13.0.0" }, "bundled": true }, "sha512-NVPTth/71cfbdYHqypcO9Lt5WFGTzFEcx81lWd7GDJIgZ95ERdYHGUfCtFejHCyqodKsQkNEx2JCkMpreDty/A=="],
@@ -1749,7 +1749,7 @@
"npm/p-map": ["p-map@7.0.4", "", { "bundled": true }, "sha512-tkAQEw8ysMzmkhgw8k+1U/iPhWNhykKnSk4Rd5zLoPJCuJaGRPo6YposrZgaxHKzDHdDWWZvE/Sk7hsL2X/CpQ=="],
- "npm/pacote": ["pacote@21.4.0", "", { "dependencies": { "@gar/promise-retry": "^1.0.0", "@npmcli/git": "^7.0.0", "@npmcli/installed-package-contents": "^4.0.0", "@npmcli/package-json": "^7.0.0", "@npmcli/promise-spawn": "^9.0.0", "@npmcli/run-script": "^10.0.0", "cacache": "^20.0.0", "fs-minipass": "^3.0.0", "minipass": "^7.0.2", "npm-package-arg": "^13.0.0", "npm-packlist": "^10.0.1", "npm-pick-manifest": "^11.0.1", "npm-registry-fetch": "^19.0.0", "proc-log": "^6.0.0", "sigstore": "^4.0.0", "ssri": "^13.0.0", "tar": "^7.4.3" }, "bundled": true, "bin": { "pacote": "bin/index.js" } }, "sha512-DR7mn7HUOomAX1BORnpYy678qVIidbvOojkBscqy27dRKN+s/hLeQT1MeYYrx1Cxh62jyKjiWiDV7RTTqB+ZEQ=="],
+ "npm/pacote": ["pacote@21.5.0", "", { "dependencies": { "@gar/promise-retry": "^1.0.0", "@npmcli/git": "^7.0.0", "@npmcli/installed-package-contents": "^4.0.0", "@npmcli/package-json": "^7.0.0", "@npmcli/promise-spawn": "^9.0.0", "@npmcli/run-script": "^10.0.0", "cacache": "^20.0.0", "fs-minipass": "^3.0.0", "minipass": "^7.0.2", "npm-package-arg": "^13.0.0", "npm-packlist": "^10.0.1", "npm-pick-manifest": "^11.0.1", "npm-registry-fetch": "^19.0.0", "proc-log": "^6.0.0", "sigstore": "^4.0.0", "ssri": "^13.0.0", "tar": "^7.4.3" }, "bundled": true, "bin": { "pacote": "bin/index.js" } }, "sha512-VtZ0SB8mb5Tzw3dXDfVAIjhyVKUHZkS/ZH9/5mpKenwC9sFOXNI0JI7kEF7IMkwOnsWMFrvAZHzx1T5fmrp9FQ=="],
"npm/parse-conflict-json": ["parse-conflict-json@5.0.1", "", { "dependencies": { "json-parse-even-better-errors": "^5.0.0", "just-diff": "^6.0.0", "just-diff-apply": "^5.2.0" }, "bundled": true }, "sha512-ZHEmNKMq1wyJXNwLxyHnluPfRAFSIliBvbK/UiOceROt4Xh9Pz0fq49NytIaeaCUf5VR86hwQ/34FCcNU5/LKQ=="],
@@ -1803,7 +1803,7 @@
"npm/supports-color": ["supports-color@10.2.2", "", { "bundled": true }, "sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g=="],
- "npm/tar": ["tar@7.5.10", "", { "dependencies": { "@isaacs/fs-minipass": "^4.0.0", "chownr": "^3.0.0", "minipass": "^7.1.2", "minizlib": "^3.1.0", "yallist": "^5.0.0" }, "bundled": true }, "sha512-8mOPs1//5q/rlkNSPcCegA6hiHJYDmSLEI8aMH/CdSQJNWztHC9WHNam5zdQlfpTwB9Xp7IBEsHfV5LKMJGVAw=="],
+ "npm/tar": ["tar@7.5.11", "", { "dependencies": { "@isaacs/fs-minipass": "^4.0.0", "chownr": "^3.0.0", "minipass": "^7.1.2", "minizlib": "^3.1.0", "yallist": "^5.0.0" }, "bundled": true }, "sha512-ChjMH33/KetonMTAtpYdgUFr0tbz69Fp2v7zWxQfYZX4g5ZN2nOBXm1R2xyA+lMIKrLKIoKAwFj93jE/avX9cQ=="],
"npm/text-table": ["text-table@0.2.0", "", { "bundled": true }, "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw=="],
@@ -1871,10 +1871,14 @@
"glob/minimatch/brace-expansion": ["brace-expansion@5.0.4", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg=="],
+ "npm/@npmcli/metavuln-calculator/pacote": ["pacote@21.4.0", "", { "dependencies": { "@gar/promise-retry": "^1.0.0", "@npmcli/git": "^7.0.0", "@npmcli/installed-package-contents": "^4.0.0", "@npmcli/package-json": "^7.0.0", "@npmcli/promise-spawn": "^9.0.0", "@npmcli/run-script": "^10.0.0", "cacache": "^20.0.0", "fs-minipass": "^3.0.0", "minipass": "^7.0.2", "npm-package-arg": "^13.0.0", "npm-packlist": "^10.0.1", "npm-pick-manifest": "^11.0.1", "npm-registry-fetch": "^19.0.0", "proc-log": "^6.0.0", "sigstore": "^4.0.0", "ssri": "^13.0.0", "tar": "^7.4.3" }, "bin": { "pacote": "bin/index.js" } }, "sha512-DR7mn7HUOomAX1BORnpYy678qVIidbvOojkBscqy27dRKN+s/hLeQT1MeYYrx1Cxh62jyKjiWiDV7RTTqB+ZEQ=="],
+
"npm/minipass-flush/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="],
"npm/minipass-pipeline/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="],
+ "npm/node-gyp/tar": ["tar@7.5.10", "", { "dependencies": { "@isaacs/fs-minipass": "^4.0.0", "chownr": "^3.0.0", "minipass": "^7.1.2", "minizlib": "^3.1.0", "yallist": "^5.0.0" } }, "sha512-8mOPs1//5q/rlkNSPcCegA6hiHJYDmSLEI8aMH/CdSQJNWztHC9WHNam5zdQlfpTwB9Xp7IBEsHfV5LKMJGVAw=="],
+
"npm/promise-retry/retry": ["retry@0.12.0", "", {}, "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow=="],
"stylelint/file-entry-cache/flat-cache": ["flat-cache@6.1.20", "", { "dependencies": { "cacheable": "^2.3.2", "flatted": "^3.3.3", "hookified": "^1.15.0" } }, "sha512-AhHYqwvN62NVLp4lObVXGVluiABTHapoB57EyegZVmazN+hhGhLTn3uZbOofoTw4DSDvVCadzzyChXhOAvy8uQ=="],
@@ -1893,6 +1897,8 @@
"glob/minimatch/brace-expansion/balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="],
+ "npm/@npmcli/metavuln-calculator/pacote/tar": ["tar@7.5.10", "", { "dependencies": { "@isaacs/fs-minipass": "^4.0.0", "chownr": "^3.0.0", "minipass": "^7.1.2", "minizlib": "^3.1.0", "yallist": "^5.0.0" } }, "sha512-8mOPs1//5q/rlkNSPcCegA6hiHJYDmSLEI8aMH/CdSQJNWztHC9WHNam5zdQlfpTwB9Xp7IBEsHfV5LKMJGVAw=="],
+
"npm/minipass-flush/minipass/yallist": ["yallist@4.0.0", "", {}, "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="],
"npm/minipass-pipeline/minipass/yallist": ["yallist@4.0.0", "", {}, "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="],
From eb3c7f75c821efc1aa76b841639d0e2bbeb09037 Mon Sep 17 00:00:00 2001
From: edideaur
Date: Thu, 12 Mar 2026 20:18:24 +0000
Subject: [PATCH 063/226] fix samidys chud styling
---
index.html | 57 +++++++++++++++++++++++++++---------------------------
1 file changed, 28 insertions(+), 29 deletions(-)
diff --git a/index.html b/index.html
index 1251b3e41..a675bff69 100644
--- a/index.html
+++ b/index.html
@@ -1285,12 +1285,7 @@ Custom Database/Auth
PocketBase URL
-
+
Appwrite Endpoint
@@ -1752,7 +1747,8 @@
Support Monochrome
+
+
+
+
Go to Playlist
+
+
+
+
+ Cancel
+
+
+
+
@@ -3283,6 +3296,15 @@
Popular Tracks
+
+
+ In Your Library
+
+
+
+
+
+
Albums
diff --git a/js/db.js b/js/db.js
index 76477aa22..122155b92 100644
--- a/js/db.js
+++ b/js/db.js
@@ -172,11 +172,13 @@ export class MusicDatabase {
if (exists) {
await this.performTransaction(storeName, 'readwrite', (store) => store.delete(key));
+ window.dispatchEvent(new CustomEvent('favorites-changed'));
return false; // Removed
} else {
const minified = this._minifyItem(type, item);
const entry = { ...minified, addedAt: Date.now() };
await this.performTransaction(storeName, 'readwrite', (store) => store.put(entry));
+ window.dispatchEvent(new CustomEvent('favorites-changed'));
return true; // Added
}
}
@@ -600,6 +602,7 @@ export class MusicDatabase {
await this.performTransaction('user_playlists', 'readwrite', (store) => store.put(playlist));
this._dispatchPlaylistSync('update', playlist);
+ window.dispatchEvent(new CustomEvent('playlist-tracks-changed'));
return playlist;
}
@@ -623,6 +626,7 @@ export class MusicDatabase {
this._updatePlaylistMetadata(playlist);
await this.performTransaction('user_playlists', 'readwrite', (store) => store.put(playlist));
this._dispatchPlaylistSync('update', playlist);
+ window.dispatchEvent(new CustomEvent('playlist-tracks-changed'));
}
return playlist;
@@ -643,6 +647,7 @@ export class MusicDatabase {
await this.performTransaction('user_playlists', 'readwrite', (store) => store.put(playlist));
this._dispatchPlaylistSync('update', playlist);
+ window.dispatchEvent(new CustomEvent('playlist-tracks-changed'));
return playlist;
}
diff --git a/js/ui.js b/js/ui.js
index 19984348a..e363cc842 100644
--- a/js/ui.js
+++ b/js/ui.js
@@ -3072,10 +3072,10 @@ export class UIRenderer {
dateDisplay =
window.innerWidth > 768
? releaseDate.toLocaleDateString('en-US', {
- year: 'numeric',
- month: 'long',
- day: 'numeric',
- })
+ year: 'numeric',
+ month: 'long',
+ day: 'numeric',
+ })
: year;
}
}
@@ -3927,6 +3927,8 @@ export class UIRenderer {
const epsSection = document.getElementById('artist-section-eps');
const similarContainer = document.getElementById('artist-detail-similar');
const similarSection = document.getElementById('artist-section-similar');
+ const inLibraryContainer = document.getElementById('artist-detail-in-library');
+ const inLibrarySection = document.getElementById('artist-section-in-library');
const dlBtn = document.getElementById('download-discography-btn');
if (dlBtn) dlBtn.innerHTML = `${SVG_DOWNLOAD}Download Discography `;
@@ -3948,6 +3950,14 @@ export class UIRenderer {
if (loadUnreleasedSection) loadUnreleasedSection.style.display = 'none';
if (similarContainer) similarContainer.innerHTML = this.createSkeletonCards(6, true);
if (similarSection) similarSection.style.display = 'block';
+ if (inLibrarySection) inLibrarySection.style.display = 'none';
+ if (inLibraryContainer) {
+ inLibraryContainer.innerHTML = '';
+ inLibraryContainer.style.display = 'none';
+ }
+ // Reset chevron state
+ const chevronEl = document.getElementById('in-library-chevron');
+ if (chevronEl) chevronEl.style.transform = 'rotate(0deg)';
try {
const artist = await this.api.getArtist(artistId, provider);
@@ -4168,9 +4178,9 @@ export class UIRenderer {
${artist.popularity}% popularity
${(artist.artistRoles || [])
- .filter((role) => role.category)
- .map((role) => `${role.category} `)
- .join('')}
+ .filter((role) => role.category)
+ .map((role) => `${role.category} `)
+ .join('')}
`;
@@ -4182,6 +4192,205 @@ export class UIRenderer {
this.renderListWithTracks(tracksContainer, artist.tracks, true);
+ // "In your library" section: find liked tracks and playlist tracks for this artist
+ if (inLibraryContainer && inLibrarySection) {
+ const artistNameLower = artist.name.toLowerCase();
+
+ const isTrackByArtist = (track) => {
+ if (track.artists && Array.isArray(track.artists)) {
+ return track.artists.some(
+ (a) => a && a.name && a.name.toLowerCase() === artistNameLower
+ );
+ }
+ if (track.artist) {
+ const artistStr = typeof track.artist === 'string' ? track.artist : track.artist.name;
+ if (artistStr && artistStr.toLowerCase() === artistNameLower) return true;
+ }
+ return false;
+ };
+
+ const refreshInLibrary = async () => {
+ try {
+ const seenIds = new Set();
+ const libraryTracks = [];
+ const trackSourceMap = new Map(); // trackId -> Array<{ label, href }>
+
+ const addSource = (trackId, source) => {
+ if (!trackSourceMap.has(trackId)) {
+ trackSourceMap.set(trackId, []);
+ }
+ trackSourceMap.get(trackId).push(source);
+ };
+
+ // Get liked tracks
+ const likedTracks = await db.getFavorites('track');
+ for (const track of likedTracks) {
+ if (isTrackByArtist(track)) {
+ if (!seenIds.has(track.id)) {
+ seenIds.add(track.id);
+ libraryTracks.push(track);
+ }
+ addSource(track.id, { label: 'Liked Tracks', href: '/library' });
+ }
+ }
+
+ // Get tracks from user playlists
+ const userPlaylists = await db.getPlaylists(true);
+ for (const playlist of userPlaylists) {
+ if (playlist.tracks && Array.isArray(playlist.tracks)) {
+ for (const track of playlist.tracks) {
+ if (isTrackByArtist(track)) {
+ if (!seenIds.has(track.id)) {
+ seenIds.add(track.id);
+ libraryTracks.push(track);
+ }
+ const label = playlist.name || playlist.title || 'Playlist';
+ addSource(track.id, {
+ label,
+ href: `/userplaylist/${playlist.id}`
+ });
+ }
+ }
+ }
+ }
+
+ // Sort alphabetically by title
+ libraryTracks.sort((a, b) => (a.title || '').localeCompare(b.title || ''));
+
+ if (libraryTracks.length > 0) {
+ inLibrarySection.style.display = 'block';
+ this.renderListWithTracks(inLibraryContainer, libraryTracks, true);
+
+ // Inject source labels into each track's .artist div
+ const trackElements = inLibraryContainer.querySelectorAll('.track-item');
+ trackElements.forEach((el, idx) => {
+ const track = libraryTracks[idx];
+ if (!track) return;
+ const sources = trackSourceMap.get(track.id);
+ if (!sources || sources.length === 0) return;
+ const artistDiv = el.querySelector('.track-item-details .artist');
+ if (!artistDiv) return;
+
+ // Extract artist name and year from existing content
+ const artistLinks = artistDiv.querySelectorAll('.artist-link');
+ const artistNames = Array.from(artistLinks).map(a => a.textContent).join(', ');
+ const truncatedArtist = artistNames.length > 15 ? artistNames.slice(0, 20) + '…' : artistNames;
+
+ // Extract year from text content (pattern: " • 2024")
+ const fullText = artistDiv.textContent;
+ const yearMatch = fullText.match(/\s•\s(\d{4})/);
+ const yearText = yearMatch ? ` • ${yearMatch[1]}` : '';
+
+ // Build source content
+ const sourceSpan = document.createElement('span');
+ sourceSpan.className = 'library-source';
+
+ if (sources.length === 1) {
+ const srcLabel = sources[0].label.length > 15 ? sources[0].label.slice(0, 15) + '…' : sources[0].label;
+ sourceSpan.innerHTML = `· Source: ${srcLabel} `;
+ sourceSpan.style.cursor = 'pointer';
+ sourceSpan.addEventListener('click', (e) => {
+ e.preventDefault();
+ e.stopPropagation();
+ navigate(sources[0].href);
+ });
+ } else {
+ sourceSpan.innerHTML = `· Source: Multiple Playlists `;
+ sourceSpan.style.cursor = 'pointer';
+ sourceSpan.addEventListener('click', (e) => {
+ e.preventDefault();
+ e.stopPropagation();
+
+ const modal = document.getElementById('goto-playlist-modal');
+ const list = document.getElementById('goto-playlist-list');
+ const cancelBtn = document.getElementById('goto-playlist-cancel');
+ const overlay = modal.querySelector('.modal-overlay');
+
+ list.innerHTML = sources.map((s) =>
+ `
+ ${s.label}
+
`
+ ).join('');
+
+ const closeModal = () => {
+ modal.classList.remove('active');
+ };
+
+ list.onclick = (ev) => {
+ const option = ev.target.closest('.modal-option');
+ if (!option) return;
+ const href = option.dataset.href;
+ closeModal();
+ if (href) navigate(href);
+ };
+
+ cancelBtn.onclick = closeModal;
+ overlay.onclick = closeModal;
+ modal.classList.add('active');
+ });
+ }
+
+ // Rebuild artist div with structured layout
+ artistDiv.innerHTML = '';
+ artistDiv.classList.add('library-artist-flex');
+
+ const artistNameSpan = document.createElement('span');
+ artistNameSpan.className = 'library-artist-name';
+ artistNameSpan.textContent = truncatedArtist;
+
+ const yearSpan = document.createElement('span');
+ yearSpan.className = 'library-year';
+ yearSpan.textContent = yearText;
+
+ artistDiv.appendChild(artistNameSpan);
+ artistDiv.appendChild(yearSpan);
+ artistDiv.appendChild(sourceSpan);
+ });
+ } else {
+ inLibrarySection.style.display = 'none';
+ }
+ } catch (err) {
+ console.warn('Failed to load library tracks for artist:', err);
+ inLibrarySection.style.display = 'none';
+ }
+ };
+
+ // Initial load
+ refreshInLibrary().then(() => {
+ inLibraryContainer.style.display = 'none';
+ });
+
+ // Setup chevron toggle (once)
+ const toggle = document.getElementById('in-library-toggle');
+ const chevron = document.getElementById('in-library-chevron');
+ if (toggle) {
+ toggle.onclick = () => {
+ const isOpen = inLibraryContainer.style.display !== 'none';
+ inLibraryContainer.style.display = isOpen ? 'none' : '';
+ if (chevron) {
+ chevron.style.transform = isOpen ? 'rotate(0deg)' : 'rotate(90deg)';
+ }
+ };
+ }
+
+ // Real-time updates: refresh when favorites or playlists change
+ let refreshTimeout;
+ const debouncedRefresh = () => {
+ clearTimeout(refreshTimeout);
+ refreshTimeout = setTimeout(() => refreshInLibrary(), 300);
+ };
+ window.addEventListener('favorites-changed', debouncedRefresh);
+ window.addEventListener('playlist-tracks-changed', debouncedRefresh);
+
+ // Cleanup listeners when navigating away
+ const cleanupOnNav = () => {
+ window.removeEventListener('favorites-changed', debouncedRefresh);
+ window.removeEventListener('playlist-tracks-changed', debouncedRefresh);
+ window.removeEventListener('popstate', cleanupOnNav);
+ };
+ window.addEventListener('popstate', cleanupOnNav, { once: true });
+ }
+
// Update header like button
const artistLikeBtn = document.getElementById('like-artist-btn');
if (artistLikeBtn) {
@@ -4812,13 +5021,13 @@ export class UIRenderer {
${
isUser
- ? `
+ ? `
`
- : ''
+ : ''
}
diff --git a/styles.css b/styles.css
index c0ef4be95..71a5579bb 100644
--- a/styles.css
+++ b/styles.css
@@ -1988,6 +1988,58 @@ input[type='search']::-webkit-search-cancel-button {
gap: 2px var(--spacing-xl);
}
+#artist-detail-in-library {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(580px, 1fr));
+ gap: 2px var(--spacing-xl);
+}
+
+.library-source {
+ color: var(--muted-foreground);
+ font-size: inherit;
+ display: flex;
+ align-items: center;
+ min-width: 0;
+ flex-shrink: 1;
+ overflow: hidden;
+ cursor: pointer;
+}
+
+.library-artist-flex {
+ display: flex;
+ align-items: center;
+ gap: 0.35em;
+ min-width: 0;
+}
+
+.library-artist-name {
+ flex-shrink: 0;
+ white-space: nowrap;
+}
+
+.library-year {
+ flex-shrink: 0;
+ white-space: nowrap;
+}
+
+.library-source-label {
+ flex-shrink: 0;
+ white-space: nowrap;
+}
+
+.library-source-link {
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ min-width: 0;
+ transition: color var(--transition);
+}
+
+.library-source:hover .library-source-link {
+ color: var(--highlight);
+ text-decoration: underline;
+}
+
#playlist-detail-recommended .track-item {
grid-template-columns: 40px 1fr 32px auto;
}
@@ -1995,6 +2047,7 @@ input[type='search']::-webkit-search-cancel-button {
@media (max-width: 1100px) {
#home-recommended-songs,
#artist-detail-tracks,
+ #artist-detail-in-library,
#playlist-detail-recommended {
grid-template-columns: 1fr;
}
@@ -2313,6 +2366,8 @@ input[type='search']::-webkit-search-cancel-button {
gap: 1rem;
flex-wrap: wrap;
overflow-wrap: break-word;
+ word-break: break-word;
+ min-width: 0;
}
.detail-header-info .title.long-title {
From 43f816ad25393ae437bf858a6e69d9731dfd77bd Mon Sep 17 00:00:00 2001
From: Xenuel
Date: Mon, 16 Mar 2026 23:46:25 +0100
Subject: [PATCH 110/226] refactor(ui): improve accessibility and security in
"In Your Library" section
Replace h2 toggle with semantic button and aria-expanded attribute,
switch from style.display to hidden attribute for visibility control,
use DOM methods instead of innerHTML for source labels and modal
options to prevent XSS, improve artist matching with ID-based lookup,
and clean up event listeners before re-attaching to prevent leaks.
---
index.html | 14 ++++++------
js/db.js | 2 ++
js/ui.js | 63 +++++++++++++++++++++++++++++++++++++-----------------
styles.css | 8 +++++--
4 files changed, 59 insertions(+), 28 deletions(-)
diff --git a/index.html b/index.html
index ce0ac89c1..633d24370 100644
--- a/index.html
+++ b/index.html
@@ -3297,13 +3297,15 @@ Popular Tracks
-
- In Your Library
-
-
-
+
+
+ In Your Library
+
+
+
+
-
+
Albums
diff --git a/js/db.js b/js/db.js
index 122155b92..1924e49ad 100644
--- a/js/db.js
+++ b/js/db.js
@@ -585,6 +585,7 @@ export class MusicDatabase {
// TRIGGER SYNC
this._dispatchPlaylistSync('create', playlist);
+ window.dispatchEvent(new CustomEvent('playlist-tracks-changed'));
return playlist;
}
@@ -657,6 +658,7 @@ export class MusicDatabase {
// TRIGGER SYNC (but for deleting)
this._dispatchPlaylistSync('delete', { id: playlistId });
+ window.dispatchEvent(new CustomEvent('playlist-tracks-changed'));
}
async getPlaylist(playlistId) {
diff --git a/js/ui.js b/js/ui.js
index e363cc842..15a6017c3 100644
--- a/js/ui.js
+++ b/js/ui.js
@@ -3953,11 +3953,13 @@ export class UIRenderer {
if (inLibrarySection) inLibrarySection.style.display = 'none';
if (inLibraryContainer) {
inLibraryContainer.innerHTML = '';
- inLibraryContainer.style.display = 'none';
+ inLibraryContainer.hidden = true;
}
- // Reset chevron state
+ // Reset chevron and toggle state
const chevronEl = document.getElementById('in-library-chevron');
if (chevronEl) chevronEl.style.transform = 'rotate(0deg)';
+ const toggleBtn = document.getElementById('in-library-toggle');
+ if (toggleBtn) toggleBtn.setAttribute('aria-expanded', 'false');
try {
const artist = await this.api.getArtist(artistId, provider);
@@ -4199,12 +4201,16 @@ export class UIRenderer {
const isTrackByArtist = (track) => {
if (track.artists && Array.isArray(track.artists)) {
return track.artists.some(
- (a) => a && a.name && a.name.toLowerCase() === artistNameLower
+ (a) => a && ((artist.id && a.id === artist.id) || (a.name && a.name.toLowerCase() === artistNameLower))
);
}
if (track.artist) {
- const artistStr = typeof track.artist === 'string' ? track.artist : track.artist.name;
- if (artistStr && artistStr.toLowerCase() === artistNameLower) return true;
+ if (typeof track.artist === 'object') {
+ if (artist.id && track.artist.id === artist.id) return true;
+ if (track.artist.name && track.artist.name.toLowerCase() === artistNameLower) return true;
+ } else if (typeof track.artist === 'string') {
+ if (track.artist.toLowerCase() === artistNameLower) return true;
+ }
}
return false;
};
@@ -4285,18 +4291,27 @@ export class UIRenderer {
const sourceSpan = document.createElement('span');
sourceSpan.className = 'library-source';
+ const labelSpan = document.createElement('span');
+ labelSpan.className = 'library-source-label';
+ labelSpan.textContent = '· Source:\u00a0';
+
+ const linkSpan = document.createElement('span');
+ linkSpan.className = 'library-source-link';
+
+ sourceSpan.style.cursor = 'pointer';
+ sourceSpan.appendChild(labelSpan);
+ sourceSpan.appendChild(linkSpan);
+
if (sources.length === 1) {
const srcLabel = sources[0].label.length > 15 ? sources[0].label.slice(0, 15) + '…' : sources[0].label;
- sourceSpan.innerHTML = `· Source: ${srcLabel} `;
- sourceSpan.style.cursor = 'pointer';
+ linkSpan.textContent = srcLabel;
sourceSpan.addEventListener('click', (e) => {
e.preventDefault();
e.stopPropagation();
navigate(sources[0].href);
});
} else {
- sourceSpan.innerHTML = `· Source: Multiple Playlists `;
- sourceSpan.style.cursor = 'pointer';
+ linkSpan.textContent = 'Multiple Playlists';
sourceSpan.addEventListener('click', (e) => {
e.preventDefault();
e.stopPropagation();
@@ -4306,11 +4321,16 @@ export class UIRenderer {
const cancelBtn = document.getElementById('goto-playlist-cancel');
const overlay = modal.querySelector('.modal-overlay');
- list.innerHTML = sources.map((s) =>
- `
- ${s.label}
-
`
- ).join('');
+ list.innerHTML = '';
+ sources.forEach((s) => {
+ const option = document.createElement('div');
+ option.className = 'modal-option';
+ option.dataset.href = s.href;
+ const span = document.createElement('span');
+ span.textContent = s.label;
+ option.appendChild(span);
+ list.appendChild(option);
+ });
const closeModal = () => {
modal.classList.remove('active');
@@ -4357,7 +4377,7 @@ export class UIRenderer {
// Initial load
refreshInLibrary().then(() => {
- inLibraryContainer.style.display = 'none';
+ inLibraryContainer.hidden = true;
});
// Setup chevron toggle (once)
@@ -4365,8 +4385,9 @@ export class UIRenderer {
const chevron = document.getElementById('in-library-chevron');
if (toggle) {
toggle.onclick = () => {
- const isOpen = inLibraryContainer.style.display !== 'none';
- inLibraryContainer.style.display = isOpen ? 'none' : '';
+ const isOpen = !inLibraryContainer.hidden;
+ inLibraryContainer.hidden = isOpen;
+ toggle.setAttribute('aria-expanded', String(!isOpen));
if (chevron) {
chevron.style.transform = isOpen ? 'rotate(0deg)' : 'rotate(90deg)';
}
@@ -4379,15 +4400,17 @@ export class UIRenderer {
clearTimeout(refreshTimeout);
refreshTimeout = setTimeout(() => refreshInLibrary(), 300);
};
- window.addEventListener('favorites-changed', debouncedRefresh);
- window.addEventListener('playlist-tracks-changed', debouncedRefresh);
- // Cleanup listeners when navigating away
+ // Cleanup previous listeners before attaching new ones
const cleanupOnNav = () => {
window.removeEventListener('favorites-changed', debouncedRefresh);
window.removeEventListener('playlist-tracks-changed', debouncedRefresh);
window.removeEventListener('popstate', cleanupOnNav);
};
+ cleanupOnNav();
+
+ window.addEventListener('favorites-changed', debouncedRefresh);
+ window.addEventListener('playlist-tracks-changed', debouncedRefresh);
window.addEventListener('popstate', cleanupOnNav, { once: true });
}
diff --git a/styles.css b/styles.css
index 71a5579bb..1740d4fe6 100644
--- a/styles.css
+++ b/styles.css
@@ -1994,6 +1994,10 @@ input[type='search']::-webkit-search-cancel-button {
gap: 2px var(--spacing-xl);
}
+#artist-detail-in-library[hidden] {
+ display: none;
+}
+
.library-source {
color: var(--muted-foreground);
font-size: inherit;
@@ -2365,8 +2369,8 @@ input[type='search']::-webkit-search-cancel-button {
align-items: center;
gap: 1rem;
flex-wrap: wrap;
- overflow-wrap: break-word;
- word-break: break-word;
+ overflow-wrap: anywhere;
+ word-break: normal;
min-width: 0;
}
From 07d78798cdbd2fe6dde0810251b46eb6941b46bb Mon Sep 17 00:00:00 2001
From: Samidy
Date: Tue, 17 Mar 2026 04:36:49 +0300
Subject: [PATCH 111/226] feat(hot & new): metal genre
---
js/ui.js | 1 +
1 file changed, 1 insertion(+)
diff --git a/js/ui.js b/js/ui.js
index 87e21c3f5..7e9685b2a 100644
--- a/js/ui.js
+++ b/js/ui.js
@@ -1995,6 +1995,7 @@ export class UIRenderer {
{ id: 'blues', name: 'Blues' },
{ id: 'soundtrack', name: 'Soundtrack' },
{ id: 'alternative', name: 'Alternative' },
+ { id: 'metal', name: 'Metal'}
];
if (GENRES.length > 0) {
From 0f0cbb71ffa78ebaaf9e13b726c47d3ff8db9f14 Mon Sep 17 00:00:00 2001
From: Samidy
Date: Tue, 17 Mar 2026 05:57:30 +0300
Subject: [PATCH 112/226] meta: point mirrors back to main domain
---
index.html | 1 +
1 file changed, 1 insertion(+)
diff --git a/index.html b/index.html
index 03bac7810..2b24f5868 100644
--- a/index.html
+++ b/index.html
@@ -4,6 +4,7 @@
Monochrome Music
+
From 971b5c94bee59fff0c60566df6126f3b24c186ed Mon Sep 17 00:00:00 2001
From: Samidy
Date: Tue, 17 Mar 2026 06:04:27 +0300
Subject: [PATCH 113/226] Feat(Hot/New): Add R&B & Kids Genre Section
---
js/ui.js | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/js/ui.js b/js/ui.js
index 7e9685b2a..4cded74f8 100644
--- a/js/ui.js
+++ b/js/ui.js
@@ -1986,7 +1986,9 @@ export class UIRenderer {
{ id: 'hip_hop', name: 'Hip Hop / Rap' },
{ id: 'pop', name: 'Pop' },
{ id: 'rock', name: 'Rock' },
+ { id: 'rnb', name: 'R&B / Soul'},
{ id: 'electronic', name: 'Electronic' },
+ { id: 'metal', name: 'Metal'},
{ id: 'country', name: 'Country' },
{ id: 'jazz', name: 'Jazz' },
{ id: 'classical', name: 'Classical' },
@@ -1995,7 +1997,7 @@ export class UIRenderer {
{ id: 'blues', name: 'Blues' },
{ id: 'soundtrack', name: 'Soundtrack' },
{ id: 'alternative', name: 'Alternative' },
- { id: 'metal', name: 'Metal'}
+ { id: 'kids', name: 'Kids'}
];
if (GENRES.length > 0) {
From 6b1619d2c3fa11689ac74187608c7f41647c55f7 Mon Sep 17 00:00:00 2001
From: edidealt
Date: Tue, 17 Mar 2026 20:40:55 +0000
Subject: [PATCH 114/226] more generas in the hot and new section
---
js/ui.js | 62 +++++++++++++++++++++++++++++++++-----------------------
1 file changed, 37 insertions(+), 25 deletions(-)
diff --git a/js/ui.js b/js/ui.js
index fc3dd6356..de410d508 100644
--- a/js/ui.js
+++ b/js/ui.js
@@ -1983,21 +1983,24 @@ export class UIRenderer {
container.innerHTML = '';
const GENRES = [
- { id: 'hip_hop', name: 'Hip Hop / Rap' },
- { id: 'pop', name: 'Pop' },
- { id: 'rock', name: 'Rock' },
- { id: 'rnb', name: 'R&B / Soul'},
- { id: 'electronic', name: 'Electronic' },
- { id: 'metal', name: 'Metal'},
+ { id: 'hip_hop', name: 'Hip-Hop' },
+ { id: 'rnb', name: 'R&B / Soul' },
+ { id: 'blues', name: 'Blues' },
+ { id: 'classical', name: 'Classical' },
{ id: 'country', name: 'Country' },
+ { id: 'dance_electronic', name: 'Dance & Electronic' },
+ { id: 'americana', name: 'Folk / Americana' },
+ { id: 'world', name: 'Global' },
+ { id: 'gospel', name: 'Gospel / Christian' },
{ id: 'jazz', name: 'Jazz' },
- { id: 'classical', name: 'Classical' },
+ { id: 'kpop', name: 'K-Pop' },
+ { id: 'kids', name: 'Kids' },
{ id: 'latin', name: 'Latin' },
+ { id: 'metal', name: 'Metal' },
+ { id: 'pop', name: 'Pop' },
{ id: 'reggae', name: 'Reggae / Dancehall' },
- { id: 'blues', name: 'Blues' },
- { id: 'soundtrack', name: 'Soundtrack' },
- { id: 'alternative', name: 'Alternative' },
- { id: 'kids', name: 'Kids'}
+ { id: 'retro', name: 'Legacy' },
+ { id: 'indierock', name: 'Rock / Indie' },
];
if (GENRES.length > 0) {
@@ -3075,10 +3078,10 @@ export class UIRenderer {
dateDisplay =
window.innerWidth > 768
? releaseDate.toLocaleDateString('en-US', {
- year: 'numeric',
- month: 'long',
- day: 'numeric',
- })
+ year: 'numeric',
+ month: 'long',
+ day: 'numeric',
+ })
: year;
}
}
@@ -4183,9 +4186,9 @@ export class UIRenderer {
${artist.popularity}% popularity
${(artist.artistRoles || [])
- .filter((role) => role.category)
- .map((role) => `${role.category} `)
- .join('')}
+ .filter((role) => role.category)
+ .map((role) => `${role.category} `)
+ .join('')}
`;
@@ -4204,7 +4207,10 @@ export class UIRenderer {
const isTrackByArtist = (track) => {
if (track.artists && Array.isArray(track.artists)) {
return track.artists.some(
- (a) => a && ((artist.id && a.id === artist.id) || (a.name && a.name.toLowerCase() === artistNameLower))
+ (a) =>
+ a &&
+ ((artist.id && a.id === artist.id) ||
+ (a.name && a.name.toLowerCase() === artistNameLower))
);
}
if (track.artist) {
@@ -4256,7 +4262,7 @@ export class UIRenderer {
const label = playlist.name || playlist.title || 'Playlist';
addSource(track.id, {
label,
- href: `/userplaylist/${playlist.id}`
+ href: `/userplaylist/${playlist.id}`,
});
}
}
@@ -4282,8 +4288,11 @@ export class UIRenderer {
// Extract artist name and year from existing content
const artistLinks = artistDiv.querySelectorAll('.artist-link');
- const artistNames = Array.from(artistLinks).map(a => a.textContent).join(', ');
- const truncatedArtist = artistNames.length > 15 ? artistNames.slice(0, 20) + '…' : artistNames;
+ const artistNames = Array.from(artistLinks)
+ .map((a) => a.textContent)
+ .join(', ');
+ const truncatedArtist =
+ artistNames.length > 15 ? artistNames.slice(0, 20) + '…' : artistNames;
// Extract year from text content (pattern: " • 2024")
const fullText = artistDiv.textContent;
@@ -4306,7 +4315,10 @@ export class UIRenderer {
sourceSpan.appendChild(linkSpan);
if (sources.length === 1) {
- const srcLabel = sources[0].label.length > 15 ? sources[0].label.slice(0, 15) + '…' : sources[0].label;
+ const srcLabel =
+ sources[0].label.length > 15
+ ? sources[0].label.slice(0, 15) + '…'
+ : sources[0].label;
linkSpan.textContent = srcLabel;
sourceSpan.addEventListener('click', (e) => {
e.preventDefault();
@@ -5048,13 +5060,13 @@ export class UIRenderer {
diff --git a/js/settings.js b/js/settings.js
index 6f1db1524..19333ecb3 100644
--- a/js/settings.js
+++ b/js/settings.js
@@ -32,6 +32,7 @@ import {
pwaUpdateSettings,
contentBlockingSettings,
musicProviderSettings,
+ gaplessPlaybackSettings,
analyticsSettings,
modalSettings,
} from './storage.js';
@@ -980,6 +981,14 @@ export function initializeSettings(scrobbler, player, api, ui) {
});
}
+ const gaplessPlaybackToggle = document.getElementById('gapless-playback-toggle');
+ if (gaplessPlaybackToggle) {
+ gaplessPlaybackToggle.checked = gaplessPlaybackSettings.isEnabled();
+ gaplessPlaybackToggle.addEventListener('change', (e) => {
+ gaplessPlaybackSettings.setEnabled(e.target.checked);
+ });
+ }
+
// ReplayGain Settings
const replayGainMode = document.getElementById('replay-gain-mode');
if (replayGainMode) {
diff --git a/js/storage.js b/js/storage.js
index 6aaff651a..2b711f8cb 100644
--- a/js/storage.js
+++ b/js/storage.js
@@ -488,6 +488,23 @@ export const nowPlayingSettings = {
},
};
+export const gaplessPlaybackSettings = {
+ STORAGE_KEY: 'gapless-playback-enabled',
+
+ isEnabled() {
+ try {
+ const val = localStorage.getItem(this.STORAGE_KEY);
+ return val === null ? true : val === 'true';
+ } catch {
+ return true;
+ }
+ },
+
+ setEnabled(enabled) {
+ localStorage.setItem(this.STORAGE_KEY, enabled ? 'true' : 'false');
+ },
+};
+
export const fullscreenCoverClickSettings = {
STORAGE_KEY: 'fullscreen-cover-click-action',
From 48fc94895a792de63b5199376297666a4c95da5e Mon Sep 17 00:00:00 2001
From: Samidy
Date: Wed, 18 Mar 2026 04:39:17 +0300
Subject: [PATCH 116/226] split site building & desktop building
---
package.json | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/package.json b/package.json
index 74ff52c56..5ea8e153c 100644
--- a/package.json
+++ b/package.json
@@ -6,7 +6,8 @@
"scripts": {
"dev": "vite",
"dev:desktop": "start bun run dev & node scripts/dev-runner.js",
- "build": "vite build --mode neutralino && bun x neu build",
+ "build": "vite build",
+ "build:desktop": "bun x neu build",
"postbuild": "node -e \"const fs = require('fs'); const path = require('path'); const src = 'extensions'; const dest = path.join('dist', 'Monochrome', 'extensions'); if (fs.existsSync(src)) { fs.mkdirSync(dest, { recursive: true }); fs.cpSync(src, dest, { recursive: true }); console.log('Extensions manually copied to ' + dest); }\"",
"preview": "vite preview",
"lint:js": "eslint .",
From 992974d790ac20cda3af45b186898c5a86bff17b Mon Sep 17 00:00:00 2001
From: edidealt
Date: Thu, 19 Mar 2026 18:58:15 +0000
Subject: [PATCH 117/226] fix image uploads
---
js/app.js | 20 ++++++++++----------
js/profile.js | 23 +++++++++++++++--------
2 files changed, 25 insertions(+), 18 deletions(-)
diff --git a/js/app.js b/js/app.js
index 4ad3fff5b..961ebc826 100644
--- a/js/app.js
+++ b/js/app.js
@@ -357,21 +357,21 @@ async function disablePwaForAuthGate() {
async function uploadCoverImage(file) {
try {
- const formData = new FormData();
- formData.append('file', file);
-
- const response = await fetch('/upload', {
- method: 'POST',
- body: formData,
+ const response = await fetch(`https://worker.uploads.monochrome.qzz.io/${file.name}`, {
+ method: 'PUT',
+ headers: {
+ 'x-api-key': 'if_youre_reading_this_fuck_off',
+ 'Content-Type': file.type || 'application/octet-stream',
+ },
+ body: file,
});
if (!response.ok) {
- const error = await response.json();
- throw new Error(error.error || `Upload failed: ${response.status}`);
+ if (response.status === 413) throw new Error('File exceeds 10MB');
+ throw new Error(`Upload failed: ${response.status}`);
}
- const data = await response.json();
- return data.url;
+ return `https://images.monochrome.qzz.io/${await response.text()}`;
} catch (error) {
console.error('Cover upload error:', error);
throw error;
diff --git a/js/profile.js b/js/profile.js
index 2910c537b..6d9159dca 100644
--- a/js/profile.js
+++ b/js/profile.js
@@ -37,15 +37,22 @@ let currentFavoriteAlbums = [];
const api = new MusicAPI(apiSettings);
async function uploadImage(file) {
- const formData = new FormData();
- formData.append('file', file);
-
try {
- const response = await fetch('/upload', { method: 'POST', body: formData });
- if (!response.ok) throw new Error(`Upload failed: ${response.status}`);
- const data = await response.json();
- if (!data.success) throw new Error(data.error || 'Upload failed');
- return data.url;
+ const response = await fetch(`https://worker.uploads.monochrome.qzz.io/${file.name}`, {
+ method: 'PUT',
+ headers: {
+ 'x-api-key': 'if_youre_reading_this_fuck_off',
+ 'Content-Type': file.type || 'application/octet-stream',
+ },
+ body: file,
+ });
+
+ if (!response.ok) {
+ if (response.status === 413) throw new Error('File exceeds 10MB');
+ throw new Error(`Upload failed: ${response.status}`);
+ }
+
+ return `https://images.monochrome.qzz.io/${await response.text()}`;
} catch (error) {
console.error('Upload error:', error);
throw error;
From ade31919654695cb7ec029c605e67717dcaa040e Mon Sep 17 00:00:00 2001
From: Daniel <790119+DanTheMan827@users.noreply.github.com>
Date: Sun, 15 Mar 2026 16:14:23 -0500
Subject: [PATCH 118/226] feat(build): enable source maps for better debugging
- Added sourcemap option to the build configuration
---
vite.config.ts | 1 +
1 file changed, 1 insertion(+)
diff --git a/vite.config.ts b/vite.config.ts
index 8b2475f0b..60088bf2c 100644
--- a/vite.config.ts
+++ b/vite.config.ts
@@ -38,6 +38,7 @@ export default defineConfig(({ mode }) => {
build: {
outDir: 'dist',
emptyOutDir: true,
+ sourcemap: true,
},
plugins: [
IS_NEUTRALINO && neutralino(),
From a4b46c3520f3551970eb5b475342478e85cbad7a Mon Sep 17 00:00:00 2001
From: Daniel <790119+DanTheMan827@users.noreply.github.com>
Date: Mon, 16 Mar 2026 09:34:42 -0500
Subject: [PATCH 119/226] fix(lint): update permissions for workflow actions
- Changed permissions from 'workflows: write' to 'actions: write'
---
.github/workflows/lint.yml | 108 ++++++++++++++++++-------------------
1 file changed, 54 insertions(+), 54 deletions(-)
diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml
index 17177ef49..ce3c38feb 100644
--- a/.github/workflows/lint.yml
+++ b/.github/workflows/lint.yml
@@ -1,61 +1,61 @@
name: Lint Codebase
on:
- push:
- branches: [main]
- pull_request:
- branches: [main]
+ push:
+ branches: [main]
+ pull_request:
+ branches: [main]
permissions:
- contents: write
- workflows: write
+ contents: write
+ actions: write
jobs:
- lint:
- runs-on: ubuntu-latest
-
- steps:
- - name: Checkout code
- uses: actions/checkout@v4
- with:
- fetch-depth: 1
-
- - name: Setup Bun
- uses: oven-sh/setup-bun@v1
- with:
- bun-version: latest
-
- - name: Cache dependencies
- uses: actions/cache@v3
- with:
- path: |
- ./bun_modules
- ./node_modules
- ./bun.lock
- key: ${{ runner.os }}-bun-${{ hashFiles('bun.lock') }}
-
- - name: Install dependencies
- run: bun install --frozen-lockfile
-
- - name: Run JS Lint
- run: bun run lint:js -- --fix
- continue-on-error: true
-
- - name: Run CSS Lint
- run: bun run lint:css -- --fix
- continue-on-error: true
-
- - name: Format with Prettier
- run: bun run format
- continue-on-error: true
-
- - name: Commit and Push lint fixes
- uses: stefanzweifel/git-auto-commit-action@v5
- with:
- commit_message: 'style: auto-fix linting issues'
- commit_user_name: 'github-actions[bot]'
- commit_user_email: 'github-actions[bot]@users.noreply.github.com'
- only_if_changed: true
-
- - name: Run HTML Lint
- run: bun run lint:html
+ lint:
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v4
+ with:
+ fetch-depth: 1
+
+ - name: Setup Bun
+ uses: oven-sh/setup-bun@v1
+ with:
+ bun-version: latest
+
+ - name: Cache dependencies
+ uses: actions/cache@v3
+ with:
+ path: |
+ ./bun_modules
+ ./node_modules
+ ./bun.lock
+ key: ${{ runner.os }}-bun-${{ hashFiles('bun.lock') }}
+
+ - name: Install dependencies
+ run: bun install --frozen-lockfile
+
+ - name: Run JS Lint
+ run: bun run lint:js -- --fix
+ continue-on-error: true
+
+ - name: Run CSS Lint
+ run: bun run lint:css -- --fix
+ continue-on-error: true
+
+ - name: Format with Prettier
+ run: bun run format
+ continue-on-error: true
+
+ - name: Commit and Push lint fixes
+ uses: stefanzweifel/git-auto-commit-action@v5
+ with:
+ commit_message: 'style: auto-fix linting issues'
+ commit_user_name: 'github-actions[bot]'
+ commit_user_email: 'github-actions[bot]@users.noreply.github.com'
+ only_if_changed: true
+
+ - name: Run HTML Lint
+ run: bun run lint:html
From 2a4136a5f61faa24fbba10d72a6ea25bd10074c6 Mon Sep 17 00:00:00 2001
From: Daniel <790119+DanTheMan827@users.noreply.github.com>
Date: Fri, 13 Mar 2026 11:59:26 -0500
Subject: [PATCH 120/226] fix(downloads): progress index now shows correct
track number in bulk download
---
js/downloads.js | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/js/downloads.js b/js/downloads.js
index 0ef08d142..edbcbf95d 100644
--- a/js/downloads.js
+++ b/js/downloads.js
@@ -838,7 +838,7 @@ function updateBulkDownloadProgress(notifEl, current, total, currentItem, progre
const percent = progress.progress || 0;
progressFill.style.width = `${percent}%`;
progressFill.style.background = '#3b82f6'; // Blue for encoding
- statusEl.textContent = `Converting ${Math.ceil(current)}/${total}: ${Math.round(percent)}%`;
+ statusEl.textContent = `Converting ${Math.floor(current + 1)}/${total}: ${Math.round(percent)}%`;
return;
}
@@ -849,7 +849,7 @@ function updateBulkDownloadProgress(notifEl, current, total, currentItem, progre
const percent = total > 0 ? Math.round((current / total) * 100) : 0;
progressFill.style.width = `${percent}%`;
progressFill.style.background = 'var(--highlight)';
- statusEl.textContent = `${Math.floor(current)}/${total} - ${currentItem}`;
+ statusEl.textContent = `${Math.floor(current + 1)}/${total} - ${currentItem}`;
}
function completeBulkDownload(notifEl, success = true, message = null) {
From c315d2dfcde2cc930ecad4278add864950206938 Mon Sep 17 00:00:00 2001
From: Daniel <790119+DanTheMan827@users.noreply.github.com>
Date: Wed, 18 Mar 2026 11:09:27 -0500
Subject: [PATCH 121/226] fix(downloads): update lossless quality labels
---
index.html | 8 ++++----
js/settings.js | 2 +-
2 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/index.html b/index.html
index 9b75ccd90..02d51dd9f 100644
--- a/index.html
+++ b/index.html
@@ -4706,8 +4706,8 @@ Custom Theme
Quality for streaming playback
- Hi-Res FLAC (24-bit)
- FLAC (Lossless)
+ Hi-Res Lossless (24-bit)
+ Lossless (16-bit)
AAC 320kbps
AAC 96kbps
@@ -5156,8 +5156,8 @@ Custom Theme
Quality for track downloads
- Hi-Res FLAC (24-bit)
- FLAC (Lossless)
+ Hi-Res Lossless (24-bit)
+ Lossless (16-bit)
AAC 320kbps
AAC 96kbps
diff --git a/js/settings.js b/js/settings.js
index 19333ecb3..41cac783d 100644
--- a/js/settings.js
+++ b/js/settings.js
@@ -830,6 +830,7 @@ export function initializeSettings(scrobbler, player, api, ui) {
};
const categoryOrder = ['Lossless', 'AAC', 'MP3', 'OGG'];
allOptions.sort((a, b) => {
+ if (a.category == b.category && a.category === 'Lossless') return 0; // Preserve original order for lossless options
const ai = categoryOrder.indexOf(a.category);
const bi = categoryOrder.indexOf(b.category);
const categoryDiff = (ai === -1 ? categoryOrder.length : ai) - (bi === -1 ? categoryOrder.length : bi);
@@ -3155,7 +3156,6 @@ export function initializeSettings(scrobbler, player, api, ui) {
// Store might not exist, continue
}
}
-
} catch (dbError) {
console.log('Could not clear IndexedDB stores:', dbError);
// Try to delete the entire database as fallback
From 393491e2c86528e5234fe6183ad231a9cfa0c3e2 Mon Sep 17 00:00:00 2001
From: Daniel <790119+DanTheMan827@users.noreply.github.com>
Date: Wed, 18 Mar 2026 12:19:29 -0500
Subject: [PATCH 122/226] refactor(platform-detection): add
platform-detection.ts for browser and platform detection
---
js/app.js | 13 +++----------
js/audio-context.js | 4 ++--
js/platform-detection.ts | 17 +++++++++++++++++
js/player.js | 3 ++-
4 files changed, 24 insertions(+), 13 deletions(-)
create mode 100644 js/platform-detection.ts
diff --git a/js/app.js b/js/app.js
index 4ad3fff5b..87485b70c 100644
--- a/js/app.js
+++ b/js/app.js
@@ -1,4 +1,5 @@
//js/app.js
+import { isIos, isSafari } from './platform-detection.js';
import { MusicAPI } from './music-api.js';
import {
apiSettings,
@@ -28,7 +29,6 @@ import { registerSW } from 'virtual:pwa-register';
import { openEditProfile } from './profile.js';
import { ThemeStore } from './themeStore.js';
import './commandPalette.js';
-
import { initTracker } from './tracker.js';
import {
initAnalytics,
@@ -65,8 +65,6 @@ import {
// Capture real iOS state before spoofing (needed for background audio)
if (typeof window !== 'undefined') {
const _ua = navigator.userAgent.toLowerCase();
- window.__IS_IOS__ = /iphone|ipad|ipod/.test(_ua) || (_ua.includes('mac') && navigator.maxTouchPoints > 1);
-
// Spoof User-Agent to bypass Google's embedded browser check
Object.defineProperty(navigator, 'userAgent', {
get: function () {
@@ -388,13 +386,8 @@ document.addEventListener('DOMContentLoaded', async () => {
const audioPlayer = document.getElementById('audio-player');
// i love ios and macos!!!! webkit fucking SUCKS BULLSHIT sorry ios/macos heads yall getting lossless only
- // Use window.__IS_IOS__ (set before UA spoof in index.html) so detection works on real iOS.
- const isIOS = typeof window !== 'undefined' && window.__IS_IOS__ === true;
- const ua = navigator.userAgent.toLowerCase();
- const isSafari =
- ua.includes('safari') && !ua.includes('chrome') && !ua.includes('crios') && !ua.includes('android');
-
- if (isIOS || isSafari) {
+ // Use isIos from platform-detection (set before UA spoof in index.html) so detection works on real iOS.
+ if (isIos || isSafari) {
const qualitySelect = document.getElementById('streaming-quality-setting');
const downloadSelect = document.getElementById('download-quality-setting');
diff --git a/js/audio-context.js b/js/audio-context.js
index adf9c6f0b..20339ef29 100644
--- a/js/audio-context.js
+++ b/js/audio-context.js
@@ -2,6 +2,7 @@
// Shared Audio Context Manager - handles EQ and provides context for visualizer
// Supports 3-32 parametric EQ bands
+import { isIos } from './platform-detection.js';
import { equalizerSettings, monoAudioSettings } from './storage.js';
// Generate frequency array for given number of bands using logarithmic spacing
@@ -300,8 +301,7 @@ class AudioContextManager {
this.audio = audioElement;
// Detect iOS - skip Web Audio initialization on iOS to avoid lock screen audio issues
- const isIOS = typeof window !== 'undefined' && window.__IS_IOS__ === true;
- if (isIOS) {
+ if (isIos) {
console.log('[AudioContext] Skipping Web Audio initialization on iOS for lock screen compatibility');
return;
}
diff --git a/js/platform-detection.ts b/js/platform-detection.ts
new file mode 100644
index 000000000..22bb79842
--- /dev/null
+++ b/js/platform-detection.ts
@@ -0,0 +1,17 @@
+/** The original user agent string before spoofing. */
+export const originalUserAgent = navigator.userAgent;
+
+/** A lowercase version of the original user agent string. */
+const lowerCaseOriginalUserAgent = originalUserAgent.toLowerCase();
+
+/** If the device is an iOS device. */
+export const isIos =
+ /iphone|ipad|ipod/.test(lowerCaseOriginalUserAgent) ||
+ (lowerCaseOriginalUserAgent.includes('mac') && navigator.maxTouchPoints > 1);
+
+/** If the browser is Safari (excluding Chrome, Chromium-based browsers, and Android browsers). */
+export const isSafari =
+ lowerCaseOriginalUserAgent.includes('safari') &&
+ !lowerCaseOriginalUserAgent.includes('chrome') &&
+ !lowerCaseOriginalUserAgent.includes('crios') &&
+ !lowerCaseOriginalUserAgent.includes('android');
diff --git a/js/player.js b/js/player.js
index 6f52b71e6..ae9739c11 100644
--- a/js/player.js
+++ b/js/player.js
@@ -21,6 +21,7 @@ import {
import { audioContextManager } from './audio-context.js';
import { db } from './db.js';
import Hls from 'hls.js';
+import { isIos } from './platform-detection.js';
export class Player {
constructor(audioElement, api, quality = 'HI_RES_LOSSLESS') {
@@ -42,7 +43,7 @@ export class Player {
this.isFallbackRetry = false;
this.isFallbackInProgress = false;
this.autoplayBlocked = false;
- this.isIOS = typeof window !== 'undefined' && window.__IS_IOS__ === true;
+ this.isIOS = isIos;
this.isPwa =
typeof window !== 'undefined' &&
(window.matchMedia?.('(display-mode: standalone)')?.matches || window.navigator?.standalone === true);
From b7cac5724dd783bffe6057e099fd4b6d4f258baf Mon Sep 17 00:00:00 2001
From: Daniel <790119+DanTheMan827@users.noreply.github.com>
Date: Wed, 18 Mar 2026 12:26:17 -0500
Subject: [PATCH 123/226] fix(downloads): don't disable hi-res downloads on iOS
or Safari
Just because the browser can't play them doesn't mean the user may not want to download them
---
index.html | 30 ++++++++++++++++++++----------
js/app.js | 9 ++++++---
js/platform-detection.ts | 4 ++--
styles.css | 10 +++++++++-
4 files changed, 37 insertions(+), 16 deletions(-)
diff --git a/index.html b/index.html
index 02d51dd9f..44ab79d39 100644
--- a/index.html
+++ b/index.html
@@ -5150,17 +5150,27 @@
Custom Theme
-
-
-
Download Quality
-
Quality for track downloads
+
+
+
+ Download Quality
+ Quality for track downloads
+
+
+ Hi-Res Lossless (24-bit)
+ Lossless (16-bit)
+ AAC 320kbps
+ AAC 96kbps
+
+
+
+
+
+ 24-bit downloads may crash the browser on some devices, or be missing
+ metadata.
+
+
-
- Hi-Res Lossless (24-bit)
- Lossless (16-bit)
- AAC 320kbps
- AAC 96kbps
-
diff --git a/js/app.js b/js/app.js
index 87485b70c..8c269f597 100644
--- a/js/app.js
+++ b/js/app.js
@@ -385,11 +385,11 @@ document.addEventListener('DOMContentLoaded', async () => {
const api = new MusicAPI(apiSettings);
const audioPlayer = document.getElementById('audio-player');
- // i love ios and macos!!!! webkit fucking SUCKS BULLSHIT sorry ios/macos heads yall getting lossless only
+ // i love ios and macos!!!! webkit fucking SUCKS BULLSHIT sorry ios/macos heads yall getting lossless only playback
// Use isIos from platform-detection (set before UA spoof in index.html) so detection works on real iOS.
if (isIos || isSafari) {
const qualitySelect = document.getElementById('streaming-quality-setting');
- const downloadSelect = document.getElementById('download-quality-setting');
+ const downloadQualitySelect = document.getElementById('download-quality-setting');
const removeHiRes = (select) => {
if (!select) return;
@@ -398,7 +398,10 @@ document.addEventListener('DOMContentLoaded', async () => {
};
removeHiRes(qualitySelect);
- removeHiRes(downloadSelect);
+
+ if (isIos) {
+ document.querySelector('#hi-res-download-warning').style.display = '';
+ }
const currentQualitySetting = localStorage.getItem('playback-quality');
if (!currentQualitySetting || currentQualitySetting === 'HI_RES_LOSSLESS') {
diff --git a/js/platform-detection.ts b/js/platform-detection.ts
index 22bb79842..a44ac38a4 100644
--- a/js/platform-detection.ts
+++ b/js/platform-detection.ts
@@ -4,9 +4,9 @@ export const originalUserAgent = navigator.userAgent;
/** A lowercase version of the original user agent string. */
const lowerCaseOriginalUserAgent = originalUserAgent.toLowerCase();
-/** If the device is an iOS device. */
+/** If the device is an iOS device. (iPhone, iPad, iPod, or Apple Vision) */
export const isIos =
- /iphone|ipad|ipod/.test(lowerCaseOriginalUserAgent) ||
+ /iphone|ipad|ipod|applevision/.test(lowerCaseOriginalUserAgent) ||
(lowerCaseOriginalUserAgent.includes('mac') && navigator.maxTouchPoints > 1);
/** If the browser is Safari (excluding Chrome, Chromium-based browsers, and Android browsers). */
diff --git a/styles.css b/styles.css
index 75775b930..4917c8964 100644
--- a/styles.css
+++ b/styles.css
@@ -2737,6 +2737,15 @@ input[type='search']::-webkit-search-cancel-button {
color: var(--muted-foreground);
}
+.setting-item .info.setting-details {
+ width: 100%;
+ display: block;
+}
+
+.setting-item .info.setting-details .description {
+ font-size: 0.8rem;
+}
+
.setting-item select,
.setting-item input[type='number'] {
background-color: var(--input);
@@ -4936,7 +4945,6 @@ input:checked + .slider::before {
margin-bottom: 1rem;
}
-
.customize-shortcut-item {
display: flex;
justify-content: space-between;
From fd06160f7e0d2caaa3763c54020a89bd739c23e4 Mon Sep 17 00:00:00 2001
From: Daniel <790119+DanTheMan827@users.noreply.github.com>
Date: Thu, 19 Mar 2026 13:58:26 -0500
Subject: [PATCH 124/226] refactor(doTimed): refactor error handling and skip
timing when not in dev mode
---
js/doTimed.ts | 51 ++++++++++++++++++++++++++++++++++++---------------
1 file changed, 36 insertions(+), 15 deletions(-)
diff --git a/js/doTimed.ts b/js/doTimed.ts
index 812ddb96f..1e5d123da 100644
--- a/js/doTimed.ts
+++ b/js/doTimed.ts
@@ -4,23 +4,44 @@ import { v7 } from 'uuid';
export const InvisibleCodec = baseCodecFrom(InvisibleDictionary);
export function doTimed
(message: string, callback: () => T): T {
- const hiddenId = InvisibleCodec.encode(v7());
- console.time(message + hiddenId);
- try {
- const output = callback();
- return output;
- } finally {
- console.timeEnd(message + hiddenId);
+ if (import.meta.env.DEV) {
+ const hiddenId = InvisibleCodec.encode(v7());
+ console.time(message + hiddenId);
+ try {
+ const output = callback();
+ return output;
+ } finally {
+ console.timeEnd(message + hiddenId);
+ }
+ } else {
+ return callback();
}
}
-export async function doTimedAsync(message: string, callback: () => T): Promise> {
- const hiddenId = InvisibleCodec.encode(v7());
- console.time(message + hiddenId);
- try {
- const output = await callback();
- return output;
- } finally {
- console.timeEnd(message + hiddenId);
+export function doTimedAsync ? Promise : T>(
+ message: string,
+ callback: () => R,
+ throwError: boolean = false
+): R {
+ if (import.meta.env.DEV) {
+ return new Promise(async (resolve, reject) => {
+ const hiddenId = InvisibleCodec.encode(v7());
+ console.time(message + hiddenId);
+ try {
+ const output = await callback();
+ resolve(output);
+ } catch (err) {
+ console.error(`Error in timed operation "${message}":`, err);
+ if (throwError) {
+ reject(err);
+ } else {
+ resolve(undefined as R);
+ }
+ } finally {
+ console.timeEnd(message + hiddenId);
+ }
+ }) as R;
+ } else {
+ return callback() as R;
}
}
From 61aebf7994b97b6dec9f0ff6042a84314ec681bd Mon Sep 17 00:00:00 2001
From: Daniel <790119+DanTheMan827@users.noreply.github.com>
Date: Wed, 18 Mar 2026 10:41:44 -0500
Subject: [PATCH 125/226] refactor(metadata): move METADATA_STRINGS to separate
file
---
js/METADATA_STRINGS.js | 6 ++++++
js/metadata.flac.js | 2 +-
js/metadata.js | 7 -------
3 files changed, 7 insertions(+), 8 deletions(-)
create mode 100644 js/METADATA_STRINGS.js
diff --git a/js/METADATA_STRINGS.js b/js/METADATA_STRINGS.js
new file mode 100644
index 000000000..5b8aa2558
--- /dev/null
+++ b/js/METADATA_STRINGS.js
@@ -0,0 +1,6 @@
+export const METADATA_STRINGS = {
+ VENDOR_STRING: 'Monochrome',
+ DEFAULT_TITLE: 'Unknown Title',
+ DEFAULT_ARTIST: 'Unknown Artist',
+ DEFAULT_ALBUM: 'Unknown Album',
+};
diff --git a/js/metadata.flac.js b/js/metadata.flac.js
index 7a3f7682a..d5045f202 100644
--- a/js/metadata.flac.js
+++ b/js/metadata.flac.js
@@ -1,6 +1,6 @@
import { getCoverBlob, getTrackTitle } from './utils.js';
import { getFullArtistString } from './utils.js';
-import { METADATA_STRINGS } from './metadata.js';
+import { METADATA_STRINGS } from './METADATA_STRINGS.js';
export const FLAC_MIME_TYPE = 'audio/flac';
const FLAC_BLOCK_TYPES = {
diff --git a/js/metadata.js b/js/metadata.js
index 0cb2aba5b..87725b76b 100644
--- a/js/metadata.js
+++ b/js/metadata.js
@@ -11,13 +11,6 @@ import { fetchTagLib, addMetadataWithTagLib, getMetadataWithTagLib } from './tag
import { doTimed, doTimedAsync } from './doTimed.ts';
import { managers } from './app.js';
-export const METADATA_STRINGS = {
- VENDOR_STRING: 'Monochrome',
- DEFAULT_TITLE: 'Unknown Title',
- DEFAULT_ARTIST: 'Unknown Artist',
- DEFAULT_ALBUM: 'Unknown Album',
-};
-
export function prefetchMetadataObjects(track, api, coverBlob = null) {
const _tagLib = fetchTagLib().catch(console.error);
const coverId = getTrackCoverId(track);
From cd64239ba17c44a8060674b65da36e976eac4ae7 Mon Sep 17 00:00:00 2001
From: Daniel <790119+DanTheMan827@users.noreply.github.com>
Date: Tue, 17 Mar 2026 19:50:29 -0500
Subject: [PATCH 126/226] refactor(downloads): add `readableStreamIterator` for
easier stream handling
- Introduced `readableStreamIterator` to convert ReadableStream into async iterable.
- Updated `LosslessAPI` to utilize `readableStreamIterator` for handling response body.
- Modified `ZipNeutralinoWriter` to use `readableStreamIterator` for reading chunks.
---
js/api.js | 15 +++++----------
js/bulk-download-writer.ts | 4 +---
js/readableStreamIterator.ts | 28 ++++++++++++++++++++++++++++
3 files changed, 34 insertions(+), 13 deletions(-)
create mode 100644 js/readableStreamIterator.ts
diff --git a/js/api.js b/js/api.js
index 725b0662b..8dbd02e90 100644
--- a/js/api.js
+++ b/js/api.js
@@ -21,6 +21,7 @@ import { triggerDownload, applyAudioPostProcessing } from './download-utils.ts';
import { isCustomFormat } from './ffmpegFormats.ts';
import { DownloadProgress } from './progressEvents.js';
import { resolveDownloadTotalBytes } from './downloadProgressUtils.js';
+import { readableStreamIterator } from './readableStreamIterator.js';
export const DASH_MANIFEST_UNAVAILABLE_CODE = 'DASH_MANIFEST_UNAVAILABLE';
export { resolveDownloadTotalBytes };
@@ -1431,19 +1432,13 @@ export class LosslessAPI {
let receivedBytes = 0;
if (response.body) {
- const reader = response.body.getReader();
const chunks = [];
- while (true) {
- const { done, value } = await reader.read();
- if (done) break;
+ for await (const chunk of readableStreamIterator(response.body)) {
+ chunks.push(chunk);
+ receivedBytes += chunk.byteLength;
- if (value) {
- chunks.push(value);
- receivedBytes += value.byteLength;
-
- onProgress?.(new DownloadProgress(receivedBytes, totalBytes || undefined));
- }
+ onProgress?.(new DownloadProgress(receivedBytes, totalBytes || undefined));
}
const defaultMime = isVideo ? 'video/mp4' : 'audio/flac';
diff --git a/js/bulk-download-writer.ts b/js/bulk-download-writer.ts
index 581eaa2cb..d0975f8c2 100644
--- a/js/bulk-download-writer.ts
+++ b/js/bulk-download-writer.ts
@@ -135,9 +135,7 @@ export class ZipNeutralinoWriter implements IBulkDownloadWriter {
const reader = response.body.getReader();
let receivedLength = 0;
- while (true) {
- const { done, value } = await reader.read();
- if (done) break;
+ for await (const value of readableStreamIterator(response.body)) {
const chunk = value.buffer.slice(value.byteOffset, value.byteOffset + value.byteLength);
await bridge.filesystem.appendBinaryFile(savePath, chunk);
receivedLength += value.length;
diff --git a/js/readableStreamIterator.ts b/js/readableStreamIterator.ts
new file mode 100644
index 000000000..fe3880266
--- /dev/null
+++ b/js/readableStreamIterator.ts
@@ -0,0 +1,28 @@
+/**
+ * Converts a ReadableStream into an async iterable iterator.
+ * @template T The type of data chunks yielded from the stream.
+ * @param stream The ReadableStream to convert into an async iterable.
+ * @yields Chunks of data from the stream as they become available.
+ * @example
+ * ```typescript
+ * const response = await fetch('https://example.com/data');
+ * for await (const chunk of readableStreamIterator(response.body)) {
+ * console.log(chunk);
+ * }
+ * ```
+ */
+export async function* readableStreamIterator(stream: ReadableStream): AsyncIterableIterator {
+ const reader = stream.getReader();
+
+ while (true) {
+ const { value, done } = await reader.read();
+
+ if (value) {
+ yield value;
+ }
+
+ if (done) {
+ break;
+ }
+ }
+}
From 98953b457275fd1e83b18a4bb4b8b4b634500802 Mon Sep 17 00:00:00 2001
From: Daniel <790119+DanTheMan827@users.noreply.github.com>
Date: Thu, 19 Mar 2026 14:18:53 -0500
Subject: [PATCH 127/226] feat(local-files): initialize localFilesCache and
render local files in UI after each track is scanned
---
js/app.js | 226 +++++++++++++++++++++++++++++++++++++-----------------
1 file changed, 155 insertions(+), 71 deletions(-)
diff --git a/js/app.js b/js/app.js
index 4ad3fff5b..78bc9ef11 100644
--- a/js/app.js
+++ b/js/app.js
@@ -496,6 +496,159 @@ document.addEventListener('DOMContentLoaded', async () => {
const ui = new UIRenderer(api, player);
window.monochromeUi = ui;
+
+ /**
+ * Scans the configured local media folder and refreshes `window.localFilesCache`.
+ * Called by the folder-select button handler and by downloads.js after a
+ * successful write to the local media folder.
+ *
+ * @param {boolean} [onlyIfAlreadyScanned=false] When true, skips the scan if
+ * `window.localFilesCache` has never been populated (i.e. the user hasn't
+ * visited the local tab yet).
+ */
+ async function scanLocalMediaFolder(onlyIfAlreadyScanned = false) {
+ // Skip the scan if the user has never visited the local tab – they'll
+ // get a fresh scan when they navigate there for the first time.
+ if (onlyIfAlreadyScanned && !window.localFilesCache) return;
+
+ // Prevent concurrent scans.
+ if (window.localFilesScanInProgress) return;
+ window.localFilesScanInProgress = true;
+
+ try {
+ const handle = await db.getSetting('local_folder_handle');
+ if (!handle) return;
+
+ const isNeutralino =
+ window.Neutralino && (window.NL_MODE || window.location.search.includes('mode=neutralino'));
+ const tracks = (window.localFilesCache = []);
+ let idCounter = 0;
+ const { readTrackMetadata } = await loadMetadataModule();
+
+ if (isNeutralino) {
+ async function scanNeu(dirPath) {
+ const entries = await window.Neutralino.filesystem.readDirectory(dirPath);
+ for (const entry of entries) {
+ if (entry.entry === '.' || entry.entry === '..') continue;
+ const fullPath = `${dirPath}/${entry.entry}`;
+ if (entry.type === 'FILE') {
+ const name = entry.entry.toLowerCase();
+ if (
+ name.endsWith('.flac') ||
+ name.endsWith('.mp3') ||
+ name.endsWith('.m4a') ||
+ name.endsWith('.wav') ||
+ name.endsWith('.ogg')
+ ) {
+ try {
+ const buffer = await window.Neutralino.filesystem.readBinaryFile(fullPath);
+ const stats = await window.Neutralino.filesystem.getStats(fullPath);
+ const file = new File([buffer], entry.entry, { lastModified: stats.mtime });
+ const metadata = await readTrackMetadata(file);
+ metadata.id = `local-${idCounter++}-${entry.entry}`;
+ tracks.push(metadata);
+ window.monochromeUi?.renderLocalFiles(
+ document.getElementById('library-local-container')
+ );
+ } catch (e) {
+ console.error('Failed to read file:', fullPath, e);
+ }
+ }
+ } else if (entry.type === 'DIRECTORY') {
+ await scanNeu(fullPath);
+ }
+ }
+ }
+ await scanNeu(handle.path);
+ } else {
+ // Request read permission before iterating. When the browser has
+ // already granted it (e.g. within the same session or via a
+ // persistent grant) this succeeds without a user gesture.
+ if (typeof handle.requestPermission === 'function') {
+ const permission = await handle.requestPermission({ mode: 'read' });
+ if (permission !== 'granted') return;
+ }
+
+ async function scanBrowser(dirHandle) {
+ for await (const entry of dirHandle.values()) {
+ if (entry.kind === 'file') {
+ const name = entry.name.toLowerCase();
+ if (
+ name.endsWith('.flac') ||
+ name.endsWith('.mp3') ||
+ name.endsWith('.m4a') ||
+ name.endsWith('.wav') ||
+ name.endsWith('.ogg')
+ ) {
+ const file = await entry.getFile();
+ const metadata = await readTrackMetadata(file);
+ metadata.id = `local-${idCounter++}-${file.name}`;
+ tracks.push(metadata);
+ window.monochromeUi?.renderLocalFiles(
+ document.getElementById('library-local-container')
+ );
+ }
+ } else if (entry.kind === 'directory') {
+ await scanBrowser(entry);
+ }
+ }
+ }
+ await scanBrowser(handle);
+ }
+
+ tracks.sort((a, b) => (a.artist.name || '').localeCompare(b.artist.name || ''));
+ // Update only the local-files section without navigating to the library page.
+ window.monochromeUi?.renderLocalFiles(document.getElementById('library-local-container'));
+ } finally {
+ window.localFilesScanInProgress = false;
+ }
+
+ return window.localFilesCache;
+ }
+
+ /**
+ * Called by downloads.js (via window) after a successful write to the local
+ * media folder so the track appears in Library > Local without the user
+ * having to manually re-scan.
+ *
+ * When called with a `blob` and `filename` (single-track download case) it
+ * performs a cheap partial update — reading metadata only from that one file
+ * and inserting it into the existing cache — so the full folder does not need
+ * to be re-walked. When called with no arguments (bulk download case, or when
+ * `localFilesCache` has never been populated) it falls back to a full rescan.
+ */
+ window.refreshLocalMediaFolder = async (blob = null, filename = null) => {
+ if (blob && filename) {
+ try {
+ /** @type {import("./metadata.js")} */
+ const { readTrackMetadata } = await loadMetadataModule();
+ const baseName = filename.split('/').pop();
+ const metadata = await readTrackMetadata(new Uint8Array(await blob.arrayBuffer()), {
+ filename: baseName,
+ });
+ const existing = window.localFilesCache || [];
+ metadata.id = `local-${existing.length}-${baseName}`;
+ window.localFilesCache = [...existing, metadata].sort((a, b) =>
+ (a.artist.name || '').localeCompare(b.artist.name || '')
+ );
+ window.monochromeUi?.renderLocalFiles(document.getElementById('library-local-container'));
+ } catch {
+ // Fall back to a full rescan if metadata extraction fails.
+ await scanLocalMediaFolder(true);
+ }
+ } else {
+ await scanLocalMediaFolder(!!window.localFilesCache);
+ }
+ };
+
+ // Kick off a background scan of the saved local media folder on startup so
+ // that the Library > Local tab is populated without requiring the user to
+ // manually press "Load [folder]" every session. The function internally
+ // checks for a saved handle and (in browser mode) requests read permission,
+ // so this is a silent no-op when no folder is configured or permission is not
+ // yet granted.
+ scanLocalMediaFolder();
+
const scrobbler = new MultiScrobbler();
window.monochromeScrobbler = scrobbler;
const lyricsManager = new LyricsManager(api);
@@ -2334,77 +2487,8 @@ document.addEventListener('DOMContentLoaded', async () => {
btn.disabled = true;
}
- const tracks = [];
- let idCounter = 0;
- const { readTrackMetadata } = await loadMetadataModule();
-
- if (isNeutralino) {
- async function scanDirectoryNeu(dirPath) {
- const entries = await window.Neutralino.filesystem.readDirectory(dirPath);
- for (const entry of entries) {
- if (entry.entry === '.' || entry.entry === '..') continue;
- const fullPath = `${dirPath}/${entry.entry}`;
- if (entry.type === 'FILE') {
- const name = entry.entry.toLowerCase();
- if (
- name.endsWith('.flac') ||
- name.endsWith('.mp3') ||
- name.endsWith('.m4a') ||
- name.endsWith('.wav') ||
- name.endsWith('.ogg')
- ) {
- try {
- const buffer = await window.Neutralino.filesystem.readBinaryFile(fullPath);
- const stats = await window.Neutralino.filesystem.getStats(fullPath);
- const file = new File([buffer], entry.entry, {
- lastModified: stats.mtime,
- });
- const metadata = await readTrackMetadata(file);
- metadata.id = `local-${idCounter++}-${entry.entry}`;
- tracks.push(metadata);
- } catch (e) {
- console.error('Failed to read file:', fullPath, e);
- }
- }
- } else if (entry.type === 'DIRECTORY') {
- await scanDirectoryNeu(fullPath);
- }
- }
- }
- await scanDirectoryNeu(path);
- } else {
- async function scanDirectory(dirHandle) {
- for await (const entry of dirHandle.values()) {
- if (entry.kind === 'file') {
- const name = entry.name.toLowerCase();
- if (
- name.endsWith('.flac') ||
- name.endsWith('.mp3') ||
- name.endsWith('.m4a') ||
- name.endsWith('.wav') ||
- name.endsWith('.ogg')
- ) {
- const file = await entry.getFile();
- const metadata = await readTrackMetadata(file);
- metadata.id = `local-${idCounter++}-${file.name}`;
- tracks.push(metadata);
- }
- } else if (entry.kind === 'directory') {
- await scanDirectory(entry);
- }
- }
- }
- await scanDirectory(handle);
- }
-
- tracks.sort((a, b) => {
- const artistA = a.artist.name || '';
- const artistB = b.artist.name || '';
- return artistA.localeCompare(artistB);
- });
-
- window.localFilesCache = tracks;
- trackSelectLocalFolder(tracks.length);
+ const tracks = scanLocalMediaFolder(true);
+ trackSelectLocalFolder(tracks?.length ?? 0);
ui.renderLibraryPage();
} catch (err) {
if (err.name !== 'AbortError') {
From 895d5dd20f6d9ee4ed38df60d4ff6c95ea06d39e Mon Sep 17 00:00:00 2001
From: Daniel <790119+DanTheMan827@users.noreply.github.com>
Date: Thu, 19 Mar 2026 13:57:54 -0500
Subject: [PATCH 128/226] feat(metadata): replace taglib-wasm with
@dantheman827/taglib-ts
- feat(taglib): updated audio buffer handling in metadata.js to use Uint8Array.
- feat(taglib): refactored addMetadataToAudio to support return type as Blob or Uint8Array
- feat(taglib): add timeout functionality to metadata functions
- Introduced `withTimeout` utility function to handle operation timeouts.
- Updated `addMetadataWithTagLib` to use `withTimeout` for promise resolution.
- Updated `getMetadataWithTagLib` to use `withTimeout` for promise resolution.
- Added default timeout parameter to both metadata functions.
- feat(taglib): improve metadata handling with ChunkedByteVectorStream
- Enhanced metadata handling in taglib.ts and taglib.worker.ts to utilize ChunkedByteVectorStream.
- fix(taglib): handle metadata addition failure gracefully
- Updated `addMetadataWithTagLib` to catch errors and return original audio data if metadata addition fails.
fix(downloads): return original blob if metadata addition fails
- Wrap addMetadataToAudio call in try-catch to handle errors.
feat(taglib): add direct calling of taglib methods
- Introduced `direct` parameter to `addMetadataWithTagLib` and `getMetadataWithTagLib` functions for direct processing in the current thread.
- Exported taglib worker functions.
---
.gitmodules | 0
bun.lock | 8 +-
js/api.js | 9 +-
js/downloads.js | 1 -
js/metadata.js | 55 ++---
js/taglib.ts | 225 ++++++++++++++-----
js/taglib.types.ts | 23 +-
js/taglib.worker.ts | 510 +++++++++++++++++++++++---------------------
package-lock.json | 35 +--
package.json | 2 +-
vite.config.ts | 3 +-
11 files changed, 499 insertions(+), 372 deletions(-)
create mode 100644 .gitmodules
diff --git a/.gitmodules b/.gitmodules
new file mode 100644
index 000000000..e69de29bb
diff --git a/bun.lock b/bun.lock
index 34ebc1d7f..fdf49cc3c 100644
--- a/bun.lock
+++ b/bun.lock
@@ -5,6 +5,7 @@
"": {
"name": "monochrome",
"dependencies": {
+ "@dantheman827/taglib-ts": "https://github.com/DanTheMan827/taglib-ts/archive/b4238b2627aceb97f58813258046f1259f68cab7.tar.gz",
"@ffmpeg/core": "^0.12.10",
"@ffmpeg/ffmpeg": "^0.12.15",
"@ffmpeg/util": "^0.12.2",
@@ -23,7 +24,6 @@
"mime": "^4.1.0",
"npm": "^11.11.1",
"pocketbase": "^0.26.8",
- "taglib-wasm": "^1.0.5",
"uuid": "^13.0.0",
},
"devDependencies": {
@@ -260,6 +260,8 @@
"@csstools/selector-specificity": ["@csstools/selector-specificity@5.0.0", "", { "peerDependencies": { "postcss-selector-parser": "^7.0.0" } }, "sha512-PCqQV3c4CoVm3kdPhyeZ07VmBRdH2EpMFA/pd9OASpOEC3aXNGoqPDAZ80D0cLpMBxnmk0+yNhGsEx31hq7Gtw=="],
+ "@dantheman827/taglib-ts": ["@dantheman827/taglib-ts@https://github.com/DanTheMan827/taglib-ts/archive/b4238b2627aceb97f58813258046f1259f68cab7.tar.gz", {}, "sha512-rvQOn9GDEj2sH4yV6oUTMMG9+rJbFG7tQkiP6/bhGJARg1Vmdy283j4YFCl+ubkqsMQ+UfAhEWSw5d5lfPVfwQ=="],
+
"@dual-bundle/import-meta-resolve": ["@dual-bundle/import-meta-resolve@4.2.1", "", {}, "sha512-id+7YRUgoUX6CgV0DtuhirQWodeeA7Lf4i2x71JS/vtA5pRb/hIGWlw+G6MeXvsM+MXrz0VAydTGElX1rAfgPg=="],
"@electron/asar": ["@electron/asar@3.4.1", "", { "dependencies": { "commander": "^5.0.0", "glob": "^7.1.6", "minimatch": "^3.0.4" }, "bin": { "asar": "bin/asar.js" } }, "sha512-i4/rNPRS84t0vSRa2HorerGRXWyF4vThfHesw0dmcWHp+cspK743UanA0suA5Q5y8kzY2y6YKrvbIUn69BCAiA=="],
@@ -426,8 +428,6 @@
"@lit/reactive-element": ["@lit/reactive-element@2.1.2", "", { "dependencies": { "@lit-labs/ssr-dom-shim": "^1.5.0" } }, "sha512-pbCDiVMnne1lYUIaYNN5wrwQXDtHaYtg7YEFPeW+hws6U47WeFvISGUWekPGKWOP1ygrs0ef0o1VJMk1exos5A=="],
- "@msgpack/msgpack": ["@msgpack/msgpack@3.1.3", "", {}, "sha512-47XIizs9XZXvuJgoaJUIE2lFoID8ugvc0jzSHP+Ptfk8nTbnR8g788wv48N03Kx0UkAv559HWRQ3yzOgzlRNUA=="],
-
"@neutralinojs/lib": ["@neutralinojs/lib@6.5.0", "", { "optionalDependencies": { "@rollup/rollup-darwin-x64": "*", "@rollup/rollup-linux-x64-gnu": "*" } }, "sha512-ECgYh+CXAfMR1JVTvDw/kHhjL6LzNNcjk8Va1DZUSBkUwROqFTQ7zseFeuFtwGvutqvlWiwpGmU3s11rg/bdvA=="],
"@neutralinojs/neu": ["@neutralinojs/neu@11.7.0", "", { "dependencies": { "@electron/asar": "^3.0.3", "chalk": "^4.1.0", "chokidar": "^4.0.3", "commander": "^7.2.0", "configstore": "^5.0.1", "edit-json-file": "^1.6.2", "follow-redirects": "^1.13.1", "fs-extra": "^9.0.1", "pe-library": "^1.0.1", "png2icons": "^2.0.1", "postject": "1.0.0-alpha.6", "recursive-readdir": "^2.2.2", "resedit": "^2.0.2", "spawn-command": "^1.0.0", "tcp-port-used": "^1.0.2", "uuid": "^8.3.2", "websocket": "^1.0.35", "zip-lib": "^1.0.4" }, "bin": { "neu": "bin/neu.js" } }, "sha512-fUqvR70a+BpKI9mrD92ldZkVC24Rs8XL/9m7zmOCLgCRys3yuWy7vEsxpHzKMzqTiQJkTYIsLmcR8VMzNIjuSw=="],
@@ -1312,8 +1312,6 @@
"table": ["table@6.9.0", "", { "dependencies": { "ajv": "^8.0.1", "lodash.truncate": "^4.4.2", "slice-ansi": "^4.0.0", "string-width": "^4.2.3", "strip-ansi": "^6.0.1" } }, "sha512-9kY+CygyYM6j02t5YFHbNz2FN5QmYGv9zAjVp4lCDjlCw7amdckXlEt/bjMhUIfj4ThGRE4gCUH5+yGnNuPo5A=="],
- "taglib-wasm": ["taglib-wasm@1.0.5", "", { "dependencies": { "@msgpack/msgpack": "^3.1.3" }, "peerDependencies": { "typescript": ">=4.5.0" }, "optionalPeers": ["typescript"] }, "sha512-kuDHX78FbjLOqldWxBBkEgjyyDagYRGcYqr4g6ObkmEMO203Sp1R0KxkELoH9VkZxsOVR8yoSWaSaG9f5oTqyQ=="],
-
"tcp-port-used": ["tcp-port-used@1.0.2", "", { "dependencies": { "debug": "4.3.1", "is2": "^2.0.6" } }, "sha512-l7ar8lLUD3XS1V2lfoJlCBaeoaWo/2xfYt81hM7VlvR4RrMVFqfmzfhLVk40hAb368uitje5gPtBRL1m/DGvLA=="],
"temp-dir": ["temp-dir@2.0.0", "", {}, "sha512-aoBAniQmmwtcKp/7BzsH8Cxzv8OL736p7v1ihGb5e9DJ9kTwGWHrQrVB5+lfVDzfGrdRzXch+ig7LHaY1JTOrg=="],
diff --git a/js/api.js b/js/api.js
index 725b0662b..6a97d6781 100644
--- a/js/api.js
+++ b/js/api.js
@@ -12,7 +12,6 @@ import {
} from './utils.js';
import { trackDateSettings } from './storage.js';
import { APICache } from './cache.js';
-import { addMetadataToAudio, prefetchMetadataObjects } from './metadata.js';
import { DashDownloader } from './dash-downloader.ts';
import { HlsDownloader } from './hls-downloader.js';
import { MP3EncodingError } from './mp3-encoder.js';
@@ -1320,6 +1319,8 @@ export class LosslessAPI {
async downloadTrack(id, quality = 'HI_RES_LOSSLESS', filename, options = {}) {
// Load ffmpeg in the background.
loadFfmpeg().catch(console.error);
+ const metadataModule = await import('./metadata.js');
+ const { prefetchMetadataObjects, addMetadataToAudio } = metadataModule;
const { onProgress, track, calculateDashBytes = true } = options;
const prefetchPromises = prefetchMetadataObjects(track, this);
@@ -1504,7 +1505,11 @@ export class LosslessAPI {
}
onProgress?.(new DownloadProgress('Adding metadata'));
- blob = await addMetadataToAudio(blob, enrichedTrack, this, quality, prefetchPromises);
+ try {
+ blob = await addMetadataToAudio(blob, enrichedTrack, this, quality, prefetchPromises);
+ } catch (err) {
+ console.error(err);
+ }
}
}
diff --git a/js/downloads.js b/js/downloads.js
index 0ef08d142..169f0a3d8 100644
--- a/js/downloads.js
+++ b/js/downloads.js
@@ -15,7 +15,6 @@ import {
import { AbortError } from './errorTypes.ts';
import { lyricsSettings, bulkDownloadSettings, playlistSettings } from './storage.js';
import { generateM3U, generateM3U8, generateCUE, generateNFO, generateJSON } from './playlist-generator.js';
-import { triggerDownload } from './download-utils.ts';
import {
ZipStreamWriter,
ZipBlobWriter,
diff --git a/js/metadata.js b/js/metadata.js
index 0cb2aba5b..f4fd9b861 100644
--- a/js/metadata.js
+++ b/js/metadata.js
@@ -1,13 +1,5 @@
-import {
- getCoverBlob,
- getTrackTitle,
- getFullArtistString,
- getMimeType,
- getTrackCoverId,
- getTrackDiscNumber,
- getExtensionFromBlob,
-} from './utils.js';
-import { fetchTagLib, addMetadataWithTagLib, getMetadataWithTagLib } from './taglib.ts';
+import { getCoverBlob, getTrackTitle, getFullArtistString, getMimeType, getTrackCoverId } from './utils.js';
+import { addMetadataWithTagLib, getMetadataWithTagLib } from './taglib.ts';
import { doTimed, doTimedAsync } from './doTimed.ts';
import { managers } from './app.js';
@@ -19,7 +11,6 @@ export const METADATA_STRINGS = {
};
export function prefetchMetadataObjects(track, api, coverBlob = null) {
- const _tagLib = fetchTagLib().catch(console.error);
const coverId = getTrackCoverId(track);
const coverFetch = coverBlob
? Promise.resolve(coverBlob)
@@ -28,7 +19,7 @@ export function prefetchMetadataObjects(track, api, coverBlob = null) {
: Promise.resolve(null);
const lyricsFetch = managers?.lyricsManager?.fetchLyrics?.(track.id, track)?.catch(console.error);
- return { _tagLib, coverFetch, lyricsFetch };
+ return { coverFetch, lyricsFetch };
}
/**
@@ -47,8 +38,6 @@ export async function addMetadataToAudio(audioBlob, track, api, _quality, prefet
*/
const data = {};
- const audioBuffer = await doTimedAsync('Get audio array buffer', () => audioBlob.arrayBuffer());
-
try {
data.title = getTrackTitle(track);
data.artist = getFullArtistString(track);
@@ -117,16 +106,14 @@ export async function addMetadataToAudio(audioBlob, track, api, _quality, prefet
console.warn('Error setting lyrics metadata', track, e);
}
- const newAudioBuffer = await addMetadataWithTagLib(audioBuffer, {
- ...data,
- });
-
- return doTimed(
- 'Create new audio blob',
- () =>
- new Blob([newAudioBuffer], {
- type: audioBlob.type,
- })
+ return await addMetadataWithTagLib(
+ audioBlob,
+ {
+ ...data,
+ },
+ undefined,
+ true,
+ true
);
} catch (err) {
console.error(err);
@@ -137,12 +124,12 @@ export async function addMetadataToAudio(audioBlob, track, api, _quality, prefet
/**
* Reads metadata from a file
- * @param {File} file
+ * @param {Uint8Array | Blob | File | FileSystemFileHandle | FileSystemFileEntry} file
* @returns {Promise} Track metadata
*/
-export async function readTrackMetadata(file, siblings = []) {
+export async function readTrackMetadata(file, { filename = file?.name || 'Unknown Title', siblings } = {}) {
const metadata = {
- title: file.name.replace(/\.[^/.]+$/, ''),
+ title: filename?.replace(/\.[^/.]+$/, ''),
artists: [],
artist: { name: 'Unknown Artist' }, // For fallback/compatibility
album: { title: 'Unknown Album', cover: 'assets/appicon.png', releaseDate: null },
@@ -152,16 +139,16 @@ export async function readTrackMetadata(file, siblings = []) {
explicit: false,
isLocal: true,
file: file,
- id: `local-${file.name}-${file.lastModified}`,
+ id: `local-${filename}-${file.lastModified}`,
};
try {
- const data = await getMetadataWithTagLib(await file.arrayBuffer());
+ const data = await getMetadataWithTagLib(file, filename, true);
if (data) {
metadata.title = data.title || metadata.title;
- const artistNames = (data.artist || "")
- .split(";")
+ const artistNames = (data.artist || '')
+ .split(';')
.map((a) => a.trim())
.filter((a) => a);
@@ -175,7 +162,7 @@ export async function readTrackMetadata(file, siblings = []) {
if (data.albumArtist) {
metadata.album.artist = { name: data.albumArtist };
- } else if (metadata.artist.name !== "Unknown Artist") {
+ } else if (metadata.artist.name !== 'Unknown Artist') {
metadata.album.artist = { name: metadata.artist.name };
}
@@ -190,11 +177,11 @@ export async function readTrackMetadata(file, siblings = []) {
metadata.explicit = !!data.explicit;
}
} catch (e) {
- console.warn('Error reading metadata for', file.name, e);
+ console.warn('Error reading metadata for', filename, e);
}
if (metadata.album.cover === 'assets/appicon.png' && siblings.length > 0) {
- const baseName = file.name.substring(0, file.name.lastIndexOf('.'));
+ const baseName = filename.substring(0, filename.lastIndexOf('.'));
const imageExtensions = ['.jpg', '.jpeg', '.png', '.webp'];
const coverFile = siblings.find((f) => {
const fName = f.name;
diff --git a/js/taglib.ts b/js/taglib.ts
index a8b63f469..8866aa2eb 100644
--- a/js/taglib.ts
+++ b/js/taglib.ts
@@ -1,79 +1,190 @@
-import { TagLib } from 'taglib-wasm';
-import { fetchBlobURL } from './utils';
-import _TagLibWasm from '!/taglib-wasm/dist/taglib-web.wasm?blob-url';
+import { doTimed, doTimedAsync } from './doTimed';
import type {
AddMetadataMessage,
TagLibFileResponse,
TagLibMetadataResponse,
TagLibReadMetadata,
+ TagLibReadTypes,
+ TagLibWriteTypes,
} from './taglib.types';
import TagLibWorker from './taglib.worker?worker';
-let tagLib: Promise | null = null;
+export async function withTimeout(callback: () => Promise, timeout: number): Promise {
+ return new Promise((resolve, reject) => {
+ const timer = setTimeout(() => {
+ reject(new Error(`Operation timed out after ${timeout} ms`));
+ }, timeout);
-async function fetchTagLib(): Promise {
- return fetchTagLib.blobUrl || (fetchTagLib.blobUrl = await _TagLibWasm());
+ callback()
+ .then((result) => {
+ clearTimeout(timer);
+ resolve(result);
+ })
+ .catch((err) => {
+ clearTimeout(timer);
+ reject(err);
+ });
+ });
}
-namespace fetchTagLib {
- export let blobUrl = '';
+function toUint8Array(audioData: ArrayBufferLike | Uint8Array) {
+ if (audioData instanceof Uint8Array) {
+ return audioData;
+ }
+
+ return doTimed(
+ `Converting audio data (${(audioData as any)?.constructor?.name}) to Uint8Array`,
+ () => new Uint8Array(audioData)
+ );
+}
+
+async function convertInputToTaglib(
+ audioData: TagLibReadTypes | TagLibWriteTypes,
+ direct: boolean = false
+): Promise {
+ if ('FileSystemFileEntry' in globalThis && audioData instanceof FileSystemFileEntry) {
+ audioData = await doTimedAsync('Getting File from FileSystemFileEntry', async () => {
+ const file = await new Promise((resolve) =>
+ (audioData as FileSystemFileEntry).file((f) => resolve(f))
+ );
+ return toUint8Array(new Uint8Array(await file.arrayBuffer()));
+ });
+ }
+
+ if ((audioData instanceof Blob || audioData instanceof File) && !direct) {
+ return (await doTimedAsync(
+ `Reading ${audioData instanceof File ? 'File' : 'Blob'} as Uint8Array`,
+ async () => new Uint8Array(await audioData.arrayBuffer())
+ )) as R;
+ } else if ('FileSystemFileHandle' in globalThis && audioData instanceof FileSystemFileHandle && !direct) {
+ return (await doTimedAsync('Reading File from FileSystemHandle as Uint8Array', async () => {
+ const file = await audioData.getFile();
+ const arrayBuffer = await file.arrayBuffer();
+ return await toUint8Array(arrayBuffer);
+ })) as R;
+ } else if (
+ !(audioData instanceof Uint8Array) &&
+ !(audioData instanceof Blob) &&
+ !(audioData instanceof File) &&
+ !('FileSystemFileEntry' in globalThis && audioData instanceof FileSystemFileEntry) &&
+ !('FileSystemFileHandle' in globalThis && audioData instanceof FileSystemFileHandle)
+ ) {
+ return toUint8Array(audioData as any) as R;
+ }
+
+ return audioData as R;
}
-export { fetchTagLib };
+const workerModule = import('./taglib.worker.js');
export async function addMetadataWithTagLib(
- audioData: Uint8Array,
- data: Omit
+ audioData: TagLibWriteTypes,
+ data: Omit,
+ filename?: string,
+ direct: boolean = false,
+ returnBlob: boolean = false,
+ timeout: number = 10000
) {
- if (!(audioData instanceof Uint8Array)) {
- audioData = new Uint8Array(audioData);
- }
+ audioData = await convertInputToTaglib(audioData, direct);
- const worker = new TagLibWorker();
- const wasmUrl = await fetchTagLib();
-
- return new Promise((resolve, reject) => {
- worker.onmessage = (e: MessageEvent) => {
- const { data, error } = e.data;
-
- if (error) {
- reject(new Error(error));
- } else {
- resolve(data!);
- }
- };
- worker.onerror = reject;
- worker.onmessageerror = reject;
-
- const transferables: Transferable[] = [audioData.buffer];
- if ((data as any).cover?.data?.buffer instanceof ArrayBuffer) {
- transferables.push((data as any).cover.data.buffer);
- }
+ if (direct) {
+ const { addMetadataToAudio } = await workerModule;
- worker.postMessage({ ...data, type: 'Add', wasmUrl, audioData }, transferables);
- });
-}
+ return await doTimedAsync('Adding metadata with taglib-ts (direct)', () =>
+ addMetadataToAudio({
+ ...data,
+ filename,
+ audioData,
+ returnType: returnBlob && direct ? 'blob' : 'uint8array',
+ })
+ );
+ } else {
+ const worker = new TagLibWorker();
+
+ try {
+ return await doTimedAsync(
+ 'Adding metadata with taglib-ts (worker)',
+ async () =>
+ await withTimeout(
+ () =>
+ new Promise((resolve, reject) => {
+ worker.onmessage = (e: MessageEvent) => {
+ const { data, error } = e.data;
-export async function getMetadataWithTagLib(audioData: Uint8Array) {
- if (!(audioData instanceof Uint8Array)) {
- audioData = new Uint8Array(audioData);
+ if (error) {
+ reject(new Error(error));
+ } else {
+ resolve(data!);
+ }
+ };
+ worker.onerror = reject;
+ worker.onmessageerror = reject;
+
+ const transferables: Transferable[] = [];
+ if ((audioData as any)?.buffer instanceof ArrayBuffer) {
+ transferables.push((audioData as any).buffer);
+ }
+
+ if ((data as any).cover?.data?.buffer instanceof ArrayBuffer) {
+ transferables.push((data as any).cover.data.buffer);
+ }
+
+ worker.postMessage({ ...data, type: 'Add', audioData, filename }, transferables);
+ }),
+ timeout
+ )
+ );
+ } finally {
+ worker.terminate();
+ }
}
+}
- const worker = new TagLibWorker();
- const wasmUrl = await fetchTagLib();
-
- return new Promise((resolve, reject) => {
- worker.onmessage = (e: MessageEvent) => {
- const { data, error } = e.data;
-
- if (error) {
- reject(new Error(error));
- } else {
- resolve(data!);
- }
- };
- worker.onerror = reject;
- worker.onmessageerror = reject;
- worker.postMessage({ type: 'Get', wasmUrl, audioData }, [audioData.buffer]);
- });
+export async function getMetadataWithTagLib(
+ audioData: TagLibReadTypes,
+ filename?: string,
+ direct: boolean = false,
+ timeout: number = 10000
+) {
+ audioData = await convertInputToTaglib(audioData, direct);
+
+ if (direct) {
+ const { getMetadataFromAudio } = await workerModule;
+
+ return await doTimedAsync('Getting metadata with taglib-ts (direct)', () =>
+ getMetadataFromAudio({ filename, audioData })
+ );
+ } else {
+ const worker = new TagLibWorker();
+
+ try {
+ return await doTimedAsync('Getting metadata with taglib-ts (worker)', () =>
+ withTimeout(
+ () =>
+ new Promise((resolve, reject) => {
+ worker.onmessage = (e: MessageEvent) => {
+ const { data, error } = e.data;
+
+ if (error) {
+ reject(new Error(error));
+ } else {
+ resolve(data!);
+ }
+ };
+ worker.onerror = reject;
+ worker.onmessageerror = reject;
+
+ const transferables: Transferable[] = [];
+ if ((audioData as any)?.buffer instanceof ArrayBuffer) {
+ transferables.push((audioData as any).buffer);
+ }
+ worker.postMessage({ type: 'Get', audioData, filename }, transferables);
+ }),
+ timeout
+ )
+ );
+ } finally {
+ worker.terminate();
+ }
+ }
}
diff --git a/js/taglib.types.ts b/js/taglib.types.ts
index 5af538d4b..08da71f64 100644
--- a/js/taglib.types.ts
+++ b/js/taglib.types.ts
@@ -1,9 +1,11 @@
+import type { FileRef } from '!/@dantheman827/taglib-ts/src/fileRef';
+
export type TagLibWorkerMessageType = 'Add' | 'Get';
-export interface TagLibWorkerMessage {
+export interface TagLibWorkerMessage {
type: TagLibWorkerMessageType;
- wasmUrl: string;
- audioData: Uint8Array;
+ audioData: T;
+ filename?: string;
}
export interface TagLibWorkerResponse {
@@ -50,6 +52,19 @@ export type AddMetadataMessage = TagLibWorkerMessage & {
type: 'Add';
} & TagLibMetadata;
-export type GetMetadataMessage = TagLibWorkerMessage & {
+export type GetMetadataMessage = TagLibWorkerMessage & {
type: 'Get';
};
+
+export type TagLibReadTypes = Uint8Array | Blob | File | FileSystemFileHandle | FileSystemFileEntry;
+export type TagLibWriteTypes = Uint8Array;
+
+export type _AddMetadataMessage = Omit & {
+ audioRef?: FileRef | null;
+ audioData?: Uint8Array;
+ returnType?: 'blob' | 'uint8array';
+};
+export type _GetMetadataMessage = Omit & {
+ audioRef?: FileRef | null;
+ audioData?: TagLibReadTypes;
+};
diff --git a/js/taglib.worker.ts b/js/taglib.worker.ts
index e7841a5c1..5f0465f1f 100644
--- a/js/taglib.worker.ts
+++ b/js/taglib.worker.ts
@@ -1,9 +1,13 @@
// filepath: /workspaces/monochrome/js/taglib.worker.ts
declare var self: DedicatedWorkerGlobalScope;
-import { TagLib, type PictureType } from 'taglib-wasm';
+import { ByteVector } from '!/@dantheman827/taglib-ts/src/byteVector.js';
+import { Mp4Tag, Mp4Item } from '!/@dantheman827/taglib-ts/src/mp4/mp4Tag.js';
+import { Variant } from '!/@dantheman827/taglib-ts/src/toolkit/variant.js';
import { doTimed, doTimedAsync } from './doTimed';
import type {
+ _AddMetadataMessage,
+ _GetMetadataMessage,
AddMetadataMessage,
GetMetadataMessage,
TagLibFileResponse,
@@ -13,15 +17,27 @@ import type {
TagLibWorkerMessage,
TagLibWorkerResponse,
} from './taglib.types';
-
-const PICTURE_TYPE_VALUES = {
- FrontCover: 3,
-};
-
-async function addMetadataToAudio(message: AddMetadataMessage): Promise {
+import { File as TagLibFile } from '!/@dantheman827/taglib-ts/src/file.js';
+import { FileRef } from '!/@dantheman827/taglib-ts/src/fileRef.js';
+import { ChunkedByteVectorStream } from '!/@dantheman827/taglib-ts/src/toolkit/chunkedByteVectorStream.js';
+import { ReadStyle } from '!/@dantheman827/taglib-ts/src/toolkit/types';
+import { BlobStream } from '!/@dantheman827/taglib-ts/src/toolkit/blobStream.js';
+import { FileSystemFileHandleStream } from '!/@dantheman827/taglib-ts/src/toolkit/fileSystemFileHandleStream.js';
+
+// Imported to ensure support is bundled in this chunk, even if not directly used
+import { FlacFile } from '!/@dantheman827/taglib-ts/src/flac/flacFile.js';
+import { MpegFile } from '!/@dantheman827/taglib-ts/src/mpeg/mpegFile.js';
+import { Mp4File } from '!/@dantheman827/taglib-ts/src/mp4/mp4File.js';
+import { OggFile } from '!/@dantheman827/taglib-ts/src/ogg/oggFile.js';
+import { OggVorbisFile } from '!/@dantheman827/taglib-ts/src/ogg/vorbis/vorbisFile.js';
+
+export const isWorker = typeof WorkerGlobalScope !== 'undefined' && self instanceof WorkerGlobalScope;
+
+export async function addMetadataToAudio(message: _AddMetadataMessage): Promise {
const {
- wasmUrl,
audioData,
+ audioRef,
+ filename,
title,
artist,
albumTitle,
@@ -38,274 +54,290 @@ async function addMetadataToAudio(message: AddMetadataMessage): Promise {
- const tagLib = await TagLib.initialize({
- wasmUrl: wasmUrl,
- });
- return await tagLib.open(audioData);
- });
+ const ref =
+ audioRef ??
+ (await doTimedAsync(
+ `Opening file (${audioData.constructor.name})`,
+ async () => await getFileRefFromAudioData(audioData)
+ ));
- try {
- doTimed('Tagging file', () => {
- const isMp4 = file.isMP4();
- const media = file.audioProperties();
- const needsCombinedTrackDisc = isMp4 || media.containerFormat.toLowerCase() === 'mp3';
+ if (!ref || !ref.isValid) {
+ console.warn('taglib-ts: failed to open file');
+ return audioData;
+ }
- if (title) {
- file.setProperty('TITLE', title);
- }
+ const underlying = ref.file();
+ const isMp4 = underlying instanceof Mp4File;
+ const isMpeg = underlying instanceof MpegFile;
+ const needsCombinedTrackDisc = isMp4 || isMpeg;
- if (artist) {
- file.setProperty('ARTIST', artist);
- }
+ doTimed('Tagging file', () => {
+ const props = ref.properties();
- if (albumTitle) {
- file.setProperty('ALBUM', albumTitle);
- }
+ if (title) props.replace('TITLE', [title]);
+ if (artist) props.replace('ARTIST', [artist]);
+ if (albumTitle) props.replace('ALBUM', [albumTitle]);
+ if (albumArtist || artist) props.replace('ALBUMARTIST', [albumArtist || artist!]);
- const _albumArtist = albumArtist || artist;
- if (_albumArtist) {
- file.setProperty('ALBUMARTIST', _albumArtist);
- }
+ if (trackNumber) {
+ const trackStr =
+ needsCombinedTrackDisc && totalTracks ? `${trackNumber}/${totalTracks}` : String(trackNumber);
+ props.replace('TRACKNUMBER', [trackStr]);
+ }
+ if (!needsCombinedTrackDisc && totalTracks) {
+ props.replace('TRACKTOTAL', [String(totalTracks)]);
+ }
- if (trackNumber) {
- let trackString = String(trackNumber);
+ if (discNumber) {
+ const discStr = needsCombinedTrackDisc && totalDiscs ? `${discNumber}/${totalDiscs}` : String(discNumber);
+ props.replace('DISCNUMBER', [discStr]);
+ }
+ if (!needsCombinedTrackDisc && totalDiscs) {
+ props.replace('DISCTOTAL', [String(totalDiscs)]);
+ }
- if (needsCombinedTrackDisc && trackNumber && totalTracks) {
- trackString = `${trackNumber}/${totalTracks}`;
- }
+ if (bpm != null && Number.isFinite(bpm)) {
+ props.replace('BPM', [String(Math.round(bpm))]);
+ }
- if (needsCombinedTrackDisc) {
- file.setProperty('TRACKNUMBER', trackString);
- } else {
- file.setProperty('TRACKNUMBER', String(trackNumber));
- }
- }
+ if (replayGain) {
+ const { albumReplayGain, albumPeakAmplitude, trackReplayGain, trackPeakAmplitude } = replayGain;
+ if (albumReplayGain != null) props.replace('REPLAYGAIN_ALBUM_GAIN', [String(albumReplayGain)]);
+ if (albumPeakAmplitude != null) props.replace('REPLAYGAIN_ALBUM_PEAK', [String(albumPeakAmplitude)]);
+ if (trackReplayGain != null) props.replace('REPLAYGAIN_TRACK_GAIN', [String(trackReplayGain)]);
+ if (trackPeakAmplitude != null) props.replace('REPLAYGAIN_TRACK_PEAK', [String(trackPeakAmplitude)]);
+ }
- if (!needsCombinedTrackDisc && totalTracks) {
- file.setProperty('TRACKTOTAL', String(totalTracks));
+ if (releaseDate) {
+ try {
+ const year = Number(releaseDate.split('-')[0]);
+ if (!isNaN(year)) props.replace('DATE', [String(year)]);
+ } catch {
+ // Invalid date, skip
}
+ }
- if (discNumber) {
- let discString = String(discNumber);
-
- if (needsCombinedTrackDisc && discNumber && totalDiscs) {
- discString = `${discNumber}/${totalDiscs}`;
- }
-
- if (needsCombinedTrackDisc) {
- file.setProperty('DISCNUMBER', discString);
- } else {
- file.setProperty('DISCNUMBER', String(discNumber));
- }
+ if (copyright) props.replace('COPYRIGHT', [copyright]);
+ if (isrc) props.replace('ISRC', [isrc]);
+ if (lyrics) props.replace('LYRICS', [lyrics.replace(/\r/g, '').replace(/\n/g, '\r\n')]);
+
+ if (explicit !== undefined) {
+ if (isMp4) {
+ // rtng is a byte item — must be set directly on the Mp4Tag
+ const mp4Tag = (underlying as Mp4File).tag() as Mp4Tag;
+ mp4Tag.setItem('rtng', Mp4Item.fromByte(explicit ? 1 : 0));
+ } else {
+ props.replace('ITUNESADVISORY', [explicit ? '1' : '0']);
}
+ }
- if (!needsCombinedTrackDisc && totalDiscs) {
- file.setProperty('DISCTOTAL', String(totalDiscs));
- }
+ ref.setProperties(props);
- if (bpm != null && Number.isFinite(bpm)) {
- file.setProperty('BPM', String(Math.round(bpm)));
- }
+ if (cover) {
+ const pictureMap = new Map();
+ pictureMap.set('data', Variant.fromByteVector(ByteVector.fromByteArray(cover.data)));
+ pictureMap.set('mimeType', Variant.fromString(cover.type));
+ pictureMap.set('pictureType', Variant.fromInt(3)); // FrontCover
+ ref.setComplexProperties('PICTURE', [pictureMap]);
+ }
+ });
- if (replayGain) {
- const { albumReplayGain, albumPeakAmplitude, trackReplayGain, trackPeakAmplitude } = replayGain;
- if (albumReplayGain) file.setProperty('REPLAYGAIN_ALBUM_GAIN', String(albumReplayGain));
- if (albumPeakAmplitude) file.setProperty('REPLAYGAIN_ALBUM_PEAK', String(albumPeakAmplitude));
- if (trackReplayGain) file.setProperty('REPLAYGAIN_TRACK_GAIN', String(trackReplayGain));
- if (trackPeakAmplitude) file.setProperty('REPLAYGAIN_TRACK_PEAK', String(trackPeakAmplitude));
- }
+ await doTimedAsync('Saving in-memory buffer', async () => {
+ await ref.save();
+ });
- if (releaseDate) {
- try {
- const year = Number(releaseDate.split('-')[0]);
- if (!isNaN(year)) {
- file.setProperty('DATE', String(year));
- }
- } catch {
- // Invalid date, skip
- }
- }
+ const file = ref.file() as TagLibFile;
+ if (!file) return audioData;
+ const stream = file.stream();
+
+ if (stream instanceof ChunkedByteVectorStream) {
+ const data = doTimed(
+ 'Converting saved file to ' + (returnType == 'blob' ? 'Blob' : 'Uint8Array'),
+ () => stream.data().data
+ );
+ if (returnType === 'blob') {
+ const blob = new Blob([data as BlobPart], { type: 'application/octet-stream' });
+ return blob;
+ }
+ return data;
+ } else if (stream instanceof BlobStream) {
+ const blob = doTimed('Converting saved file to ' + (returnType == 'blob' ? 'Blob' : 'Uint8Array'), () =>
+ stream.toBlob()
+ );
+ if (returnType === 'blob') {
+ return blob;
+ }
+ const arrayBuffer = await doTimed('Reading Blob as ArrayBuffer', async () => await blob.arrayBuffer());
+ return new Uint8Array(arrayBuffer);
+ }
- if (copyright) {
- file.setProperty('COPYRIGHT', copyright);
- }
+ console.warn('taglib-ts: unexpected stream type after saving file', stream);
+ return audioData;
+}
- if (isrc) {
- file.setProperty('ISRC', isrc);
+export async function getMetadataFromAudio(message: _GetMetadataMessage): Promise {
+ const { audioData, audioRef, filename } = message;
+ const data: TagLibReadMetadata = { duration: 0 };
- if (isMp4) {
- file.setMP4Item('xid ', `:isrc:${isrc}`);
- }
- }
+ const ref =
+ audioRef ??
+ (await doTimedAsync(
+ `Opening file (${audioData.constructor.name})`,
+ async () => await getFileRefFromAudioData(audioData)
+ ));
- if (explicit) {
- if (isMp4) {
- file.setMP4Item('rtng', '1');
- } else {
- file.setProperty('ITUNESADVISORY', '1');
- }
- }
+ if (!ref || !ref.isValid) return data;
- if (lyrics) {
- file.setProperty('LYRICS', lyrics.replace(/\r/g, '').replace(/\n/g, '\r\n'));
- }
+ const underlying = ref.file();
+ const isMp4 = underlying instanceof Mp4File;
+ const ap = ref.audioProperties();
- if (cover) {
- file.setPictures([
- {
- mimeType: cover.type,
- data: cover.data,
- type: 'FrontCover',
- description: 'Cover Art',
- },
- ]);
- }
- });
+ if (ap) data.duration = ap.lengthInSeconds;
- await doTimedAsync('Saving in-memory buffer', () => file.save());
+ const props = ref.properties();
- return file.getFileBuffer();
- } catch (err) {
- console.error(err);
- } finally {
- file.dispose();
- }
+ data.title = props.get('TITLE')?.[0] || undefined;
+ data.artist = props.get('ARTIST')?.[0] || undefined;
+ data.albumTitle = props.get('ALBUM')?.[0] || undefined;
+ data.albumArtist = props.get('ALBUMARTIST')?.[0] || undefined;
- return audioData;
-}
+ const trackStr = props.get('TRACKNUMBER')?.[0] ?? '';
+ const [trackNum, trackTotal] = trackStr.split('/').map((t) => Number(t.trim() || 0) || undefined);
+ data.trackNumber = trackNum || undefined;
+ data.totalTracks = trackTotal ?? (Number(props.get('TRACKTOTAL')?.[0] || 0) || undefined);
-async function getMetadataFromAudio(message: GetMetadataMessage): Promise {
- const { wasmUrl, audioData } = message;
- const data: TagLibReadMetadata = {
- duration: 0,
- };
+ const discStr = props.get('DISCNUMBER')?.[0] ?? '';
+ const [discNum, discTotal] = discStr.split('/').map((t) => Number(t.trim() || 0) || undefined);
+ data.discNumber = discNum || undefined;
+ if (!data.totalDiscs) {
+ data.totalDiscs = discTotal ?? (Number(props.get('DISCTOTAL')?.[0] || 0) || undefined);
+ }
- const file = await doTimedAsync('Open file with taglib', async () => {
- const tagLib = await TagLib.initialize({
- wasmUrl: wasmUrl,
- });
- return await tagLib.open(audioData);
- });
+ data.bpm = Number(props.get('BPM')?.[0] || 0) || undefined;
+ data.copyright = props.get('COPYRIGHT')?.[0] || undefined;
+ data.lyrics = props.get('LYRICS')?.[0] || undefined;
+ data.releaseDate = props.get('DATE')?.[0] || undefined;
+
+ const replayGain: TagLibMetadata['replayGain'] = {};
+ const albumGain = props.get('REPLAYGAIN_ALBUM_GAIN')?.[0];
+ const albumPeak = props.get('REPLAYGAIN_ALBUM_PEAK')?.[0];
+ const trackGain = props.get('REPLAYGAIN_TRACK_GAIN')?.[0];
+ const trackPeak = props.get('REPLAYGAIN_TRACK_PEAK')?.[0];
+ if (albumGain) replayGain.albumReplayGain = albumGain;
+ if (albumPeak) replayGain.albumPeakAmplitude = Number(albumPeak);
+ if (trackGain) replayGain.trackReplayGain = trackGain;
+ if (trackPeak) replayGain.trackPeakAmplitude = Number(trackPeak);
+ if (Object.keys(replayGain).length > 0) data.replayGain = replayGain;
+
+ data.isrc = props.get('ISRC')?.[0] || undefined;
+
+ if (isMp4) {
+ const mp4Tag = (underlying as Mp4File).tag() as Mp4Tag;
+ data.explicit = mp4Tag.item('rtng')?.toByte() === 1;
+ } else {
+ data.explicit = props.get('ITUNESADVISORY')?.[0] === '1';
+ }
- try {
- const pictures = file.getPictures();
- const isMp4 = file.isMP4();
- const media = file.audioProperties();
-
- data.duration = media.duration;
-
- data.title = file.getProperty('TITLE') || undefined;
- data.artist = file.getProperty('ARTIST') || undefined;
- data.albumTitle = file.getProperty('ALBUM') || undefined;
- data.albumArtist = file.getProperty('ALBUMARTIST') || undefined;
- const [trackNumber, trackTotal] = file
- .getProperty('TRACKNUMBER')
- ?.split('/')
- .map((t) => Number(t.trim() || 0) || undefined);
- data.trackNumber = trackNumber || undefined;
- data.totalTracks = trackTotal ? trackTotal : Number(file.getProperty('TRACKTOTAL') || 0) || undefined;
-
- const [discNumber, discTotal] = file
- .getProperty('DISCNUMBER')
- ?.split('/')
- .map((t) => Number(t.trim() || 0) || undefined);
- data.discNumber = Number(file.getProperty('DISCNUMBER') || 0) || undefined;
-
- data.bpm = Number(file.getProperty('BPM') || 0) || undefined;
- data.copyright = file.getProperty('COPYRIGHT') || undefined;
- data.lyrics = file.getProperty('LYRICS') || undefined;
- data.releaseDate = file.getProperty('DATE') || undefined;
-
- const [replayGainAlbumGain, replayGainAlbumPeak, replayGainTrackGain, replayGainTrackPeak] = [
- file.getProperty('REPLAYGAIN_ALBUM_GAIN'),
- file.getProperty('REPLAYGAIN_ALBUM_PEAK'),
- file.getProperty('REPLAYGAIN_TRACK_GAIN'),
- file.getProperty('REPLAYGAIN_TRACK_PEAK'),
- ];
-
- const replayGain: TagLibMetadata['replayGain'] = {};
- if (replayGainAlbumGain) replayGain.albumReplayGain = replayGainAlbumGain;
- if (replayGainAlbumPeak) replayGain.albumPeakAmplitude = Number(replayGainAlbumPeak);
- if (replayGainTrackGain) replayGain.trackReplayGain = replayGainTrackGain;
- if (replayGainTrackPeak) replayGain.trackPeakAmplitude = Number(replayGainTrackPeak);
- if (Object.keys(replayGain).length > 0) {
- data.replayGain = replayGain;
+ const pictures = ref.complexProperties('PICTURE');
+ if (pictures.length > 0) {
+ const pic = pictures[0];
+ const picData = pic.get('data')?.toByteVector();
+ const mimeType = pic.get('mimeType')?.toString() ?? '';
+ if (picData && picData.length > 0) {
+ data.cover = { data: picData.data, type: mimeType };
}
+ }
- data.isrc = (isMp4 && file.getMP4Item('xid ')?.split(':').at(-1)) || file.getProperty('ISRC') || undefined;
- data.explicit = (isMp4 && file.getMP4Item('rtng') === '1') || file.getProperty('ITUNESADVISORY') === '1';
+ return data;
+}
- if (pictures.length > 0) {
- const picture = pictures.filter((p) => p.type === 'FrontCover')[0];
- if (picture) {
- data.cover = {
- data: picture.data,
- type: picture.mimeType,
- };
- }
- }
- } catch (err) {
- console.error(err);
- } finally {
- file.dispose();
+async function getFileRefFromAudioData(
+ audioData: Uint8Array | Blob | File | FileSystemFileHandle | FileSystemFileEntry
+): Promise {
+ if (audioData instanceof Blob || audioData instanceof File) {
+ const stream = new BlobStream(audioData);
+ return await FileRef.open(stream, true, ReadStyle.Average);
+ } else if (audioData instanceof FileSystemFileHandle) {
+ const stream = await FileSystemFileHandleStream.open(audioData, true);
+ return await FileRef.open(stream, true, ReadStyle.Average);
+ } else if ('FileSystemFileEntry' in globalThis && audioData instanceof FileSystemFileEntry) {
+ const file = await new Promise((resolve) => audioData.file((f) => resolve(f)));
+ const stream = new BlobStream(file);
+ return await FileRef.open(stream, true, ReadStyle.Average);
+ } else if (audioData instanceof Uint8Array) {
+ const stream = new ChunkedByteVectorStream(audioData);
+ return await FileRef.open(stream, true, ReadStyle.Average);
}
- return data;
+ throw new Error('Unsupported audio data type');
}
-self.onmessage = async (event: MessageEvent) => {
- const transfer: Transferable[] = [event.data.audioData.buffer];
+if (isWorker) {
+ self.onmessage = async (event: MessageEvent) => {
+ const transfer: Transferable[] = [];
+ if (event.data.audioData?.buffer instanceof ArrayBuffer) {
+ transfer.push(event.data.audioData.buffer);
+ }
- switch (event.data.type) {
- case 'Add':
- if ((event.data as AddMetadataMessage).cover?.data?.buffer instanceof ArrayBuffer) {
- transfer.push((event.data as AddMetadataMessage).cover.data.buffer);
- }
+ switch (event.data.type) {
+ case 'Add':
+ if ((event.data as AddMetadataMessage).cover?.data?.buffer instanceof ArrayBuffer) {
+ transfer.push((event.data as AddMetadataMessage).cover.data.buffer);
+ }
- try {
- const result = await addMetadataToAudio(event.data as AddMetadataMessage);
- transfer.push(result.buffer);
-
- self.postMessage(
- {
- type: event.data.type,
- data: result,
- } satisfies TagLibFileResponse,
- transfer
- );
- } catch (error) {
- self.postMessage(
- {
- type: event.data.type,
- error: error instanceof Error ? error.message : String(error),
- } satisfies TagLibWorkerResponse,
- transfer
- );
- }
- break;
+ try {
+ const result = (await addMetadataToAudio({
+ ...event.data,
+ returnType: 'uint8array',
+ } as _AddMetadataMessage)) as Uint8Array;
- case 'Get':
- try {
- const result = await getMetadataFromAudio(event.data as GetMetadataMessage);
- self.postMessage(
- {
- type: event.data.type,
- data: result,
- } satisfies TagLibMetadataResponse,
- transfer
- );
- } catch (error) {
- self.postMessage(
- {
- type: event.data.type,
- error: error instanceof Error ? error.message : String(error),
- } satisfies TagLibWorkerResponse,
- transfer
- );
- }
- break;
- }
-};
+ if (result.buffer !== event.data.audioData.buffer) {
+ transfer.push(result.buffer);
+ }
+
+ self.postMessage(
+ {
+ type: event.data.type,
+ data: result,
+ } satisfies TagLibFileResponse,
+ transfer
+ );
+ } catch (error) {
+ self.postMessage(
+ {
+ type: event.data.type,
+ error: error instanceof Error ? error.message : String(error),
+ } satisfies TagLibWorkerResponse,
+ transfer
+ );
+ }
+ break;
+
+ case 'Get':
+ try {
+ const result = await getMetadataFromAudio({
+ ...event.data,
+ } as _GetMetadataMessage);
+ self.postMessage(
+ {
+ type: event.data.type,
+ data: result,
+ } satisfies TagLibMetadataResponse,
+ transfer
+ );
+ } catch (error) {
+ self.postMessage(
+ {
+ type: event.data.type,
+ error: error instanceof Error ? error.message : String(error),
+ } satisfies TagLibWorkerResponse,
+ transfer
+ );
+ }
+ break;
+ }
+ };
+}
diff --git a/package-lock.json b/package-lock.json
index 87e059e33..ffdbcf26c 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -9,6 +9,7 @@
"version": "2.5.0",
"license": "ISC",
"dependencies": {
+ "@dantheman827/taglib-ts": "https://github.com/DanTheMan827/taglib-ts/archive/b4238b2627aceb97f58813258046f1259f68cab7.tar.gz",
"@ffmpeg/core": "^0.12.10",
"@ffmpeg/ffmpeg": "^0.12.15",
"@ffmpeg/util": "^0.12.2",
@@ -27,7 +28,6 @@
"mime": "^4.1.0",
"npm": "^11.11.1",
"pocketbase": "^0.26.8",
- "taglib-wasm": "^1.0.5",
"uuid": "^13.0.0"
},
"devDependencies": {
@@ -1676,6 +1676,12 @@
"postcss-selector-parser": "^7.0.0"
}
},
+ "node_modules/@dantheman827/taglib-ts": {
+ "version": "0.1.4",
+ "resolved": "https://github.com/DanTheMan827/taglib-ts/archive/b4238b2627aceb97f58813258046f1259f68cab7.tar.gz",
+ "integrity": "sha512-rvQOn9GDEj2sH4yV6oUTMMG9+rJbFG7tQkiP6/bhGJARg1Vmdy283j4YFCl+ubkqsMQ+UfAhEWSw5d5lfPVfwQ==",
+ "license": "LGPL-2.1-or-later"
+ },
"node_modules/@dual-bundle/import-meta-resolve": {
"version": "4.2.1",
"dev": true,
@@ -3012,13 +3018,6 @@
"@lit-labs/ssr-dom-shim": "^1.5.0"
}
},
- "node_modules/@msgpack/msgpack": {
- "version": "3.1.3",
- "license": "ISC",
- "engines": {
- "node": ">= 18"
- }
- },
"node_modules/@neutralinojs/lib": {
"version": "6.5.0",
"license": "MIT",
@@ -10307,24 +10306,6 @@
"node": ">=10.0.0"
}
},
- "node_modules/taglib-wasm": {
- "version": "1.0.5",
- "license": "MIT",
- "dependencies": {
- "@msgpack/msgpack": "^3.1.3"
- },
- "engines": {
- "node": ">=22.6.0"
- },
- "peerDependencies": {
- "typescript": ">=4.5.0"
- },
- "peerDependenciesMeta": {
- "typescript": {
- "optional": true
- }
- }
- },
"node_modules/tcp-port-used": {
"version": "1.0.2",
"dev": true,
@@ -10550,7 +10531,7 @@
},
"node_modules/typescript": {
"version": "5.9.3",
- "devOptional": true,
+ "dev": true,
"license": "Apache-2.0",
"bin": {
"tsc": "bin/tsc",
diff --git a/package.json b/package.json
index 5ea8e153c..9de2ff06d 100644
--- a/package.json
+++ b/package.json
@@ -51,6 +51,7 @@
"serialize-javascript": "^7.0.3"
},
"dependencies": {
+ "@dantheman827/taglib-ts": "https://github.com/DanTheMan827/taglib-ts/archive/b4238b2627aceb97f58813258046f1259f68cab7.tar.gz",
"@ffmpeg/core": "^0.12.10",
"@ffmpeg/ffmpeg": "^0.12.15",
"@ffmpeg/util": "^0.12.2",
@@ -69,7 +70,6 @@
"mime": "^4.1.0",
"npm": "^11.11.1",
"pocketbase": "^0.26.8",
- "taglib-wasm": "^1.0.5",
"uuid": "^13.0.0"
}
}
diff --git a/vite.config.ts b/vite.config.ts
index 8b2475f0b..c3ab6fe43 100644
--- a/vite.config.ts
+++ b/vite.config.ts
@@ -21,8 +21,7 @@ export default defineConfig(({ mode }) => {
},
},
optimizeDeps: {
- exclude: ['pocketbase', '@ffmpeg/ffmpeg', '@ffmpeg/util', 'taglib-wasm'],
- external: ['taglib-wasm'],
+ exclude: ['pocketbase', '@ffmpeg/ffmpeg', '@ffmpeg/util'],
},
server: {
fs: {
From 7c5424437e52c9453fb29578a035989cc98fdf61 Mon Sep 17 00:00:00 2001
From: Daniel <790119+DanTheMan827@users.noreply.github.com>
Date: Thu, 19 Mar 2026 14:13:58 -0500
Subject: [PATCH 129/226] feat(ffmpeg): add WAV format support and detection
for use in dev mode
- Added WAV format to customFormats with appropriate metadata.
- Implemented detection for RIFF/WAVE format in detectAudioFormat function.
---
js/ffmpegFormats.ts | 12 ++++++++++++
js/utils.js | 16 ++++++++++++++++
2 files changed, 28 insertions(+)
diff --git a/js/ffmpegFormats.ts b/js/ffmpegFormats.ts
index ef6927728..562cbd039 100644
--- a/js/ffmpegFormats.ts
+++ b/js/ffmpegFormats.ts
@@ -130,6 +130,18 @@ export const customFormats: Record = {
},
};
+// Add wav to custom formats when vite is in dev mode
+if (import.meta.env.DEV) {
+ customFormats.FFMPEG_WAV = {
+ displayName: 'WAV (16-bit PCM)',
+ ffmpegArgs: ['-map_metadata', '-1'],
+ outputFilename: 'output.wav',
+ outputMime: 'audio/wav',
+ extension: 'wav',
+ category: 'WAV',
+ };
+}
+
/**
* Container format definitions for lossless re-muxing. Each entry describes
* the ffmpeg arguments needed to produce that container and provides a
diff --git a/js/utils.js b/js/utils.js
index 75588d79b..022e1b587 100644
--- a/js/utils.js
+++ b/js/utils.js
@@ -140,6 +140,22 @@ export const detectAudioFormat = (view, mimeType = '') => {
return 'mp3';
}
+ // Detect RIFF/WAVE by "RIFF" at offset 0 and "WAVE" at offset 8 (only in dev mode)
+ if (
+ import.meta.env.DEV &&
+ view.byteLength >= 12 &&
+ view.getUint8(0) === 0x52 && // R
+ view.getUint8(1) === 0x49 && // I
+ view.getUint8(2) === 0x46 && // F
+ view.getUint8(3) === 0x46 && // F
+ view.getUint8(8) === 0x57 && // W
+ view.getUint8(9) === 0x41 && // A
+ view.getUint8(10) === 0x56 && // V
+ view.getUint8(11) === 0x45 // E
+ ) {
+ return 'wav';
+ }
+
// Check for MPEG frame sync (0xFF 0xFB or 0xFF 0xFA)
if (view.byteLength >= 2 && view.getUint8(0) === 0xff && (view.getUint8(1) & 0xe0) === 0xe0) {
return 'mp3';
From e109a5f23661fcfa3a0410aefc7b394ffb62f05d Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Thu, 19 Mar 2026 20:23:20 +0000
Subject: [PATCH 130/226] chore(deps-dev): bump flatted from 3.4.0 to 3.4.2
Bumps [flatted](https://github.com/WebReflection/flatted) from 3.4.0 to 3.4.2.
- [Commits](https://github.com/WebReflection/flatted/compare/v3.4.0...v3.4.2)
---
updated-dependencies:
- dependency-name: flatted
dependency-version: 3.4.2
dependency-type: indirect
...
Signed-off-by: dependabot[bot]
---
package-lock.json | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/package-lock.json b/package-lock.json
index ffdbcf26c..aede45e36 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -5403,7 +5403,9 @@
}
},
"node_modules/flatted": {
- "version": "3.4.0",
+ "version": "3.4.2",
+ "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz",
+ "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==",
"dev": true,
"license": "ISC"
},
From e2a09b9b323fd7e038b1ac071950697676d760cb Mon Sep 17 00:00:00 2001
From: Daniel <790119+DanTheMan827@users.noreply.github.com>
Date: Wed, 18 Mar 2026 17:25:01 -0500
Subject: [PATCH 131/226] refactor: externalize svg images and add new vite
plugin
The plugin runs all imported SVG files through svgo.
For index.html, you can use the following syntax:
```html
```
For scripts, use the `?svg` import query
```javascript
import SVG_FILE from './file.svg?svg&size=24
```
Note: size is shorthand for specifying both width and height individually. You can also set any property of the base SVG element.
You can also use the `?svg&icon` query to return a function that allows dynamically resizing the SVG string.
---
bun.lock | 209 ++---
images/animate-spin.svg | 4 +
images/apple.svg | 20 +
images/discord.svg | 22 +
images/facebook.svg | 4 +
images/genius-active.svg | 5 +
images/genius-inactive.svg | 4 +
images/github.svg | 16 +
images/instagram.svg | 16 +
images/linux.svg | 17 +
images/mic.svg | 16 +
images/mix.svg | 4 +
images/monochrome-logo.svg | 13 +
images/pause-large.svg | 6 +
images/pause.svg | 5 +
images/play-large.svg | 13 +
images/play.svg | 4 +
images/recent.svg | 15 +
images/side-menu.svg | 14 +
images/sort.svg | 1 +
images/soundcloud.svg | 8 +
images/squares.svg | 16 +
images/twitter.svg | 5 +
images/windows.svg | 4 +
images/youtube.svg | 20 +
index.html | 1510 +++---------------------------------
js/app.js | 54 +-
js/downloads.js | 9 +-
js/events.js | 18 +-
js/global.d.ts | 15 +
js/icons.ts | 42 +
js/lyrics.js | 39 +-
js/player.js | 17 +-
js/storage.js | 6 +-
js/themeStore.js | 5 +-
js/tracker.js | 15 +-
js/ui-interactions.js | 44 +-
js/ui.js | 182 ++---
js/utils.js | 21 -
package-lock.json | 700 ++++++++++-------
package.json | 3 +
vite-plugin-svg-use.ts | 234 ++++++
vite.config.ts | 7 +
43 files changed, 1340 insertions(+), 2042 deletions(-)
create mode 100644 images/animate-spin.svg
create mode 100644 images/apple.svg
create mode 100644 images/discord.svg
create mode 100644 images/facebook.svg
create mode 100644 images/genius-active.svg
create mode 100644 images/genius-inactive.svg
create mode 100644 images/github.svg
create mode 100644 images/instagram.svg
create mode 100644 images/linux.svg
create mode 100644 images/mic.svg
create mode 100644 images/mix.svg
create mode 100644 images/monochrome-logo.svg
create mode 100644 images/pause-large.svg
create mode 100644 images/pause.svg
create mode 100644 images/play-large.svg
create mode 100644 images/play.svg
create mode 100644 images/recent.svg
create mode 100644 images/side-menu.svg
create mode 100644 images/sort.svg
create mode 100644 images/soundcloud.svg
create mode 100644 images/squares.svg
create mode 100644 images/twitter.svg
create mode 100644 images/windows.svg
create mode 100644 images/youtube.svg
create mode 100644 js/icons.ts
create mode 100644 vite-plugin-svg-use.ts
diff --git a/bun.lock b/bun.lock
index fdf49cc3c..e9356fd16 100644
--- a/bun.lock
+++ b/bun.lock
@@ -21,9 +21,12 @@
"fuse.js": "^7.1.0",
"hls.js": "^1.6.15",
"jose": "^6.2.0",
+ "lucide-static": "^0.577.0",
"mime": "^4.1.0",
"npm": "^11.11.1",
"pocketbase": "^0.26.8",
+ "simple-icons": "^16.12.0",
+ "svgo": "^4.0.1",
"uuid": "^13.0.0",
},
"devDependencies": {
@@ -70,7 +73,7 @@
"@babel/helper-create-regexp-features-plugin": ["@babel/helper-create-regexp-features-plugin@7.28.5", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.3", "regexpu-core": "^6.3.1", "semver": "^6.3.1" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-N1EhvLtHzOvj7QQOUCCS3NrPJP8c5W6ZXCHDn7Yialuy1iu4r5EmIYkXlKNqT99Ciw+W0mDqWoR6HWMZlFP3hw=="],
- "@babel/helper-define-polyfill-provider": ["@babel/helper-define-polyfill-provider@0.6.7", "", { "dependencies": { "@babel/helper-compilation-targets": "^7.28.6", "@babel/helper-plugin-utils": "^7.28.6", "debug": "^4.4.3", "lodash.debounce": "^4.0.8", "resolve": "^1.22.11" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, "sha512-6Fqi8MtQ/PweQ9xvux65emkLQ83uB+qAVtfHkC9UodyHMIZdxNI01HjLCLUtybElp2KY2XNE0nOgyP1E1vXw9w=="],
+ "@babel/helper-define-polyfill-provider": ["@babel/helper-define-polyfill-provider@0.6.8", "", { "dependencies": { "@babel/helper-compilation-targets": "^7.28.6", "@babel/helper-plugin-utils": "^7.28.6", "debug": "^4.4.3", "lodash.debounce": "^4.0.8", "resolve": "^1.22.11" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, "sha512-47UwBLPpQi1NoWzLuHNjRoHlYXMwIJoBf7MFou6viC/sIHWYygpvr0B6IAyh5sBdA2nr2LPIRww8lfaUVQINBA=="],
"@babel/helper-globals": ["@babel/helper-globals@7.28.0", "", {}, "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw=="],
@@ -98,9 +101,9 @@
"@babel/helper-wrap-function": ["@babel/helper-wrap-function@7.28.6", "", { "dependencies": { "@babel/template": "^7.28.6", "@babel/traverse": "^7.28.6", "@babel/types": "^7.28.6" } }, "sha512-z+PwLziMNBeSQJonizz2AGnndLsP2DeGHIxDAn+wdHOGuo4Fo1x1HBPPXeE9TAOPHNNWQKCSlA2VZyYyyibDnQ=="],
- "@babel/helpers": ["@babel/helpers@7.28.6", "", { "dependencies": { "@babel/template": "^7.28.6", "@babel/types": "^7.28.6" } }, "sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw=="],
+ "@babel/helpers": ["@babel/helpers@7.29.2", "", { "dependencies": { "@babel/template": "^7.28.6", "@babel/types": "^7.29.0" } }, "sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw=="],
- "@babel/parser": ["@babel/parser@7.29.0", "", { "dependencies": { "@babel/types": "^7.29.0" }, "bin": "./bin/babel-parser.js" }, "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww=="],
+ "@babel/parser": ["@babel/parser@7.29.2", "", { "dependencies": { "@babel/types": "^7.29.0" }, "bin": "./bin/babel-parser.js" }, "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA=="],
"@babel/plugin-bugfix-firefox-class-in-computed-class-key": ["@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.28.5", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1", "@babel/traverse": "^7.28.5" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-87GDMS3tsmMSi/3bWOte1UblL+YUTFMV8SZPZ2eSEL17s74Cw/l63rR6NmGVKMYW2GYi85nE+/d6Hw5N0bEk2Q=="],
@@ -222,11 +225,11 @@
"@babel/plugin-transform-unicode-sets-regex": ["@babel/plugin-transform-unicode-sets-regex@7.28.6", "", { "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.28.5", "@babel/helper-plugin-utils": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-/wHc/paTUmsDYN7SZkpWxogTOBNnlx7nBQYfy6JJlCT7G3mVhltk3e++N7zV0XfgGsrqBxd4rJQt9H16I21Y1Q=="],
- "@babel/preset-env": ["@babel/preset-env@7.29.0", "", { "dependencies": { "@babel/compat-data": "^7.29.0", "@babel/helper-compilation-targets": "^7.28.6", "@babel/helper-plugin-utils": "^7.28.6", "@babel/helper-validator-option": "^7.27.1", "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.28.5", "@babel/plugin-bugfix-safari-class-field-initializer-scope": "^7.27.1", "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.27.1", "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.27.1", "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.28.6", "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", "@babel/plugin-syntax-import-assertions": "^7.28.6", "@babel/plugin-syntax-import-attributes": "^7.28.6", "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", "@babel/plugin-transform-arrow-functions": "^7.27.1", "@babel/plugin-transform-async-generator-functions": "^7.29.0", "@babel/plugin-transform-async-to-generator": "^7.28.6", "@babel/plugin-transform-block-scoped-functions": "^7.27.1", "@babel/plugin-transform-block-scoping": "^7.28.6", "@babel/plugin-transform-class-properties": "^7.28.6", "@babel/plugin-transform-class-static-block": "^7.28.6", "@babel/plugin-transform-classes": "^7.28.6", "@babel/plugin-transform-computed-properties": "^7.28.6", "@babel/plugin-transform-destructuring": "^7.28.5", "@babel/plugin-transform-dotall-regex": "^7.28.6", "@babel/plugin-transform-duplicate-keys": "^7.27.1", "@babel/plugin-transform-duplicate-named-capturing-groups-regex": "^7.29.0", "@babel/plugin-transform-dynamic-import": "^7.27.1", "@babel/plugin-transform-explicit-resource-management": "^7.28.6", "@babel/plugin-transform-exponentiation-operator": "^7.28.6", "@babel/plugin-transform-export-namespace-from": "^7.27.1", "@babel/plugin-transform-for-of": "^7.27.1", "@babel/plugin-transform-function-name": "^7.27.1", "@babel/plugin-transform-json-strings": "^7.28.6", "@babel/plugin-transform-literals": "^7.27.1", "@babel/plugin-transform-logical-assignment-operators": "^7.28.6", "@babel/plugin-transform-member-expression-literals": "^7.27.1", "@babel/plugin-transform-modules-amd": "^7.27.1", "@babel/plugin-transform-modules-commonjs": "^7.28.6", "@babel/plugin-transform-modules-systemjs": "^7.29.0", "@babel/plugin-transform-modules-umd": "^7.27.1", "@babel/plugin-transform-named-capturing-groups-regex": "^7.29.0", "@babel/plugin-transform-new-target": "^7.27.1", "@babel/plugin-transform-nullish-coalescing-operator": "^7.28.6", "@babel/plugin-transform-numeric-separator": "^7.28.6", "@babel/plugin-transform-object-rest-spread": "^7.28.6", "@babel/plugin-transform-object-super": "^7.27.1", "@babel/plugin-transform-optional-catch-binding": "^7.28.6", "@babel/plugin-transform-optional-chaining": "^7.28.6", "@babel/plugin-transform-parameters": "^7.27.7", "@babel/plugin-transform-private-methods": "^7.28.6", "@babel/plugin-transform-private-property-in-object": "^7.28.6", "@babel/plugin-transform-property-literals": "^7.27.1", "@babel/plugin-transform-regenerator": "^7.29.0", "@babel/plugin-transform-regexp-modifiers": "^7.28.6", "@babel/plugin-transform-reserved-words": "^7.27.1", "@babel/plugin-transform-shorthand-properties": "^7.27.1", "@babel/plugin-transform-spread": "^7.28.6", "@babel/plugin-transform-sticky-regex": "^7.27.1", "@babel/plugin-transform-template-literals": "^7.27.1", "@babel/plugin-transform-typeof-symbol": "^7.27.1", "@babel/plugin-transform-unicode-escapes": "^7.27.1", "@babel/plugin-transform-unicode-property-regex": "^7.28.6", "@babel/plugin-transform-unicode-regex": "^7.27.1", "@babel/plugin-transform-unicode-sets-regex": "^7.28.6", "@babel/preset-modules": "0.1.6-no-external-plugins", "babel-plugin-polyfill-corejs2": "^0.4.15", "babel-plugin-polyfill-corejs3": "^0.14.0", "babel-plugin-polyfill-regenerator": "^0.6.6", "core-js-compat": "^3.48.0", "semver": "^6.3.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-fNEdfc0yi16lt6IZo2Qxk3knHVdfMYX33czNb4v8yWhemoBhibCpQK/uYHtSKIiO+p/zd3+8fYVXhQdOVV608w=="],
+ "@babel/preset-env": ["@babel/preset-env@7.29.2", "", { "dependencies": { "@babel/compat-data": "^7.29.0", "@babel/helper-compilation-targets": "^7.28.6", "@babel/helper-plugin-utils": "^7.28.6", "@babel/helper-validator-option": "^7.27.1", "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.28.5", "@babel/plugin-bugfix-safari-class-field-initializer-scope": "^7.27.1", "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.27.1", "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.27.1", "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.28.6", "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", "@babel/plugin-syntax-import-assertions": "^7.28.6", "@babel/plugin-syntax-import-attributes": "^7.28.6", "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", "@babel/plugin-transform-arrow-functions": "^7.27.1", "@babel/plugin-transform-async-generator-functions": "^7.29.0", "@babel/plugin-transform-async-to-generator": "^7.28.6", "@babel/plugin-transform-block-scoped-functions": "^7.27.1", "@babel/plugin-transform-block-scoping": "^7.28.6", "@babel/plugin-transform-class-properties": "^7.28.6", "@babel/plugin-transform-class-static-block": "^7.28.6", "@babel/plugin-transform-classes": "^7.28.6", "@babel/plugin-transform-computed-properties": "^7.28.6", "@babel/plugin-transform-destructuring": "^7.28.5", "@babel/plugin-transform-dotall-regex": "^7.28.6", "@babel/plugin-transform-duplicate-keys": "^7.27.1", "@babel/plugin-transform-duplicate-named-capturing-groups-regex": "^7.29.0", "@babel/plugin-transform-dynamic-import": "^7.27.1", "@babel/plugin-transform-explicit-resource-management": "^7.28.6", "@babel/plugin-transform-exponentiation-operator": "^7.28.6", "@babel/plugin-transform-export-namespace-from": "^7.27.1", "@babel/plugin-transform-for-of": "^7.27.1", "@babel/plugin-transform-function-name": "^7.27.1", "@babel/plugin-transform-json-strings": "^7.28.6", "@babel/plugin-transform-literals": "^7.27.1", "@babel/plugin-transform-logical-assignment-operators": "^7.28.6", "@babel/plugin-transform-member-expression-literals": "^7.27.1", "@babel/plugin-transform-modules-amd": "^7.27.1", "@babel/plugin-transform-modules-commonjs": "^7.28.6", "@babel/plugin-transform-modules-systemjs": "^7.29.0", "@babel/plugin-transform-modules-umd": "^7.27.1", "@babel/plugin-transform-named-capturing-groups-regex": "^7.29.0", "@babel/plugin-transform-new-target": "^7.27.1", "@babel/plugin-transform-nullish-coalescing-operator": "^7.28.6", "@babel/plugin-transform-numeric-separator": "^7.28.6", "@babel/plugin-transform-object-rest-spread": "^7.28.6", "@babel/plugin-transform-object-super": "^7.27.1", "@babel/plugin-transform-optional-catch-binding": "^7.28.6", "@babel/plugin-transform-optional-chaining": "^7.28.6", "@babel/plugin-transform-parameters": "^7.27.7", "@babel/plugin-transform-private-methods": "^7.28.6", "@babel/plugin-transform-private-property-in-object": "^7.28.6", "@babel/plugin-transform-property-literals": "^7.27.1", "@babel/plugin-transform-regenerator": "^7.29.0", "@babel/plugin-transform-regexp-modifiers": "^7.28.6", "@babel/plugin-transform-reserved-words": "^7.27.1", "@babel/plugin-transform-shorthand-properties": "^7.27.1", "@babel/plugin-transform-spread": "^7.28.6", "@babel/plugin-transform-sticky-regex": "^7.27.1", "@babel/plugin-transform-template-literals": "^7.27.1", "@babel/plugin-transform-typeof-symbol": "^7.27.1", "@babel/plugin-transform-unicode-escapes": "^7.27.1", "@babel/plugin-transform-unicode-property-regex": "^7.28.6", "@babel/plugin-transform-unicode-regex": "^7.27.1", "@babel/plugin-transform-unicode-sets-regex": "^7.28.6", "@babel/preset-modules": "0.1.6-no-external-plugins", "babel-plugin-polyfill-corejs2": "^0.4.15", "babel-plugin-polyfill-corejs3": "^0.14.0", "babel-plugin-polyfill-regenerator": "^0.6.6", "core-js-compat": "^3.48.0", "semver": "^6.3.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-DYD23veRYGvBFhcTY1iUvJnDNpuqNd/BzBwCvzOTKUnJjKg5kpUBh3/u9585Agdkgj+QuygG7jLfOPWMa2KVNw=="],
"@babel/preset-modules": ["@babel/preset-modules@0.1.6-no-external-plugins", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.0.0", "@babel/types": "^7.4.4", "esutils": "^2.0.2" }, "peerDependencies": { "@babel/core": "^7.0.0-0 || ^8.0.0-0 <8.0.0" } }, "sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA=="],
- "@babel/runtime": ["@babel/runtime@7.28.6", "", {}, "sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA=="],
+ "@babel/runtime": ["@babel/runtime@7.29.2", "", {}, "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g=="],
"@babel/template": ["@babel/template@7.28.6", "", { "dependencies": { "@babel/code-frame": "^7.28.6", "@babel/parser": "^7.28.6", "@babel/types": "^7.28.6" } }, "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ=="],
@@ -238,21 +241,21 @@
"@cacheable/utils": ["@cacheable/utils@2.4.0", "", { "dependencies": { "hashery": "^1.5.0", "keyv": "^5.6.0" } }, "sha512-PeMMsqjVq+bF0WBsxFBxr/WozBJiZKY0rUojuaCoIaKnEl3Ju1wfEwS+SV1DU/cSe8fqHIPiYJFif8T3MVt4cQ=="],
- "@cloudflare/workerd-darwin-64": ["@cloudflare/workerd-darwin-64@1.20260301.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-+kJvwociLrvy1JV9BAvoSVsMEIYD982CpFmo/yMEvBwxDIjltYsLTE8DLi0mCkGsQ8Ygidv2fD9wavzXeiY7OQ=="],
+ "@cloudflare/workerd-darwin-64": ["@cloudflare/workerd-darwin-64@1.20260317.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-8hjh3sPMwY8M/zedq3/sXoA2Q4BedlGufn3KOOleIG+5a4ReQKLlUah140D7J6zlKmYZAFMJ4tWC7hCuI/s79g=="],
- "@cloudflare/workerd-darwin-arm64": ["@cloudflare/workerd-darwin-arm64@1.20260301.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-PPIetY3e67YBr9O4UhILK8nbm5TqUDl14qx4rwFNrRSBOvlzuczzbd4BqgpAtbGVFxKp1PWpjAnBvGU/OI/tLQ=="],
+ "@cloudflare/workerd-darwin-arm64": ["@cloudflare/workerd-darwin-arm64@1.20260317.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-M/MnNyvO5HMgoIdr3QHjdCj2T1ki9gt0vIUnxYxBu9ISXS/jgtMl6chUVPJ7zHYBn9MyYr8ByeN6frjYxj0MGg=="],
- "@cloudflare/workerd-linux-64": ["@cloudflare/workerd-linux-64@1.20260301.1", "", { "os": "linux", "cpu": "x64" }, "sha512-Gu5vaVTZuYl3cHa+u5CDzSVDBvSkfNyuAHi6Mdfut7TTUdcb3V5CIcR/mXRSyMXzEy9YxEWIfdKMxOMBjupvYQ=="],
+ "@cloudflare/workerd-linux-64": ["@cloudflare/workerd-linux-64@1.20260317.1", "", { "os": "linux", "cpu": "x64" }, "sha512-1ltuEjkRcS3fsVF7CxsKlWiRmzq2ZqMfqDN0qUOgbUwkpXsLVJsXmoblaLf5OP00ELlcgF0QsN0p2xPEua4Uug=="],
- "@cloudflare/workerd-linux-arm64": ["@cloudflare/workerd-linux-arm64@1.20260301.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-igL1pkyCXW6GiGpjdOAvqMi87UW0LMc/+yIQe/CSzuZJm5GzXoAMrwVTkCFnikk6JVGELrM5x0tGYlxa0sk5Iw=="],
+ "@cloudflare/workerd-linux-arm64": ["@cloudflare/workerd-linux-arm64@1.20260317.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-3QrNnPF1xlaNwkHpasvRvAMidOvQs2NhXQmALJrEfpIJ/IDL2la8g499yXp3eqhG3hVMCB07XVY149GTs42Xtw=="],
- "@cloudflare/workerd-windows-64": ["@cloudflare/workerd-windows-64@1.20260301.1", "", { "os": "win32", "cpu": "x64" }, "sha512-Q0wMJ4kcujXILwQKQFc1jaYamVsNvjuECzvRrTI8OxGFMx2yq9aOsswViE4X1gaS2YQQ5u0JGwuGi5WdT1Lt7A=="],
+ "@cloudflare/workerd-windows-64": ["@cloudflare/workerd-windows-64@1.20260317.1", "", { "os": "win32", "cpu": "x64" }, "sha512-MfZTz+7LfuIpMGTa3RLXHX8Z/pnycZLItn94WRdHr8LPVet+C5/1Nzei399w/jr3+kzT4pDKk26JF/tlI5elpQ=="],
"@cspotcode/source-map-support": ["@cspotcode/source-map-support@0.8.1", "", { "dependencies": { "@jridgewell/trace-mapping": "0.3.9" } }, "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw=="],
"@csstools/css-parser-algorithms": ["@csstools/css-parser-algorithms@3.0.5", "", { "peerDependencies": { "@csstools/css-tokenizer": "^3.0.4" } }, "sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ=="],
- "@csstools/css-syntax-patches-for-csstree": ["@csstools/css-syntax-patches-for-csstree@1.1.0", "", {}, "sha512-H4tuz2nhWgNKLt1inYpoVCfbJbMwX/lQKp3g69rrrIMIYlFD9+zTykOKhNR8uGrAmbS/kT9n6hTFkmDkxLgeTA=="],
+ "@csstools/css-syntax-patches-for-csstree": ["@csstools/css-syntax-patches-for-csstree@1.1.1", "", { "peerDependencies": { "css-tree": "^3.2.1" }, "optionalPeers": ["css-tree"] }, "sha512-BvqN0AMWNAnLk9G8jnUT77D+mUbY/H2b3uDTvg2isJkHaOufUE2R3AOwxWo7VBQKT1lOdwdvorddo2B/lk64+w=="],
"@csstools/css-tokenizer": ["@csstools/css-tokenizer@3.0.4", "", {}, "sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw=="],
@@ -266,59 +269,59 @@
"@electron/asar": ["@electron/asar@3.4.1", "", { "dependencies": { "commander": "^5.0.0", "glob": "^7.1.6", "minimatch": "^3.0.4" }, "bin": { "asar": "bin/asar.js" } }, "sha512-i4/rNPRS84t0vSRa2HorerGRXWyF4vThfHesw0dmcWHp+cspK743UanA0suA5Q5y8kzY2y6YKrvbIUn69BCAiA=="],
- "@emnapi/runtime": ["@emnapi/runtime@1.8.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg=="],
+ "@emnapi/runtime": ["@emnapi/runtime@1.9.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-VYi5+ZVLhpgK4hQ0TAjiQiZ6ol0oe4mBx7mVv7IflsiEp0OWoVsp/+f9Vc1hOhE0TtkORVrI1GvzyreqpgWtkA=="],
- "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.27.3", "", { "os": "aix", "cpu": "ppc64" }, "sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg=="],
+ "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.27.4", "", { "os": "aix", "cpu": "ppc64" }, "sha512-cQPwL2mp2nSmHHJlCyoXgHGhbEPMrEEU5xhkcy3Hs/O7nGZqEpZ2sUtLaL9MORLtDfRvVl2/3PAuEkYZH0Ty8Q=="],
- "@esbuild/android-arm": ["@esbuild/android-arm@0.27.3", "", { "os": "android", "cpu": "arm" }, "sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA=="],
+ "@esbuild/android-arm": ["@esbuild/android-arm@0.27.4", "", { "os": "android", "cpu": "arm" }, "sha512-X9bUgvxiC8CHAGKYufLIHGXPJWnr0OCdR0anD2e21vdvgCI8lIfqFbnoeOz7lBjdrAGUhqLZLcQo6MLhTO2DKQ=="],
- "@esbuild/android-arm64": ["@esbuild/android-arm64@0.27.3", "", { "os": "android", "cpu": "arm64" }, "sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg=="],
+ "@esbuild/android-arm64": ["@esbuild/android-arm64@0.27.4", "", { "os": "android", "cpu": "arm64" }, "sha512-gdLscB7v75wRfu7QSm/zg6Rx29VLdy9eTr2t44sfTW7CxwAtQghZ4ZnqHk3/ogz7xao0QAgrkradbBzcqFPasw=="],
- "@esbuild/android-x64": ["@esbuild/android-x64@0.27.3", "", { "os": "android", "cpu": "x64" }, "sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ=="],
+ "@esbuild/android-x64": ["@esbuild/android-x64@0.27.4", "", { "os": "android", "cpu": "x64" }, "sha512-PzPFnBNVF292sfpfhiyiXCGSn9HZg5BcAz+ivBuSsl6Rk4ga1oEXAamhOXRFyMcjwr2DVtm40G65N3GLeH1Lvw=="],
- "@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.27.3", "", { "os": "darwin", "cpu": "arm64" }, "sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg=="],
+ "@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.27.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-b7xaGIwdJlht8ZFCvMkpDN6uiSmnxxK56N2GDTMYPr2/gzvfdQN8rTfBsvVKmIVY/X7EM+/hJKEIbbHs9oA4tQ=="],
- "@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.27.3", "", { "os": "darwin", "cpu": "x64" }, "sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg=="],
+ "@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.27.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-sR+OiKLwd15nmCdqpXMnuJ9W2kpy0KigzqScqHI3Hqwr7IXxBp3Yva+yJwoqh7rE8V77tdoheRYataNKL4QrPw=="],
- "@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.27.3", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w=="],
+ "@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.27.4", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-jnfpKe+p79tCnm4GVav68A7tUFeKQwQyLgESwEAUzyxk/TJr4QdGog9sqWNcUbr/bZt/O/HXouspuQDd9JxFSw=="],
- "@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.27.3", "", { "os": "freebsd", "cpu": "x64" }, "sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA=="],
+ "@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.27.4", "", { "os": "freebsd", "cpu": "x64" }, "sha512-2kb4ceA/CpfUrIcTUl1wrP/9ad9Atrp5J94Lq69w7UwOMolPIGrfLSvAKJp0RTvkPPyn6CIWrNy13kyLikZRZQ=="],
- "@esbuild/linux-arm": ["@esbuild/linux-arm@0.27.3", "", { "os": "linux", "cpu": "arm" }, "sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw=="],
+ "@esbuild/linux-arm": ["@esbuild/linux-arm@0.27.4", "", { "os": "linux", "cpu": "arm" }, "sha512-aBYgcIxX/wd5n2ys0yESGeYMGF+pv6g0DhZr3G1ZG4jMfruU9Tl1i2Z+Wnj9/KjGz1lTLCcorqE2viePZqj4Eg=="],
- "@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.27.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg=="],
+ "@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.27.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-7nQOttdzVGth1iz57kxg9uCz57dxQLHWxopL6mYuYthohPKEK0vU0C3O21CcBK6KDlkYVcnDXY099HcCDXd9dA=="],
- "@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.27.3", "", { "os": "linux", "cpu": "ia32" }, "sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg=="],
+ "@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.27.4", "", { "os": "linux", "cpu": "ia32" }, "sha512-oPtixtAIzgvzYcKBQM/qZ3R+9TEUd1aNJQu0HhGyqtx6oS7qTpvjheIWBbes4+qu1bNlo2V4cbkISr8q6gRBFA=="],
- "@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.27.3", "", { "os": "linux", "cpu": "none" }, "sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA=="],
+ "@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.27.4", "", { "os": "linux", "cpu": "none" }, "sha512-8mL/vh8qeCoRcFH2nM8wm5uJP+ZcVYGGayMavi8GmRJjuI3g1v6Z7Ni0JJKAJW+m0EtUuARb6Lmp4hMjzCBWzA=="],
- "@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.27.3", "", { "os": "linux", "cpu": "none" }, "sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw=="],
+ "@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.27.4", "", { "os": "linux", "cpu": "none" }, "sha512-1RdrWFFiiLIW7LQq9Q2NES+HiD4NyT8Itj9AUeCl0IVCA459WnPhREKgwrpaIfTOe+/2rdntisegiPWn/r/aAw=="],
- "@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.27.3", "", { "os": "linux", "cpu": "ppc64" }, "sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA=="],
+ "@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.27.4", "", { "os": "linux", "cpu": "ppc64" }, "sha512-tLCwNG47l3sd9lpfyx9LAGEGItCUeRCWeAx6x2Jmbav65nAwoPXfewtAdtbtit/pJFLUWOhpv0FpS6GQAmPrHA=="],
- "@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.27.3", "", { "os": "linux", "cpu": "none" }, "sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ=="],
+ "@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.27.4", "", { "os": "linux", "cpu": "none" }, "sha512-BnASypppbUWyqjd1KIpU4AUBiIhVr6YlHx/cnPgqEkNoVOhHg+YiSVxM1RLfiy4t9cAulbRGTNCKOcqHrEQLIw=="],
- "@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.27.3", "", { "os": "linux", "cpu": "s390x" }, "sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw=="],
+ "@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.27.4", "", { "os": "linux", "cpu": "s390x" }, "sha512-+eUqgb/Z7vxVLezG8bVB9SfBie89gMueS+I0xYh2tJdw3vqA/0ImZJ2ROeWwVJN59ihBeZ7Tu92dF/5dy5FttA=="],
- "@esbuild/linux-x64": ["@esbuild/linux-x64@0.27.3", "", { "os": "linux", "cpu": "x64" }, "sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA=="],
+ "@esbuild/linux-x64": ["@esbuild/linux-x64@0.27.4", "", { "os": "linux", "cpu": "x64" }, "sha512-S5qOXrKV8BQEzJPVxAwnryi2+Iq5pB40gTEIT69BQONqR7JH1EPIcQ/Uiv9mCnn05jff9umq/5nqzxlqTOg9NA=="],
- "@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.27.3", "", { "os": "none", "cpu": "arm64" }, "sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA=="],
+ "@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.27.4", "", { "os": "none", "cpu": "arm64" }, "sha512-xHT8X4sb0GS8qTqiwzHqpY00C95DPAq7nAwX35Ie/s+LO9830hrMd3oX0ZMKLvy7vsonee73x0lmcdOVXFzd6Q=="],
- "@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.27.3", "", { "os": "none", "cpu": "x64" }, "sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA=="],
+ "@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.27.4", "", { "os": "none", "cpu": "x64" }, "sha512-RugOvOdXfdyi5Tyv40kgQnI0byv66BFgAqjdgtAKqHoZTbTF2QqfQrFwa7cHEORJf6X2ht+l9ABLMP0dnKYsgg=="],
- "@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.27.3", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw=="],
+ "@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.27.4", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-2MyL3IAaTX+1/qP0O1SwskwcwCoOI4kV2IBX1xYnDDqthmq5ArrW94qSIKCAuRraMgPOmG0RDTA74mzYNQA9ow=="],
- "@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.27.3", "", { "os": "openbsd", "cpu": "x64" }, "sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ=="],
+ "@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.27.4", "", { "os": "openbsd", "cpu": "x64" }, "sha512-u8fg/jQ5aQDfsnIV6+KwLOf1CmJnfu1ShpwqdwC0uA7ZPwFws55Ngc12vBdeUdnuWoQYx/SOQLGDcdlfXhYmXQ=="],
- "@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.27.3", "", { "os": "none", "cpu": "arm64" }, "sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g=="],
+ "@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.27.4", "", { "os": "none", "cpu": "arm64" }, "sha512-JkTZrl6VbyO8lDQO3yv26nNr2RM2yZzNrNHEsj9bm6dOwwu9OYN28CjzZkH57bh4w0I2F7IodpQvUAEd1mbWXg=="],
- "@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.27.3", "", { "os": "sunos", "cpu": "x64" }, "sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA=="],
+ "@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.27.4", "", { "os": "sunos", "cpu": "x64" }, "sha512-/gOzgaewZJfeJTlsWhvUEmUG4tWEY2Spp5M20INYRg2ZKl9QPO3QEEgPeRtLjEWSW8FilRNacPOg8R1uaYkA6g=="],
- "@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.27.3", "", { "os": "win32", "cpu": "arm64" }, "sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA=="],
+ "@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.27.4", "", { "os": "win32", "cpu": "arm64" }, "sha512-Z9SExBg2y32smoDQdf1HRwHRt6vAHLXcxD2uGgO/v2jK7Y718Ix4ndsbNMU/+1Qiem9OiOdaqitioZwxivhXYg=="],
- "@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.27.3", "", { "os": "win32", "cpu": "ia32" }, "sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q=="],
+ "@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.27.4", "", { "os": "win32", "cpu": "ia32" }, "sha512-DAyGLS0Jz5G5iixEbMHi5KdiApqHBWMGzTtMiJ72ZOLhbu/bzxgAe8Ue8CTS3n3HbIUHQz/L51yMdGMeoxXNJw=="],
- "@esbuild/win32-x64": ["@esbuild/win32-x64@0.27.3", "", { "os": "win32", "cpu": "x64" }, "sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA=="],
+ "@esbuild/win32-x64": ["@esbuild/win32-x64@0.27.4", "", { "os": "win32", "cpu": "x64" }, "sha512-+knoa0BDoeXgkNvvV1vvbZX4+hizelrkwmGJBdT17t8FNPwG2lKemmuMZlmaNQ3ws3DKKCxpb4zRZEIp3UxFCg=="],
"@eslint-community/eslint-utils": ["@eslint-community/eslint-utils@4.9.1", "", { "dependencies": { "eslint-visitor-keys": "^3.4.3" }, "peerDependencies": { "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ=="],
@@ -510,7 +513,7 @@
"@sindresorhus/is": ["@sindresorhus/is@7.2.0", "", {}, "sha512-P1Cz1dWaFfR4IR+U13mqqiGsLFf1KbayybWwdd2vfctdV6hDpUkgCY0nKOLLTMSoRd/jJNjtbqzf13K8DCCXQw=="],
- "@speed-highlight/core": ["@speed-highlight/core@1.2.14", "", {}, "sha512-G4ewlBNhUtlLvrJTb88d2mdy2KRijzs4UhnlrOSRT4bmjh/IqNElZa3zkrZ+TC47TwtlDWzVLFADljF1Ijp5hA=="],
+ "@speed-highlight/core": ["@speed-highlight/core@1.2.15", "", {}, "sha512-BMq1K3DsElxDWawkX6eLg9+CKJrTVGCBAWVuHXVUV2u0s2711qiChLSId6ikYPfxhdYocLNt3wWwSvDiTvFabw=="],
"@surma/rollup-plugin-off-main-thread": ["@surma/rollup-plugin-off-main-thread@2.2.3", "", { "dependencies": { "ejs": "^3.1.6", "json5": "^2.2.0", "magic-string": "^0.25.0", "string.prototype.matchall": "^4.0.6" } }, "sha512-lR8q/9W7hZpMWweNiAKU7NQerBnzQQLvi8qnTDU/fxItPhtZVMbPV3lbCwjhIlNBe9Bbr5V+KHshvWmVSG9cxQ=="],
@@ -538,7 +541,7 @@
"@types/json-schema": ["@types/json-schema@7.0.15", "", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="],
- "@types/node": ["@types/node@25.3.5", "", { "dependencies": { "undici-types": "~7.18.0" } }, "sha512-oX8xrhvpiyRCQkG1MFchB09f+cXftgIXb3a7UUa4Y3wpmZPw5tyZGTLWhlESOLq1Rq6oDlc8npVU2/9xiCuXMA=="],
+ "@types/node": ["@types/node@25.5.0", "", { "dependencies": { "undici-types": "~7.18.0" } }, "sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw=="],
"@types/resolve": ["@types/resolve@1.20.2", "", {}, "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q=="],
@@ -580,17 +583,17 @@
"available-typed-arrays": ["available-typed-arrays@1.0.7", "", { "dependencies": { "possible-typed-array-names": "^1.0.0" } }, "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ=="],
- "babel-plugin-polyfill-corejs2": ["babel-plugin-polyfill-corejs2@0.4.16", "", { "dependencies": { "@babel/compat-data": "^7.28.6", "@babel/helper-define-polyfill-provider": "^0.6.7", "semver": "^6.3.1" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, "sha512-xaVwwSfebXf0ooE11BJovZYKhFjIvQo7TsyVpETuIeH2JHv0k/T6Y5j22pPTvqYqmpkxdlPAJlyJ0tfOJAoMxw=="],
+ "babel-plugin-polyfill-corejs2": ["babel-plugin-polyfill-corejs2@0.4.17", "", { "dependencies": { "@babel/compat-data": "^7.28.6", "@babel/helper-define-polyfill-provider": "^0.6.8", "semver": "^6.3.1" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, "sha512-aTyf30K/rqAsNwN76zYrdtx8obu0E4KoUME29B1xj+B3WxgvWkp943vYQ+z8Mv3lw9xHXMHpvSPOBxzAkIa94w=="],
- "babel-plugin-polyfill-corejs3": ["babel-plugin-polyfill-corejs3@0.14.1", "", { "dependencies": { "@babel/helper-define-polyfill-provider": "^0.6.7", "core-js-compat": "^3.48.0" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, "sha512-ENp89vM9Pw4kv/koBb5N2f9bDZsR0hpf3BdPMOg/pkS3pwO4dzNnQZVXtBbeyAadgm865DmQG2jMMLqmZXvuCw=="],
+ "babel-plugin-polyfill-corejs3": ["babel-plugin-polyfill-corejs3@0.14.2", "", { "dependencies": { "@babel/helper-define-polyfill-provider": "^0.6.8", "core-js-compat": "^3.48.0" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, "sha512-coWpDLJ410R781Npmn/SIBZEsAetR4xVi0SxLMXPaMO4lSf1MwnkGYMtkFxew0Dn8B3/CpbpYxN0JCgg8mn67g=="],
- "babel-plugin-polyfill-regenerator": ["babel-plugin-polyfill-regenerator@0.6.7", "", { "dependencies": { "@babel/helper-define-polyfill-provider": "^0.6.7" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, "sha512-OTYbUlSwXhNgr4g6efMZgsO8//jA61P7ZbRX3iTT53VON8l+WQS8IAUEVo4a4cWknrg2W8Cj4gQhRYNCJ8GkAA=="],
+ "babel-plugin-polyfill-regenerator": ["babel-plugin-polyfill-regenerator@0.6.8", "", { "dependencies": { "@babel/helper-define-polyfill-provider": "^0.6.8" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, "sha512-M762rNHfSF1EV3SLtnCJXFoQbbIIz0OyRwnCmV0KPC7qosSfCO0QLTSuJX3ayAebubhE6oYBAYPrBA5ljowaZg=="],
"babel-runtime": ["babel-runtime@6.26.0", "", { "dependencies": { "core-js": "^2.4.0", "regenerator-runtime": "^0.11.0" } }, "sha512-ITKNuq2wKlW1fJg9sSW52eepoYgZBggvOAHC0u/CYu/qxQ9EVzThCgR69BnSXLHjy2f7SY5zaQ4yt7H9ZVxY2g=="],
"balanced-match": ["balanced-match@2.0.0", "", {}, "sha512-1ugUSr8BHXRnK23KfuYS+gVMC3LB8QGH9W1iGtDPsNWoQbgtXSExkBu2aDR4epiGWZOjZsj6lDl/N/AqqTC3UA=="],
- "baseline-browser-mapping": ["baseline-browser-mapping@2.10.0", "", { "bin": { "baseline-browser-mapping": "dist/cli.cjs" } }, "sha512-lIyg0szRfYbiy67j9KN8IyeD7q7hcmqnJ1ddWmNt19ItGpNN64mnllmxUNFIOdOm6by97jlL6wfpTTJrmnjWAA=="],
+ "baseline-browser-mapping": ["baseline-browser-mapping@2.10.9", "", { "bin": { "baseline-browser-mapping": "dist/cli.cjs" } }, "sha512-OZd0e2mU11ClX8+IdXe3r0dbqMEznRiT4TfbhYIbcRPZkqJ7Qwer8ij3GZAmLsRKa+II9V1v5czCkvmHH3XZBg=="],
"bcp-47": ["bcp-47@2.1.0", "", { "dependencies": { "is-alphabetical": "^2.0.0", "is-alphanumerical": "^2.0.0", "is-decimal": "^2.0.0" } }, "sha512-9IIS3UPrvIa1Ej+lVDdDwO7zLehjqsaByECw0bu2RRGP73jALm6FYbzI5gWbgHLvNdkvfXB5YrSbocZdOS0c0w=="],
@@ -600,6 +603,8 @@
"bignumber.js": ["bignumber.js@9.3.1", "", {}, "sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ=="],
+ "boolbase": ["boolbase@1.0.0", "", {}, "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww=="],
+
"brace-expansion": ["brace-expansion@1.1.12", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg=="],
"braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="],
@@ -616,7 +621,7 @@
"butterchurn-presets": ["butterchurn-presets@2.4.7", "", { "dependencies": { "babel-runtime": "^6.26.0", "ecma-proposal-math-extensions": "0.0.2", "lodash": "^4.17.4" } }, "sha512-4MdM8ripz/VfH1BCldrIKdAc/1ryJFBDvqlyow6Ivo1frwj0H3duzvSMFC7/wIjAjxb1QpwVHVqGqS9uAFKhpg=="],
- "cacheable": ["cacheable@2.3.3", "", { "dependencies": { "@cacheable/memory": "^2.0.8", "@cacheable/utils": "^2.4.0", "hookified": "^1.15.0", "keyv": "^5.6.0", "qified": "^0.6.0" } }, "sha512-iffYMX4zxKp54evOH27fm92hs+DeC1DhXmNVN8Tr94M/iZIV42dqTHSR2Ik4TOSPyOAwKr7Yu3rN9ALoLkbWyQ=="],
+ "cacheable": ["cacheable@2.3.4", "", { "dependencies": { "@cacheable/memory": "^2.0.8", "@cacheable/utils": "^2.4.0", "hookified": "^1.15.0", "keyv": "^5.6.0", "qified": "^0.9.0" } }, "sha512-djgxybDbw9fL/ZWMI3+CE8ZilNxcwFkVtDc1gJ+IlOSSWkSMPQabhV/XCHTQ6pwwN6aivXPZ43omTooZiX06Ew=="],
"call-bind": ["call-bind@1.0.8", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.0", "es-define-property": "^1.0.0", "get-intrinsic": "^1.2.4", "set-function-length": "^1.2.2" } }, "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww=="],
@@ -626,7 +631,7 @@
"callsites": ["callsites@3.1.0", "", {}, "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ=="],
- "caniuse-lite": ["caniuse-lite@1.0.30001777", "", {}, "sha512-tmN+fJxroPndC74efCdp12j+0rk0RHwV5Jwa1zWaFVyw2ZxAuPeG8ZgWC3Wz7uSjT3qMRQ5XHZ4COgQmsCMJAQ=="],
+ "caniuse-lite": ["caniuse-lite@1.0.30001780", "", {}, "sha512-llngX0E7nQci5BPJDqoZSbuZ5Bcs9F5db7EtgfwBerX9XGtkkiO4NwfDDIRzHTTwcYC8vC7bmeUEPGrKlR/TkQ=="],
"chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="],
@@ -660,7 +665,7 @@
"core-js": ["core-js@2.6.12", "", {}, "sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ=="],
- "core-js-compat": ["core-js-compat@3.48.0", "", { "dependencies": { "browserslist": "^4.28.1" } }, "sha512-OM4cAF3D6VtH/WkLtWvyNC56EZVXsZdU3iqaMG2B4WvYrlqU831pc4UtG5yp0sE9z8Y02wVN7PjW5Zf9Gt0f1Q=="],
+ "core-js-compat": ["core-js-compat@3.49.0", "", { "dependencies": { "browserslist": "^4.28.1" } }, "sha512-VQXt1jr9cBz03b331DFDCCP90b3fanciLkgiOoy8SBHy06gNf+vQ1A3WFLqG7I8TipYIKeYK9wxd0tUrvHcOZA=="],
"cosmiconfig": ["cosmiconfig@9.0.1", "", { "dependencies": { "env-paths": "^2.2.1", "import-fresh": "^3.3.0", "js-yaml": "^4.1.0", "parse-json": "^5.2.0" }, "peerDependencies": { "typescript": ">=4.9.5" }, "optionalPeers": ["typescript"] }, "sha512-hr4ihw+DBqcvrsEDioRO31Z17x71pUYoNe/4h6Z0wB72p7MU7/9gH8Q3s12NFhHPfYBBOV3qyfUxmr/Yn3shnQ=="],
@@ -670,10 +675,16 @@
"css-functions-list": ["css-functions-list@3.3.3", "", {}, "sha512-8HFEBPKhOpJPEPu70wJJetjKta86Gw9+CCyCnB3sui2qQfOvRyqBy4IKLKKAwdMpWb2lHXWk9Wb4Z6AmaUT1Pg=="],
+ "css-select": ["css-select@5.2.2", "", { "dependencies": { "boolbase": "^1.0.0", "css-what": "^6.1.0", "domhandler": "^5.0.2", "domutils": "^3.0.1", "nth-check": "^2.0.1" } }, "sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw=="],
+
"css-tree": ["css-tree@3.2.1", "", { "dependencies": { "mdn-data": "2.27.1", "source-map-js": "^1.2.1" } }, "sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA=="],
+ "css-what": ["css-what@6.2.2", "", {}, "sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA=="],
+
"cssesc": ["cssesc@3.0.0", "", { "bin": { "cssesc": "bin/cssesc" } }, "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg=="],
+ "csso": ["csso@5.0.5", "", { "dependencies": { "css-tree": "~2.2.0" } }, "sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ=="],
+
"d": ["d@1.0.2", "", { "dependencies": { "es5-ext": "^0.10.64", "type": "^2.7.2" } }, "sha512-MOqHvMWF9/9MX6nza0KgvFH4HpMU0EF5uUDXqX/BtxtU8NfB0QzRtJ8Oe/6SuS4kbhyzVJwjd97EA4PKrzJ8bw=="],
"dashjs": ["dashjs@5.1.1", "", { "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" } }, "sha512-BzNXlUgzEjhuZ5M5hlSp1qIyQHZ7NpXAR0loP9DAAFVZj/ntL1DHeZ7qp/L3bvI4rq50X5indkAZQ3zEHWJoCA=="],
@@ -702,6 +713,14 @@
"dir-glob": ["dir-glob@3.0.1", "", { "dependencies": { "path-type": "^4.0.0" } }, "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA=="],
+ "dom-serializer": ["dom-serializer@2.0.0", "", { "dependencies": { "domelementtype": "^2.3.0", "domhandler": "^5.0.2", "entities": "^4.2.0" } }, "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg=="],
+
+ "domelementtype": ["domelementtype@2.3.0", "", {}, "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw=="],
+
+ "domhandler": ["domhandler@5.0.3", "", { "dependencies": { "domelementtype": "^2.3.0" } }, "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w=="],
+
+ "domutils": ["domutils@3.2.2", "", { "dependencies": { "dom-serializer": "^2.0.0", "domelementtype": "^2.3.0", "domhandler": "^5.0.3" } }, "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw=="],
+
"dot-prop": ["dot-prop@5.3.0", "", { "dependencies": { "is-obj": "^2.0.0" } }, "sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q=="],
"dunder-proto": ["dunder-proto@1.0.1", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="],
@@ -712,10 +731,12 @@
"ejs": ["ejs@3.1.10", "", { "dependencies": { "jake": "^10.8.5" }, "bin": { "ejs": "bin/cli.js" } }, "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA=="],
- "electron-to-chromium": ["electron-to-chromium@1.5.307", "", {}, "sha512-5z3uFKBWjiNR44nFcYdkcXjKMbg5KXNdciu7mhTPo9tB7NbqSNP2sSnGR+fqknZSCwKkBN+oxiiajWs4dT6ORg=="],
+ "electron-to-chromium": ["electron-to-chromium@1.5.321", "", {}, "sha512-L2C7Q279W2D/J4PLZLk7sebOILDSWos7bMsMNN06rK482umHUrh/3lM8G7IlHFOYip2oAg5nha1rCMxr/rs6ZQ=="],
"emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
+ "entities": ["entities@4.5.0", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="],
+
"env-paths": ["env-paths@2.2.1", "", {}, "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A=="],
"error-ex": ["error-ex@1.3.4", "", { "dependencies": { "is-arrayish": "^0.2.1" } }, "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ=="],
@@ -740,7 +761,7 @@
"es6-symbol": ["es6-symbol@3.1.4", "", { "dependencies": { "d": "^1.0.2", "ext": "^1.7.0" } }, "sha512-U9bFFjX8tFiATgtkJ1zg25+KviIXpgRvRHS8sau3GfhVzThRQrOeksPeT0BWW2MNZs1OEWJ1DPXOQMn0KKRkvg=="],
- "esbuild": ["esbuild@0.27.3", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.27.3", "@esbuild/android-arm": "0.27.3", "@esbuild/android-arm64": "0.27.3", "@esbuild/android-x64": "0.27.3", "@esbuild/darwin-arm64": "0.27.3", "@esbuild/darwin-x64": "0.27.3", "@esbuild/freebsd-arm64": "0.27.3", "@esbuild/freebsd-x64": "0.27.3", "@esbuild/linux-arm": "0.27.3", "@esbuild/linux-arm64": "0.27.3", "@esbuild/linux-ia32": "0.27.3", "@esbuild/linux-loong64": "0.27.3", "@esbuild/linux-mips64el": "0.27.3", "@esbuild/linux-ppc64": "0.27.3", "@esbuild/linux-riscv64": "0.27.3", "@esbuild/linux-s390x": "0.27.3", "@esbuild/linux-x64": "0.27.3", "@esbuild/netbsd-arm64": "0.27.3", "@esbuild/netbsd-x64": "0.27.3", "@esbuild/openbsd-arm64": "0.27.3", "@esbuild/openbsd-x64": "0.27.3", "@esbuild/openharmony-arm64": "0.27.3", "@esbuild/sunos-x64": "0.27.3", "@esbuild/win32-arm64": "0.27.3", "@esbuild/win32-ia32": "0.27.3", "@esbuild/win32-x64": "0.27.3" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg=="],
+ "esbuild": ["esbuild@0.27.4", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.27.4", "@esbuild/android-arm": "0.27.4", "@esbuild/android-arm64": "0.27.4", "@esbuild/android-x64": "0.27.4", "@esbuild/darwin-arm64": "0.27.4", "@esbuild/darwin-x64": "0.27.4", "@esbuild/freebsd-arm64": "0.27.4", "@esbuild/freebsd-x64": "0.27.4", "@esbuild/linux-arm": "0.27.4", "@esbuild/linux-arm64": "0.27.4", "@esbuild/linux-ia32": "0.27.4", "@esbuild/linux-loong64": "0.27.4", "@esbuild/linux-mips64el": "0.27.4", "@esbuild/linux-ppc64": "0.27.4", "@esbuild/linux-riscv64": "0.27.4", "@esbuild/linux-s390x": "0.27.4", "@esbuild/linux-x64": "0.27.4", "@esbuild/netbsd-arm64": "0.27.4", "@esbuild/netbsd-x64": "0.27.4", "@esbuild/openbsd-arm64": "0.27.4", "@esbuild/openbsd-x64": "0.27.4", "@esbuild/openharmony-arm64": "0.27.4", "@esbuild/sunos-x64": "0.27.4", "@esbuild/win32-arm64": "0.27.4", "@esbuild/win32-ia32": "0.27.4", "@esbuild/win32-x64": "0.27.4" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-Rq4vbHnYkK5fws5NF7MYTU68FPRE1ajX7heQ/8QXXWqNgqqJ/GkmmyxIzUnf2Sr/bakf8l54716CcMGHYhMrrQ=="],
"escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="],
@@ -800,7 +821,7 @@
"flat-cache": ["flat-cache@4.0.1", "", { "dependencies": { "flatted": "^3.2.9", "keyv": "^4.5.4" } }, "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw=="],
- "flatted": ["flatted@3.4.0", "", {}, "sha512-kC6Bb+ooptOIvWj5B63EQWkF0FEnNjV2ZNkLMLZRDDduIiWeFF4iKnslwhiWxjAdbg4NzTNo6h0qLuvFrcx+Sw=="],
+ "flatted": ["flatted@3.4.2", "", {}, "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA=="],
"follow-redirects": ["follow-redirects@1.15.11", "", {}, "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ=="],
@@ -994,7 +1015,7 @@
"jake": ["jake@10.9.4", "", { "dependencies": { "async": "^3.2.6", "filelist": "^1.0.4", "picocolors": "^1.1.1" }, "bin": { "jake": "bin/cli.js" } }, "sha512-wpHYzhxiVQL+IV05BLE2Xn34zW1S223hvjtqk0+gsPrwd/8JNLXJgZZM/iPFsYc1xyphF+6M6EvdE5E9MBGkDA=="],
- "jose": ["jose@6.2.0", "", {}, "sha512-xsfE1TcSCbUdo6U07tR0mvhg0flGxU8tPLbF03mirl2ukGQENhUg4ubGYQnhVH0b5stLlPM+WOqDkEl1R1y5sQ=="],
+ "jose": ["jose@6.2.2", "", {}, "sha512-d7kPDd34KO/YnzaDOlikGpOurfF0ByC2sEV4cANCtdqLlTfBlw2p14O/5d/zv40gJPbIQxfES3nSx1/oYNyuZQ=="],
"js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="],
@@ -1056,7 +1077,9 @@
"lodash.truncate": ["lodash.truncate@4.4.2", "", {}, "sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw=="],
- "lru-cache": ["lru-cache@11.2.6", "", {}, "sha512-ESL2CrkS/2wTPfuend7Zhkzo2u0daGJ/A2VucJOgQ/C48S/zB8MMeMHSGKYpXhIjbPxfuezITkaBH1wqv00DDQ=="],
+ "lru-cache": ["lru-cache@11.2.7", "", {}, "sha512-aY/R+aEsRelme17KGQa/1ZSIpLpNYYrhcrepKTZgE+W3WM16YMCaPwOHLHsmopZHELU0Ojin1lPVxKR0MihncA=="],
+
+ "lucide-static": ["lucide-static@0.577.0", "", {}, "sha512-hx39J5Tq4JWF2ALY+5YRg+SxQLpeAmLJDXNcqiBJH/UuVwp43it9fyki/onZO7AVFgG5ZbB+fWwZR9mwGHE2XQ=="],
"magic-string": ["magic-string@0.25.9", "", { "dependencies": { "sourcemap-codec": "^1.4.8" } }, "sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ=="],
@@ -1076,7 +1099,7 @@
"mime": ["mime@4.1.0", "", { "bin": { "mime": "bin/cli.js" } }, "sha512-X5ju04+cAzsojXKes0B/S4tcYtFAJ6tTMuSPBEn9CPGlrWr8Fiw7qYeLT0XyH80HSoAoqWCaz+MWKh22P7G1cw=="],
- "miniflare": ["miniflare@4.20260301.1", "", { "dependencies": { "@cspotcode/source-map-support": "0.8.1", "sharp": "^0.34.5", "undici": "7.18.2", "workerd": "1.20260301.1", "ws": "8.18.0", "youch": "4.1.0-beta.10" }, "bin": { "miniflare": "bootstrap.js" } }, "sha512-fqkHx0QMKswRH9uqQQQOU/RoaS3Wjckxy3CUX3YGJr0ZIMu7ObvI+NovdYi6RIsSPthNtq+3TPmRNxjeRiasog=="],
+ "miniflare": ["miniflare@4.20260317.0", "", { "dependencies": { "@cspotcode/source-map-support": "0.8.1", "sharp": "^0.34.5", "undici": "7.24.4", "workerd": "1.20260317.1", "ws": "8.18.0", "youch": "4.1.0-beta.10" }, "bin": { "miniflare": "bootstrap.js" } }, "sha512-xuwk5Kjv+shi5iUBAdCrRl9IaWSGnTU8WuTQzsUS2GlSDIMCJuu8DiF/d9ExjMXYiQG5ml+k9SVKnMj8cRkq0w=="],
"minimatch": ["minimatch@3.1.5", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w=="],
@@ -1098,7 +1121,9 @@
"normalize-path": ["normalize-path@3.0.0", "", {}, "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA=="],
- "npm": ["npm@11.11.1", "", { "dependencies": { "@isaacs/string-locale-compare": "^1.1.0", "@npmcli/arborist": "^9.4.1", "@npmcli/config": "^10.7.1", "@npmcli/fs": "^5.0.0", "@npmcli/map-workspaces": "^5.0.3", "@npmcli/metavuln-calculator": "^9.0.3", "@npmcli/package-json": "^7.0.5", "@npmcli/promise-spawn": "^9.0.1", "@npmcli/redact": "^4.0.0", "@npmcli/run-script": "^10.0.4", "@sigstore/tuf": "^4.0.1", "abbrev": "^4.0.0", "archy": "~1.0.0", "cacache": "^20.0.3", "chalk": "^5.6.2", "ci-info": "^4.4.0", "fastest-levenshtein": "^1.0.16", "fs-minipass": "^3.0.3", "glob": "^13.0.6", "graceful-fs": "^4.2.11", "hosted-git-info": "^9.0.2", "ini": "^6.0.0", "init-package-json": "^8.2.5", "is-cidr": "^6.0.3", "json-parse-even-better-errors": "^5.0.0", "libnpmaccess": "^10.0.3", "libnpmdiff": "^8.1.4", "libnpmexec": "^10.2.4", "libnpmfund": "^7.0.18", "libnpmorg": "^8.0.1", "libnpmpack": "^9.1.4", "libnpmpublish": "^11.1.3", "libnpmsearch": "^9.0.1", "libnpmteam": "^8.0.2", "libnpmversion": "^8.0.3", "make-fetch-happen": "^15.0.4", "minimatch": "^10.2.4", "minipass": "^7.1.3", "minipass-pipeline": "^1.2.4", "ms": "^2.1.2", "node-gyp": "^12.2.0", "nopt": "^9.0.0", "npm-audit-report": "^7.0.0", "npm-install-checks": "^8.0.0", "npm-package-arg": "^13.0.2", "npm-pick-manifest": "^11.0.3", "npm-profile": "^12.0.1", "npm-registry-fetch": "^19.1.1", "npm-user-validate": "^4.0.0", "p-map": "^7.0.4", "pacote": "^21.5.0", "parse-conflict-json": "^5.0.1", "proc-log": "^6.1.0", "qrcode-terminal": "^0.12.0", "read": "^5.0.1", "semver": "^7.7.4", "spdx-expression-parse": "^4.0.0", "ssri": "^13.0.1", "supports-color": "^10.2.2", "tar": "^7.5.11", "text-table": "~0.2.0", "tiny-relative-date": "^2.0.2", "treeverse": "^3.0.0", "validate-npm-package-name": "^7.0.2", "which": "^6.0.1" }, "bin": { "npm": "bin/npm-cli.js", "npx": "bin/npx-cli.js" } }, "sha512-asazCodkFdz1ReQzukyzS/DD77uGCIqUFeRG3gtaT8b9UR0ne1m9QOBuMgT72ij1rt7TRrOox4A1WzntMWIuEg=="],
+ "npm": ["npm@11.12.0", "", { "dependencies": { "@isaacs/string-locale-compare": "^1.1.0", "@npmcli/arborist": "^9.4.2", "@npmcli/config": "^10.8.0", "@npmcli/fs": "^5.0.0", "@npmcli/map-workspaces": "^5.0.3", "@npmcli/metavuln-calculator": "^9.0.3", "@npmcli/package-json": "^7.0.5", "@npmcli/promise-spawn": "^9.0.1", "@npmcli/redact": "^4.0.0", "@npmcli/run-script": "^10.0.4", "@sigstore/tuf": "^4.0.2", "abbrev": "^4.0.0", "archy": "~1.0.0", "cacache": "^20.0.4", "chalk": "^5.6.2", "ci-info": "^4.4.0", "fastest-levenshtein": "^1.0.16", "fs-minipass": "^3.0.3", "glob": "^13.0.6", "graceful-fs": "^4.2.11", "hosted-git-info": "^9.0.2", "ini": "^6.0.0", "init-package-json": "^8.2.5", "is-cidr": "^6.0.3", "json-parse-even-better-errors": "^5.0.0", "libnpmaccess": "^10.0.3", "libnpmdiff": "^8.1.5", "libnpmexec": "^10.2.5", "libnpmfund": "^7.0.19", "libnpmorg": "^8.0.1", "libnpmpack": "^9.1.5", "libnpmpublish": "^11.1.3", "libnpmsearch": "^9.0.1", "libnpmteam": "^8.0.2", "libnpmversion": "^8.0.3", "make-fetch-happen": "^15.0.5", "minimatch": "^10.2.4", "minipass": "^7.1.3", "minipass-pipeline": "^1.2.4", "ms": "^2.1.2", "node-gyp": "^12.2.0", "nopt": "^9.0.0", "npm-audit-report": "^7.0.0", "npm-install-checks": "^8.0.0", "npm-package-arg": "^13.0.2", "npm-pick-manifest": "^11.0.3", "npm-profile": "^12.0.1", "npm-registry-fetch": "^19.1.1", "npm-user-validate": "^4.0.0", "p-map": "^7.0.4", "pacote": "^21.5.0", "parse-conflict-json": "^5.0.1", "proc-log": "^6.1.0", "qrcode-terminal": "^0.12.0", "read": "^5.0.1", "semver": "^7.7.4", "spdx-expression-parse": "^4.0.0", "ssri": "^13.0.1", "supports-color": "^10.2.2", "tar": "^7.5.11", "text-table": "~0.2.0", "tiny-relative-date": "^2.0.2", "treeverse": "^3.0.0", "validate-npm-package-name": "^7.0.2", "which": "^6.0.1" }, "bin": { "npm": "bin/npm-cli.js", "npx": "bin/npx-cli.js" } }, "sha512-xPhOap4ZbJWyd7DAOukP564WFwNSGu/2FeTRFHhiiKthcauxhH/NpkJAQm24xD+cAn8av5tQ00phi98DqtfLsg=="],
+
+ "nth-check": ["nth-check@2.1.1", "", { "dependencies": { "boolbase": "^1.0.0" } }, "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w=="],
"object-inspect": ["object-inspect@1.13.4", "", {}, "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew=="],
@@ -1176,7 +1201,7 @@
"punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="],
- "qified": ["qified@0.6.0", "", { "dependencies": { "hookified": "^1.14.0" } }, "sha512-tsSGN1x3h569ZSU1u6diwhltLyfUWDp3YbFHedapTmpBl0B3P6U3+Qptg7xu+v+1io1EwhdPyyRHYbEw0KN2FA=="],
+ "qified": ["qified@0.9.0", "", { "dependencies": { "hookified": "^2.1.0" } }, "sha512-4q61YgkHbY6gmwkqm0BsxyLDO3UYdrdiJTJ7JiaZb3xpW1duxn135SB7KqUEkCiuu5O4W+TtwEWP2VjmSRanvA=="],
"queue-microtask": ["queue-microtask@1.2.3", "", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="],
@@ -1224,7 +1249,7 @@
"safe-regex-test": ["safe-regex-test@1.1.0", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "is-regex": "^1.2.1" } }, "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw=="],
- "sax": ["sax@1.2.1", "", {}, "sha512-8I2a3LovHTOpm7NV5yOyO8IHqgVsfK4+UuySrXU8YXkSRX7k6hCV9b3HrkKCr3nMpgj+0bmocaJJWpvp1oc7ZA=="],
+ "sax": ["sax@1.6.0", "", {}, "sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA=="],
"semver": ["semver@7.7.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="],
@@ -1254,6 +1279,8 @@
"signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="],
+ "simple-icons": ["simple-icons@16.12.0", "", {}, "sha512-fDJDqXUpkb2twqH+eBQpJsCYUE6jEH7VkuuPL9dH16sbLf6KKnwyijULmcx7SCoy3c2L6pl8WCzt+4rpYjoWfw=="],
+
"slash": ["slash@3.0.0", "", {}, "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q=="],
"slice-ansi": ["slice-ansi@4.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "astral-regex": "^2.0.0", "is-fullwidth-code-point": "^3.0.0" } }, "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ=="],
@@ -1310,6 +1337,8 @@
"svg-tags": ["svg-tags@1.0.0", "", {}, "sha512-ovssysQTa+luh7A5Weu3Rta6FJlFBBbInjOh722LIt6klpU2/HtdUbszju/G4devcvk8PGt7FCLv5wftu3THUA=="],
+ "svgo": ["svgo@4.0.1", "", { "dependencies": { "commander": "^11.1.0", "css-select": "^5.1.0", "css-tree": "^3.0.1", "css-what": "^6.1.0", "csso": "^5.0.5", "picocolors": "^1.1.1", "sax": "^1.5.0" }, "bin": "./bin/svgo.js" }, "sha512-XDpWUOPC6FEibaLzjfe0ucaV0YrOjYotGJO1WpF0Zd+n6ZGEQUsSugaoLq9QkEZtAfQIxT42UChcssDVPP3+/w=="],
+
"table": ["table@6.9.0", "", { "dependencies": { "ajv": "^8.0.1", "lodash.truncate": "^4.4.2", "slice-ansi": "^4.0.0", "string-width": "^4.2.3", "strip-ansi": "^6.0.1" } }, "sha512-9kY+CygyYM6j02t5YFHbNz2FN5QmYGv9zAjVp4lCDjlCw7amdckXlEt/bjMhUIfj4ThGRE4gCUH5+yGnNuPo5A=="],
"tcp-port-used": ["tcp-port-used@1.0.2", "", { "dependencies": { "debug": "4.3.1", "is2": "^2.0.6" } }, "sha512-l7ar8lLUD3XS1V2lfoJlCBaeoaWo/2xfYt81hM7VlvR4RrMVFqfmzfhLVk40hAb368uitje5gPtBRL1m/DGvLA=="],
@@ -1318,7 +1347,7 @@
"tempy": ["tempy@0.6.0", "", { "dependencies": { "is-stream": "^2.0.0", "temp-dir": "^2.0.0", "type-fest": "^0.16.0", "unique-string": "^2.0.0" } }, "sha512-G13vtMYPT/J8A4X2SjdtBTphZlrp1gKv6hZiOjw14RCWg6GbHuQBGtjlx75xLbYV/wEc0D7G5K4rxKP/cXk8Bw=="],
- "terser": ["terser@5.46.0", "", { "dependencies": { "@jridgewell/source-map": "^0.3.3", "acorn": "^8.15.0", "commander": "^2.20.0", "source-map-support": "~0.5.20" }, "bin": { "terser": "bin/terser" } }, "sha512-jTwoImyr/QbOWFFso3YoU3ik0jBBDJ6JTOQiy/J2YxVJdZCc+5u7skhNwiOR3FQIygFqVUPHl7qbbxtjW2K3Qg=="],
+ "terser": ["terser@5.46.1", "", { "dependencies": { "@jridgewell/source-map": "^0.3.3", "acorn": "^8.15.0", "commander": "^2.20.0", "source-map-support": "~0.5.20" }, "bin": { "terser": "bin/terser" } }, "sha512-vzCjQO/rgUuK9sf8VJZvjqiqiHFaZLnOiimmUuOKODxWL8mm/xua7viT7aqX7dgPY60otQjUotzFMmCB4VdmqQ=="],
"tinyglobby": ["tinyglobby@0.2.15", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.3" } }, "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ=="],
@@ -1350,7 +1379,7 @@
"unbox-primitive": ["unbox-primitive@1.1.0", "", { "dependencies": { "call-bound": "^1.0.3", "has-bigints": "^1.0.2", "has-symbols": "^1.1.0", "which-boxed-primitive": "^1.1.1" } }, "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw=="],
- "undici": ["undici@7.18.2", "", {}, "sha512-y+8YjDFzWdQlSE9N5nzKMT3g4a5UBX1HKowfdXh0uvAnTaqqwqB92Jt4UXBAeKekDs5IaDKyJFR4X1gYVCgXcw=="],
+ "undici": ["undici@7.24.4", "", {}, "sha512-BM/JzwwaRXxrLdElV2Uo6cTLEjhSb3WXboncJamZ15NgUURmvlXvxa6xkwIOILIjPNo9i8ku136ZvWV0Uly8+w=="],
"undici-types": ["undici-types@7.18.2", "", {}, "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w=="],
@@ -1432,7 +1461,7 @@
"workbox-window": ["workbox-window@7.4.0", "", { "dependencies": { "@types/trusted-types": "^2.0.2", "workbox-core": "7.4.0" } }, "sha512-/bIYdBLAVsNR3v7gYGaV4pQW3M3kEPx5E8vDxGvxo6khTrGtSSCS7QiFKv9ogzBgZiy0OXLP9zO28U/1nF1mfw=="],
- "workerd": ["workerd@1.20260301.1", "", { "optionalDependencies": { "@cloudflare/workerd-darwin-64": "1.20260301.1", "@cloudflare/workerd-darwin-arm64": "1.20260301.1", "@cloudflare/workerd-linux-64": "1.20260301.1", "@cloudflare/workerd-linux-arm64": "1.20260301.1", "@cloudflare/workerd-windows-64": "1.20260301.1" }, "bin": { "workerd": "bin/workerd" } }, "sha512-oterQ1IFd3h7PjCfT4znSFOkJCvNQ6YMOyZ40YsnO3nrSpgB4TbJVYWFOnyJAw71/RQuupfVqZZWKvsy8GO3fw=="],
+ "workerd": ["workerd@1.20260317.1", "", { "optionalDependencies": { "@cloudflare/workerd-darwin-64": "1.20260317.1", "@cloudflare/workerd-darwin-arm64": "1.20260317.1", "@cloudflare/workerd-linux-64": "1.20260317.1", "@cloudflare/workerd-linux-arm64": "1.20260317.1", "@cloudflare/workerd-windows-64": "1.20260317.1" }, "bin": { "workerd": "bin/workerd" } }, "sha512-ZuEq1OdrJBS+NV+L5HMYPCzVn49a2O60slQiiLpG44jqtlOo+S167fWC76kEXteXLLLydeuRrluRel7WdOUa4g=="],
"wrappy": ["wrappy@1.0.2", "", {}, "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="],
@@ -1448,7 +1477,7 @@
"yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="],
- "yauzl": ["yauzl@3.2.0", "", { "dependencies": { "buffer-crc32": "~0.2.3", "pend": "~1.2.0" } }, "sha512-Ow9nuGZE+qp1u4JIPvg+uCiUr7xGQWdff7JQSk5VGYTAZMDe2q8lxJ10ygv10qmSj031Ty/6FNJpLO4o1Sgc+w=="],
+ "yauzl": ["yauzl@3.2.1", "", { "dependencies": { "buffer-crc32": "~0.2.3", "pend": "~1.2.0" } }, "sha512-k1isifdbpNSFEHFJ1ZY4YDewv0IH9FR61lDetaRMD3j2ae3bIXGV+7c+LHCqtQGofSd8PIyV4X6+dHMAnSr60A=="],
"yazl": ["yazl@3.3.1", "", { "dependencies": { "buffer-crc32": "^1.0.0" } }, "sha512-BbETDVWG+VcMUle37k5Fqp//7SDOK2/1+T7X8TD96M3D9G8jK5VLUdQVdVjGi8im7FGkazX7kk5hkU8X4L5Bng=="],
@@ -1458,7 +1487,7 @@
"youch-core": ["youch-core@0.3.3", "", { "dependencies": { "@poppinss/exception": "^1.2.2", "error-stack-parser-es": "^1.0.5" } }, "sha512-ho7XuGjLaJ2hWHoK8yFnsUGy2Y5uDpqSTq1FkHLK4/oqKtyUU1AFbOOxY4IpC9f0fTLjwYbslUz0Po5BpD1wrA=="],
- "zip-lib": ["zip-lib@1.2.1", "", { "dependencies": { "yauzl": "^3.2.0", "yazl": "^3.3.1" } }, "sha512-7MT4fvAoEJPxB/TILqapSpZlJQk5gH29NzhU4ySWXRauXh290KnHK/QkOggL9jmN92H31fv+lkbFIaFKb78ySg=="],
+ "zip-lib": ["zip-lib@1.2.2", "", { "dependencies": { "yauzl": "^3.2.1", "yazl": "^3.3.1" } }, "sha512-Pcve5TTLj1dazgcnru5PFq3ChSSmDO3FWgKIvQslQ54dwt9YqTAFa3pIR/s2eSNBoBEcPahdjCPSYzrtYva0mA=="],
"@apideck/better-ajv-errors/ajv": ["ajv@8.18.0", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A=="],
@@ -1522,6 +1551,8 @@
"cookie-session/debug": ["debug@3.2.7", "", { "dependencies": { "ms": "^2.1.1" } }, "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ=="],
+ "csso/css-tree": ["css-tree@2.2.1", "", { "dependencies": { "mdn-data": "2.0.28", "source-map-js": "^1.0.1" } }, "sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA=="],
+
"fast-glob/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="],
"filelist/minimatch": ["minimatch@5.1.9", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw=="],
@@ -1534,13 +1565,15 @@
"import-fresh/resolve-from": ["resolve-from@4.0.0", "", {}, "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="],
+ "imsc/sax": ["sax@1.2.1", "", {}, "sha512-8I2a3LovHTOpm7NV5yOyO8IHqgVsfK4+UuySrXU8YXkSRX7k6hCV9b3HrkKCr3nMpgj+0bmocaJJWpvp1oc7ZA=="],
+
"make-dir/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
"micromatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
"node-sarif-builder/fs-extra": ["fs-extra@11.3.4", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-CTXd6rk/M3/ULNQj8FBqBWHYBVYybQ3VPBw0xGKFe3tuH7ytT6ACnvzpIQ3UZtB8yvUKC2cXn1a+x+5EVQLovA=="],
- "npm/@gar/promise-retry": ["@gar/promise-retry@1.0.2", "", { "dependencies": { "retry": "^0.13.1" } }, "sha512-Lm/ZLhDZcBECta3TmCQSngiQykFdfw+QtI1/GYMsZd4l3nG+P8WLB16XuS7WaBGLQ+9E+cOcWQsth9cayuGt8g=="],
+ "npm/@gar/promise-retry": ["@gar/promise-retry@1.0.3", "", {}, "sha512-GmzA9ckNokPypTg10pgpeHNQe7ph+iIKKmhKu3Ob9ANkswreCx7R3cKmY781K8QK3AqVL3xVh9A42JvIAbkkSA=="],
"npm/@isaacs/fs-minipass": ["@isaacs/fs-minipass@4.0.1", "", { "dependencies": { "minipass": "^7.0.4" } }, "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w=="],
@@ -1548,9 +1581,9 @@
"npm/@npmcli/agent": ["@npmcli/agent@4.0.0", "", { "dependencies": { "agent-base": "^7.1.0", "http-proxy-agent": "^7.0.0", "https-proxy-agent": "^7.0.1", "lru-cache": "^11.2.1", "socks-proxy-agent": "^8.0.3" } }, "sha512-kAQTcEN9E8ERLVg5AsGwLNoFb+oEG6engbqAU2P43gD4JEIkNGMHdVQ096FsOAAYpZPB0RSt0zgInKIAS1l5QA=="],
- "npm/@npmcli/arborist": ["@npmcli/arborist@9.4.1", "", { "dependencies": { "@gar/promise-retry": "^1.0.0", "@isaacs/string-locale-compare": "^1.1.0", "@npmcli/fs": "^5.0.0", "@npmcli/installed-package-contents": "^4.0.0", "@npmcli/map-workspaces": "^5.0.0", "@npmcli/metavuln-calculator": "^9.0.2", "@npmcli/name-from-folder": "^4.0.0", "@npmcli/node-gyp": "^5.0.0", "@npmcli/package-json": "^7.0.0", "@npmcli/query": "^5.0.0", "@npmcli/redact": "^4.0.0", "@npmcli/run-script": "^10.0.0", "bin-links": "^6.0.0", "cacache": "^20.0.1", "common-ancestor-path": "^2.0.0", "hosted-git-info": "^9.0.0", "json-stringify-nice": "^1.1.4", "lru-cache": "^11.2.1", "minimatch": "^10.0.3", "nopt": "^9.0.0", "npm-install-checks": "^8.0.0", "npm-package-arg": "^13.0.0", "npm-pick-manifest": "^11.0.1", "npm-registry-fetch": "^19.0.0", "pacote": "^21.0.2", "parse-conflict-json": "^5.0.1", "proc-log": "^6.0.0", "proggy": "^4.0.0", "promise-all-reject-late": "^1.0.0", "promise-call-limit": "^3.0.1", "semver": "^7.3.7", "ssri": "^13.0.0", "treeverse": "^3.0.0", "walk-up-path": "^4.0.0" }, "bundled": true, "bin": { "arborist": "bin/index.js" } }, "sha512-SaXiFtYcAbzPI+VmuI+O6hii9fEVe36vm6XRAu0QcvCR9YphHfNF8PIDeDapVkE+LJ0c7BN7uPGd3plbh9zbrw=="],
+ "npm/@npmcli/arborist": ["@npmcli/arborist@9.4.2", "", { "dependencies": { "@gar/promise-retry": "^1.0.0", "@isaacs/string-locale-compare": "^1.1.0", "@npmcli/fs": "^5.0.0", "@npmcli/installed-package-contents": "^4.0.0", "@npmcli/map-workspaces": "^5.0.0", "@npmcli/metavuln-calculator": "^9.0.2", "@npmcli/name-from-folder": "^4.0.0", "@npmcli/node-gyp": "^5.0.0", "@npmcli/package-json": "^7.0.0", "@npmcli/query": "^5.0.0", "@npmcli/redact": "^4.0.0", "@npmcli/run-script": "^10.0.0", "bin-links": "^6.0.0", "cacache": "^20.0.1", "common-ancestor-path": "^2.0.0", "hosted-git-info": "^9.0.0", "json-stringify-nice": "^1.1.4", "lru-cache": "^11.2.1", "minimatch": "^10.0.3", "nopt": "^9.0.0", "npm-install-checks": "^8.0.0", "npm-package-arg": "^13.0.0", "npm-pick-manifest": "^11.0.1", "npm-registry-fetch": "^19.0.0", "pacote": "^21.0.2", "parse-conflict-json": "^5.0.1", "proc-log": "^6.0.0", "proggy": "^4.0.0", "promise-all-reject-late": "^1.0.0", "promise-call-limit": "^3.0.1", "semver": "^7.3.7", "ssri": "^13.0.0", "treeverse": "^3.0.0", "walk-up-path": "^4.0.0" }, "bundled": true, "bin": { "arborist": "bin/index.js" } }, "sha512-omJgPyzt11cEGrxzgrECoOyxAunmPMgBFTcAB/FbaB+9iOYhGmRdsQqySV8o0LWQ/l2kTeASUIMR4xJufVwmtw=="],
- "npm/@npmcli/config": ["@npmcli/config@10.7.1", "", { "dependencies": { "@npmcli/map-workspaces": "^5.0.0", "@npmcli/package-json": "^7.0.0", "ci-info": "^4.0.0", "ini": "^6.0.0", "nopt": "^9.0.0", "proc-log": "^6.0.0", "semver": "^7.3.5", "walk-up-path": "^4.0.0" }, "bundled": true }, "sha512-lh0eZYOknIpIKYKxbQKX7xFmb4FbmrOHUD25+0iEo3djRQP6YleHwBFgjH3X7QvUVM4t+Xm7rGsjDwJp63WkAg=="],
+ "npm/@npmcli/config": ["@npmcli/config@10.8.0", "", { "dependencies": { "@npmcli/map-workspaces": "^5.0.0", "@npmcli/package-json": "^7.0.0", "ci-info": "^4.0.0", "ini": "^6.0.0", "nopt": "^9.0.0", "proc-log": "^6.0.0", "semver": "^7.3.5", "walk-up-path": "^4.0.0" }, "bundled": true }, "sha512-YkhoXZQU7zxyGi3V7J0zdK2pghzF9YXHiRdpRX8QNhsefk/zAJZJjRsbbw1hD67hlMp2gSygUGgW4y7FlrUThw=="],
"npm/@npmcli/fs": ["@npmcli/fs@5.0.0", "", { "dependencies": { "semver": "^7.3.5" }, "bundled": true }, "sha512-7OsC1gNORBEawOa5+j2pXN9vsicaIOH5cPXxoR6fJOmH6/EXpJB2CajXOu1fPRFun2m1lktEFX11+P89hqO/og=="],
@@ -1578,13 +1611,13 @@
"npm/@sigstore/bundle": ["@sigstore/bundle@4.0.0", "", { "dependencies": { "@sigstore/protobuf-specs": "^0.5.0" } }, "sha512-NwCl5Y0V6Di0NexvkTqdoVfmjTaQwoLM236r89KEojGmq/jMls8S+zb7yOwAPdXvbwfKDlP+lmXgAL4vKSQT+A=="],
- "npm/@sigstore/core": ["@sigstore/core@3.1.0", "", {}, "sha512-o5cw1QYhNQ9IroioJxpzexmPjfCe7gzafd2RY3qnMpxr4ZEja+Jad/U8sgFpaue6bOaF+z7RVkyKVV44FN+N8A=="],
+ "npm/@sigstore/core": ["@sigstore/core@3.2.0", "", {}, "sha512-kxHrDQ9YgfrWUSXU0cjsQGv8JykOFZQ9ErNKbFPWzk3Hgpwu8x2hHrQ9IdA8yl+j9RTLTC3sAF3Tdq1IQCP4oA=="],
"npm/@sigstore/protobuf-specs": ["@sigstore/protobuf-specs@0.5.0", "", {}, "sha512-MM8XIwUjN2bwvCg1QvrMtbBmpcSHrkhFSCu1D11NyPvDQ25HEc4oG5/OcQfd/Tlf/OxmKWERDj0zGE23jQaMwA=="],
- "npm/@sigstore/sign": ["@sigstore/sign@4.1.0", "", { "dependencies": { "@sigstore/bundle": "^4.0.0", "@sigstore/core": "^3.1.0", "@sigstore/protobuf-specs": "^0.5.0", "make-fetch-happen": "^15.0.3", "proc-log": "^6.1.0", "promise-retry": "^2.0.1" } }, "sha512-Vx1RmLxLGnSUqx/o5/VsCjkuN5L7y+vxEEwawvc7u+6WtX2W4GNa7b9HEjmcRWohw/d6BpATXmvOwc78m+Swdg=="],
+ "npm/@sigstore/sign": ["@sigstore/sign@4.1.1", "", { "dependencies": { "@gar/promise-retry": "^1.0.2", "@sigstore/bundle": "^4.0.0", "@sigstore/core": "^3.2.0", "@sigstore/protobuf-specs": "^0.5.0", "make-fetch-happen": "^15.0.4", "proc-log": "^6.1.0" } }, "sha512-Hf4xglukg0XXQ2RiD5vSoLjdPe8OBUPA8XeVjUObheuDcWdYWrnH/BNmxZCzkAy68MzmNCxXLeurJvs6hcP2OQ=="],
- "npm/@sigstore/tuf": ["@sigstore/tuf@4.0.1", "", { "dependencies": { "@sigstore/protobuf-specs": "^0.5.0", "tuf-js": "^4.1.0" }, "bundled": true }, "sha512-OPZBg8y5Vc9yZjmWCHrlWPMBqW5yd8+wFNl+thMdtcWz3vjVSoJQutF8YkrzI0SLGnkuFof4HSsWUhXrf219Lw=="],
+ "npm/@sigstore/tuf": ["@sigstore/tuf@4.0.2", "", { "dependencies": { "@sigstore/protobuf-specs": "^0.5.0", "tuf-js": "^4.1.0" }, "bundled": true }, "sha512-TCAzTy0xzdP79EnxSjq9KQ3eaR7+FmudLC6eRKknVKZbV7ZNlGLClAAQb/HMNJ5n2OBNk2GT1tEmU0xuPr+SLQ=="],
"npm/@sigstore/verify": ["@sigstore/verify@3.1.0", "", { "dependencies": { "@sigstore/bundle": "^4.0.0", "@sigstore/core": "^3.1.0", "@sigstore/protobuf-specs": "^0.5.0" } }, "sha512-mNe0Iigql08YupSOGv197YdHpPPr+EzDZmfCgMc7RPNaZTw5aLN01nBl6CHJOh3BGtnMIj83EeN4butBchc8Ag=="],
@@ -1608,7 +1641,7 @@
"npm/brace-expansion": ["brace-expansion@5.0.4", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg=="],
- "npm/cacache": ["cacache@20.0.3", "", { "dependencies": { "@npmcli/fs": "^5.0.0", "fs-minipass": "^3.0.0", "glob": "^13.0.0", "lru-cache": "^11.1.0", "minipass": "^7.0.3", "minipass-collect": "^2.0.1", "minipass-flush": "^1.0.5", "minipass-pipeline": "^1.2.4", "p-map": "^7.0.2", "ssri": "^13.0.0", "unique-filename": "^5.0.0" }, "bundled": true }, "sha512-3pUp4e8hv07k1QlijZu6Kn7c9+ZpWWk4j3F8N3xPuCExULobqJydKYOTj1FTq58srkJsXvO7LbGAH4C0ZU3WGw=="],
+ "npm/cacache": ["cacache@20.0.4", "", { "dependencies": { "@npmcli/fs": "^5.0.0", "fs-minipass": "^3.0.0", "glob": "^13.0.0", "lru-cache": "^11.1.0", "minipass": "^7.0.3", "minipass-collect": "^2.0.1", "minipass-flush": "^1.0.5", "minipass-pipeline": "^1.2.4", "p-map": "^7.0.2", "ssri": "^13.0.0" }, "bundled": true }, "sha512-M3Lab8NPYlZU2exsL3bMVvMrMqgwCnMWfdZbK28bn3pK6APT/Te/I8hjRPNu1uwORY9a1eEQoifXbKPQMfMTOA=="],
"npm/chalk": ["chalk@5.6.2", "", { "bundled": true }, "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="],
@@ -1630,8 +1663,6 @@
"npm/env-paths": ["env-paths@2.2.1", "", {}, "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A=="],
- "npm/err-code": ["err-code@2.0.3", "", {}, "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA=="],
-
"npm/exponential-backoff": ["exponential-backoff@3.1.3", "", {}, "sha512-ZgEeZXj30q+I0EN+CbSSpIyPaJ5HVQD18Z1m+u1FXbAeT94mr1zw50q4q6jiiC447Nl/YTcIYSAftiGqetwXCA=="],
"npm/fastest-levenshtein": ["fastest-levenshtein@1.0.16", "", { "bundled": true }, "sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg=="],
@@ -1656,8 +1687,6 @@
"npm/ignore-walk": ["ignore-walk@8.0.0", "", { "dependencies": { "minimatch": "^10.0.3" } }, "sha512-FCeMZT4NiRQGh+YkeKMtWrOmBgWjHjMJ26WQWrRQyoyzqevdaGSakUaJW5xQYmjLlUVk2qUnCjYVBax9EKKg8A=="],
- "npm/imurmurhash": ["imurmurhash@0.1.4", "", {}, "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA=="],
-
"npm/ini": ["ini@6.0.0", "", { "bundled": true }, "sha512-IBTdIkzZNOpqm7q3dRqJvMaldXjDHWkEDfrwGEQTs5eaQMWV+djAhR+wahyNNMAa+qpbDUhBMVt4ZKNwpPm7xQ=="],
"npm/init-package-json": ["init-package-json@8.2.5", "", { "dependencies": { "@npmcli/package-json": "^7.0.0", "npm-package-arg": "^13.0.0", "promzard": "^3.0.1", "read": "^5.0.1", "semver": "^7.7.2", "validate-npm-package-name": "^7.0.0" }, "bundled": true }, "sha512-IknQ+upLuJU6t3p0uo9wS3GjFD/1GtxIwcIGYOWR8zL2HxQeJwvxYTgZr9brJ8pyZ4kvpkebM8ZKcyqOeLOHSg=="],
@@ -1680,15 +1709,15 @@
"npm/libnpmaccess": ["libnpmaccess@10.0.3", "", { "dependencies": { "npm-package-arg": "^13.0.0", "npm-registry-fetch": "^19.0.0" }, "bundled": true }, "sha512-JPHTfWJxIK+NVPdNMNGnkz4XGX56iijPbe0qFWbdt68HL+kIvSzh+euBL8npLZvl2fpaxo+1eZSdoG15f5YdIQ=="],
- "npm/libnpmdiff": ["libnpmdiff@8.1.4", "", { "dependencies": { "@npmcli/arborist": "^9.4.1", "@npmcli/installed-package-contents": "^4.0.0", "binary-extensions": "^3.0.0", "diff": "^8.0.2", "minimatch": "^10.0.3", "npm-package-arg": "^13.0.0", "pacote": "^21.0.2", "tar": "^7.5.1" }, "bundled": true }, "sha512-AvkBTAHfW4C/YzdZYtQ9qDXCpHrIz4EISUOlOwX+IjZguuhj/uniFg1K6m7kCfp9EsFK/K1U2mhzw2VyNfJ3Vg=="],
+ "npm/libnpmdiff": ["libnpmdiff@8.1.5", "", { "dependencies": { "@npmcli/arborist": "^9.4.2", "@npmcli/installed-package-contents": "^4.0.0", "binary-extensions": "^3.0.0", "diff": "^8.0.2", "minimatch": "^10.0.3", "npm-package-arg": "^13.0.0", "pacote": "^21.0.2", "tar": "^7.5.1" }, "bundled": true }, "sha512-3tknN/GosDOpIYjBplXpr7WVjpBDodAxXkZEtv410XlIsfMD+v/6mt9sYe/s/x+TRmmCRpzP/bxfhUorvV6Cqg=="],
- "npm/libnpmexec": ["libnpmexec@10.2.4", "", { "dependencies": { "@gar/promise-retry": "^1.0.0", "@npmcli/arborist": "^9.4.1", "@npmcli/package-json": "^7.0.0", "@npmcli/run-script": "^10.0.0", "ci-info": "^4.0.0", "npm-package-arg": "^13.0.0", "pacote": "^21.0.2", "proc-log": "^6.0.0", "read": "^5.0.1", "semver": "^7.3.7", "signal-exit": "^4.1.0", "walk-up-path": "^4.0.0" }, "bundled": true }, "sha512-kDfvrMN1aN7RTOKsRFtF83rhr5H0Frxp5ayMWWhZf6SMBv5wmyTZNRHomsKgcib/AsTUdmla8zIOtwpwcT+Suw=="],
+ "npm/libnpmexec": ["libnpmexec@10.2.5", "", { "dependencies": { "@gar/promise-retry": "^1.0.0", "@npmcli/arborist": "^9.4.2", "@npmcli/package-json": "^7.0.0", "@npmcli/run-script": "^10.0.0", "ci-info": "^4.0.0", "npm-package-arg": "^13.0.0", "pacote": "^21.0.2", "proc-log": "^6.0.0", "read": "^5.0.1", "semver": "^7.3.7", "signal-exit": "^4.1.0", "walk-up-path": "^4.0.0" }, "bundled": true }, "sha512-ayouyoml/4NmcgH+nWzK6QB5w0gKrftsYB8TAHu5TB5v6Nj3fgz8ZBK9FsG2A1SNuHZVTjvrNMDyF2VzDih/bA=="],
- "npm/libnpmfund": ["libnpmfund@7.0.18", "", { "dependencies": { "@npmcli/arborist": "^9.4.1" }, "bundled": true }, "sha512-eYwOtxFOgqHyPcNvp1UEHEFJdc7fj/s5C51R2E46bPYVya+KjV4pDIuQ1m9xJNBYKWPGNKkURLbuyp7BPm5E/w=="],
+ "npm/libnpmfund": ["libnpmfund@7.0.19", "", { "dependencies": { "@npmcli/arborist": "^9.4.2" }, "bundled": true }, "sha512-RNyp5gnjVXaqlx0asRLmAOrFkTwANntzqkRyTT6Iu2nUt1F2eiMZNMOpO2HNfA7/NceBVBk/xsrzas3miCz9oQ=="],
"npm/libnpmorg": ["libnpmorg@8.0.1", "", { "dependencies": { "aproba": "^2.0.0", "npm-registry-fetch": "^19.0.0" }, "bundled": true }, "sha512-/QeyXXg4hqMw0ESM7pERjIT2wbR29qtFOWIOug/xO4fRjS3jJJhoAPQNsnHtdwnCqgBdFpGQ45aIdFFZx2YhTA=="],
- "npm/libnpmpack": ["libnpmpack@9.1.4", "", { "dependencies": { "@npmcli/arborist": "^9.4.1", "@npmcli/run-script": "^10.0.0", "npm-package-arg": "^13.0.0", "pacote": "^21.0.2" }, "bundled": true }, "sha512-WJ4KArsfQ3h1rNIVw+1uRRzbp0oZS+k/E9M9Uw4g1SZz67SInp74TWYTknLepgahma/OJHDxlT2LinnHvtDk8g=="],
+ "npm/libnpmpack": ["libnpmpack@9.1.5", "", { "dependencies": { "@npmcli/arborist": "^9.4.2", "@npmcli/run-script": "^10.0.0", "npm-package-arg": "^13.0.0", "pacote": "^21.0.2" }, "bundled": true }, "sha512-H1IX364ZwpeRfrL6UYSuxFNgP16/TvlwtCm8ZallbB7/1FZ3h1FBZHamQtv7PqcZUTWE27mygdQ4wCCW2BmVlg=="],
"npm/libnpmpublish": ["libnpmpublish@11.1.3", "", { "dependencies": { "@npmcli/package-json": "^7.0.0", "ci-info": "^4.0.0", "npm-package-arg": "^13.0.0", "npm-registry-fetch": "^19.0.0", "proc-log": "^6.0.0", "semver": "^7.3.7", "sigstore": "^4.0.0", "ssri": "^13.0.0" }, "bundled": true }, "sha512-NVPTth/71cfbdYHqypcO9Lt5WFGTzFEcx81lWd7GDJIgZ95ERdYHGUfCtFejHCyqodKsQkNEx2JCkMpreDty/A=="],
@@ -1698,9 +1727,9 @@
"npm/libnpmversion": ["libnpmversion@8.0.3", "", { "dependencies": { "@npmcli/git": "^7.0.0", "@npmcli/run-script": "^10.0.0", "json-parse-even-better-errors": "^5.0.0", "proc-log": "^6.0.0", "semver": "^7.3.7" }, "bundled": true }, "sha512-Avj1GG3DT6MGzWOOk3yA7rORcMDUPizkIGbI8glHCO7WoYn3NYNmskLDwxg2NMY1Tyf2vrHAqTuSG58uqd1lJg=="],
- "npm/lru-cache": ["lru-cache@11.2.6", "", {}, "sha512-ESL2CrkS/2wTPfuend7Zhkzo2u0daGJ/A2VucJOgQ/C48S/zB8MMeMHSGKYpXhIjbPxfuezITkaBH1wqv00DDQ=="],
+ "npm/lru-cache": ["lru-cache@11.2.7", "", {}, "sha512-aY/R+aEsRelme17KGQa/1ZSIpLpNYYrhcrepKTZgE+W3WM16YMCaPwOHLHsmopZHELU0Ojin1lPVxKR0MihncA=="],
- "npm/make-fetch-happen": ["make-fetch-happen@15.0.4", "", { "dependencies": { "@gar/promise-retry": "^1.0.0", "@npmcli/agent": "^4.0.0", "cacache": "^20.0.1", "http-cache-semantics": "^4.1.1", "minipass": "^7.0.2", "minipass-fetch": "^5.0.0", "minipass-flush": "^1.0.5", "minipass-pipeline": "^1.2.4", "negotiator": "^1.0.0", "proc-log": "^6.0.0", "ssri": "^13.0.0" }, "bundled": true }, "sha512-vM2sG+wbVeVGYcCm16mM3d5fuem9oC28n436HjsGO3LcxoTI8LNVa4rwZDn3f76+cWyT4GGJDxjTYU1I2nr6zw=="],
+ "npm/make-fetch-happen": ["make-fetch-happen@15.0.5", "", { "dependencies": { "@gar/promise-retry": "^1.0.0", "@npmcli/agent": "^4.0.0", "@npmcli/redact": "^4.0.0", "cacache": "^20.0.1", "http-cache-semantics": "^4.1.1", "minipass": "^7.0.2", "minipass-fetch": "^5.0.0", "minipass-flush": "^1.0.5", "minipass-pipeline": "^1.2.4", "negotiator": "^1.0.0", "proc-log": "^6.0.0", "ssri": "^13.0.0" }, "bundled": true }, "sha512-uCbIa8jWWmQZt4dSnEStkVC6gdakiinAm4PiGsywIkguF0eWMdcjDz0ECYhUolFU3pFLOev9VNPCEygydXnddg=="],
"npm/minimatch": ["minimatch@10.2.4", "", { "dependencies": { "brace-expansion": "^5.0.2" }, "bundled": true }, "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg=="],
@@ -1768,8 +1797,6 @@
"npm/promise-call-limit": ["promise-call-limit@3.0.2", "", {}, "sha512-mRPQO2T1QQVw11E7+UdCJu7S61eJVWknzml9sC1heAdj1jxl0fWMBypIt9ZOcLFf8FkG995ZD7RnVk7HH72fZw=="],
- "npm/promise-retry": ["promise-retry@2.0.1", "", { "dependencies": { "err-code": "^2.0.2", "retry": "^0.12.0" } }, "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g=="],
-
"npm/promzard": ["promzard@3.0.1", "", { "dependencies": { "read": "^5.0.0" } }, "sha512-M5mHhWh+Adz0BIxgSrqcc6GTCSconR7zWQV9vnOSptNtr6cSFlApLc28GbQhuN6oOWBQeV2C0bNE47JCY/zu3Q=="],
"npm/qrcode-terminal": ["qrcode-terminal@0.12.0", "", { "bundled": true, "bin": { "qrcode-terminal": "./bin/qrcode-terminal.js" } }, "sha512-EXtzRZmC+YGmGlDFbXKxQiMZNwCLEO6BANKXG4iCtSIM0yqc/pappSx3RIKr4r0uh5JsBckOXeKrB3Iz7mdQpQ=="],
@@ -1778,8 +1805,6 @@
"npm/read-cmd-shim": ["read-cmd-shim@6.0.0", "", {}, "sha512-1zM5HuOfagXCBWMN83fuFI/x+T/UhZ7k+KIzhrHXcQoeX5+7gmaDYjELQHmmzIodumBHeByBJT4QYS7ufAgs7A=="],
- "npm/retry": ["retry@0.13.1", "", {}, "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg=="],
-
"npm/safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="],
"npm/semver": ["semver@7.7.4", "", { "bundled": true, "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="],
@@ -1816,10 +1841,6 @@
"npm/tuf-js": ["tuf-js@4.1.0", "", { "dependencies": { "@tufjs/models": "4.1.0", "debug": "^4.4.3", "make-fetch-happen": "^15.0.1" } }, "sha512-50QV99kCKH5P/Vs4E2Gzp7BopNV+KzTXqWeaxrfu5IQJBOULRsTIS9seSsOVT8ZnGXzCyx55nYWAi4qJzpZKEQ=="],
- "npm/unique-filename": ["unique-filename@5.0.0", "", { "dependencies": { "unique-slug": "^6.0.0" } }, "sha512-2RaJTAvAb4owyjllTfXzFClJ7WsGxlykkPvCr9pA//LD9goVq+m4PPAeBgNodGZ7nSrntT/auWpJ6Y5IFXcfjg=="],
-
- "npm/unique-slug": ["unique-slug@6.0.0", "", { "dependencies": { "imurmurhash": "^0.1.4" } }, "sha512-4Lup7Ezn8W3d52/xBhZBVdx323ckxa7DEvd9kPQHppTkLoJXw6ltrBCyj5pnrxj0qKDxYMJ56CoxNuFCscdTiw=="],
-
"npm/util-deprecate": ["util-deprecate@1.0.2", "", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="],
"npm/validate-npm-package-name": ["validate-npm-package-name@7.0.2", "", { "bundled": true }, "sha512-hVDIBwsRruT73PbK7uP5ebUt+ezEtCmzZz3F59BSr2F6OVFnJ/6h8liuvdLrQ88Xmnk6/+xGGuq+pG9WwTuy3A=="],
@@ -1834,6 +1855,8 @@
"postject/commander": ["commander@9.5.0", "", {}, "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ=="],
+ "qified/hookified": ["hookified@2.1.0", "", {}, "sha512-ootKng4eaxNxa7rx6FJv2YKef3DuhqbEj3l70oGXwddPQEEnISm50TEZQclqiLTAtilT2nu7TErtCO523hHkyg=="],
+
"r-json/w-json": ["w-json@1.3.10", "", {}, "sha512-XadVyw0xE+oZ5FGApXsdswv96rOhStzKqL53uSe5UaTadABGkWIg1+DTx8kiZ/VqTZTBneoL0l65RcPe4W3ecw=="],
"set-value/is-plain-object": ["is-plain-object@2.0.4", "", { "dependencies": { "isobject": "^3.0.1" } }, "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og=="],
@@ -1844,6 +1867,8 @@
"stylelint/ignore": ["ignore@7.0.5", "", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="],
+ "svgo/commander": ["commander@11.1.0", "", {}, "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ=="],
+
"table/ajv": ["ajv@8.18.0", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A=="],
"tcp-port-used/debug": ["debug@4.3.1", "", { "dependencies": { "ms": "2.1.2" } }, "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ=="],
@@ -1868,21 +1893,17 @@
"configstore/write-file-atomic/signal-exit": ["signal-exit@3.0.7", "", {}, "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="],
+ "csso/css-tree/mdn-data": ["mdn-data@2.0.28", "", {}, "sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g=="],
+
"filelist/minimatch/brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="],
"glob/minimatch/brace-expansion": ["brace-expansion@5.0.4", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg=="],
- "npm/@npmcli/metavuln-calculator/pacote": ["pacote@21.4.0", "", { "dependencies": { "@gar/promise-retry": "^1.0.0", "@npmcli/git": "^7.0.0", "@npmcli/installed-package-contents": "^4.0.0", "@npmcli/package-json": "^7.0.0", "@npmcli/promise-spawn": "^9.0.0", "@npmcli/run-script": "^10.0.0", "cacache": "^20.0.0", "fs-minipass": "^3.0.0", "minipass": "^7.0.2", "npm-package-arg": "^13.0.0", "npm-packlist": "^10.0.1", "npm-pick-manifest": "^11.0.1", "npm-registry-fetch": "^19.0.0", "proc-log": "^6.0.0", "sigstore": "^4.0.0", "ssri": "^13.0.0", "tar": "^7.4.3" }, "bin": { "pacote": "bin/index.js" } }, "sha512-DR7mn7HUOomAX1BORnpYy678qVIidbvOojkBscqy27dRKN+s/hLeQT1MeYYrx1Cxh62jyKjiWiDV7RTTqB+ZEQ=="],
-
"npm/minipass-flush/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="],
"npm/minipass-pipeline/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="],
- "npm/node-gyp/tar": ["tar@7.5.10", "", { "dependencies": { "@isaacs/fs-minipass": "^4.0.0", "chownr": "^3.0.0", "minipass": "^7.1.2", "minizlib": "^3.1.0", "yallist": "^5.0.0" } }, "sha512-8mOPs1//5q/rlkNSPcCegA6hiHJYDmSLEI8aMH/CdSQJNWztHC9WHNam5zdQlfpTwB9Xp7IBEsHfV5LKMJGVAw=="],
-
- "npm/promise-retry/retry": ["retry@0.12.0", "", {}, "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow=="],
-
- "stylelint/file-entry-cache/flat-cache": ["flat-cache@6.1.20", "", { "dependencies": { "cacheable": "^2.3.2", "flatted": "^3.3.3", "hookified": "^1.15.0" } }, "sha512-AhHYqwvN62NVLp4lObVXGVluiABTHapoB57EyegZVmazN+hhGhLTn3uZbOofoTw4DSDvVCadzzyChXhOAvy8uQ=="],
+ "stylelint/file-entry-cache/flat-cache": ["flat-cache@6.1.21", "", { "dependencies": { "cacheable": "^2.3.3", "flatted": "^3.4.1", "hookified": "^1.15.0" } }, "sha512-2u7cJfSf7Th7NxEk/VzQjnPoglok2YCsevS7TSbJjcDQWJPbqUUnSYtriHSvtnq+fRZHy1s0ugk4ApnQyhPGoQ=="],
"table/ajv/json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="],
@@ -1898,8 +1919,6 @@
"glob/minimatch/brace-expansion/balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="],
- "npm/@npmcli/metavuln-calculator/pacote/tar": ["tar@7.5.10", "", { "dependencies": { "@isaacs/fs-minipass": "^4.0.0", "chownr": "^3.0.0", "minipass": "^7.1.2", "minizlib": "^3.1.0", "yallist": "^5.0.0" } }, "sha512-8mOPs1//5q/rlkNSPcCegA6hiHJYDmSLEI8aMH/CdSQJNWztHC9WHNam5zdQlfpTwB9Xp7IBEsHfV5LKMJGVAw=="],
-
"npm/minipass-flush/minipass/yallist": ["yallist@4.0.0", "", {}, "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="],
"npm/minipass-pipeline/minipass/yallist": ["yallist@4.0.0", "", {}, "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="],
diff --git a/images/animate-spin.svg b/images/animate-spin.svg
new file mode 100644
index 000000000..e85f924c0
--- /dev/null
+++ b/images/animate-spin.svg
@@ -0,0 +1,4 @@
+
+
+
\ No newline at end of file
diff --git a/images/apple.svg b/images/apple.svg
new file mode 100644
index 000000000..19452c474
--- /dev/null
+++ b/images/apple.svg
@@ -0,0 +1,20 @@
+
+
+
+
+ apple [#9E9E9E173]
+ Created with Sketch.
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/images/discord.svg b/images/discord.svg
new file mode 100644
index 000000000..829e5247a
--- /dev/null
+++ b/images/discord.svg
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/images/facebook.svg b/images/facebook.svg
new file mode 100644
index 000000000..c54c0b183
--- /dev/null
+++ b/images/facebook.svg
@@ -0,0 +1,4 @@
+
+
+
\ No newline at end of file
diff --git a/images/genius-active.svg b/images/genius-active.svg
new file mode 100644
index 000000000..988d65e1f
--- /dev/null
+++ b/images/genius-active.svg
@@ -0,0 +1,5 @@
+
+
+
+
\ No newline at end of file
diff --git a/images/genius-inactive.svg b/images/genius-inactive.svg
new file mode 100644
index 000000000..c984e154f
--- /dev/null
+++ b/images/genius-inactive.svg
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/images/github.svg b/images/github.svg
new file mode 100644
index 000000000..f278dd59f
--- /dev/null
+++ b/images/github.svg
@@ -0,0 +1,16 @@
+
+
+
+
\ No newline at end of file
diff --git a/images/instagram.svg b/images/instagram.svg
new file mode 100644
index 000000000..b8a0a85e0
--- /dev/null
+++ b/images/instagram.svg
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/images/linux.svg b/images/linux.svg
new file mode 100644
index 000000000..2479f272a
--- /dev/null
+++ b/images/linux.svg
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/images/mic.svg b/images/mic.svg
new file mode 100644
index 000000000..fa2c7213c
--- /dev/null
+++ b/images/mic.svg
@@ -0,0 +1,16 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/images/mix.svg b/images/mix.svg
new file mode 100644
index 000000000..1a3153b14
--- /dev/null
+++ b/images/mix.svg
@@ -0,0 +1,4 @@
+
+
+
\ No newline at end of file
diff --git a/images/monochrome-logo.svg b/images/monochrome-logo.svg
new file mode 100644
index 000000000..2313e279c
--- /dev/null
+++ b/images/monochrome-logo.svg
@@ -0,0 +1,13 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/images/pause-large.svg b/images/pause-large.svg
new file mode 100644
index 000000000..228b7094d
--- /dev/null
+++ b/images/pause-large.svg
@@ -0,0 +1,6 @@
+
+
+
+
\ No newline at end of file
diff --git a/images/pause.svg b/images/pause.svg
new file mode 100644
index 000000000..1a2b240d0
--- /dev/null
+++ b/images/pause.svg
@@ -0,0 +1,5 @@
+
+
+
+
\ No newline at end of file
diff --git a/images/play-large.svg b/images/play-large.svg
new file mode 100644
index 000000000..91358b35f
--- /dev/null
+++ b/images/play-large.svg
@@ -0,0 +1,13 @@
+
+
+
\ No newline at end of file
diff --git a/images/play.svg b/images/play.svg
new file mode 100644
index 000000000..567557296
--- /dev/null
+++ b/images/play.svg
@@ -0,0 +1,4 @@
+
+
+
\ No newline at end of file
diff --git a/images/recent.svg b/images/recent.svg
new file mode 100644
index 000000000..3d60f6f59
--- /dev/null
+++ b/images/recent.svg
@@ -0,0 +1,15 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/images/side-menu.svg b/images/side-menu.svg
new file mode 100644
index 000000000..64dde4b55
--- /dev/null
+++ b/images/side-menu.svg
@@ -0,0 +1,14 @@
+
+
+
+
\ No newline at end of file
diff --git a/images/sort.svg b/images/sort.svg
new file mode 100644
index 000000000..c962f18b2
--- /dev/null
+++ b/images/sort.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/images/soundcloud.svg b/images/soundcloud.svg
new file mode 100644
index 000000000..1f55321e8
--- /dev/null
+++ b/images/soundcloud.svg
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/images/squares.svg b/images/squares.svg
new file mode 100644
index 000000000..f57e71d5e
--- /dev/null
+++ b/images/squares.svg
@@ -0,0 +1,16 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/images/twitter.svg b/images/twitter.svg
new file mode 100644
index 000000000..a56b86602
--- /dev/null
+++ b/images/twitter.svg
@@ -0,0 +1,5 @@
+
+
+
\ No newline at end of file
diff --git a/images/windows.svg b/images/windows.svg
new file mode 100644
index 000000000..3fb994e0d
--- /dev/null
+++ b/images/windows.svg
@@ -0,0 +1,4 @@
+
+ windows [#FFF174]
+
+
\ No newline at end of file
diff --git a/images/youtube.svg b/images/youtube.svg
new file mode 100644
index 000000000..c9fb8b1e1
--- /dev/null
+++ b/images/youtube.svg
@@ -0,0 +1,20 @@
+
+
+
+
+ youtube [#9E9E9E168]
+ Created with Sketch.
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/index.html b/index.html
index 44ab79d39..b2d24ff00 100644
--- a/index.html
+++ b/index.html
@@ -140,38 +140,10 @@ Panel
"
>
-
-
-
-
+
-
-
-
-
-
-
+
×
@@ -186,93 +158,19 @@
-
-
-
+
-
-
-
-
+
-
-
-
-
-
+
-
-
-
-
-
-
+
-
-
-
-
-
-
-
-
+
@@ -290,130 +188,29 @@
-
-
-
-
+
@@ -461,18 +258,7 @@
Create Playlist
gap: 0.5rem;
"
>
-
-
-
-
-
+
Upload
Edit Profile
cursor: pointer;
"
>
-
-
-
-
-
+
Upload
Edit Profile
cursor: pointer;
"
>
-
-
-
-
-
+
Upload
Theme Store
gap: 0.5rem;
"
>
-
-
-
+
Back
@@ -1460,21 +1212,7 @@
Description
style="flex: 1; overflow-y: auto; min-height: 0"
>
@@ -1693,21 +1419,7 @@
-
-
-
-
-
+
Photosensitivity Warning
@@ -1730,21 +1442,7 @@
-
-
-
+
Maintenance Notice
@@ -1875,19 +1573,7 @@
Importing Tracks from CSV
@@ -2348,20 +1781,7 @@
Recommended Songs
title="Refresh"
style="padding: 4px 8px"
>
-
-
-
-
+
@@ -2382,20 +1802,7 @@ Recommended Albums
title="Refresh"
style="padding: 4px 8px"
>
-
-
-
-
+
@@ -2416,20 +1823,7 @@
Recommended Artists
title="Refresh"
style="padding: 4px 8px"
>
-
-
-
-
+
@@ -2450,21 +1844,7 @@
Jump Back In
title="Clear History"
style="padding: 4px 8px"
>
-
-
-
-
-
+
@@ -2567,23 +1947,7 @@
Favorites
"
title="Shuffle Liked Tracks"
>
-
-
-
-
-
-
-
+
Favorites
"
title="Download Liked Tracks"
>
-
-
-
-
-
+
@@ -2634,19 +1984,7 @@
Favorites
-
-
-
+
Select Music Folder
Local Files
>
Recently played
-
-
-
-
-
-
-
+
Clear
@@ -2731,33 +2053,11 @@
@@ -2780,84 +2080,23 @@
@@ -2965,53 +2175,15 @@
@@ -3146,33 +2277,11 @@
@@ -3222,69 +2316,19 @@
@@ -3323,9 +2352,12 @@
Popular Tracks
In Your Library
-
-
-
+
@@ -3376,52 +2408,15 @@
@@ -3554,21 +2520,7 @@
Settings
onsubmit="return false;"
style="margin: 1rem 0"
>
-
-
-
-
+
Custom Theme
class="btn-secondary"
title="Reset to Flat"
>
-
-
-
-
+
Custom Theme
style="display: none"
title="Delete selected custom preset"
>
-
-
-
-
+
Delete Preset
@@ -5642,37 +4566,7 @@ Monochrome Official App
@@ -5685,23 +4579,7 @@
Monochrome Official App
@@ -5757,80 +4635,17 @@
Donate to Monochrome
@@ -5851,20 +4666,7 @@
Donate to Monochrome
style="display: none"
>
-
-
-
-
+
Donate to Monochrome
title="Track Mix"
style="display: none"
>
-
-
-
+
-
-
-
-
-
-
+
-
-
-
-
-
+
-
-
-
-
-
-
+
-
-
-
-
+
-
-
-
-
+
-
-
-
-
-
-
-
-
+
@@ -6001,20 +4704,7 @@
Donate to Monochrome
-
-
-
-
+
diff --git a/js/app.js b/js/app.js
index 9917db952..d85473e76 100644
--- a/js/app.js
+++ b/js/app.js
@@ -19,7 +19,7 @@ import { LyricsManager, openLyricsPanel, clearLyricsPanelSync } from './lyrics.j
import { createRouter, updateTabTitle, navigate } from './router.js';
import { initializePlayerEvents, initializeTrackInteractions, handleTrackAction } from './events.js';
import { initializeUIInteractions } from './ui-interactions.js';
-import { debounce, SVG_PLAY, getShareUrl } from './utils.js';
+import { debounce, getShareUrl } from './utils.js';
import { sidePanelManager } from './side-panel.js';
import { db } from './db.js';
import { showNotification } from './downloads.js';
@@ -61,6 +61,15 @@ import {
parseDynamicCSV,
importToLibrary,
} from './playlist-importer.js';
+import {
+ SVG_OFFLINE,
+ SVG_RIGHT_ARROW,
+ SVG_LEFT_ARROW,
+ SVG_ANIMATE_SPIN,
+ SVG_PLAY,
+ SVG_CLOSE,
+ SVG_RESET,
+} from './icons.js';
// Capture real iOS state before spoofing (needed for background audio)
if (typeof window !== 'undefined') {
@@ -310,11 +319,7 @@ function showOfflineNotification() {
const notification = document.createElement('div');
notification.className = 'offline-notification';
notification.innerHTML = `
-
-
-
-
-
+ ${SVG_OFFLINE(20)}
You are offline. Some features may not work.
`;
document.body.appendChild(notification);
@@ -859,9 +864,7 @@ document.addEventListener('DOMContentLoaded', async () => {
const isCollapsed = document.body.classList.contains('sidebar-collapsed');
const toggleBtn = document.getElementById('sidebar-toggle');
if (toggleBtn) {
- toggleBtn.innerHTML = isCollapsed
- ? '
'
- : '
';
+ toggleBtn.innerHTML = isCollapsed ? SVG_RIGHT_ARROW(20) : SVG_LEFT_ARROW(20);
}
// Save sidebar state to localStorage
sidebarSettings.setCollapsed(isCollapsed);
@@ -1184,8 +1187,7 @@ document.addEventListener('DOMContentLoaded', async () => {
btn.disabled = true;
const originalHTML = btn.innerHTML;
- btn.innerHTML =
- '
Shuffling... ';
+ btn.innerHTML = `${SVG_ANIMATE_SPIN(18)}
Shuffling... `;
try {
const artist = await api.getArtist(artistId);
@@ -1257,8 +1259,7 @@ document.addEventListener('DOMContentLoaded', async () => {
btn.disabled = true;
const originalHTML = btn.innerHTML;
- btn.innerHTML =
- '
Downloading... ';
+ btn.innerHTML = `${SVG_ANIMATE_SPIN(20)}
Downloading... `;
try {
const { mix, tracks } = await api.getMix(mixId);
@@ -1282,8 +1283,7 @@ document.addEventListener('DOMContentLoaded', async () => {
btn.disabled = true;
const originalHTML = btn.innerHTML;
- btn.innerHTML =
- '
Downloading... ';
+ btn.innerHTML = `${SVG_ANIMATE_SPIN(20)}
Downloading... `;
try {
let playlist, tracks;
@@ -2200,8 +2200,7 @@ document.addEventListener('DOMContentLoaded', async () => {
btn.disabled = true;
const originalHTML = btn.innerHTML;
- btn.innerHTML =
- '
Downloading... ';
+ btn.innerHTML = `${SVG_ANIMATE_SPIN(20)}
Downloading... `;
try {
const { album, tracks } = await api.getAlbum(albumId);
@@ -2324,8 +2323,7 @@ document.addEventListener('DOMContentLoaded', async () => {
btn.disabled = true;
const originalHTML = btn.innerHTML;
- btn.innerHTML =
- '
Loading... ';
+ btn.innerHTML = `${SVG_ANIMATE_SPIN(20)}
Loading... `;
try {
const artist = await api.getArtist(artistId);
@@ -2413,8 +2411,7 @@ document.addEventListener('DOMContentLoaded', async () => {
btn.disabled = true;
const originalHTML = btn.innerHTML;
- btn.innerHTML =
- '
';
+ btn.innerHTML = SVG_ANIMATE_SPIN(16);
try {
const likedTracks = await db.getFavorites('track');
@@ -2600,7 +2597,7 @@ document.addEventListener('DOMContentLoaded', async () => {
console.log('Gone offline');
});
- document.querySelector('.now-playing-bar .play-pause-btn').innerHTML = SVG_PLAY;
+ document.querySelector('.now-playing-bar .play-pause-btn').innerHTML = SVG_PLAY(20);
const router = createRouter(ui);
@@ -2898,10 +2895,7 @@ function showUpdateNotification(updateCallback) {
Update Now
-
-
-
-
+ ${SVG_CLOSE(16)}
`;
@@ -3075,8 +3069,7 @@ function showDiscographyDownloadModal(artist, api, quality, lyricsManager, trigg
triggerBtn.disabled = true;
const originalHTML = triggerBtn.innerHTML;
- triggerBtn.innerHTML =
- '
Downloading... ';
+ triggerBtn.innerHTML = `${SVG_ANIMATE_SPIN(20)}
Downloading... `;
try {
const { downloadDiscography } = await loadDownloadsModule();
@@ -3174,10 +3167,7 @@ function showCustomizeShortcutsModal() {
${keyDisplay}
-
-
-
-
+ ${SVG_RESET(16)}
`;
diff --git a/js/downloads.js b/js/downloads.js
index f1db7e171..e7b000b5b 100644
--- a/js/downloads.js
+++ b/js/downloads.js
@@ -5,8 +5,6 @@ import {
RATE_LIMIT_ERROR_MESSAGE,
getTrackArtists,
getTrackTitle,
- formatTemplate,
- SVG_CLOSE,
getCoverBlob,
getExtensionFromBlob,
escapeHtml,
@@ -24,6 +22,7 @@ import {
} from './bulk-download-writer.ts';
import { FfmpegProgress } from './ffmpeg.types.js';
import { DownloadProgress, ProgressMessage, SegmentedDownloadProgress } from './progressEvents.js';
+import { SVG_CLOSE } from './icons.ts';
const downloadTasks = new Map();
const bulkDownloadTasks = new Map();
@@ -186,7 +185,7 @@ export function addDownloadTask(trackId, track, filename, api, abortController)
Starting...
- ${SVG_CLOSE}
+ ${SVG_CLOSE(20)}
`;
@@ -265,7 +264,7 @@ export function completeDownloadTask(trackId, success = true, message = null) {
statusEl.textContent = message || '✗ Download failed';
statusEl.style.color = '#ef4444';
cancelBtn.innerHTML = `
- ${SVG_CLOSE}
+ ${SVG_CLOSE(20)}
`;
cancelBtn.onclick = () => removeDownloadTask(trackId);
@@ -811,7 +810,7 @@ function createBulkDownloadNotification(type, name, _totalItems) {
Starting...
-
+ ${SVG_CLOSE(20)}
`;
diff --git a/js/events.js b/js/events.js
index 29dbc81c3..11f1ffae1 100644
--- a/js/events.js
+++ b/js/events.js
@@ -1,13 +1,8 @@
//js/events.js
import {
- SVG_PLAY,
- SVG_PAUSE,
- SVG_VOLUME,
- SVG_MUTE,
REPEAT_MODE,
trackDataStore,
formatTime,
- SVG_BIN,
getTrackArtists,
positionMenu,
getShareUrl,
@@ -52,6 +47,7 @@ import {
trackStartMix,
trackEvent,
} from './analytics.js';
+import { SVG_BIN, SVG_MUTE, SVG_PAUSE, SVG_PLAY, SVG_VOLUME } from './icons.js';
let currentTrackIdForWaveform = null;
@@ -72,7 +68,7 @@ export function initializePlayerEvents(player, audioPlayer, scrobbler, ui) {
const activeEl = player.activeElement;
const { muted } = activeEl;
const volume = player.userVolume;
- volumeBtn.innerHTML = muted || volume === 0 ? SVG_MUTE : SVG_VOLUME;
+ volumeBtn.innerHTML = muted || volume === 0 ? SVG_MUTE(20) : SVG_VOLUME(20);
const effectiveVolume = muted ? 0 : volume * 100;
volumeFill.style.setProperty('--volume-level', `${effectiveVolume}%`);
volumeFill.style.width = `${effectiveVolume}%`;
@@ -117,7 +113,7 @@ export function initializePlayerEvents(player, audioPlayer, scrobbler, ui) {
updateWaveform();
}
- playPauseBtn.innerHTML = SVG_PAUSE;
+ playPauseBtn.innerHTML = SVG_PAUSE(20);
player.updateMediaSessionPlaybackState();
player.updateMediaSessionPositionState();
updateTabTitle(player);
@@ -134,7 +130,7 @@ export function initializePlayerEvents(player, audioPlayer, scrobbler, ui) {
if (player.currentTrack) {
trackPauseTrack(player.currentTrack);
}
- playPauseBtn.innerHTML = SVG_PLAY;
+ playPauseBtn.innerHTML = SVG_PLAY(20);
player.updateMediaSessionPlaybackState();
player.updateMediaSessionPositionState();
});
@@ -200,7 +196,7 @@ export function initializePlayerEvents(player, audioPlayer, scrobbler, ui) {
}
console.error(`Media playback error (${element.id}):`, errorMsg, e);
- playPauseBtn.innerHTML = SVG_PLAY;
+ playPauseBtn.innerHTML = SVG_PLAY(20);
const canFallback =
player.quality === 'HI_RES_LOSSLESS' &&
@@ -712,7 +708,7 @@ export async function showAddToPlaylistModal(track) {
${p.name}
${
alreadyContains
- ? `${SVG_BIN} `
+ ? `${SVG_BIN(20)} `
: ''
}
@@ -1207,7 +1203,7 @@ export async function handleTrackAction(
${p.name}
${
alreadyContains
- ? `${SVG_BIN} `
+ ? `${SVG_BIN(20)} `
: ''
}
diff --git a/js/global.d.ts b/js/global.d.ts
index 735d7e068..c8dbc549f 100644
--- a/js/global.d.ts
+++ b/js/global.d.ts
@@ -8,6 +8,21 @@ declare module '*?blob-url' {
export default urlPromise;
}
+declare module '*?svg&icon' {
+ const resize: (size: number, attrs?: Record) => string;
+ export default resize;
+}
+
+declare module '*?svg&icon&class=heart-icon' {
+ const resize: (size: number, attrs?: Record) => string;
+ export default resize;
+}
+
+declare module '*?svg&icon&class=heart-icon+filled' {
+ const resize: (size: number, attrs?: Record) => string;
+ export default resize;
+}
+
declare module 'https://cdn.jsdelivr.net/npm/client-zip@2.4.5/+esm' {
/** Creates a ZIP stream from an async iterable of file entries. */
export function downloadZip(files: AsyncIterable): Response;
diff --git a/js/icons.ts b/js/icons.ts
new file mode 100644
index 000000000..0533f29c0
--- /dev/null
+++ b/js/icons.ts
@@ -0,0 +1,42 @@
+export { default as SVG_ANIMATE_SPIN } from '../images/animate-spin.svg?svg&icon';
+export { default as SVG_APPLE } from '../images/apple.svg?svg&icon';
+export { default as SVG_BIN } from '!lucide/trash-2.svg?svg&icon';
+export { default as SVG_CLOCK } from '!lucide/clock.svg?svg&icon';
+export { default as SVG_CLOSE } from '!lucide/x.svg?svg&icon';
+export { default as SVG_DOWNLOAD } from '!lucide/download.svg?svg&icon';
+export { default as SVG_EQUAL } from '!lucide/equal.svg?svg&icon';
+export { default as SVG_FACEBOOK } from '../images/facebook.svg?svg&icon';
+export { default as SVG_GENIUS_ACTIVE } from '../images/genius-active.svg?svg&icon';
+export { default as SVG_GENIUS_INACTIVE } from '../images/genius-inactive.svg?svg&icon';
+export { default as SVG_GLOBE } from '!lucide/globe.svg?svg&icon';
+export { default as SVG_HEART } from '!lucide/heart.svg?svg&icon&class=heart-icon';
+export { default as SVG_HEART_FILLED } from '!lucide/heart.svg?svg&icon&class=heart-icon+filled';
+export { default as SVG_INSTAGRAM } from '../images/instagram.svg?svg&icon';
+export { default as SVG_LEFT_ARROW } from '!lucide/chevron-left.svg?svg&icon';
+export { default as SVG_LINK } from '!lucide/link.svg?svg&icon';
+export { default as SVG_MENU } from '!lucide/ellipsis-vertical.svg?svg&icon';
+export { default as SVG_MINUS } from '!lucide/minus.svg?svg&icon';
+export { default as SVG_MIX } from '../images/mix.svg?svg&icon';
+export { default as SVG_MOVE_DOWN } from '!lucide/move-down.svg?svg&icon';
+export { default as SVG_MOVE_UP } from '!lucide/move-up.svg?svg&icon';
+export { default as SVG_MUTE } from '!lucide/volume-x.svg?svg&icon';
+export { default as SVG_OFFLINE } from '!lucide/triangle-alert.svg?svg&icon';
+export { default as SVG_PAUSE } from '../images/pause.svg?svg&icon';
+export { default as SVG_PAUSE_LARGE } from '../images/pause-large.svg?svg&icon';
+export { default as SVG_PLAY } from '../images/play.svg?svg&icon';
+export { default as SVG_PLAY_LARGE } from '../images/play-large.svg?svg&icon';
+export { default as SVG_PLUS } from '!lucide/plus.svg?svg&icon';
+export { default as SVG_REPEAT } from '!lucide/repeat.svg?svg&icon';
+export { default as SVG_REPEAT_ONE } from '!lucide/repeat-1.svg?svg&icon';
+export { default as SVG_RESET } from '!lucide/rotate-ccw.svg?svg&icon';
+export { default as SVG_RIGHT_ARROW } from '!lucide/chevron-right.svg?svg&icon';
+export { default as SVG_SHARE } from '!lucide/share.svg?svg&icon';
+export { default as SVG_SHUFFLE } from '!lucide/shuffle.svg?svg&icon';
+export { default as SVG_SORT } from '../images/sort.svg?svg&icon';
+export { default as SVG_SOUNDCLOUD } from '../images/soundcloud.svg?svg&icon';
+export { default as SVG_SQUARE_PEN } from '!lucide/square-pen.svg?svg&icon';
+export { default as SVG_TRASH } from '!lucide/trash.svg?svg&icon';
+export { default as SVG_TWITTER } from '../images/twitter.svg?svg&icon';
+export { default as SVG_VIDEO } from '!lucide/video.svg?svg&icon';
+export { default as SVG_VOLUME } from '!lucide/volume-2.svg?svg&icon';
+export { default as SVG_YOUTUBE } from '../images/youtube.svg?svg&icon';
diff --git a/js/lyrics.js b/js/lyrics.js
index f5342b1b8..dfbc81ad9 100644
--- a/js/lyrics.js
+++ b/js/lyrics.js
@@ -1,10 +1,17 @@
//js/lyrics.js
-import { getTrackTitle, getTrackArtists, buildTrackFilename, SVG_CLOSE } from './utils.js';
+import { getTrackTitle, getTrackArtists, buildTrackFilename } from './utils.js';
+import {
+ SVG_CLOSE,
+ SVG_GENIUS_ACTIVE,
+ SVG_GENIUS_INACTIVE,
+ SVG_MINUS,
+ SVG_PLUS,
+ SVG_RESET,
+ SVG_GLOBE,
+} from './icons.js';
import { sidePanelManager } from './side-panel.js';
import '@uimaxbai/am-lyrics/am-lyrics.js';
-const SVG_GENIUS_ACTIVE = ` `;
-
// Check if text contains Japanese, Chinese, or Korean characters
function containsAsianText(text) {
if (!text) return false;
@@ -37,8 +44,6 @@ function cleanTrackerSearch(text) {
return cleaned.trim();
}
-const SVG_GENIUS_INACTIVE = ` `;
-
class GeniusManager {
constructor() {
this.cache = new Map();
@@ -774,34 +779,24 @@ export function openLyricsPanel(track, audioPlayer, lyricsManager, forceOpen = f
container.innerHTML = `
-
-
-
+ ${SVG_MINUS(18)}
${offsetDisplay}
-
-
-
+ ${SVG_PLUS(18)}
-
-
-
-
+ ${SVG_RESET(16)}
-
-
-
-
+ ${SVG_GLOBE(20)}
- ${isGeniusMode ? SVG_GENIUS_ACTIVE : SVG_GENIUS_INACTIVE}
+ ${isGeniusMode ? SVG_GENIUS_ACTIVE(20) : SVG_GENIUS_INACTIVE(20)}
- ${SVG_CLOSE}
+ ${SVG_CLOSE(20)}
`;
@@ -864,7 +859,7 @@ export function openLyricsPanel(track, audioPlayer, lyricsManager, forceOpen = f
geniusBtn.classList.toggle('active-genius', enabled);
geniusBtn.style.color = enabled ? '#ffff64' : '';
- geniusBtn.innerHTML = enabled ? SVG_GENIUS_ACTIVE : SVG_GENIUS_INACTIVE;
+ geniusBtn.innerHTML = enabled ? SVG_GENIUS_ACTIVE(20) : SVG_GENIUS_INACTIVE(20);
if (enabled) {
try {
diff --git a/js/player.js b/js/player.js
index ae9739c11..adad1e02a 100644
--- a/js/player.js
+++ b/js/player.js
@@ -21,8 +21,7 @@ import {
import { audioContextManager } from './audio-context.js';
import { db } from './db.js';
import Hls from 'hls.js';
-import { isIos } from './platform-detection.js';
-
+import { SVG_CLOCK } from './icons.js';
export class Player {
constructor(audioElement, api, quality = 'HI_RES_LOSSLESS') {
this.audio = audioElement;
@@ -1603,23 +1602,13 @@ export class Player {
btn.classList.add('active');
btn.style.color = 'var(--primary)';
} else {
- btn.innerHTML = `
-
-
-
-
- `;
+ btn.innerHTML = SVG_CLOCK(20);
btn.title = 'Sleep Timer';
btn.classList.remove('active');
btn.style.color = '';
}
} else {
- btn.innerHTML = `
-
-
-
-
- `;
+ btn.innerHTML = SVG_CLOCK(20);
btn.title = 'Sleep Timer';
btn.classList.remove('active');
btn.style.color = '';
diff --git a/js/storage.js b/js/storage.js
index 2b711f8cb..11082844a 100644
--- a/js/storage.js
+++ b/js/storage.js
@@ -1,4 +1,7 @@
//storage.js
+
+import { SVG_RIGHT_ARROW } from './icons';
+
export const apiSettings = {
STORAGE_KEY: 'monochrome-api-instances-v9',
INSTANCES_URLS: [
@@ -1579,8 +1582,7 @@ export const sidebarSettings = {
document.body.classList.add('sidebar-collapsed');
const toggleBtn = document.getElementById('sidebar-toggle');
if (toggleBtn) {
- toggleBtn.innerHTML =
- ' ';
+ toggleBtn.innerHTML = SVG_RIGHT_ARROW(20);
}
}
},
diff --git a/js/themeStore.js b/js/themeStore.js
index 009f6737c..2f2cd195c 100644
--- a/js/themeStore.js
+++ b/js/themeStore.js
@@ -1,6 +1,7 @@
import { syncManager } from './accounts/pocketbase.js';
import { authManager } from './accounts/auth.js';
import { navigate } from './router.js';
+import { SVG_BIN, SVG_SQUARE_PEN } from './icons.js';
const THEMES_PER_PAGE = 50;
@@ -196,10 +197,10 @@ export class ThemeStore {
actionBtnsHtml = `
-
+ ${SVG_SQUARE_PEN(14)}
-
+ ${SVG_BIN(20)}
`;
diff --git a/js/tracker.js b/js/tracker.js
index e1e733f2c..aba6df872 100644
--- a/js/tracker.js
+++ b/js/tracker.js
@@ -1,6 +1,7 @@
//js/tracker.js
-import { escapeHtml, SVG_MENU, SVG_PLAY, trackDataStore, formatTime, SVG_HEART } from './utils.js';
+import { escapeHtml, trackDataStore, formatTime } from './utils.js';
import { navigate } from './router.js';
+import { SVG_MENU, SVG_PLAY, SVG_HEART } from './icons.js';
let artistsData = [];
let artistsPopularity = new Map(); // name -> popularity score
@@ -218,7 +219,7 @@ function createTrackerTrackItemHTML(track, index) {
? ''
: `
`;
@@ -282,10 +283,10 @@ function renderTrackerTracks(container, tracks) {
export function createProjectCardHTML(era, artist, sheetId, trackCount) {
const playBtnHTML = `
- ${SVG_PLAY}
+ ${SVG_PLAY(20)}
`;
@@ -298,7 +299,7 @@ export function createProjectCardHTML(era, artist, sheetId, trackCount) {
loading="lazy"
onerror="this.src='assets/logo.svg'">
- ${SVG_HEART}
+ ${SVG_HEART(20)}
${playBtnHTML}
@@ -596,7 +597,7 @@ export async function renderTrackerProjectPage(sheetId, projectName, container,
// Setup buttons
if (playBtn) {
- playBtn.innerHTML = `${SVG_PLAY}Play Project `;
+ playBtn.innerHTML = `${SVG_PLAY(20)}Play Project `;
playBtn.onclick = () => {
const availableTracks = eraTracks.filter((t) => !t.unavailable);
if (availableTracks.length > 0) {
@@ -901,7 +902,7 @@ export async function renderTrackerTrackPage(trackId, container, _ui) {
prodEl.innerHTML = `By ${artist.name} • From ${era.name} `;
if (playBtn) {
- playBtn.innerHTML = `${SVG_PLAY}Play Track `;
+ playBtn.innerHTML = `${SVG_PLAY(20)}Play Track `;
playBtn.onclick = () => {
const availableTracks = allTracks.filter((t) => !t.unavailable);
const trackPos = availableTracks.findIndex((t) => t.id === currentTrack.id);
diff --git a/js/ui-interactions.js b/js/ui-interactions.js
index 59f2e069b..13a15c000 100644
--- a/js/ui-interactions.js
+++ b/js/ui-interactions.js
@@ -1,9 +1,5 @@
//js/ui-interactions.js
import {
- SVG_CLOSE,
- SVG_BIN,
- SVG_HEART,
- SVG_DOWNLOAD,
formatTime,
getTrackTitle,
getTrackArtists,
@@ -17,6 +13,16 @@ import { db } from './db.js';
import { syncManager } from './accounts/pocketbase.js';
import { showNotification, downloadTracks } from './downloads.js';
import { trackSearchTabChange, trackOpenQueue } from './analytics.js';
+import {
+ SVG_CLOSE,
+ SVG_BIN,
+ SVG_HEART,
+ SVG_DOWNLOAD,
+ SVG_HEART_FILLED,
+ SVG_SQUARE_PEN,
+ SVG_TRASH,
+ SVG_EQUAL,
+} from './icons.js';
export function initializeUIInteractions(player, api, ui) {
const sidebar = document.querySelector('.sidebar');
@@ -111,22 +117,19 @@ export function initializeUIInteractions(player, api, ui) {
container.innerHTML = `
- ${SVG_DOWNLOAD}
+ ${SVG_DOWNLOAD(20)}
- ${SVG_HEART}
+ ${SVG_HEART(20)}
-
-
-
-
+ ${SVG_SQUARE_PEN(20)}
-
+ ${SVG_TRASH(20)}
- ${SVG_CLOSE}
+ ${SVG_CLOSE(20)}
`;
@@ -258,10 +261,7 @@ export function initializeUIInteractions(player, api, ui) {
return `
-
-
-
-
+ ${SVG_EQUAL(16)}
${isBlocked ? '--:--' : formatTime(track.duration)}
- ${SVG_HEART}
+ ${SVG_HEART(20)}
- ${SVG_BIN}
+ ${SVG_BIN(20)}
`;
@@ -307,9 +307,7 @@ export function initializeUIInteractions(player, api, ui) {
syncManager.syncLibraryItem('track', track, added);
likeBtn.classList.toggle('active', added);
- likeBtn.innerHTML = added
- ? SVG_HEART.replace('class="heart-icon"', 'class="heart-icon filled"')
- : SVG_HEART;
+ likeBtn.innerHTML = added ? SVG_HEART_FILLED(20) : SVG_HEART(20);
showNotification(added ? `Added to Liked: ${track.title}` : `Removed from Liked: ${track.title}`);
}
@@ -462,9 +460,7 @@ export function initializeUIInteractions(player, api, ui) {
if (likeBtn && track) {
const isLiked = await db.isFavorite('track', track.id);
likeBtn.classList.toggle('active', isLiked);
- likeBtn.innerHTML = isLiked
- ? SVG_HEART.replace('class="heart-icon"', 'class="heart-icon filled"')
- : SVG_HEART;
+ likeBtn.innerHTML = isLiked ? SVG_HEART_FILLED(20) : SVG_HEART(20);
}
});
diff --git a/js/ui.js b/js/ui.js
index de410d508..80236e443 100644
--- a/js/ui.js
+++ b/js/ui.js
@@ -1,12 +1,6 @@
//js/ui.js
import { showNotification } from './downloads.js';
import {
- SVG_PLAY,
- SVG_DOWNLOAD,
- SVG_MENU,
- SVG_HEART,
- SVG_VOLUME,
- SVG_MUTE,
formatTime,
createPlaceholder,
trackDataStore,
@@ -55,22 +49,41 @@ import Hls from 'hls.js';
fontSettings.applyFont();
fontSettings.applyFontSize();
-const SVG_GLOBE =
- '
';
-const SVG_INSTAGRAM =
- '
';
-const SVG_FACEBOOK =
- '
';
-const SVG_YOUTUBE =
- '
youtube [#9E9E9E168] Created with Sketch. ';
-const SVG_TWITTER =
- '
';
-const SVG_LINK =
- '
';
-const SVG_SOUNDCLOUD =
- '
';
-const SVG_APPLE =
- '
apple [#9E9E9E173] Created with Sketch. ';
+import {
+ SVG_PLAY,
+ SVG_DOWNLOAD,
+ SVG_MENU,
+ SVG_HEART,
+ SVG_VOLUME,
+ SVG_MUTE,
+ SVG_HEART_FILLED,
+ SVG_CLOSE,
+ SVG_SORT,
+ SVG_BIN,
+ SVG_TRASH,
+ SVG_GLOBE,
+ SVG_INSTAGRAM,
+ SVG_FACEBOOK,
+ SVG_YOUTUBE,
+ SVG_TWITTER,
+ SVG_LINK,
+ SVG_SOUNDCLOUD,
+ SVG_APPLE,
+ SVG_REPEAT,
+ SVG_REPEAT_ONE,
+ SVG_PLAY_LARGE,
+ SVG_PAUSE_LARGE,
+ SVG_MINUS,
+ SVG_SQUARE_PEN,
+ SVG_SHARE,
+ SVG_SHUFFLE,
+ SVG_VIDEO,
+ SVG_LEFT_ARROW,
+ SVG_RIGHT_ARROW,
+ SVG_CLOCK,
+ SVG_MOVE_UP,
+ SVG_MOVE_DOWN,
+} from './icons.js';
function sortTracks(tracks, sortType) {
if (sortType === 'custom') return [...tracks];
@@ -134,9 +147,9 @@ export class UIRenderer {
// Helper for Heart Icon
createHeartIcon(filled = false) {
if (filled) {
- return SVG_HEART.replace('class="heart-icon"', 'class="heart-icon filled"');
+ return SVG_HEART_FILLED(20);
}
- return SVG_HEART;
+ return SVG_HEART(20);
}
async extractAndApplyColor(url) {
@@ -352,14 +365,14 @@ export class UIRenderer {
if (videoCoverUrl) {
trackImageHTML = `
`;
} else {
- trackImageHTML = `
`;
+ trackImageHTML = `
${SVG_VIDEO(20, { style: 'opacity: 0.7;' })}
`;
}
} else if (isVideo && (this.currentPage === 'search' || this.currentPage === 'library')) {
const videoCoverUrl = this.api.getVideoCoverUrl(track.imageId);
if (videoCoverUrl) {
trackImageHTML = `
`;
} else {
- trackImageHTML = `
`;
+ trackImageHTML = `
${SVG_PLAY(16, { style: 'opacity: 0.7;' })}
`;
}
} else {
trackImageHTML = this.getCoverHTML(
@@ -382,7 +395,7 @@ export class UIRenderer {
}
const videoIcon = isVideo
- ? '
'
+ ? `
${SVG_VIDEO(14)} `
: '';
const trackNumberHTML = `
${showCover ? trackImageHTML : displayIndex}
`;
const explicitBadge = hasExplicitContent(track) ? this.createExplicitBadge() : '';
@@ -400,7 +413,7 @@ export class UIRenderer {
? ''
: `
`;
@@ -469,10 +482,10 @@ export class UIRenderer {
type !== 'artist'
? `
- ${SVG_PLAY}
+ ${SVG_PLAY(20)}
`
: '';
@@ -607,19 +620,10 @@ export class UIRenderer {
imageHTML: imageHTML,
actionButtonsHTML: `
-
-
-
-
+ ${SVG_SQUARE_PEN(20)}
-
-
-
-
-
-
-
+ ${SVG_BIN(20)}
`,
isCompact,
@@ -689,7 +693,7 @@ export class UIRenderer {
} else if (cover) {
imageHTML = this.getCoverHTML(cover, escapeHtml(video.title));
} else {
- imageHTML = `
`;
+ imageHTML = `
${SVG_PLAY(48, { style: 'opacity: 0.7;' })}
`;
}
return `
@@ -698,7 +702,7 @@ export class UIRenderer {
${imageHTML}
@@ -1327,11 +1331,9 @@ export class UIRenderer {
lastPausedState = isPaused;
if (isPaused) {
- playBtn.innerHTML =
- ' ';
+ playBtn.innerHTML = SVG_PLAY_LARGE(32);
} else {
- playBtn.innerHTML =
- ' ';
+ playBtn.innerHTML = SVG_PAUSE_LARGE(32);
}
};
@@ -1354,11 +1356,9 @@ export class UIRenderer {
const mode = this.player.toggleRepeat();
repeatBtn.classList.toggle('active', mode !== 0);
if (mode === 2) {
- repeatBtn.innerHTML =
- ' ';
+ repeatBtn.innerHTML = SVG_REPEAT_ONE(24);
} else {
- repeatBtn.innerHTML =
- ' ';
+ repeatBtn.innerHTML = SVG_REPEAT(24);
}
};
@@ -1474,8 +1474,7 @@ export class UIRenderer {
const mode = this.player.repeatMode;
repeatBtn.classList.toggle('active', mode !== 0);
if (mode === 2) {
- repeatBtn.innerHTML =
- ' ';
+ repeatBtn.innerHTML = SVG_REPEAT_ONE(24);
}
// Fullscreen volume controls
@@ -1488,7 +1487,7 @@ export class UIRenderer {
const activeEl = this.player.activeElement;
const { muted } = activeEl;
const volume = this.player.userVolume;
- fsVolumeBtn.innerHTML = muted || volume === 0 ? SVG_MUTE : SVG_VOLUME;
+ fsVolumeBtn.innerHTML = muted || volume === 0 ? SVG_MUTE(20) : SVG_VOLUME(20);
fsVolumeBtn.classList.toggle('muted', muted || volume === 0);
const effectiveVolume = muted ? 0 : volume * 100;
fsVolumeFill.style.setProperty('--fs-volume-level', `${effectiveVolume}%`);
@@ -2115,7 +2114,7 @@ export class UIRenderer {
container.innerHTML = `
-
+ ${SVG_LEFT_ARROW(20)}
Back
${escapeHtml(genreName)}
@@ -2902,13 +2901,10 @@ export class UIRenderer {
.map(
(query) => `
-
-
-
-
+ ${SVG_CLOCK(16)}
${escapeHtml(query)}
-
+ ${SVG_CLOSE(14)}
`
@@ -2974,9 +2970,9 @@ export class UIRenderer {
const prodEl = document.getElementById('album-detail-producer');
const tracklistContainer = document.getElementById('album-detail-tracklist');
const playBtn = document.getElementById('play-album-btn');
- if (playBtn) playBtn.innerHTML = `${SVG_PLAY}
Play Album `;
+ if (playBtn) playBtn.innerHTML = `${SVG_PLAY(20)}
Play Album `;
const dlBtn = document.getElementById('download-album-btn');
- if (dlBtn) dlBtn.innerHTML = `${SVG_DOWNLOAD}
Download Album `;
+ if (dlBtn) dlBtn.innerHTML = `${SVG_DOWNLOAD(20)}
Download Album `;
const mixBtn = document.getElementById('album-mix-btn');
if (mixBtn) mixBtn.style.display = 'none';
@@ -3291,8 +3287,7 @@ export class UIRenderer {
const addToPlaylistBtn = document.createElement('button');
addToPlaylistBtn.className = 'track-action-btn add-to-playlist-btn';
addToPlaylistBtn.title = 'Add to this playlist';
- addToPlaylistBtn.innerHTML =
- '
';
+ addToPlaylistBtn.innerHTML = SVG_MINUS(20);
addToPlaylistBtn.onclick = async (e) => {
e.stopPropagation();
const trackData = trackDataStore.get(item);
@@ -3376,9 +3371,9 @@ export class UIRenderer {
const descEl = document.getElementById('playlist-detail-description');
const tracklistContainer = document.getElementById('playlist-detail-tracklist');
const playBtn = document.getElementById('play-playlist-btn');
- if (playBtn) playBtn.innerHTML = `${SVG_PLAY}
Play `;
+ if (playBtn) playBtn.innerHTML = `${SVG_PLAY(20)}
Play `;
const dlBtn = document.getElementById('download-playlist-btn');
- if (dlBtn) dlBtn.innerHTML = `${SVG_DOWNLOAD}
Download `;
+ if (dlBtn) dlBtn.innerHTML = `${SVG_DOWNLOAD(20)}
Download `;
const addPlaylistBtn = document.getElementById('add-playlist-to-playlist-btn');
imageEl.src = '';
@@ -3502,8 +3497,7 @@ export class UIRenderer {
const removeBtn = document.createElement('button');
removeBtn.className = 'track-action-btn remove-from-playlist-btn';
removeBtn.title = 'Remove from playlist';
- removeBtn.innerHTML =
- '
';
+ removeBtn.innerHTML = SVG_BIN(20);
removeBtn.dataset.trackId = currentTracks[index].id;
removeBtn.dataset.type = currentTracks[index].type || 'track';
@@ -3757,9 +3751,9 @@ export class UIRenderer {
const descEl = document.getElementById('mix-detail-description');
const tracklistContainer = document.getElementById('mix-detail-tracklist');
const playBtn = document.getElementById('play-mix-btn');
- if (playBtn) playBtn.innerHTML = `${SVG_PLAY}
Play `;
+ if (playBtn) playBtn.innerHTML = `${SVG_PLAY(20)}
Play `;
const dlBtn = document.getElementById('download-mix-btn');
- if (dlBtn) dlBtn.innerHTML = `${SVG_DOWNLOAD}
Download `;
+ if (dlBtn) dlBtn.innerHTML = `${SVG_DOWNLOAD(20)}
Download `;
// Skeleton loading
imageEl.src = '';
@@ -3936,7 +3930,7 @@ export class UIRenderer {
const inLibraryContainer = document.getElementById('artist-detail-in-library');
const inLibrarySection = document.getElementById('artist-section-in-library');
const dlBtn = document.getElementById('download-discography-btn');
- if (dlBtn) dlBtn.innerHTML = `${SVG_DOWNLOAD}
Download Discography `;
+ if (dlBtn) dlBtn.innerHTML = `${SVG_DOWNLOAD(20)}
Download Discography `;
imageEl.src = '';
imageEl.style.backgroundColor = 'var(--muted)';
@@ -4596,29 +4590,29 @@ export class UIRenderer {
if (url.includes('tidal.com')) return '';
if (url.includes('qobuz.com')) return '';
- let icon = SVG_GLOBE;
+ let icon = SVG_GLOBE(24);
let title = 'Website';
if (url.includes('twitter.com') || url.includes('x.com')) {
- icon = SVG_TWITTER;
+ icon = SVG_TWITTER(24);
title = 'Twitter';
} else if (url.includes('instagram.com')) {
- icon = SVG_INSTAGRAM;
+ icon = SVG_INSTAGRAM(24);
title = 'Instagram';
} else if (url.includes('facebook.com')) {
- icon = SVG_FACEBOOK;
+ icon = SVG_FACEBOOK(24);
title = 'Facebook';
} else if (url.includes('youtube.com')) {
- icon = SVG_YOUTUBE;
+ icon = SVG_YOUTUBE(24);
title = 'YouTube';
} else if (url.includes('spotify.com') || url.includes('open.spotify.com')) {
- icon = SVG_LINK;
+ icon = SVG_LINK(24);
title = 'Spotify';
} else if (url.includes('soundcloud.com')) {
- icon = SVG_SOUNDCLOUD;
+ icon = SVG_SOUNDCLOUD(24);
title = 'SoundCloud';
} else if (url.includes('apple.com')) {
- icon = SVG_APPLE;
+ icon = SVG_APPLE(24);
title = 'Apple Music';
}
@@ -4760,8 +4754,7 @@ export class UIRenderer {
const shuffleBtn = document.createElement('button');
shuffleBtn.id = 'shuffle-playlist-btn';
shuffleBtn.className = 'btn-primary';
- shuffleBtn.innerHTML =
- '
Shuffle ';
+ shuffleBtn.innerHTML = `${SVG_SHUFFLE(20)}
Shuffle `;
shuffleBtn.onclick = () => {
const shuffledTracks = [...tracks].sort(() => Math.random() - 0.5);
this.player.setQueue(shuffledTracks, 0);
@@ -4774,8 +4767,7 @@ export class UIRenderer {
sortBtn = document.createElement('button');
sortBtn.id = 'sort-playlist-btn';
sortBtn.className = 'btn-secondary';
- sortBtn.innerHTML =
- '
Sort ';
+ sortBtn.innerHTML = `${SVG_SORT(20)}
Sort `;
sortBtn.onclick = (e) => {
e.stopPropagation();
@@ -4823,15 +4815,13 @@ export class UIRenderer {
const editBtn = document.createElement('button');
editBtn.id = 'edit-playlist-btn';
editBtn.className = 'btn-secondary';
- editBtn.innerHTML =
- '
Edit ';
+ editBtn.innerHTML = `${SVG_SQUARE_PEN(24)}
Edit `;
fragment.appendChild(editBtn);
const deleteBtn = document.createElement('button');
deleteBtn.id = 'delete-playlist-btn';
deleteBtn.className = 'btn-secondary danger';
- deleteBtn.innerHTML =
- '
Delete ';
+ deleteBtn.innerHTML = `${SVG_BIN(24)}
Delete `;
fragment.appendChild(deleteBtn);
}
@@ -4840,8 +4830,7 @@ export class UIRenderer {
const shareBtn = document.createElement('button');
shareBtn.id = 'share-playlist-btn';
shareBtn.className = 'btn-secondary';
- shareBtn.innerHTML =
- '
Share ';
+ shareBtn.innerHTML = `${SVG_SHARE(20)}
Share `;
shareBtn.onclick = () => {
const url = getShareUrl(`/userplaylist/${playlist.id || playlist.uuid}`);
@@ -5062,21 +5051,15 @@ export class UIRenderer {
isUser
? `
-
-
-
+ ${SVG_TRASH(16)}
`
: ''
}
-
-
-
+ ${SVG_MOVE_UP(16)}
-
-
-
+ ${SVG_MOVE_DOWN(16)}
@@ -5112,8 +5095,7 @@ export class UIRenderer {
document.body.classList.add('sidebar-collapsed');
const toggleBtn = document.getElementById('sidebar-toggle');
if (toggleBtn) {
- toggleBtn.innerHTML =
- ' ';
+ toggleBtn.innerHTML = SVG_RIGHT_ARROW(20);
}
const imageEl = document.getElementById('track-detail-image');
diff --git a/js/utils.js b/js/utils.js
index 022e1b587..c244fc8ee 100644
--- a/js/utils.js
+++ b/js/utils.js
@@ -38,27 +38,6 @@ export const QUALITY_TOKENS = {
export const RATE_LIMIT_ERROR_MESSAGE = 'Too Many Requests. Please wait a moment and try again.';
-export const SVG_PLAY =
- ' ';
-export const SVG_PAUSE =
- ' ';
-export const SVG_VOLUME =
- ' ';
-export const SVG_MUTE =
- ' ';
-export const SVG_DOWNLOAD =
- ' ';
-export const SVG_MENU =
- ' ';
-export const SVG_HEART =
- ' ';
-export const SVG_CLOSE =
- ' ';
-export const SVG_BIN =
- ' ';
-export const SVG_MIX =
- ' ';
-
export const formatTime = (seconds) => {
if (isNaN(seconds)) return '0:00';
const m = Math.floor(seconds / 60);
diff --git a/package-lock.json b/package-lock.json
index ffdbcf26c..b67dea15e 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -25,9 +25,12 @@
"fuse.js": "^7.1.0",
"hls.js": "^1.6.15",
"jose": "^6.2.0",
+ "lucide-static": "^0.577.0",
"mime": "^4.1.0",
"npm": "^11.11.1",
"pocketbase": "^0.26.8",
+ "simple-icons": "^16.12.0",
+ "svgo": "^4.0.1",
"uuid": "^13.0.0"
},
"devDependencies": {
@@ -242,7 +245,7 @@
}
},
"node_modules/@babel/helper-define-polyfill-provider": {
- "version": "0.6.7",
+ "version": "0.6.8",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -405,19 +408,19 @@
}
},
"node_modules/@babel/helpers": {
- "version": "7.28.6",
+ "version": "7.29.2",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/template": "^7.28.6",
- "@babel/types": "^7.28.6"
+ "@babel/types": "^7.29.0"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/parser": {
- "version": "7.29.0",
+ "version": "7.29.2",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -1313,7 +1316,7 @@
}
},
"node_modules/@babel/preset-env": {
- "version": "7.29.0",
+ "version": "7.29.2",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -1417,7 +1420,7 @@
}
},
"node_modules/@babel/runtime": {
- "version": "7.28.6",
+ "version": "7.29.2",
"license": "MIT",
"engines": {
"node": ">=6.9.0"
@@ -1486,9 +1489,9 @@
}
},
"node_modules/@cloudflare/workerd-darwin-64": {
- "version": "1.20260301.1",
- "resolved": "https://registry.npmjs.org/@cloudflare/workerd-darwin-64/-/workerd-darwin-64-1.20260301.1.tgz",
- "integrity": "sha512-+kJvwociLrvy1JV9BAvoSVsMEIYD982CpFmo/yMEvBwxDIjltYsLTE8DLi0mCkGsQ8Ygidv2fD9wavzXeiY7OQ==",
+ "version": "1.20260317.1",
+ "resolved": "https://registry.npmjs.org/@cloudflare/workerd-darwin-64/-/workerd-darwin-64-1.20260317.1.tgz",
+ "integrity": "sha512-8hjh3sPMwY8M/zedq3/sXoA2Q4BedlGufn3KOOleIG+5a4ReQKLlUah140D7J6zlKmYZAFMJ4tWC7hCuI/s79g==",
"cpu": [
"x64"
],
@@ -1503,9 +1506,9 @@
}
},
"node_modules/@cloudflare/workerd-darwin-arm64": {
- "version": "1.20260301.1",
- "resolved": "https://registry.npmjs.org/@cloudflare/workerd-darwin-arm64/-/workerd-darwin-arm64-1.20260301.1.tgz",
- "integrity": "sha512-PPIetY3e67YBr9O4UhILK8nbm5TqUDl14qx4rwFNrRSBOvlzuczzbd4BqgpAtbGVFxKp1PWpjAnBvGU/OI/tLQ==",
+ "version": "1.20260317.1",
+ "resolved": "https://registry.npmjs.org/@cloudflare/workerd-darwin-arm64/-/workerd-darwin-arm64-1.20260317.1.tgz",
+ "integrity": "sha512-M/MnNyvO5HMgoIdr3QHjdCj2T1ki9gt0vIUnxYxBu9ISXS/jgtMl6chUVPJ7zHYBn9MyYr8ByeN6frjYxj0MGg==",
"cpu": [
"arm64"
],
@@ -1520,7 +1523,9 @@
}
},
"node_modules/@cloudflare/workerd-linux-64": {
- "version": "1.20260301.1",
+ "version": "1.20260317.1",
+ "resolved": "https://registry.npmjs.org/@cloudflare/workerd-linux-64/-/workerd-linux-64-1.20260317.1.tgz",
+ "integrity": "sha512-1ltuEjkRcS3fsVF7CxsKlWiRmzq2ZqMfqDN0qUOgbUwkpXsLVJsXmoblaLf5OP00ELlcgF0QsN0p2xPEua4Uug==",
"cpu": [
"x64"
],
@@ -1535,9 +1540,9 @@
}
},
"node_modules/@cloudflare/workerd-linux-arm64": {
- "version": "1.20260301.1",
- "resolved": "https://registry.npmjs.org/@cloudflare/workerd-linux-arm64/-/workerd-linux-arm64-1.20260301.1.tgz",
- "integrity": "sha512-igL1pkyCXW6GiGpjdOAvqMi87UW0LMc/+yIQe/CSzuZJm5GzXoAMrwVTkCFnikk6JVGELrM5x0tGYlxa0sk5Iw==",
+ "version": "1.20260317.1",
+ "resolved": "https://registry.npmjs.org/@cloudflare/workerd-linux-arm64/-/workerd-linux-arm64-1.20260317.1.tgz",
+ "integrity": "sha512-3QrNnPF1xlaNwkHpasvRvAMidOvQs2NhXQmALJrEfpIJ/IDL2la8g499yXp3eqhG3hVMCB07XVY149GTs42Xtw==",
"cpu": [
"arm64"
],
@@ -1552,9 +1557,7 @@
}
},
"node_modules/@cloudflare/workerd-windows-64": {
- "version": "1.20260301.1",
- "resolved": "https://registry.npmjs.org/@cloudflare/workerd-windows-64/-/workerd-windows-64-1.20260301.1.tgz",
- "integrity": "sha512-Q0wMJ4kcujXILwQKQFc1jaYamVsNvjuECzvRrTI8OxGFMx2yq9aOsswViE4X1gaS2YQQ5u0JGwuGi5WdT1Lt7A==",
+ "version": "1.20260317.1",
"cpu": [
"x64"
],
@@ -1601,7 +1604,7 @@
}
},
"node_modules/@csstools/css-syntax-patches-for-csstree": {
- "version": "1.1.0",
+ "version": "1.1.1",
"dev": true,
"funding": [
{
@@ -1613,7 +1616,15 @@
"url": "https://opencollective.com/csstools"
}
],
- "license": "MIT-0"
+ "license": "MIT-0",
+ "peerDependencies": {
+ "css-tree": "^3.2.1"
+ },
+ "peerDependenciesMeta": {
+ "css-tree": {
+ "optional": true
+ }
+ }
},
"node_modules/@csstools/css-tokenizer": {
"version": "3.0.4",
@@ -1735,9 +1746,9 @@
}
},
"node_modules/@emnapi/runtime": {
- "version": "1.9.0",
- "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.9.0.tgz",
- "integrity": "sha512-QN75eB0IH2ywSpRpNddCRfQIhmJYBCJ1x5Lb3IscKAL8bMnVAKnRg8dCoXbHzVLLH7P38N2Z3mtulB7W0J0FKw==",
+ "version": "1.9.1",
+ "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.9.1.tgz",
+ "integrity": "sha512-VYi5+ZVLhpgK4hQ0TAjiQiZ6ol0oe4mBx7mVv7IflsiEp0OWoVsp/+f9Vc1hOhE0TtkORVrI1GvzyreqpgWtkA==",
"dev": true,
"license": "MIT",
"optional": true,
@@ -1746,9 +1757,9 @@
}
},
"node_modules/@esbuild/aix-ppc64": {
- "version": "0.27.3",
- "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.3.tgz",
- "integrity": "sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg==",
+ "version": "0.27.4",
+ "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.4.tgz",
+ "integrity": "sha512-cQPwL2mp2nSmHHJlCyoXgHGhbEPMrEEU5xhkcy3Hs/O7nGZqEpZ2sUtLaL9MORLtDfRvVl2/3PAuEkYZH0Ty8Q==",
"cpu": [
"ppc64"
],
@@ -1763,9 +1774,9 @@
}
},
"node_modules/@esbuild/android-arm": {
- "version": "0.27.3",
- "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.3.tgz",
- "integrity": "sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA==",
+ "version": "0.27.4",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.4.tgz",
+ "integrity": "sha512-X9bUgvxiC8CHAGKYufLIHGXPJWnr0OCdR0anD2e21vdvgCI8lIfqFbnoeOz7lBjdrAGUhqLZLcQo6MLhTO2DKQ==",
"cpu": [
"arm"
],
@@ -1780,9 +1791,9 @@
}
},
"node_modules/@esbuild/android-arm64": {
- "version": "0.27.3",
- "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.3.tgz",
- "integrity": "sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg==",
+ "version": "0.27.4",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.4.tgz",
+ "integrity": "sha512-gdLscB7v75wRfu7QSm/zg6Rx29VLdy9eTr2t44sfTW7CxwAtQghZ4ZnqHk3/ogz7xao0QAgrkradbBzcqFPasw==",
"cpu": [
"arm64"
],
@@ -1797,9 +1808,9 @@
}
},
"node_modules/@esbuild/android-x64": {
- "version": "0.27.3",
- "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.3.tgz",
- "integrity": "sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ==",
+ "version": "0.27.4",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.4.tgz",
+ "integrity": "sha512-PzPFnBNVF292sfpfhiyiXCGSn9HZg5BcAz+ivBuSsl6Rk4ga1oEXAamhOXRFyMcjwr2DVtm40G65N3GLeH1Lvw==",
"cpu": [
"x64"
],
@@ -1814,9 +1825,9 @@
}
},
"node_modules/@esbuild/darwin-arm64": {
- "version": "0.27.3",
- "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.3.tgz",
- "integrity": "sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg==",
+ "version": "0.27.4",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.4.tgz",
+ "integrity": "sha512-b7xaGIwdJlht8ZFCvMkpDN6uiSmnxxK56N2GDTMYPr2/gzvfdQN8rTfBsvVKmIVY/X7EM+/hJKEIbbHs9oA4tQ==",
"cpu": [
"arm64"
],
@@ -1831,9 +1842,9 @@
}
},
"node_modules/@esbuild/darwin-x64": {
- "version": "0.27.3",
- "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.3.tgz",
- "integrity": "sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg==",
+ "version": "0.27.4",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.4.tgz",
+ "integrity": "sha512-sR+OiKLwd15nmCdqpXMnuJ9W2kpy0KigzqScqHI3Hqwr7IXxBp3Yva+yJwoqh7rE8V77tdoheRYataNKL4QrPw==",
"cpu": [
"x64"
],
@@ -1848,9 +1859,9 @@
}
},
"node_modules/@esbuild/freebsd-arm64": {
- "version": "0.27.3",
- "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.3.tgz",
- "integrity": "sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w==",
+ "version": "0.27.4",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.4.tgz",
+ "integrity": "sha512-jnfpKe+p79tCnm4GVav68A7tUFeKQwQyLgESwEAUzyxk/TJr4QdGog9sqWNcUbr/bZt/O/HXouspuQDd9JxFSw==",
"cpu": [
"arm64"
],
@@ -1865,9 +1876,9 @@
}
},
"node_modules/@esbuild/freebsd-x64": {
- "version": "0.27.3",
- "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.3.tgz",
- "integrity": "sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA==",
+ "version": "0.27.4",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.4.tgz",
+ "integrity": "sha512-2kb4ceA/CpfUrIcTUl1wrP/9ad9Atrp5J94Lq69w7UwOMolPIGrfLSvAKJp0RTvkPPyn6CIWrNy13kyLikZRZQ==",
"cpu": [
"x64"
],
@@ -1882,9 +1893,9 @@
}
},
"node_modules/@esbuild/linux-arm": {
- "version": "0.27.3",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.3.tgz",
- "integrity": "sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw==",
+ "version": "0.27.4",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.4.tgz",
+ "integrity": "sha512-aBYgcIxX/wd5n2ys0yESGeYMGF+pv6g0DhZr3G1ZG4jMfruU9Tl1i2Z+Wnj9/KjGz1lTLCcorqE2viePZqj4Eg==",
"cpu": [
"arm"
],
@@ -1899,9 +1910,9 @@
}
},
"node_modules/@esbuild/linux-arm64": {
- "version": "0.27.3",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.3.tgz",
- "integrity": "sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg==",
+ "version": "0.27.4",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.4.tgz",
+ "integrity": "sha512-7nQOttdzVGth1iz57kxg9uCz57dxQLHWxopL6mYuYthohPKEK0vU0C3O21CcBK6KDlkYVcnDXY099HcCDXd9dA==",
"cpu": [
"arm64"
],
@@ -1916,9 +1927,9 @@
}
},
"node_modules/@esbuild/linux-ia32": {
- "version": "0.27.3",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.3.tgz",
- "integrity": "sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg==",
+ "version": "0.27.4",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.4.tgz",
+ "integrity": "sha512-oPtixtAIzgvzYcKBQM/qZ3R+9TEUd1aNJQu0HhGyqtx6oS7qTpvjheIWBbes4+qu1bNlo2V4cbkISr8q6gRBFA==",
"cpu": [
"ia32"
],
@@ -1933,9 +1944,9 @@
}
},
"node_modules/@esbuild/linux-loong64": {
- "version": "0.27.3",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.3.tgz",
- "integrity": "sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA==",
+ "version": "0.27.4",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.4.tgz",
+ "integrity": "sha512-8mL/vh8qeCoRcFH2nM8wm5uJP+ZcVYGGayMavi8GmRJjuI3g1v6Z7Ni0JJKAJW+m0EtUuARb6Lmp4hMjzCBWzA==",
"cpu": [
"loong64"
],
@@ -1950,9 +1961,9 @@
}
},
"node_modules/@esbuild/linux-mips64el": {
- "version": "0.27.3",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.3.tgz",
- "integrity": "sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw==",
+ "version": "0.27.4",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.4.tgz",
+ "integrity": "sha512-1RdrWFFiiLIW7LQq9Q2NES+HiD4NyT8Itj9AUeCl0IVCA459WnPhREKgwrpaIfTOe+/2rdntisegiPWn/r/aAw==",
"cpu": [
"mips64el"
],
@@ -1967,9 +1978,9 @@
}
},
"node_modules/@esbuild/linux-ppc64": {
- "version": "0.27.3",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.3.tgz",
- "integrity": "sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA==",
+ "version": "0.27.4",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.4.tgz",
+ "integrity": "sha512-tLCwNG47l3sd9lpfyx9LAGEGItCUeRCWeAx6x2Jmbav65nAwoPXfewtAdtbtit/pJFLUWOhpv0FpS6GQAmPrHA==",
"cpu": [
"ppc64"
],
@@ -1984,9 +1995,9 @@
}
},
"node_modules/@esbuild/linux-riscv64": {
- "version": "0.27.3",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.3.tgz",
- "integrity": "sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ==",
+ "version": "0.27.4",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.4.tgz",
+ "integrity": "sha512-BnASypppbUWyqjd1KIpU4AUBiIhVr6YlHx/cnPgqEkNoVOhHg+YiSVxM1RLfiy4t9cAulbRGTNCKOcqHrEQLIw==",
"cpu": [
"riscv64"
],
@@ -2001,9 +2012,9 @@
}
},
"node_modules/@esbuild/linux-s390x": {
- "version": "0.27.3",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.3.tgz",
- "integrity": "sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw==",
+ "version": "0.27.4",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.4.tgz",
+ "integrity": "sha512-+eUqgb/Z7vxVLezG8bVB9SfBie89gMueS+I0xYh2tJdw3vqA/0ImZJ2ROeWwVJN59ihBeZ7Tu92dF/5dy5FttA==",
"cpu": [
"s390x"
],
@@ -2018,7 +2029,9 @@
}
},
"node_modules/@esbuild/linux-x64": {
- "version": "0.27.3",
+ "version": "0.27.4",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.4.tgz",
+ "integrity": "sha512-S5qOXrKV8BQEzJPVxAwnryi2+Iq5pB40gTEIT69BQONqR7JH1EPIcQ/Uiv9mCnn05jff9umq/5nqzxlqTOg9NA==",
"cpu": [
"x64"
],
@@ -2033,9 +2046,9 @@
}
},
"node_modules/@esbuild/netbsd-arm64": {
- "version": "0.27.3",
- "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.3.tgz",
- "integrity": "sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA==",
+ "version": "0.27.4",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.4.tgz",
+ "integrity": "sha512-xHT8X4sb0GS8qTqiwzHqpY00C95DPAq7nAwX35Ie/s+LO9830hrMd3oX0ZMKLvy7vsonee73x0lmcdOVXFzd6Q==",
"cpu": [
"arm64"
],
@@ -2050,9 +2063,9 @@
}
},
"node_modules/@esbuild/netbsd-x64": {
- "version": "0.27.3",
- "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.3.tgz",
- "integrity": "sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA==",
+ "version": "0.27.4",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.4.tgz",
+ "integrity": "sha512-RugOvOdXfdyi5Tyv40kgQnI0byv66BFgAqjdgtAKqHoZTbTF2QqfQrFwa7cHEORJf6X2ht+l9ABLMP0dnKYsgg==",
"cpu": [
"x64"
],
@@ -2067,9 +2080,9 @@
}
},
"node_modules/@esbuild/openbsd-arm64": {
- "version": "0.27.3",
- "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.3.tgz",
- "integrity": "sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw==",
+ "version": "0.27.4",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.4.tgz",
+ "integrity": "sha512-2MyL3IAaTX+1/qP0O1SwskwcwCoOI4kV2IBX1xYnDDqthmq5ArrW94qSIKCAuRraMgPOmG0RDTA74mzYNQA9ow==",
"cpu": [
"arm64"
],
@@ -2084,9 +2097,9 @@
}
},
"node_modules/@esbuild/openbsd-x64": {
- "version": "0.27.3",
- "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.3.tgz",
- "integrity": "sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ==",
+ "version": "0.27.4",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.4.tgz",
+ "integrity": "sha512-u8fg/jQ5aQDfsnIV6+KwLOf1CmJnfu1ShpwqdwC0uA7ZPwFws55Ngc12vBdeUdnuWoQYx/SOQLGDcdlfXhYmXQ==",
"cpu": [
"x64"
],
@@ -2101,9 +2114,9 @@
}
},
"node_modules/@esbuild/openharmony-arm64": {
- "version": "0.27.3",
- "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.3.tgz",
- "integrity": "sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g==",
+ "version": "0.27.4",
+ "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.4.tgz",
+ "integrity": "sha512-JkTZrl6VbyO8lDQO3yv26nNr2RM2yZzNrNHEsj9bm6dOwwu9OYN28CjzZkH57bh4w0I2F7IodpQvUAEd1mbWXg==",
"cpu": [
"arm64"
],
@@ -2118,9 +2131,9 @@
}
},
"node_modules/@esbuild/sunos-x64": {
- "version": "0.27.3",
- "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.3.tgz",
- "integrity": "sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA==",
+ "version": "0.27.4",
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.4.tgz",
+ "integrity": "sha512-/gOzgaewZJfeJTlsWhvUEmUG4tWEY2Spp5M20INYRg2ZKl9QPO3QEEgPeRtLjEWSW8FilRNacPOg8R1uaYkA6g==",
"cpu": [
"x64"
],
@@ -2135,9 +2148,9 @@
}
},
"node_modules/@esbuild/win32-arm64": {
- "version": "0.27.3",
- "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.3.tgz",
- "integrity": "sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA==",
+ "version": "0.27.4",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.4.tgz",
+ "integrity": "sha512-Z9SExBg2y32smoDQdf1HRwHRt6vAHLXcxD2uGgO/v2jK7Y718Ix4ndsbNMU/+1Qiem9OiOdaqitioZwxivhXYg==",
"cpu": [
"arm64"
],
@@ -2152,9 +2165,9 @@
}
},
"node_modules/@esbuild/win32-ia32": {
- "version": "0.27.3",
- "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.3.tgz",
- "integrity": "sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q==",
+ "version": "0.27.4",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.4.tgz",
+ "integrity": "sha512-DAyGLS0Jz5G5iixEbMHi5KdiApqHBWMGzTtMiJ72ZOLhbu/bzxgAe8Ue8CTS3n3HbIUHQz/L51yMdGMeoxXNJw==",
"cpu": [
"ia32"
],
@@ -2169,9 +2182,7 @@
}
},
"node_modules/@esbuild/win32-x64": {
- "version": "0.27.3",
- "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.3.tgz",
- "integrity": "sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA==",
+ "version": "0.27.4",
"cpu": [
"x64"
],
@@ -2280,8 +2291,6 @@
},
"node_modules/@eslint/eslintrc/node_modules/ajv": {
"version": "6.14.0",
- "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz",
- "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -2308,8 +2317,6 @@
},
"node_modules/@eslint/eslintrc/node_modules/json-schema-traverse": {
"version": "0.4.1",
- "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
- "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
"dev": true,
"license": "MIT"
},
@@ -2594,6 +2601,8 @@
},
"node_modules/@img/sharp-libvips-linux-x64": {
"version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.2.4.tgz",
+ "integrity": "sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==",
"cpu": [
"x64"
],
@@ -2626,6 +2635,8 @@
},
"node_modules/@img/sharp-libvips-linuxmusl-x64": {
"version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.2.4.tgz",
+ "integrity": "sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==",
"cpu": [
"x64"
],
@@ -2756,6 +2767,8 @@
},
"node_modules/@img/sharp-linux-x64": {
"version": "0.34.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.5.tgz",
+ "integrity": "sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==",
"cpu": [
"x64"
],
@@ -2800,6 +2813,8 @@
},
"node_modules/@img/sharp-linuxmusl-x64": {
"version": "0.34.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.34.5.tgz",
+ "integrity": "sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==",
"cpu": [
"x64"
],
@@ -2881,8 +2896,6 @@
},
"node_modules/@img/sharp-win32-x64": {
"version": "0.34.5",
- "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.34.5.tgz",
- "integrity": "sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==",
"cpu": [
"x64"
],
@@ -3526,6 +3539,8 @@
},
"node_modules/@rollup/rollup-linux-x64-gnu": {
"version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.59.0.tgz",
+ "integrity": "sha512-3AHmtQq/ppNuUspKAlvA8HtLybkDflkMuLK4DPo77DfthRb71V84/c4MlWJXixZz4uruIH4uaa07IqoAkG64fg==",
"cpu": [
"x64"
],
@@ -3607,8 +3622,6 @@
},
"node_modules/@rollup/rollup-win32-x64-gnu": {
"version": "4.59.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.59.0.tgz",
- "integrity": "sha512-laBkYlSS1n2L8fSo1thDNGrCTQMmxjYY5G0WFWjFFYZkKPjsMBsgJfGf4TLxXrF6RyhI60L8TMOjBMvXiTcxeA==",
"cpu": [
"x64"
],
@@ -3621,8 +3634,6 @@
},
"node_modules/@rollup/rollup-win32-x64-msvc": {
"version": "4.59.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.59.0.tgz",
- "integrity": "sha512-2HRCml6OztYXyJXAvdDXPKcawukWY2GpR5/nxKp4iBgiO3wcoEGkAaqctIbZcNB6KlUQBIqt8VYkNSj2397EfA==",
"cpu": [
"x64"
],
@@ -3645,7 +3656,7 @@
}
},
"node_modules/@speed-highlight/core": {
- "version": "1.2.14",
+ "version": "1.2.15",
"dev": true,
"license": "CC0-1.0"
},
@@ -3774,7 +3785,7 @@
"license": "MIT"
},
"node_modules/@types/node": {
- "version": "25.3.5",
+ "version": "25.5.0",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -3797,8 +3808,6 @@
},
"node_modules/@uimaxbai/am-lyrics": {
"version": "1.1.4",
- "resolved": "https://registry.npmjs.org/@uimaxbai/am-lyrics/-/am-lyrics-1.1.4.tgz",
- "integrity": "sha512-LEwvbfgz6o71kYTq1vMlfou/powr8q4CJQWuyL2H48Dwo1/vH59SKiB3nz/WOEQ1S69uaSmfqf8Prtx6+ZNIrQ==",
"license": "MPL-2.0",
"dependencies": {
"@babel/runtime": "^7.27.6",
@@ -3979,12 +3988,12 @@
}
},
"node_modules/babel-plugin-polyfill-corejs2": {
- "version": "0.4.16",
+ "version": "0.4.17",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/compat-data": "^7.28.6",
- "@babel/helper-define-polyfill-provider": "^0.6.7",
+ "@babel/helper-define-polyfill-provider": "^0.6.8",
"semver": "^6.3.1"
},
"peerDependencies": {
@@ -4000,11 +4009,11 @@
}
},
"node_modules/babel-plugin-polyfill-corejs3": {
- "version": "0.14.1",
+ "version": "0.14.2",
"dev": true,
"license": "MIT",
"dependencies": {
- "@babel/helper-define-polyfill-provider": "^0.6.7",
+ "@babel/helper-define-polyfill-provider": "^0.6.8",
"core-js-compat": "^3.48.0"
},
"peerDependencies": {
@@ -4012,11 +4021,11 @@
}
},
"node_modules/babel-plugin-polyfill-regenerator": {
- "version": "0.6.7",
+ "version": "0.6.8",
"dev": true,
"license": "MIT",
"dependencies": {
- "@babel/helper-define-polyfill-provider": "^0.6.7"
+ "@babel/helper-define-polyfill-provider": "^0.6.8"
},
"peerDependencies": {
"@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0"
@@ -4036,7 +4045,7 @@
"license": "MIT"
},
"node_modules/baseline-browser-mapping": {
- "version": "2.10.0",
+ "version": "2.10.9",
"dev": true,
"license": "Apache-2.0",
"bin": {
@@ -4086,6 +4095,10 @@
"node": "*"
}
},
+ "node_modules/boolbase": {
+ "version": "1.0.0",
+ "license": "ISC"
+ },
"node_modules/brace-expansion": {
"version": "1.1.12",
"dev": true,
@@ -4186,7 +4199,7 @@
}
},
"node_modules/cacheable": {
- "version": "2.3.3",
+ "version": "2.3.4",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -4194,7 +4207,7 @@
"@cacheable/utils": "^2.4.0",
"hookified": "^1.15.0",
"keyv": "^5.6.0",
- "qified": "^0.6.0"
+ "qified": "^0.9.0"
}
},
"node_modules/call-bind": {
@@ -4250,7 +4263,7 @@
}
},
"node_modules/caniuse-lite": {
- "version": "1.0.30001777",
+ "version": "1.0.30001780",
"dev": true,
"funding": [
{
@@ -4433,7 +4446,7 @@
"license": "MIT"
},
"node_modules/core-js-compat": {
- "version": "3.48.0",
+ "version": "3.49.0",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -4498,9 +4511,22 @@
"node": ">=12"
}
},
+ "node_modules/css-select": {
+ "version": "5.2.2",
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "boolbase": "^1.0.0",
+ "css-what": "^6.1.0",
+ "domhandler": "^5.0.2",
+ "domutils": "^3.0.1",
+ "nth-check": "^2.0.1"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/fb55"
+ }
+ },
"node_modules/css-tree": {
"version": "3.2.1",
- "dev": true,
"license": "MIT",
"dependencies": {
"mdn-data": "2.27.1",
@@ -4510,6 +4536,16 @@
"node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0"
}
},
+ "node_modules/css-what": {
+ "version": "6.2.2",
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">= 6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/fb55"
+ }
+ },
"node_modules/cssesc": {
"version": "3.0.0",
"dev": true,
@@ -4521,6 +4557,33 @@
"node": ">=4"
}
},
+ "node_modules/csso": {
+ "version": "5.0.5",
+ "license": "MIT",
+ "dependencies": {
+ "css-tree": "~2.2.0"
+ },
+ "engines": {
+ "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0",
+ "npm": ">=7.0.0"
+ }
+ },
+ "node_modules/csso/node_modules/css-tree": {
+ "version": "2.2.1",
+ "license": "MIT",
+ "dependencies": {
+ "mdn-data": "2.0.28",
+ "source-map-js": "^1.0.1"
+ },
+ "engines": {
+ "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0",
+ "npm": ">=7.0.0"
+ }
+ },
+ "node_modules/csso/node_modules/css-tree/node_modules/mdn-data": {
+ "version": "2.0.28",
+ "license": "CC0-1.0"
+ },
"node_modules/d": {
"version": "1.0.2",
"dev": true,
@@ -4699,6 +4762,53 @@
"node": ">=8"
}
},
+ "node_modules/dom-serializer": {
+ "version": "2.0.0",
+ "license": "MIT",
+ "dependencies": {
+ "domelementtype": "^2.3.0",
+ "domhandler": "^5.0.2",
+ "entities": "^4.2.0"
+ },
+ "funding": {
+ "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1"
+ }
+ },
+ "node_modules/domelementtype": {
+ "version": "2.3.0",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/fb55"
+ }
+ ],
+ "license": "BSD-2-Clause"
+ },
+ "node_modules/domhandler": {
+ "version": "5.0.3",
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "domelementtype": "^2.3.0"
+ },
+ "engines": {
+ "node": ">= 4"
+ },
+ "funding": {
+ "url": "https://github.com/fb55/domhandler?sponsor=1"
+ }
+ },
+ "node_modules/domutils": {
+ "version": "3.2.2",
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "dom-serializer": "^2.0.0",
+ "domelementtype": "^2.3.0",
+ "domhandler": "^5.0.3"
+ },
+ "funding": {
+ "url": "https://github.com/fb55/domutils?sponsor=1"
+ }
+ },
"node_modules/dot-prop": {
"version": "5.3.0",
"dev": true,
@@ -4754,7 +4864,7 @@
}
},
"node_modules/electron-to-chromium": {
- "version": "1.5.307",
+ "version": "1.5.321",
"dev": true,
"license": "ISC"
},
@@ -4763,6 +4873,16 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/entities": {
+ "version": "4.5.0",
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=0.12"
+ },
+ "funding": {
+ "url": "https://github.com/fb55/entities?sponsor=1"
+ }
+ },
"node_modules/env-paths": {
"version": "2.2.1",
"dev": true,
@@ -4949,7 +5069,7 @@
}
},
"node_modules/esbuild": {
- "version": "0.27.3",
+ "version": "0.27.4",
"dev": true,
"hasInstallScript": true,
"license": "MIT",
@@ -4960,32 +5080,32 @@
"node": ">=18"
},
"optionalDependencies": {
- "@esbuild/aix-ppc64": "0.27.3",
- "@esbuild/android-arm": "0.27.3",
- "@esbuild/android-arm64": "0.27.3",
- "@esbuild/android-x64": "0.27.3",
- "@esbuild/darwin-arm64": "0.27.3",
- "@esbuild/darwin-x64": "0.27.3",
- "@esbuild/freebsd-arm64": "0.27.3",
- "@esbuild/freebsd-x64": "0.27.3",
- "@esbuild/linux-arm": "0.27.3",
- "@esbuild/linux-arm64": "0.27.3",
- "@esbuild/linux-ia32": "0.27.3",
- "@esbuild/linux-loong64": "0.27.3",
- "@esbuild/linux-mips64el": "0.27.3",
- "@esbuild/linux-ppc64": "0.27.3",
- "@esbuild/linux-riscv64": "0.27.3",
- "@esbuild/linux-s390x": "0.27.3",
- "@esbuild/linux-x64": "0.27.3",
- "@esbuild/netbsd-arm64": "0.27.3",
- "@esbuild/netbsd-x64": "0.27.3",
- "@esbuild/openbsd-arm64": "0.27.3",
- "@esbuild/openbsd-x64": "0.27.3",
- "@esbuild/openharmony-arm64": "0.27.3",
- "@esbuild/sunos-x64": "0.27.3",
- "@esbuild/win32-arm64": "0.27.3",
- "@esbuild/win32-ia32": "0.27.3",
- "@esbuild/win32-x64": "0.27.3"
+ "@esbuild/aix-ppc64": "0.27.4",
+ "@esbuild/android-arm": "0.27.4",
+ "@esbuild/android-arm64": "0.27.4",
+ "@esbuild/android-x64": "0.27.4",
+ "@esbuild/darwin-arm64": "0.27.4",
+ "@esbuild/darwin-x64": "0.27.4",
+ "@esbuild/freebsd-arm64": "0.27.4",
+ "@esbuild/freebsd-x64": "0.27.4",
+ "@esbuild/linux-arm": "0.27.4",
+ "@esbuild/linux-arm64": "0.27.4",
+ "@esbuild/linux-ia32": "0.27.4",
+ "@esbuild/linux-loong64": "0.27.4",
+ "@esbuild/linux-mips64el": "0.27.4",
+ "@esbuild/linux-ppc64": "0.27.4",
+ "@esbuild/linux-riscv64": "0.27.4",
+ "@esbuild/linux-s390x": "0.27.4",
+ "@esbuild/linux-x64": "0.27.4",
+ "@esbuild/netbsd-arm64": "0.27.4",
+ "@esbuild/netbsd-x64": "0.27.4",
+ "@esbuild/openbsd-arm64": "0.27.4",
+ "@esbuild/openbsd-x64": "0.27.4",
+ "@esbuild/openharmony-arm64": "0.27.4",
+ "@esbuild/sunos-x64": "0.27.4",
+ "@esbuild/win32-arm64": "0.27.4",
+ "@esbuild/win32-ia32": "0.27.4",
+ "@esbuild/win32-x64": "0.27.4"
}
},
"node_modules/escalade": {
@@ -5107,8 +5227,6 @@
},
"node_modules/eslint/node_modules/ajv": {
"version": "6.14.0",
- "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz",
- "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -5124,8 +5242,6 @@
},
"node_modules/eslint/node_modules/json-schema-traverse": {
"version": "0.4.1",
- "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
- "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
"dev": true,
"license": "MIT"
},
@@ -5394,8 +5510,6 @@
},
"node_modules/flat-cache/node_modules/keyv": {
"version": "4.5.4",
- "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
- "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -5403,7 +5517,7 @@
}
},
"node_modules/flatted": {
- "version": "3.4.0",
+ "version": "3.4.2",
"dev": true,
"license": "ISC"
},
@@ -5985,6 +6099,10 @@
"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,
@@ -6541,7 +6659,7 @@
}
},
"node_modules/jose": {
- "version": "6.2.0",
+ "version": "6.2.2",
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/panva"
@@ -6583,8 +6701,6 @@
},
"node_modules/json-buffer": {
"version": "3.0.1",
- "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz",
- "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==",
"dev": true,
"license": "MIT"
},
@@ -6779,13 +6895,17 @@
"license": "MIT"
},
"node_modules/lru-cache": {
- "version": "11.2.6",
+ "version": "11.2.7",
"dev": true,
"license": "BlueOak-1.0.0",
"engines": {
"node": "20 || >=22"
}
},
+ "node_modules/lucide-static": {
+ "version": "0.577.0",
+ "license": "ISC"
+ },
"node_modules/magic-string": {
"version": "0.25.9",
"dev": true,
@@ -6835,7 +6955,6 @@
},
"node_modules/mdn-data": {
"version": "2.27.1",
- "dev": true,
"license": "CC0-1.0"
},
"node_modules/meow": {
@@ -6882,8 +7001,6 @@
},
"node_modules/mime": {
"version": "4.1.0",
- "resolved": "https://registry.npmjs.org/mime/-/mime-4.1.0.tgz",
- "integrity": "sha512-X5ju04+cAzsojXKes0B/S4tcYtFAJ6tTMuSPBEn9CPGlrWr8Fiw7qYeLT0XyH80HSoAoqWCaz+MWKh22P7G1cw==",
"funding": [
"https://github.com/sponsors/broofa"
],
@@ -6896,14 +7013,14 @@
}
},
"node_modules/miniflare": {
- "version": "4.20260301.1",
+ "version": "4.20260317.0",
"dev": true,
"license": "MIT",
"dependencies": {
"@cspotcode/source-map-support": "0.8.1",
"sharp": "^0.34.5",
- "undici": "7.18.2",
- "workerd": "1.20260301.1",
+ "undici": "7.24.4",
+ "workerd": "1.20260317.1",
"ws": "8.18.0",
"youch": "4.1.0-beta.10"
},
@@ -7013,7 +7130,7 @@
}
},
"node_modules/npm": {
- "version": "11.11.1",
+ "version": "11.12.0",
"bundleDependencies": [
"@isaacs/string-locale-compare",
"@npmcli/arborist",
@@ -7091,8 +7208,8 @@
],
"dependencies": {
"@isaacs/string-locale-compare": "^1.1.0",
- "@npmcli/arborist": "^9.4.1",
- "@npmcli/config": "^10.7.1",
+ "@npmcli/arborist": "^9.4.2",
+ "@npmcli/config": "^10.8.0",
"@npmcli/fs": "^5.0.0",
"@npmcli/map-workspaces": "^5.0.3",
"@npmcli/metavuln-calculator": "^9.0.3",
@@ -7100,10 +7217,10 @@
"@npmcli/promise-spawn": "^9.0.1",
"@npmcli/redact": "^4.0.0",
"@npmcli/run-script": "^10.0.4",
- "@sigstore/tuf": "^4.0.1",
+ "@sigstore/tuf": "^4.0.2",
"abbrev": "^4.0.0",
"archy": "~1.0.0",
- "cacache": "^20.0.3",
+ "cacache": "^20.0.4",
"chalk": "^5.6.2",
"ci-info": "^4.4.0",
"fastest-levenshtein": "^1.0.16",
@@ -7116,16 +7233,16 @@
"is-cidr": "^6.0.3",
"json-parse-even-better-errors": "^5.0.0",
"libnpmaccess": "^10.0.3",
- "libnpmdiff": "^8.1.4",
- "libnpmexec": "^10.2.4",
- "libnpmfund": "^7.0.18",
+ "libnpmdiff": "^8.1.5",
+ "libnpmexec": "^10.2.5",
+ "libnpmfund": "^7.0.19",
"libnpmorg": "^8.0.1",
- "libnpmpack": "^9.1.4",
+ "libnpmpack": "^9.1.5",
"libnpmpublish": "^11.1.3",
"libnpmsearch": "^9.0.1",
"libnpmteam": "^8.0.2",
"libnpmversion": "^8.0.3",
- "make-fetch-happen": "^15.0.4",
+ "make-fetch-happen": "^15.0.5",
"minimatch": "^10.2.4",
"minipass": "^7.1.3",
"minipass-pipeline": "^1.2.4",
@@ -7165,24 +7282,13 @@
}
},
"node_modules/npm/node_modules/@gar/promise-retry": {
- "version": "1.0.2",
+ "version": "1.0.3",
"inBundle": true,
"license": "MIT",
- "dependencies": {
- "retry": "^0.13.1"
- },
"engines": {
"node": "^20.17.0 || >=22.9.0"
}
},
- "node_modules/npm/node_modules/@gar/promise-retry/node_modules/retry": {
- "version": "0.13.1",
- "inBundle": true,
- "license": "MIT",
- "engines": {
- "node": ">= 4"
- }
- },
"node_modules/npm/node_modules/@isaacs/fs-minipass": {
"version": "4.0.1",
"inBundle": true,
@@ -7215,7 +7321,7 @@
}
},
"node_modules/npm/node_modules/@npmcli/arborist": {
- "version": "9.4.1",
+ "version": "9.4.2",
"inBundle": true,
"license": "ISC",
"dependencies": {
@@ -7262,7 +7368,7 @@
}
},
"node_modules/npm/node_modules/@npmcli/config": {
- "version": "10.7.1",
+ "version": "10.8.0",
"inBundle": true,
"license": "ISC",
"dependencies": {
@@ -7442,7 +7548,7 @@
}
},
"node_modules/npm/node_modules/@sigstore/core": {
- "version": "3.1.0",
+ "version": "3.2.0",
"inBundle": true,
"license": "Apache-2.0",
"engines": {
@@ -7458,23 +7564,23 @@
}
},
"node_modules/npm/node_modules/@sigstore/sign": {
- "version": "4.1.0",
+ "version": "4.1.1",
"inBundle": true,
"license": "Apache-2.0",
"dependencies": {
+ "@gar/promise-retry": "^1.0.2",
"@sigstore/bundle": "^4.0.0",
- "@sigstore/core": "^3.1.0",
+ "@sigstore/core": "^3.2.0",
"@sigstore/protobuf-specs": "^0.5.0",
- "make-fetch-happen": "^15.0.3",
- "proc-log": "^6.1.0",
- "promise-retry": "^2.0.1"
+ "make-fetch-happen": "^15.0.4",
+ "proc-log": "^6.1.0"
},
"engines": {
"node": "^20.17.0 || >=22.9.0"
}
},
"node_modules/npm/node_modules/@sigstore/tuf": {
- "version": "4.0.1",
+ "version": "4.0.2",
"inBundle": true,
"license": "Apache-2.0",
"dependencies": {
@@ -7590,7 +7696,7 @@
}
},
"node_modules/npm/node_modules/cacache": {
- "version": "20.0.3",
+ "version": "20.0.4",
"inBundle": true,
"license": "ISC",
"dependencies": {
@@ -7603,8 +7709,7 @@
"minipass-flush": "^1.0.5",
"minipass-pipeline": "^1.2.4",
"p-map": "^7.0.2",
- "ssri": "^13.0.0",
- "unique-filename": "^5.0.0"
+ "ssri": "^13.0.0"
},
"engines": {
"node": "^20.17.0 || >=22.9.0"
@@ -7710,11 +7815,6 @@
"node": ">=6"
}
},
- "node_modules/npm/node_modules/err-code": {
- "version": "2.0.3",
- "inBundle": true,
- "license": "MIT"
- },
"node_modules/npm/node_modules/exponential-backoff": {
"version": "3.1.3",
"inBundle": true,
@@ -7827,14 +7927,6 @@
"node": "^20.17.0 || >=22.9.0"
}
},
- "node_modules/npm/node_modules/imurmurhash": {
- "version": "0.1.4",
- "inBundle": true,
- "license": "MIT",
- "engines": {
- "node": ">=0.8.19"
- }
- },
"node_modules/npm/node_modules/ini": {
"version": "6.0.0",
"inBundle": true,
@@ -7933,11 +8025,11 @@
}
},
"node_modules/npm/node_modules/libnpmdiff": {
- "version": "8.1.4",
+ "version": "8.1.5",
"inBundle": true,
"license": "ISC",
"dependencies": {
- "@npmcli/arborist": "^9.4.1",
+ "@npmcli/arborist": "^9.4.2",
"@npmcli/installed-package-contents": "^4.0.0",
"binary-extensions": "^3.0.0",
"diff": "^8.0.2",
@@ -7951,12 +8043,12 @@
}
},
"node_modules/npm/node_modules/libnpmexec": {
- "version": "10.2.4",
+ "version": "10.2.5",
"inBundle": true,
"license": "ISC",
"dependencies": {
"@gar/promise-retry": "^1.0.0",
- "@npmcli/arborist": "^9.4.1",
+ "@npmcli/arborist": "^9.4.2",
"@npmcli/package-json": "^7.0.0",
"@npmcli/run-script": "^10.0.0",
"ci-info": "^4.0.0",
@@ -7973,11 +8065,11 @@
}
},
"node_modules/npm/node_modules/libnpmfund": {
- "version": "7.0.18",
+ "version": "7.0.19",
"inBundle": true,
"license": "ISC",
"dependencies": {
- "@npmcli/arborist": "^9.4.1"
+ "@npmcli/arborist": "^9.4.2"
},
"engines": {
"node": "^20.17.0 || >=22.9.0"
@@ -7996,11 +8088,11 @@
}
},
"node_modules/npm/node_modules/libnpmpack": {
- "version": "9.1.4",
+ "version": "9.1.5",
"inBundle": true,
"license": "ISC",
"dependencies": {
- "@npmcli/arborist": "^9.4.1",
+ "@npmcli/arborist": "^9.4.2",
"@npmcli/run-script": "^10.0.0",
"npm-package-arg": "^13.0.0",
"pacote": "^21.0.2"
@@ -8066,7 +8158,7 @@
}
},
"node_modules/npm/node_modules/lru-cache": {
- "version": "11.2.6",
+ "version": "11.2.7",
"inBundle": true,
"license": "BlueOak-1.0.0",
"engines": {
@@ -8074,12 +8166,13 @@
}
},
"node_modules/npm/node_modules/make-fetch-happen": {
- "version": "15.0.4",
+ "version": "15.0.5",
"inBundle": true,
"license": "ISC",
"dependencies": {
"@gar/promise-retry": "^1.0.0",
"@npmcli/agent": "^4.0.0",
+ "@npmcli/redact": "^4.0.0",
"cacache": "^20.0.1",
"http-cache-semantics": "^4.1.1",
"minipass": "^7.0.2",
@@ -8506,18 +8599,6 @@
"url": "https://github.com/sponsors/isaacs"
}
},
- "node_modules/npm/node_modules/promise-retry": {
- "version": "2.0.1",
- "inBundle": true,
- "license": "MIT",
- "dependencies": {
- "err-code": "^2.0.2",
- "retry": "^0.12.0"
- },
- "engines": {
- "node": ">=10"
- }
- },
"node_modules/npm/node_modules/promzard": {
"version": "3.0.1",
"inBundle": true,
@@ -8555,14 +8636,6 @@
"node": "^20.17.0 || >=22.9.0"
}
},
- "node_modules/npm/node_modules/retry": {
- "version": "0.12.0",
- "inBundle": true,
- "license": "MIT",
- "engines": {
- "node": ">= 4"
- }
- },
"node_modules/npm/node_modules/safer-buffer": {
"version": "2.1.2",
"inBundle": true,
@@ -8771,28 +8844,6 @@
"node": "^20.17.0 || >=22.9.0"
}
},
- "node_modules/npm/node_modules/unique-filename": {
- "version": "5.0.0",
- "inBundle": true,
- "license": "ISC",
- "dependencies": {
- "unique-slug": "^6.0.0"
- },
- "engines": {
- "node": "^20.17.0 || >=22.9.0"
- }
- },
- "node_modules/npm/node_modules/unique-slug": {
- "version": "6.0.0",
- "inBundle": true,
- "license": "ISC",
- "dependencies": {
- "imurmurhash": "^0.1.4"
- },
- "engines": {
- "node": "^20.17.0 || >=22.9.0"
- }
- },
"node_modules/npm/node_modules/util-deprecate": {
"version": "1.0.2",
"inBundle": true,
@@ -8847,6 +8898,16 @@
"node": ">=18"
}
},
+ "node_modules/nth-check": {
+ "version": "2.1.1",
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "boolbase": "^1.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/fb55/nth-check?sponsor=1"
+ }
+ },
"node_modules/object-inspect": {
"version": "1.13.4",
"dev": true,
@@ -9069,7 +9130,6 @@
},
"node_modules/picocolors": {
"version": "1.1.1",
- "dev": true,
"license": "ISC"
},
"node_modules/picomatch": {
@@ -9264,8 +9324,6 @@
},
"node_modules/punycode": {
"version": "2.3.1",
- "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
- "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
"dev": true,
"license": "MIT",
"engines": {
@@ -9273,16 +9331,21 @@
}
},
"node_modules/qified": {
- "version": "0.6.0",
+ "version": "0.9.0",
"dev": true,
"license": "MIT",
"dependencies": {
- "hookified": "^1.14.0"
+ "hookified": "^2.1.0"
},
"engines": {
"node": ">=20"
}
},
+ "node_modules/qified/node_modules/hookified": {
+ "version": "2.1.0",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/queue-microtask": {
"version": "1.2.3",
"dev": true,
@@ -9596,8 +9659,11 @@
}
},
"node_modules/sax": {
- "version": "1.2.1",
- "license": "ISC"
+ "version": "1.6.0",
+ "license": "BlueOak-1.0.0",
+ "engines": {
+ "node": ">=11.0.0"
+ }
},
"node_modules/semver": {
"version": "7.7.4",
@@ -9830,6 +9896,23 @@
"url": "https://github.com/sponsors/isaacs"
}
},
+ "node_modules/simple-icons": {
+ "version": "16.12.0",
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/simple-icons"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/simple-icons"
+ }
+ ],
+ "license": "CC0-1.0",
+ "engines": {
+ "node": ">=0.12.18"
+ }
+ },
"node_modules/slash": {
"version": "3.0.0",
"dev": true,
@@ -9872,7 +9955,6 @@
},
"node_modules/source-map-js": {
"version": "1.2.1",
- "dev": true,
"license": "BSD-3-Clause",
"engines": {
"node": ">=0.10.0"
@@ -10233,12 +10315,12 @@
}
},
"node_modules/stylelint/node_modules/file-entry-cache/node_modules/flat-cache": {
- "version": "6.1.20",
+ "version": "6.1.21",
"dev": true,
"license": "MIT",
"dependencies": {
- "cacheable": "^2.3.2",
- "flatted": "^3.3.3",
+ "cacheable": "^2.3.3",
+ "flatted": "^3.4.1",
"hookified": "^1.15.0"
}
},
@@ -10291,6 +10373,36 @@
"version": "1.0.0",
"dev": true
},
+ "node_modules/svgo": {
+ "version": "4.0.1",
+ "license": "MIT",
+ "dependencies": {
+ "commander": "^11.1.0",
+ "css-select": "^5.1.0",
+ "css-tree": "^3.0.1",
+ "css-what": "^6.1.0",
+ "csso": "^5.0.5",
+ "picocolors": "^1.1.1",
+ "sax": "^1.5.0"
+ },
+ "bin": {
+ "svgo": "bin/svgo.js"
+ },
+ "engines": {
+ "node": ">=16"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/svgo"
+ }
+ },
+ "node_modules/svgo/node_modules/commander": {
+ "version": "11.1.0",
+ "license": "MIT",
+ "engines": {
+ "node": ">=16"
+ }
+ },
"node_modules/table": {
"version": "6.9.0",
"dev": true,
@@ -10362,7 +10474,7 @@
}
},
"node_modules/terser": {
- "version": "5.46.0",
+ "version": "5.46.1",
"dev": true,
"license": "BSD-2-Clause",
"dependencies": {
@@ -10583,7 +10695,7 @@
}
},
"node_modules/undici": {
- "version": "7.18.2",
+ "version": "7.24.4",
"dev": true,
"license": "MIT",
"engines": {
@@ -10690,8 +10802,6 @@
},
"node_modules/uri-js": {
"version": "4.4.1",
- "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
- "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
"dev": true,
"license": "BSD-2-Clause",
"dependencies": {
@@ -10835,8 +10945,6 @@
},
"node_modules/vite/node_modules/rollup": {
"version": "4.59.0",
- "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.59.0.tgz",
- "integrity": "sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -11257,7 +11365,7 @@
}
},
"node_modules/workerd": {
- "version": "1.20260301.1",
+ "version": "1.20260317.1",
"dev": true,
"hasInstallScript": true,
"license": "Apache-2.0",
@@ -11268,11 +11376,11 @@
"node": ">=16"
},
"optionalDependencies": {
- "@cloudflare/workerd-darwin-64": "1.20260301.1",
- "@cloudflare/workerd-darwin-arm64": "1.20260301.1",
- "@cloudflare/workerd-linux-64": "1.20260301.1",
- "@cloudflare/workerd-linux-arm64": "1.20260301.1",
- "@cloudflare/workerd-windows-64": "1.20260301.1"
+ "@cloudflare/workerd-darwin-64": "1.20260317.1",
+ "@cloudflare/workerd-darwin-arm64": "1.20260317.1",
+ "@cloudflare/workerd-linux-64": "1.20260317.1",
+ "@cloudflare/workerd-linux-arm64": "1.20260317.1",
+ "@cloudflare/workerd-windows-64": "1.20260317.1"
}
},
"node_modules/wrappy": {
@@ -11340,8 +11448,6 @@
},
"node_modules/yauzl": {
"version": "3.2.1",
- "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-3.2.1.tgz",
- "integrity": "sha512-k1isifdbpNSFEHFJ1ZY4YDewv0IH9FR61lDetaRMD3j2ae3bIXGV+7c+LHCqtQGofSd8PIyV4X6+dHMAnSr60A==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -11401,11 +11507,11 @@
}
},
"node_modules/zip-lib": {
- "version": "1.2.1",
+ "version": "1.2.2",
"dev": true,
"license": "MIT",
"dependencies": {
- "yauzl": "^3.2.0",
+ "yauzl": "^3.2.1",
"yazl": "^3.3.1"
},
"engines": {
diff --git a/package.json b/package.json
index 9de2ff06d..86f321d19 100644
--- a/package.json
+++ b/package.json
@@ -67,9 +67,12 @@
"fuse.js": "^7.1.0",
"hls.js": "^1.6.15",
"jose": "^6.2.0",
+ "lucide-static": "^0.577.0",
"mime": "^4.1.0",
"npm": "^11.11.1",
"pocketbase": "^0.26.8",
+ "simple-icons": "^16.12.0",
+ "svgo": "^4.0.1",
"uuid": "^13.0.0"
}
}
diff --git a/vite-plugin-svg-use.ts b/vite-plugin-svg-use.ts
new file mode 100644
index 000000000..edbfc69ac
--- /dev/null
+++ b/vite-plugin-svg-use.ts
@@ -0,0 +1,234 @@
+import { normalizePath, Plugin } from 'vite';
+import path from 'path';
+import fs from 'fs';
+import { optimize } from 'svgo';
+
+const virtualModuleId = 'svg-merge-attributes';
+
+/**
+ * Regex for matching attributes inside a tag
+ */
+const ATTR_REGEX = /([a-z0-9_-]+)="([^"]*)"/gim;
+
+/**
+ * Regex for matching
+ */
+const SVG_USE_REGEX = /]*?)svg="([^"]+\.svg)"([^>]*)\/?>/gim;
+
+/**
+ * Parse attribute string to object
+ */
+function parseAttrs(str: string): Record {
+ const out: Record = {};
+ for (const [, name, value] of str.matchAll(ATTR_REGEX)) {
+ out[name] = value;
+ }
+ return out;
+}
+
+/**
+ * Merge attributes into root
+ */
+function mergeSvgAttributes(svg: string, attrs: Record) {
+ return svg.replace(/]*)>/i, (match, existingAttrs) => {
+ // Size is shorthand for setting both width and height to the same value
+ if (attrs['size']) {
+ attrs['width'] = attrs['size'];
+ attrs['height'] = attrs['size'];
+ delete attrs['size'];
+ }
+
+ const map = new Map();
+
+ for (const [, name, value] of existingAttrs.matchAll(ATTR_REGEX)) {
+ map.set(name, value);
+ }
+
+ for (const [k, v] of Object.entries(attrs)) {
+ // optional: merge class and style
+ if (k === 'class' && map.has('class')) {
+ map.set('class', map.get('class') + ' ' + v);
+ } else if (k === 'style' && map.has('style')) {
+ map.set('style', map.get('style') + ';' + v);
+ } else {
+ map.set(k, v);
+ }
+ }
+
+ const merged = [...map.entries()].map(([k, v]) => `${k}="${v}"`).join(' ');
+ return ``;
+ });
+}
+
+function getResizer(base: string, params: string) {
+ const cache: Record = {};
+
+ return function getIcon(size: number, attrs: Record = {}) {
+ const attributes = {
+ ...getParams(params),
+ ...attrs,
+ height: size.toString(),
+ width: size.toString(),
+ };
+ return (cache[JSON.stringify(attributes)] ??= mergeSvgAttributes(base, attributes));
+ };
+}
+
+function getParams(str: string): Record {
+ return Object.fromEntries(new URLSearchParams(str).entries());
+}
+
+/**
+ * Load SVG content from disk
+ */
+function loadSvg>(
+ filePath: string,
+ sync: S = true as S
+): T {
+ if (sync) {
+ return fs.readFileSync(filePath, 'utf-8') as T;
+ }
+
+ return new Promise((resolve, reject) => {
+ fs.readFile(filePath, { encoding: 'utf-8' }, (err, data) => {
+ if (err) {
+ reject(err);
+ } else {
+ resolve(data);
+ }
+ });
+ }) as T;
+}
+
+/**
+ * Main plugin
+ */
+export default function viteSvgUsePlugin(): Plugin {
+ let config: any;
+ const watched = new Set();
+
+ /**
+ * Resolve path
+ */
+ function resolveSvg(root: string, importer: string, src: string) {
+ // Handle Vite aliases
+ if (src.startsWith('.')) {
+ return normalizePath(path.resolve(path.dirname(importer), src));
+ }
+ // Check for alias
+ if (config && config.resolve && config.resolve.alias) {
+ for (const [_, { find, replacement }] of Object.entries<{ find: string; replacement: string }>(
+ config.resolve.alias
+ )) {
+ if (src.startsWith(find)) {
+ // Remove alias prefix and resolve
+ const aliasedPath = src.replace(find, replacement);
+ return normalizePath(path.resolve(root, aliasedPath.replace(/^\//, '')));
+ }
+ }
+ }
+ return normalizePath(path.resolve(root, src.replace(/^\//, '')));
+ }
+
+ return {
+ name: 'vite-svg-use-plugin',
+ enforce: 'pre',
+
+ configResolved(resolvedConfig) {
+ config = resolvedConfig;
+ },
+
+ /**
+ * HTML transform
+ */
+ transformIndexHtml: {
+ order: 'pre',
+ async handler(html, ctx) {
+ return html.replace(SVG_USE_REGEX, (full, before, src, after) => {
+ const attrs = {
+ ...parseAttrs(before || ''),
+ ...parseAttrs(after || ''),
+ };
+
+ delete attrs['use'];
+
+ const filePath = resolveSvg(config.root, ctx.filename || '', src);
+
+ watched.add(filePath);
+
+ let svg = loadSvg(filePath);
+ svg = mergeSvgAttributes(optimize(svg).data, attrs);
+
+ return svg;
+ });
+ },
+ },
+
+ /** Resolve virtual modules */
+ resolveId(id) {
+ if (id == virtualModuleId) {
+ return id;
+ }
+
+ return null;
+ },
+
+ /** Load virtual modules */
+ async load(id) {
+ if (id === virtualModuleId) {
+ return [
+ `const ATTR_REGEX = ${ATTR_REGEX};`,
+ `export ${getParams.toString()}`,
+ `export ${mergeSvgAttributes.toString()}`,
+ `export ${getResizer.toString()}`,
+ ].join('\n');
+ }
+
+ if (id.includes('?svg')) {
+ const [file, queryString] = id.split('?');
+ const params = new URLSearchParams(queryString);
+ const absPath = path.resolve(file);
+
+ // Derived module: import base and merge attributes
+ params.delete('svg');
+
+ if (params.size === 0) {
+ // No attributes to merge, just return raw content
+ watched.add(absPath);
+
+ // Read and return the SVG content directly as a string export
+ const svgContent = optimize(await loadSvg(absPath, false)).data;
+ return `export default ${JSON.stringify(svgContent)};`;
+ }
+
+ const baseImport = file + '?svg';
+
+ if (params.has('icon')) {
+ params.delete('icon');
+ return [
+ `import base from ${JSON.stringify(baseImport)};`,
+ `import { getResizer } from ${JSON.stringify(virtualModuleId)};`,
+ `export default getResizer(base, ${JSON.stringify(params.toString())});`,
+ ].join('\n');
+ }
+
+ return [
+ `import base from ${JSON.stringify(baseImport)};`,
+ `import { getParams, mergeSvgAttributes } from ${JSON.stringify(virtualModuleId)};`,
+ `export default mergeSvgAttributes(base, getParams(${JSON.stringify(params.toString())}));`,
+ ].join('\n');
+ }
+ },
+
+ /**
+ * HMR support
+ */
+ handleHotUpdate({ file, server }) {
+ if (watched.has(normalizePath(file))) {
+ server.ws.send({
+ type: 'full-reload',
+ });
+ }
+ },
+ };
+}
diff --git a/vite.config.ts b/vite.config.ts
index 214555568..d7887f9f4 100644
--- a/vite.config.ts
+++ b/vite.config.ts
@@ -5,6 +5,8 @@ import authGatePlugin from './vite-plugin-auth-gate.js';
import path from 'path';
import uploadPlugin from './vite-plugin-upload.js';
import blobAssetPlugin from './vite-plugin-blob.js';
+import injectHTML from 'vite-plugin-html-inject';
+import svgUse from './vite-plugin-svg-use.js';
export default defineConfig(({ mode }) => {
const IS_NEUTRALINO = mode === 'neutralino';
@@ -16,7 +18,10 @@ export default defineConfig(({ mode }) => {
},
resolve: {
alias: {
+ '!lucide': '/node_modules/lucide-static/icons',
+ '!simpleicons': '/node_modules/simple-icons/icons',
'!': '/node_modules',
+
pocketbase: '/node_modules/pocketbase/dist/pocketbase.es.js',
},
},
@@ -44,6 +49,8 @@ export default defineConfig(({ mode }) => {
authGatePlugin(),
uploadPlugin(),
blobAssetPlugin(),
+ svgUse(),
+ injectHTML(),
VitePWA({
registerType: 'prompt',
workbox: {
From 4e2a595504662be927cb5a9a563b04b31eb49133 Mon Sep 17 00:00:00 2001
From: Daniel <790119+DanTheMan827@users.noreply.github.com>
Date: Thu, 19 Mar 2026 12:20:41 -0500
Subject: [PATCH 132/226] refactor(hls/dash): externalize hls.js and dashjs to
reduce initial bundle size
---
bun.lock | 13 ++++++-
js/app.js | 13 ++++---
js/commandPalette.js | 86 ++++++++++++++++++++---------------------
js/dash-media-player.ts | 1 +
js/player.js | 23 ++++++-----
js/ui.js | 84 ++++++++++++++++++++--------------------
package-lock.json | 25 ++++++++++--
package.json | 5 ++-
stream-stub.js | 1 +
vite.config.ts | 3 +-
10 files changed, 146 insertions(+), 108 deletions(-)
create mode 100644 js/dash-media-player.ts
create mode 100644 stream-stub.js
diff --git a/bun.lock b/bun.lock
index e9356fd16..a32b58e1b 100644
--- a/bun.lock
+++ b/bun.lock
@@ -11,13 +11,15 @@
"@ffmpeg/util": "^0.12.2",
"@kawarp/core": "^1.1.1",
"@neutralinojs/lib": "^6.5.0",
+ "@svta/common-media-library": "^0.18.1",
"@uimaxbai/am-lyrics": "^1.1.4",
"appwrite": "^23.0.0",
"butterchurn": "^2.6.7",
"butterchurn-presets": "^2.4.7",
"client-zip": "^2.5.0",
"cookie-session": "^2.1.1",
- "dashjs": "^5.1.1",
+ "dashjs": "https://github.com/Dash-Industry-Forum/dash.js/archive/refs/tags/v5.1.1.tar.gz",
+ "eventemitter3": "^5.0.4",
"fuse.js": "^7.1.0",
"hls.js": "^1.6.15",
"jose": "^6.2.0",
@@ -27,6 +29,7 @@
"pocketbase": "^0.26.8",
"simple-icons": "^16.12.0",
"svgo": "^4.0.1",
+ "url-toolkit": "^2.2.5",
"uuid": "^13.0.0",
},
"devDependencies": {
@@ -537,6 +540,8 @@
"@svta/cml-xml": ["@svta/cml-xml@1.0.1", "", { "peerDependencies": { "@svta/cml-utils": "1.0.1" } }, "sha512-11LkJa5kDEcsRMWkVI1ABH3KLCxGoiSVe4kQ293ItVj8ncTTQ7htmCGiJDjS+Cmy35UgF3e/vc0ysJIiWRTx2g=="],
+ "@svta/common-media-library": ["@svta/common-media-library@0.18.1", "", {}, "sha512-VMj1jI8OWphurcozF+dezABUm9Mht6iAsSiKsFUKVT35fddOowvLoGz23Gx6lEHaAHkDy9o/aVi5s9DSp3K15Q=="],
+
"@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="],
"@types/json-schema": ["@types/json-schema@7.0.15", "", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="],
@@ -687,7 +692,7 @@
"d": ["d@1.0.2", "", { "dependencies": { "es5-ext": "^0.10.64", "type": "^2.7.2" } }, "sha512-MOqHvMWF9/9MX6nza0KgvFH4HpMU0EF5uUDXqX/BtxtU8NfB0QzRtJ8Oe/6SuS4kbhyzVJwjd97EA4PKrzJ8bw=="],
- "dashjs": ["dashjs@5.1.1", "", { "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" } }, "sha512-BzNXlUgzEjhuZ5M5hlSp1qIyQHZ7NpXAR0loP9DAAFVZj/ntL1DHeZ7qp/L3bvI4rq50X5indkAZQ3zEHWJoCA=="],
+ "dashjs": ["dashjs@https://github.com/Dash-Industry-Forum/dash.js/archive/refs/tags/v5.1.1.tar.gz", { "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" } }, "sha512-lhD1tvEe4PO6t086flm6WfO2Jt1EOIolDQ17F3vLomMthaL1RH96h8peIQTvrDvfSJTRXeisL+CwPj4oud5e9g=="],
"data-view-buffer": ["data-view-buffer@1.0.2", "", { "dependencies": { "call-bound": "^1.0.3", "es-errors": "^1.3.0", "is-data-view": "^1.0.2" } }, "sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ=="],
@@ -791,6 +796,8 @@
"event-emitter": ["event-emitter@0.3.5", "", { "dependencies": { "d": "1", "es5-ext": "~0.10.14" } }, "sha512-D9rRn9y7kLPnJ+hMq7S/nhvoKwwvVJahBi2BPmx3bvbsEdK3W9ii8cBSGjP+72/LnM4n6fo3+dkCX5FeTQruXA=="],
+ "eventemitter3": ["eventemitter3@5.0.4", "", {}, "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw=="],
+
"ext": ["ext@1.7.0", "", { "dependencies": { "type": "^2.7.2" } }, "sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw=="],
"fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="],
@@ -1401,6 +1408,8 @@
"uri-js": ["uri-js@4.4.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg=="],
+ "url-toolkit": ["url-toolkit@2.2.5", "", {}, "sha512-mtN6xk+Nac+oyJ/PrI7tzfmomRVNFIWKUbG8jdYFt52hxbiReFAXIjYskvu64/dvuW71IcB7lV8l0HvZMac6Jg=="],
+
"utf-8-validate": ["utf-8-validate@5.0.10", "", { "dependencies": { "node-gyp-build": "^4.3.0" } }, "sha512-Z6czzLq4u8fPOyx7TU6X3dvUZVvoJmxSQ+IcrlmagKhilxlhZgxPK6C5Jqbkw1IDUmFTM+cz9QDnnLTwDz/2gQ=="],
"util-deprecate": ["util-deprecate@1.0.2", "", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="],
diff --git a/js/app.js b/js/app.js
index d85473e76..479f60d42 100644
--- a/js/app.js
+++ b/js/app.js
@@ -416,6 +416,7 @@ document.addEventListener('DOMContentLoaded', async () => {
const currentQuality = localStorage.getItem('playback-quality') || 'HI_RES_LOSSLESS';
const player = new Player(audioPlayer, api, currentQuality);
+ await player.init();
window.monochromePlayer = player;
// Initialize tracker
@@ -1136,7 +1137,7 @@ document.addEventListener('DOMContentLoaded', async () => {
const shuffleBtn = document.getElementById('shuffle-btn');
if (shuffleBtn) shuffleBtn.classList.remove('active');
player.shuffleActive = false;
- player.playTrackFromQueue();
+ await player.playTrackFromQueue();
}
} catch (error) {
console.error('Failed to play album:', error);
@@ -1167,7 +1168,7 @@ document.addEventListener('DOMContentLoaded', async () => {
const shuffleBtn = document.getElementById('shuffle-btn');
if (shuffleBtn) shuffleBtn.classList.remove('active');
player.shuffleActive = false;
- player.playTrackFromQueue();
+ await player.playTrackFromQueue();
const { showNotification } = await loadDownloadsModule();
showNotification('Shuffling album');
@@ -1235,7 +1236,7 @@ document.addEventListener('DOMContentLoaded', async () => {
const shuffleBtn = document.getElementById('shuffle-btn');
if (shuffleBtn) shuffleBtn.classList.remove('active');
player.shuffleActive = false;
- player.playTrackFromQueue();
+ await player.playTrackFromQueue();
const { showNotification } = await loadDownloadsModule();
showNotification('Shuffling artist discography');
@@ -2183,7 +2184,7 @@ document.addEventListener('DOMContentLoaded', async () => {
if (tracks.length > 0) {
player.setQueue(tracks, 0);
document.getElementById('shuffle-btn').classList.remove('active');
- player.playTrackFromQueue();
+ await player.playTrackFromQueue();
}
} catch (error) {
console.error('Failed to play playlist:', error);
@@ -2369,7 +2370,7 @@ document.addEventListener('DOMContentLoaded', async () => {
}
player.setQueue(allTracks, 0);
- player.playTrackFromQueue();
+ await player.playTrackFromQueue();
} else {
throw new Error('No tracks found across all albums');
}
@@ -2398,7 +2399,7 @@ document.addEventListener('DOMContentLoaded', async () => {
}
player.setQueue(likedTracks, 0);
document.getElementById('shuffle-btn').classList.remove('active');
- player.playTrackFromQueue();
+ await player.playTrackFromQueue();
}
} catch (error) {
console.error('Failed to shuffle liked tracks:', error);
diff --git a/js/commandPalette.js b/js/commandPalette.js
index b9bae5180..fef1b847e 100644
--- a/js/commandPalette.js
+++ b/js/commandPalette.js
@@ -66,15 +66,15 @@ class CommandPalette {
}
init() {
- document.addEventListener('keydown', (e) => {
+ document.addEventListener('keydown', async (e) => {
if ((e.ctrlKey || e.metaKey) && e.key === 'k') {
e.preventDefault();
- this.toggle();
+ await this.toggle();
}
});
this.input.addEventListener('input', () => this.handleInput());
- this.input.addEventListener('keydown', (e) => this.handleKeydown(e));
+ this.input.addEventListener('keydown', async (e) => await this.handleKeydown(e));
this.overlay.addEventListener('click', (e) => {
if (e.target === this.overlay) this.close();
@@ -83,17 +83,17 @@ class CommandPalette {
this.cacheAllSettings();
}
- toggle() {
+ async toggle() {
if (this.isOpen) this.close();
- else this.open();
+ else await this.open();
}
- open() {
+ async open() {
this.isOpen = true;
this.overlay.style.display = 'flex';
this.input.value = '>';
this.input.focus();
- this.handleInput();
+ await this.handleInput();
}
close() {
@@ -101,18 +101,18 @@ class CommandPalette {
this.overlay.style.display = 'none';
}
- handleInput() {
+ async handleInput() {
const value = this.input.value;
this.selectedIndex = 0;
if (!value.startsWith('>')) {
- this.renderResults([
+ await this.renderResults([
{
name: 'Type > to use commands',
description: 'e.g. >theme White, >play The Whole World Is Free',
- action: () => {
+ action: async () => {
this.input.value = '>';
- this.handleInput();
+ await this.handleInput();
},
type: 'hint',
},
@@ -124,7 +124,7 @@ class CommandPalette {
const match = fullQuery.match(/^(\S+)(?:\s+(.*))?$/);
if (!match) {
- this.renderDefaultCommands();
+ await this.renderDefaultCommands();
return;
}
@@ -140,7 +140,7 @@ class CommandPalette {
return;
}
- this.renderResults([
+ await this.renderResults([
{
name: `Execute: ${command.name} ${args}`,
description: args ? `Run ${command.name} for "${args}"` : command.description,
@@ -153,11 +153,11 @@ class CommandPalette {
this.debouncedSearch(cmdName, args.trim());
}
} else {
- this.renderDefaultCommands(cmdName);
+ await this.renderDefaultCommands(cmdName);
}
}
- handleKeydown(e) {
+ async handleKeydown(e) {
if (e.key === 'ArrowDown') {
e.preventDefault();
this.selectedIndex = Math.min(this.selectedIndex + 1, this.results.length - 1);
@@ -168,13 +168,13 @@ class CommandPalette {
this.updateSelection();
} else if (e.key === 'Enter') {
e.preventDefault();
- this.executeSelected();
+ await this.executeSelected();
} else if (e.key === 'Escape') {
this.close();
}
}
- renderDefaultCommands(filter = '') {
+ async renderDefaultCommands(filter = '') {
let cmds = this.commands;
if (filter) {
if (Fuse) {
@@ -185,20 +185,20 @@ class CommandPalette {
}
}
- this.renderResults(
+ await this.renderResults(
cmds.map((c) => ({
name: c.name,
description: c.description,
- action: () => {
+ action: async () => {
this.input.value = `>${c.name} `;
- this.handleInput();
+ await this.handleInput();
},
type: 'command',
}))
);
}
- renderResults(results) {
+ async renderResults(results) {
this.results = results;
this.resultsContainer.innerHTML = '';
@@ -222,9 +222,9 @@ class CommandPalette {
${result.name} ${result.description || ''}
`;
- div.addEventListener('click', () => {
+ div.addEventListener('click', async () => {
this.selectedIndex = index;
- this.executeSelected();
+ await this.executeSelected();
});
this.resultsContainer.appendChild(div);
});
@@ -242,16 +242,16 @@ class CommandPalette {
});
}
- executeSelected() {
+ async executeSelected() {
const result = this.results[this.selectedIndex];
if (result && result.action) {
- result.action();
+ await result.action();
if (result.type !== 'hint') {
this.close();
}
} else if (result && result.type === 'command') {
this.input.value = `>${result.name} `;
- this.handleInput();
+ await this.handleInput();
}
}
@@ -273,7 +273,7 @@ class CommandPalette {
showNotification(message);
}
- handleQueue(args) {
+ async handleQueue(args) {
const player = window.monochromePlayer;
const ui = window.monochromeUi;
@@ -283,7 +283,7 @@ class CommandPalette {
}
if (!args || !args.trim()) {
- this.renderResults(
+ await this.renderResults(
[
{ name: '>queue wipe', description: 'Clear the queue and stop playback' },
{ name: '>queue like all', description: 'Like all tracks in the current queue' },
@@ -291,9 +291,9 @@ class CommandPalette {
].map((c) => ({
...c,
type: 'command',
- action: () => {
+ action: async () => {
this.input.value = c.name;
- this.handleInput();
+ await this.handleInput();
},
}))
);
@@ -358,11 +358,11 @@ class CommandPalette {
this.close();
}
- handleNavigation(args) {
+ async handleNavigation(args) {
const validPages = ['home', 'library', 'recent', 'settings', 'unreleased', 'about', 'download'];
if (!args || !args.trim()) {
- this.renderResults(
+ await this.renderResults(
validPages.map((p) => ({
name: `>go ${p}`,
description: `Navigate to ${p}`,
@@ -386,9 +386,9 @@ class CommandPalette {
}
}
- handleSleepTimer(args) {
+ async handleSleepTimer(args) {
if (!args || !args.trim()) {
- this.renderResults(
+ await this.renderResults(
[15, 30, 45, 60, 120].map((m) => ({
name: `>sleep ${m}`,
description: `Set sleep timer for ${m} minutes`,
@@ -418,7 +418,7 @@ class CommandPalette {
}
}
- handleQuality(args) {
+ async handleQuality(args) {
const qualityMap = {
low: 'LOW',
high: 'HIGH',
@@ -452,7 +452,7 @@ class CommandPalette {
action: () => {},
type: 'hint',
});
- this.renderResults(results);
+ await this.renderResults(results);
return;
}
@@ -524,7 +524,7 @@ class CommandPalette {
async handleVisualizer(args) {
if (!args || !args.trim()) {
- this.renderResults(
+ await this.renderResults(
[
{ name: '>visualizer toggle', description: 'Toggle visualizer on/off', cmd: 'toggle' },
{ name: '>visualizer butterchurn', description: 'Set preset to Butterchurn', cmd: 'butterchurn' },
@@ -630,7 +630,7 @@ class CommandPalette {
const query = args.trim().toLowerCase();
if (!query) {
- this.renderResults(
+ await this.renderResults(
this.allSettings.map((setting) => ({
name: setting.label,
description: `[${setting.tab}] ${setting.description}`,
@@ -659,7 +659,7 @@ class CommandPalette {
return;
}
- this.renderResults(
+ await this.renderResults(
results.map((setting) => ({
name: setting.label,
description: `[${setting.tab}] ${setting.description}`,
@@ -713,9 +713,9 @@ class CommandPalette {
name: track.title,
description: `${track.artist?.name || 'Unknown'} • ${track.album?.title || 'Unknown'}`,
image: api.getCoverUrl(track.album?.cover, 80),
- action: () => {
+ action: async () => {
window.monochromePlayer.setQueue([track], 0);
- window.monochromePlayer.playTrackFromQueue();
+ await window.monochromePlayer.playTrackFromQueue();
this.close();
},
type: 'result',
@@ -769,7 +769,7 @@ class CommandPalette {
}
if (this.isOpen && results.length > 0) {
- this.renderResults(results);
+ await this.renderResults(results);
}
}
@@ -782,7 +782,7 @@ class CommandPalette {
if (results.items.length > 0) {
const track = results.items[0];
window.monochromePlayer.setQueue([track], 0);
- window.monochromePlayer.playTrackFromQueue();
+ await window.monochromePlayer.playTrackFromQueue();
this.close();
}
}
diff --git a/js/dash-media-player.ts b/js/dash-media-player.ts
new file mode 100644
index 000000000..7c165ec81
--- /dev/null
+++ b/js/dash-media-player.ts
@@ -0,0 +1 @@
+export { default as MediaPlayer } from '!/dashjs/src/streaming/MediaPlayer.js';
diff --git a/js/player.js b/js/player.js
index adad1e02a..9879ba589 100644
--- a/js/player.js
+++ b/js/player.js
@@ -1,5 +1,4 @@
//js/player.js
-import { MediaPlayer } from 'dashjs';
import {
REPEAT_MODE,
formatTime,
@@ -20,7 +19,8 @@ import {
} from './storage.js';
import { audioContextManager } from './audio-context.js';
import { db } from './db.js';
-import Hls from 'hls.js';
+
+import('./dash-media-player.js');
import { SVG_CLOCK } from './icons.js';
export class Player {
constructor(audioElement, api, quality = 'HI_RES_LOSSLESS') {
@@ -52,7 +52,9 @@ export class Player {
this.sleepTimer = null;
this.sleepTimerEndTime = null;
this.sleepTimerInterval = null;
+ }
+ async init() {
// Apply audio effects when track is ready
this.audio.addEventListener('canplay', () => {
this.applyAudioEffects();
@@ -64,6 +66,7 @@ export class Player {
}
// Initialize dash.js player
+ const { MediaPlayer } = await import('./dash-media-player.js');
this.dashPlayer = MediaPlayer().create();
this.dashPlayer.updateSettings({
streaming: {
@@ -452,8 +455,9 @@ export class Player {
}
}
- setupHlsVideo(video, result, fallbackImg) {
+ async setupHlsVideo(video, result, fallbackImg) {
const url = result.videoUrl || result.hlsUrl || result;
+ const Hls = (await import('hls.js')).default;
if (!url) return;
if (this.hls) {
@@ -471,9 +475,9 @@ export class Player {
this.hls = new Hls();
this.hls.loadSource(url);
this.hls.attachMedia(video);
- this.hls.on(Hls.Events.MANIFEST_PARSED, () => {
+ this.hls.on(Hls.Events.MANIFEST_PARSED, async () => {
video.play().catch(() => {});
- this.setupVideoQualitySelector();
+ await this.setupVideoQualitySelector();
});
this.hls.on(Hls.Events.ERROR, (event, data) => {
if (data.fatal) {
@@ -490,9 +494,9 @@ export class Player {
}
} else {
video.src = url;
- video.onerror = () => {
+ video.onerror = async () => {
if (result && result.hlsUrl) {
- this.setupHlsVideo(video, { videoUrl: null, hlsUrl: result.hlsUrl }, fallbackImg);
+ await this.setupHlsVideo(video, { videoUrl: null, hlsUrl: result.hlsUrl }, fallbackImg);
} else if (fallbackImg) {
video.replaceWith(fallbackImg);
}
@@ -500,8 +504,9 @@ export class Player {
}
}
- setupVideoQualitySelector() {
+ async setupVideoQualitySelector() {
if (!this.hls || !this.hls.levels || this.hls.levels.length === 0) return;
+ const Hls = (await import('hls.js')).default;
const qualityBtn = document.getElementById('fs-quality-btn');
const qualityMenu = document.getElementById('fs-quality-menu');
@@ -802,7 +807,7 @@ export class Player {
if (this.playbackSequence !== currentSequence) return;
if (streamUrl.includes('.m3u8') || streamUrl.includes('application/vnd.apple.mpegurl')) {
- this.setupHlsVideo(activeElement, streamUrl, null);
+ await this.setupHlsVideo(activeElement, streamUrl, null);
} else if (streamUrl.startsWith('blob:') || streamUrl.includes('.mpd')) {
this.dashPlayer.initialize(activeElement, streamUrl, false);
this.dashInitialized = true;
diff --git a/js/ui.js b/js/ui.js
index 80236e443..be136c50b 100644
--- a/js/ui.js
+++ b/js/ui.js
@@ -44,7 +44,6 @@ import {
createTrackFromSong,
} from './tracker.js';
import { trackSearch, trackChangeSort } from './analytics.js';
-import Hls from 'hls.js';
fontSettings.applyFont();
fontSettings.applyFontSize();
@@ -2652,12 +2651,13 @@ export class UIRenderer {
return items.filter((item) => !favoriteIds.has(item.id));
}
- setupHlsVideo(video, result, fallbackImg) {
+ async setupHlsVideo(video, result, fallbackImg) {
if (!result) return;
const url = typeof result === 'string' ? result : result.videoUrl || result.hlsUrl;
if (!url) return;
if (url.endsWith('.m3u8')) {
+ const Hls = (await import('hls.js')).default;
if (Hls.isSupported()) {
const hls = new Hls();
video._hls = hls;
@@ -2692,17 +2692,17 @@ export class UIRenderer {
video.play().catch(() => {});
});
}
- video.onerror = () => {
+ video.onerror = async () => {
if (result.hlsUrl) {
// HLS fallback (for some reason alot of animated covers js dont work on MP4 lol)
- this.setupHlsVideo(video, { videoUrl: null, hlsUrl: result.hlsUrl }, fallbackImg);
+ await this.setupHlsVideo(video, { videoUrl: null, hlsUrl: result.hlsUrl }, fallbackImg);
} else {
video.replaceWith(fallbackImg);
}
};
}
- replaceVideoArtwork(container, type, id, result) {
+ async replaceVideoArtwork(container, type, id, result) {
const url = result.videoUrl || result.hlsUrl;
if (!url) return;
@@ -2722,9 +2722,9 @@ export class UIRenderer {
video.poster = img.src;
- video.onerror = () => {
+ video.onerror = async () => {
if (video.src === result.videoUrl && result.hlsUrl) {
- this.setupHlsVideo(video, { videoUrl: null, hlsUrl: result.hlsUrl }, img);
+ await this.setupHlsVideo(video, { videoUrl: null, hlsUrl: result.hlsUrl }, img);
return;
}
video.replaceWith(img);
@@ -2732,9 +2732,9 @@ export class UIRenderer {
video.addEventListener(
'error',
- (e) => {
+ async (e) => {
if (video.src === result.videoUrl && result.hlsUrl) {
- this.setupHlsVideo(video, { videoUrl: null, hlsUrl: result.hlsUrl }, img);
+ await this.setupHlsVideo(video, { videoUrl: null, hlsUrl: result.hlsUrl }, img);
return;
}
console.warn('Video decoding error:', e);
@@ -2745,7 +2745,7 @@ export class UIRenderer {
img.replaceWith(video);
- this.setupHlsVideo(video, result, img);
+ await this.setupHlsVideo(video, result, img);
}
}
@@ -2999,7 +2999,7 @@ export class UIRenderer {
if (!videoCoverUrl && tracks.length > 0) {
const firstTrack = tracks[0];
- this.api.getVideoArtwork(firstTrack.title, getTrackArtists(firstTrack)).then((result) => {
+ this.api.getVideoArtwork(firstTrack.title, getTrackArtists(firstTrack)).then(async (result) => {
if (result && this.currentPage === 'album' && this.currentAlbumId === albumId) {
const url = result.videoUrl || result.hlsUrl;
if (!url) return;
@@ -3017,7 +3017,7 @@ export class UIRenderer {
video.style.opacity = '1';
video.poster = currentImageEl.src;
- this.setupHlsVideo(video, result, currentImageEl);
+ await this.setupHlsVideo(video, result, currentImageEl);
currentImageEl.replaceWith(video);
}
}
@@ -3036,10 +3036,10 @@ export class UIRenderer {
video.preload = 'auto';
video.className = imageEl.className;
video.id = imageEl.id;
- this.setupHlsVideo(video, videoCoverUrl, imageEl);
+ await this.setupHlsVideo(video, videoCoverUrl, imageEl);
imageEl.replaceWith(video);
} else {
- this.setupHlsVideo(imageEl, videoCoverUrl, null);
+ await this.setupHlsVideo(imageEl, videoCoverUrl, null);
}
} else {
if (imageEl.tagName === 'VIDEO') {
@@ -3788,30 +3788,32 @@ export class UIRenderer {
if (!videoCoverUrl && (firstTrack.album || firstTrack.type === 'video')) {
const fetchArtwork = () => {
- this.api.getVideoArtwork(firstTrack.title, getTrackArtists(firstTrack)).then((result) => {
- if (result && this.currentPage === 'mix' && this.currentMixId === mixId) {
- const url = result.videoUrl || result.hlsUrl;
- if (!url) return;
- firstTrack.album = firstTrack.album || {};
- firstTrack.album.videoCoverUrl = url;
- const currentImageEl = document.getElementById('mix-detail-image');
- if (currentImageEl && currentImageEl.tagName !== 'VIDEO') {
- const video = document.createElement('video');
- video.autoplay = true;
- video.loop = true;
- video.muted = true;
- video.playsInline = true;
- video.preload = 'auto';
- video.className = currentImageEl.className;
- video.id = currentImageEl.id;
- video.style.opacity = '1';
- video.poster = currentImageEl.src;
-
- this.setupHlsVideo(video, result, currentImageEl);
- currentImageEl.replaceWith(video);
+ this.api
+ .getVideoArtwork(firstTrack.title, getTrackArtists(firstTrack))
+ .then(async (result) => {
+ if (result && this.currentPage === 'mix' && this.currentMixId === mixId) {
+ const url = result.videoUrl || result.hlsUrl;
+ if (!url) return;
+ firstTrack.album = firstTrack.album || {};
+ firstTrack.album.videoCoverUrl = url;
+ const currentImageEl = document.getElementById('mix-detail-image');
+ if (currentImageEl && currentImageEl.tagName !== 'VIDEO') {
+ const video = document.createElement('video');
+ video.autoplay = true;
+ video.loop = true;
+ video.muted = true;
+ video.playsInline = true;
+ video.preload = 'auto';
+ video.className = currentImageEl.className;
+ video.id = currentImageEl.id;
+ video.style.opacity = '1';
+ video.poster = currentImageEl.src;
+
+ await this.setupHlsVideo(video, result, currentImageEl);
+ currentImageEl.replaceWith(video);
+ }
}
- }
- });
+ });
};
if (firstTrack.type === 'video') {
@@ -5141,7 +5143,7 @@ export class UIRenderer {
if (!videoCoverUrl && (track.album || track.type === 'video')) {
const fetchArtwork = () => {
- this.api.getVideoArtwork(track.title, getTrackArtists(track)).then((result) => {
+ this.api.getVideoArtwork(track.title, getTrackArtists(track)).then(async (result) => {
if (result && this.currentPage === 'track' && this.currentTrackPageId === track.id) {
const url = result.videoUrl || result.hlsUrl;
if (!url) return;
@@ -5160,7 +5162,7 @@ export class UIRenderer {
video.style.opacity = '1';
video.poster = currentImageEl.src;
- this.setupHlsVideo(video, result, currentImageEl);
+ await this.setupHlsVideo(video, result, currentImageEl);
currentImageEl.replaceWith(video);
}
}
@@ -5196,10 +5198,10 @@ export class UIRenderer {
video.preload = 'auto';
video.className = imageEl.className;
video.id = imageEl.id;
- this.setupHlsVideo(video, videoCoverUrl, imageEl);
+ await this.setupHlsVideo(video, videoCoverUrl, imageEl);
imageEl.replaceWith(video);
} else {
- this.setupHlsVideo(imageEl, videoCoverUrl, null);
+ await this.setupHlsVideo(imageEl, videoCoverUrl, null);
}
} else {
if (imageEl.tagName === 'VIDEO') {
diff --git a/package-lock.json b/package-lock.json
index 2297a39d9..d1092c27c 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -15,13 +15,15 @@
"@ffmpeg/util": "^0.12.2",
"@kawarp/core": "^1.1.1",
"@neutralinojs/lib": "^6.5.0",
+ "@svta/common-media-library": "^0.18.1",
"@uimaxbai/am-lyrics": "^1.1.4",
"appwrite": "^23.0.0",
"butterchurn": "^2.6.7",
"butterchurn-presets": "^2.4.7",
"client-zip": "^2.5.0",
"cookie-session": "^2.1.1",
- "dashjs": "^5.1.1",
+ "dashjs": "https://github.com/Dash-Industry-Forum/dash.js/archive/refs/tags/v5.1.1.tar.gz",
+ "eventemitter3": "^5.0.4",
"fuse.js": "^7.1.0",
"hls.js": "^1.6.15",
"jose": "^6.2.0",
@@ -31,6 +33,7 @@
"pocketbase": "^0.26.8",
"simple-icons": "^16.12.0",
"svgo": "^4.0.1",
+ "url-toolkit": "^2.2.5",
"uuid": "^13.0.0"
},
"devDependencies": {
@@ -3774,6 +3777,13 @@
"@svta/cml-utils": "1.0.1"
}
},
+ "node_modules/@svta/common-media-library": {
+ "version": "0.18.1",
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=20"
+ }
+ },
"node_modules/@types/estree": {
"version": "1.0.8",
"dev": true,
@@ -4598,6 +4608,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",
@@ -5327,6 +5339,10 @@
"es5-ext": "~0.10.14"
}
},
+ "node_modules/eventemitter3": {
+ "version": "5.0.4",
+ "license": "MIT"
+ },
"node_modules/ext": {
"version": "1.7.0",
"dev": true,
@@ -5518,11 +5534,8 @@
},
"node_modules/flatted": {
"version": "3.4.2",
-<<<<<<< svg-refactor
-=======
"resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz",
"integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==",
->>>>>>> main
"dev": true,
"license": "ISC"
},
@@ -10813,6 +10826,10 @@
"punycode": "^2.1.0"
}
},
+ "node_modules/url-toolkit": {
+ "version": "2.2.5",
+ "license": "Apache-2.0"
+ },
"node_modules/utf-8-validate": {
"version": "5.0.10",
"dev": true,
diff --git a/package.json b/package.json
index 86f321d19..999cf2dcd 100644
--- a/package.json
+++ b/package.json
@@ -57,13 +57,15 @@
"@ffmpeg/util": "^0.12.2",
"@kawarp/core": "^1.1.1",
"@neutralinojs/lib": "^6.5.0",
+ "@svta/common-media-library": "^0.18.1",
"@uimaxbai/am-lyrics": "^1.1.4",
"appwrite": "^23.0.0",
"butterchurn": "^2.6.7",
"butterchurn-presets": "^2.4.7",
"client-zip": "^2.5.0",
"cookie-session": "^2.1.1",
- "dashjs": "^5.1.1",
+ "dashjs": "https://github.com/Dash-Industry-Forum/dash.js/archive/refs/tags/v5.1.1.tar.gz",
+ "eventemitter3": "^5.0.4",
"fuse.js": "^7.1.0",
"hls.js": "^1.6.15",
"jose": "^6.2.0",
@@ -73,6 +75,7 @@
"pocketbase": "^0.26.8",
"simple-icons": "^16.12.0",
"svgo": "^4.0.1",
+ "url-toolkit": "^2.2.5",
"uuid": "^13.0.0"
}
}
diff --git a/stream-stub.js b/stream-stub.js
new file mode 100644
index 000000000..f5265c0ac
--- /dev/null
+++ b/stream-stub.js
@@ -0,0 +1 @@
+export function Stream() {}
diff --git a/vite.config.ts b/vite.config.ts
index d7887f9f4..7211e1459 100644
--- a/vite.config.ts
+++ b/vite.config.ts
@@ -5,7 +5,6 @@ import authGatePlugin from './vite-plugin-auth-gate.js';
import path from 'path';
import uploadPlugin from './vite-plugin-upload.js';
import blobAssetPlugin from './vite-plugin-blob.js';
-import injectHTML from 'vite-plugin-html-inject';
import svgUse from './vite-plugin-svg-use.js';
export default defineConfig(({ mode }) => {
@@ -23,6 +22,7 @@ export default defineConfig(({ mode }) => {
'!': '/node_modules',
pocketbase: '/node_modules/pocketbase/dist/pocketbase.es.js',
+ stream: path.resolve(__dirname, 'stream-stub.js'), // Stub for stream module
},
},
optimizeDeps: {
@@ -50,7 +50,6 @@ export default defineConfig(({ mode }) => {
uploadPlugin(),
blobAssetPlugin(),
svgUse(),
- injectHTML(),
VitePWA({
registerType: 'prompt',
workbox: {
From c19dbcf52aa955da73f12092d4822ebe038c7f5d Mon Sep 17 00:00:00 2001
From: Daniel <790119+DanTheMan827@users.noreply.github.com>
Date: Wed, 18 Mar 2026 10:34:58 -0500
Subject: [PATCH 133/226] refactor: adjust imports to allow for file splitting
- dynamically import router
- dynamically import visualizers
- update import syntax for am-lyrics to use dynamic import syntax
---
js/commandPalette.js | 8 ++++----
js/lyrics.js | 2 +-
js/settings.js | 44 ++++++++++++++++++++++++--------------------
js/ui.js | 11 ++++++-----
js/visualizer.js | 31 ++++++++++++++-----------------
5 files changed, 49 insertions(+), 47 deletions(-)
diff --git a/js/commandPalette.js b/js/commandPalette.js
index fef1b847e..154f47752 100644
--- a/js/commandPalette.js
+++ b/js/commandPalette.js
@@ -1,6 +1,7 @@
import { debounce } from './utils.js';
import { db } from './db.js';
import Fuse from 'fuse.js';
+import { navigate } from './router.js';
class CommandPalette {
constructor() {
@@ -368,7 +369,7 @@ class CommandPalette {
description: `Navigate to ${p}`,
action: () => {
this.close();
- import('./router.js').then((m) => m.navigate(p === 'home' ? '/' : `/${p}`));
+ navigate(p === 'home' ? '/' : `/${p}`);
},
type: 'command',
}))
@@ -380,7 +381,7 @@ class CommandPalette {
if (validPages.includes(page)) {
this.close();
- import('./router.js').then((m) => m.navigate(page === 'home' ? '/' : `/${page}`));
+ navigate(page === 'home' ? '/' : `/${page}`);
} else {
this.showNotification(`Unknown page: ${page}`);
}
@@ -673,8 +674,7 @@ class CommandPalette {
}
async navigateToSetting(setting) {
- const router = await import('./router.js');
- router.navigate('/settings');
+ navigate('/settings');
await new Promise((resolve) => setTimeout(resolve, 100));
diff --git a/js/lyrics.js b/js/lyrics.js
index dfbc81ad9..b5d5378c7 100644
--- a/js/lyrics.js
+++ b/js/lyrics.js
@@ -10,7 +10,7 @@ import {
SVG_GLOBE,
} from './icons.js';
import { sidePanelManager } from './side-panel.js';
-import '@uimaxbai/am-lyrics/am-lyrics.js';
+import('@uimaxbai/am-lyrics/am-lyrics.js');
// Check if text contains Japanese, Chinese, or Korean characters
function containsAsianText(text) {
diff --git a/js/settings.js b/js/settings.js
index 41cac783d..e43c0fa7c 100644
--- a/js/settings.js
+++ b/js/settings.js
@@ -37,12 +37,16 @@ import {
modalSettings,
} from './storage.js';
import { audioContextManager, EQ_PRESETS } from './audio-context.js';
-import { getButterchurnPresets } from './visualizers/butterchurn.js';
import { db } from './db.js';
import { authManager } from './accounts/auth.js';
import { syncManager } from './accounts/pocketbase.js';
import { containerFormats, customFormats } from './ffmpegFormats.ts';
+async function getButterchurnPresets(...args) {
+ const butterchurnModule = await import('./visualizers/butterchurn.js');
+ return butterchurnModule.getButterchurnPresets(...args);
+}
+
export function initializeSettings(scrobbler, player, api, ui) {
// Restore last active settings tab
const savedTab = settingsUiState.getActiveTab();
@@ -2311,7 +2315,7 @@ export function initializeSettings(scrobbler, player, api, ui) {
const butterchurnDurationInput = document.getElementById('butterchurn-duration-input');
const butterchurnRandomizeToggle = document.getElementById('butterchurn-randomize-toggle');
- const updateButterchurnSettingsVisibility = () => {
+ const updateButterchurnSettingsVisibility = async () => {
const isEnabled = visualizerEnabledToggle ? visualizerEnabledToggle.checked : false;
const isButterchurn = visualizerPresetSelect ? visualizerPresetSelect.value === 'butterchurn' : false;
const show = isEnabled && isButterchurn;
@@ -2327,7 +2331,7 @@ export function initializeSettings(scrobbler, player, api, ui) {
if (butterchurnRandomizeSetting) butterchurnRandomizeSetting.style.display = showSubSettings ? 'flex' : 'none';
// Populate preset list using module-level cache (works even before visualizer initializes)
- const { keys: presetNames } = getButterchurnPresets();
+ const { keys: presetNames } = await getButterchurnPresets();
const select = butterchurnSpecificPresetSelect;
if (select && presetNames.length > 0) {
@@ -2362,7 +2366,7 @@ export function initializeSettings(scrobbler, player, api, ui) {
}
};
- const updateVisualizerSettingsVisibility = (enabled) => {
+ const updateVisualizerSettingsVisibility = async (enabled) => {
const display = enabled ? 'flex' : 'none';
if (visualizerModeSetting) visualizerModeSetting.style.display = display;
if (visualizerSmartIntensitySetting) visualizerSmartIntensitySetting.style.display = display;
@@ -2370,7 +2374,7 @@ export function initializeSettings(scrobbler, player, api, ui) {
if (visualizerPresetSetting) visualizerPresetSetting.style.display = display;
// Also update Butterchurn specific visibility
- updateButterchurnSettingsVisibility();
+ await updateButterchurnSettingsVisibility();
};
// Initialize preset select value early so visibility logic works correctly on load
@@ -2381,24 +2385,24 @@ export function initializeSettings(scrobbler, player, api, ui) {
if (visualizerEnabledToggle) {
visualizerEnabledToggle.checked = visualizerSettings.isEnabled();
- updateVisualizerSettingsVisibility(visualizerEnabledToggle.checked);
+ await updateVisualizerSettingsVisibility(visualizerEnabledToggle.checked);
- visualizerEnabledToggle.addEventListener('change', (e) => {
+ visualizerEnabledToggle.addEventListener('change', async (e) => {
visualizerSettings.setEnabled(e.target.checked);
- updateVisualizerSettingsVisibility(e.target.checked);
+ await updateVisualizerSettingsVisibility(e.target.checked);
});
}
// Visualizer Preset Select
if (visualizerPresetSelect) {
// value set above
- visualizerPresetSelect.addEventListener('change', (e) => {
+ visualizerPresetSelect.addEventListener('change', async (e) => {
const val = e.target.value;
visualizerSettings.setPreset(val);
if (ui && ui.visualizer) {
ui.visualizer.setPreset(val);
}
- updateButterchurnSettingsVisibility();
+ await updateButterchurnSettingsVisibility();
//Since changing the preset breaks the visualizer, a location.reload() is added to make sure that it works
window.location.reload();
@@ -2407,9 +2411,9 @@ export function initializeSettings(scrobbler, player, api, ui) {
if (butterchurnCycleToggle) {
butterchurnCycleToggle.checked = visualizerSettings.isButterchurnCycleEnabled();
- butterchurnCycleToggle.addEventListener('change', (e) => {
+ butterchurnCycleToggle.addEventListener('change', async (e) => {
visualizerSettings.setButterchurnCycleEnabled(e.target.checked);
- updateButterchurnSettingsVisibility();
+ await updateButterchurnSettingsVisibility();
});
}
@@ -2441,30 +2445,30 @@ export function initializeSettings(scrobbler, player, api, ui) {
}
// Refresh settings when presets are loaded asynchronously
- window.addEventListener('butterchurn-presets-loaded', () => {
+ window.addEventListener('butterchurn-presets-loaded', async () => {
console.log('[Settings] Butterchurn presets loaded event received');
- updateButterchurnSettingsVisibility();
+ await updateButterchurnSettingsVisibility();
});
// Check if presets already cached and update immediately
- const { keys: cachedKeys } = getButterchurnPresets();
+ const { keys: cachedKeys } = await getButterchurnPresets();
if (cachedKeys.length > 0) {
console.log('[Settings] Presets already cached, updating dropdown immediately');
- updateButterchurnSettingsVisibility();
+ await updateButterchurnSettingsVisibility();
}
// Watch for appearance tab becoming active and refresh presets
const appearanceTabContent = document.getElementById('settings-tab-appearance');
if (appearanceTabContent) {
- const observer = new MutationObserver((mutations) => {
- mutations.forEach((mutation) => {
+ const observer = new MutationObserver(async (mutations) => {
+ for (const mutation of mutations) {
if (mutation.type === 'attributes' && mutation.attributeName === 'class') {
if (appearanceTabContent.classList.contains('active')) {
console.log('[Settings] Appearance tab became active, refreshing presets');
- updateButterchurnSettingsVisibility();
+ await updateButterchurnSettingsVisibility();
}
}
- });
+ }
});
observer.observe(appearanceTabContent, { attributes: true });
}
diff --git a/js/ui.js b/js/ui.js
index be136c50b..0f625ab8d 100644
--- a/js/ui.js
+++ b/js/ui.js
@@ -1126,7 +1126,7 @@ export class UIRenderer {
overlay.style.display = 'flex';
- const startVisualizer = () => {
+ const startVisualizer = async () => {
if (!visualizerSettings.isEnabled()) {
if (this.visualizer) this.visualizer.stop();
return;
@@ -1136,6 +1136,7 @@ export class UIRenderer {
const canvas = document.getElementById('visualizer-canvas');
if (canvas) {
this.visualizer = new Visualizer(canvas, activeElement);
+ await this.visualizer.initPresets();
}
}
if (this.visualizer) {
@@ -1150,7 +1151,7 @@ export class UIRenderer {
this.setupUIToggleButton(overlay);
if (localStorage.getItem('epilepsy-warning-dismissed') === 'true') {
- startVisualizer();
+ await startVisualizer();
} else {
const modal = document.getElementById('epilepsy-warning-modal');
if (modal) {
@@ -1159,17 +1160,17 @@ export class UIRenderer {
const acceptBtn = document.getElementById('epilepsy-accept-btn');
const cancelBtn = document.getElementById('epilepsy-cancel-btn');
- acceptBtn.onclick = () => {
+ acceptBtn.onclick = async () => {
modal.classList.remove('active');
localStorage.setItem('epilepsy-warning-dismissed', 'true');
- startVisualizer();
+ await startVisualizer();
};
cancelBtn.onclick = () => {
modal.classList.remove('active');
this.closeFullscreenCover();
};
} else {
- startVisualizer();
+ await startVisualizer();
}
}
}
diff --git a/js/visualizer.js b/js/visualizer.js
index 2e58dae87..5cfdc2f75 100644
--- a/js/visualizer.js
+++ b/js/visualizer.js
@@ -1,10 +1,5 @@
// js/visualizer.js
import { visualizerSettings } from './storage.js';
-import { LCDPreset } from './visualizers/lcd.js';
-import { ParticlesPreset } from './visualizers/particles.js';
-import { UnknownPleasuresWebGL } from './visualizers/unknown_pleasures_webgl.js';
-import { ButterchurnPreset } from './visualizers/butterchurn.js';
-import { KawarpPreset } from './visualizers/kawarp.js';
import { audioContextManager } from './audio-context.js';
export class Visualizer {
@@ -12,21 +7,10 @@ export class Visualizer {
this.canvas = canvas;
this.ctx = null;
this.audio = audio;
-
this.audioContext = null;
this.analyser = null;
-
this.isActive = false;
this.animationId = null;
-
- this.presets = {
- lcd: new LCDPreset(),
- particles: new ParticlesPreset(),
- 'unknown-pleasures': new UnknownPleasuresWebGL(),
- butterchurn: new ButterchurnPreset(),
- kawarp: new KawarpPreset(),
- };
-
this.activePresetKey = visualizerSettings.getPreset();
// ---- AUDIO BUFFERS (REUSED) ----
@@ -51,6 +35,19 @@ export class Visualizer {
this._resizeBound = () => this.resize();
}
+ /**
+ * Must be called after class is constructed!
+ */
+ async initPresets() {
+ this.presets = {
+ lcd: new (await import('./visualizers/lcd.js')).LCDPreset(),
+ particles: new (await import('./visualizers/particles.js')).ParticlesPreset(),
+ 'unknown-pleasures': new (await import('./visualizers/unknown_pleasures_webgl.js')).UnknownPleasuresWebGL(),
+ butterchurn: new (await import('./visualizers/butterchurn.js')).ButterchurnPreset(),
+ kawarp: new (await import('./visualizers/kawarp.js')).KawarpPreset(),
+ };
+ }
+
updateDimming() {
if (!this.canvas || !this.canvas.parentElement) return;
const dimAmount = visualizerSettings.getDimAmount();
@@ -61,7 +58,7 @@ export class Visualizer {
return this.presets[this.activePresetKey] || this.presets['lcd'];
}
- init() {
+ async init() {
// Ensure shared audio context is initialized
if (!audioContextManager.isReady()) {
audioContextManager.init(this.audio);
From 0b2b91a8a4dde462e99a611b92a7c22f56f2fd60 Mon Sep 17 00:00:00 2001
From: edidealt
Date: Thu, 19 Mar 2026 20:49:16 +0000
Subject: [PATCH 134/226] fix builds
---
js/app.js | 2 +-
js/settings.js | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/js/app.js b/js/app.js
index 479f60d42..11eb00fb2 100644
--- a/js/app.js
+++ b/js/app.js
@@ -692,7 +692,7 @@ document.addEventListener('DOMContentLoaded', async () => {
// Load settings module and initialize
const { initializeSettings } = await loadSettingsModule();
- initializeSettings(scrobbler, player, api, ui);
+ await initializeSettings(scrobbler, player, api, ui);
// Track sidebar navigation clicks
document.querySelectorAll('.sidebar-nav a').forEach((link) => {
diff --git a/js/settings.js b/js/settings.js
index e43c0fa7c..8ec3c3ead 100644
--- a/js/settings.js
+++ b/js/settings.js
@@ -47,7 +47,7 @@ async function getButterchurnPresets(...args) {
return butterchurnModule.getButterchurnPresets(...args);
}
-export function initializeSettings(scrobbler, player, api, ui) {
+export async function initializeSettings(scrobbler, player, api, ui) {
// Restore last active settings tab
const savedTab = settingsUiState.getActiveTab();
const settingsTab = document.querySelector(`.settings-tab[data-tab="${savedTab}"]`);
From 5bb8713bbfffc434653049880855248e6f37ea37 Mon Sep 17 00:00:00 2001
From: binimum
Date: Thu, 19 Mar 2026 21:01:29 +0000
Subject: [PATCH 135/226] refactor: update MediaPlayer import and add platform
detection
---
js/dash-media-player.ts | 2 +-
js/player.js | 1 +
package-lock.json | 17 +++++++++++++++++
3 files changed, 19 insertions(+), 1 deletion(-)
diff --git a/js/dash-media-player.ts b/js/dash-media-player.ts
index 7c165ec81..d0e76bb82 100644
--- a/js/dash-media-player.ts
+++ b/js/dash-media-player.ts
@@ -1 +1 @@
-export { default as MediaPlayer } from '!/dashjs/src/streaming/MediaPlayer.js';
+export { MediaPlayer } from 'dashjs';
diff --git a/js/player.js b/js/player.js
index 9879ba589..94e12bb42 100644
--- a/js/player.js
+++ b/js/player.js
@@ -18,6 +18,7 @@ import {
radioSettings,
} from './storage.js';
import { audioContextManager } from './audio-context.js';
+import { isIos } from './platform-detection.js';
import { db } from './db.js';
import('./dash-media-player.js');
diff --git a/package-lock.json b/package-lock.json
index d1092c27c..1183cdba8 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -96,6 +96,7 @@
"version": "7.29.0",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"@babel/code-frame": "^7.29.0",
"@babel/generator": "^7.29.0",
@@ -1599,6 +1600,7 @@
}
],
"license": "MIT",
+ "peer": true,
"engines": {
"node": ">=18"
},
@@ -1643,6 +1645,7 @@
}
],
"license": "MIT",
+ "peer": true,
"engines": {
"node": ">=18"
}
@@ -3770,6 +3773,7 @@
"node_modules/@svta/cml-xml": {
"version": "1.0.1",
"license": "Apache-2.0",
+ "peer": true,
"engines": {
"node": ">=20"
},
@@ -3798,6 +3802,7 @@
"version": "25.5.0",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"undici-types": "~7.18.0"
}
@@ -3840,6 +3845,7 @@
"version": "8.16.0",
"dev": true,
"license": "MIT",
+ "peer": true,
"bin": {
"acorn": "bin/acorn"
},
@@ -3861,6 +3867,7 @@
"integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"fast-deep-equal": "^3.1.3",
"fast-uri": "^3.0.1",
@@ -4152,6 +4159,7 @@
}
],
"license": "MIT",
+ "peer": true,
"dependencies": {
"baseline-browser-mapping": "^2.9.0",
"caniuse-lite": "^1.0.30001759",
@@ -5143,6 +5151,7 @@
"version": "9.39.4",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"@eslint-community/eslint-utils": "^4.8.0",
"@eslint-community/regexpp": "^4.12.1",
@@ -6790,6 +6799,7 @@
"integrity": "sha512-CYDD3SOtsHtyXeEORYRx2qBtpDJFjRTGXUtmNEMGyzYOKj1TE3tycdlho7kA1Ufx9OYWZzg52QFBGALTirzDSw==",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"@keyv/serialize": "^1.1.1"
}
@@ -8834,6 +8844,7 @@
"version": "4.0.3",
"inBundle": true,
"license": "MIT",
+ "peer": true,
"engines": {
"node": ">=12"
},
@@ -9199,6 +9210,7 @@
}
],
"license": "MIT",
+ "peer": true,
"dependencies": {
"nanoid": "^3.3.11",
"picocolors": "^1.1.1",
@@ -9272,6 +9284,7 @@
"version": "7.1.1",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"cssesc": "^3.0.0",
"util-deprecate": "^1.0.2"
@@ -9577,6 +9590,7 @@
"integrity": "sha512-cIFJOD1DESzpjOBl763Kp1AH7UE/0fcdHe6rZXUdQ9c50uvgigvW97u3IcSeBwOkgqL/PXPBktBCh0KEu5L8XQ==",
"dev": true,
"license": "MIT",
+ "peer": true,
"bin": {
"rollup": "dist/bin/rollup"
},
@@ -10167,6 +10181,7 @@
}
],
"license": "MIT",
+ "peer": true,
"dependencies": {
"@csstools/css-parser-algorithms": "^3.0.5",
"@csstools/css-syntax-patches-for-csstree": "^1.0.19",
@@ -10495,6 +10510,7 @@
"version": "5.46.1",
"dev": true,
"license": "BSD-2-Clause",
+ "peer": true,
"dependencies": {
"@jridgewell/source-map": "^0.3.3",
"acorn": "^8.15.0",
@@ -10663,6 +10679,7 @@
"version": "5.9.3",
"dev": true,
"license": "Apache-2.0",
+ "peer": true,
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
From f378552028a15611cc86fa088605e7fe12148d8d Mon Sep 17 00:00:00 2001
From: binimum
Date: Fri, 20 Mar 2026 00:33:05 +0000
Subject: [PATCH 136/226] refactor: add formatTemplate import to downloads.js
---
js/downloads.js | 1 +
1 file changed, 1 insertion(+)
diff --git a/js/downloads.js b/js/downloads.js
index e7b000b5b..987a0b880 100644
--- a/js/downloads.js
+++ b/js/downloads.js
@@ -7,6 +7,7 @@ import {
getTrackTitle,
getCoverBlob,
getExtensionFromBlob,
+ formatTemplate,
escapeHtml,
getTrackDiscNumber,
} from './utils.js';
From 47b8c36594113cfe275860ac4e75907768797e2b Mon Sep 17 00:00:00 2001
From: Daniel <790119+DanTheMan827@users.noreply.github.com>
Date: Fri, 20 Mar 2026 11:26:49 -0500
Subject: [PATCH 137/226] fix(api): use direct queries when possible
---
js/HiFi.ts | 688 +++++++++++++++++++++++++++++++++++++++++++++++++++++
js/api.js | 16 ++
2 files changed, 704 insertions(+)
create mode 100644 js/HiFi.ts
diff --git a/js/HiFi.ts b/js/HiFi.ts
new file mode 100644
index 000000000..0cc222ae5
--- /dev/null
+++ b/js/HiFi.ts
@@ -0,0 +1,688 @@
+const API_VERSION = '2.6';
+const CLIENT_ID = 'txNoH4kkV41MfH25';
+const CLIENT_SECRET = 'dQjy0MinCEvxi1O4UmxvxWnDjt4cgHBPw8ll6nYBk98=';
+
+type Params = Record;
+
+class ResponseError extends Error {
+ status: number;
+ constructor(status: number, message: string) {
+ super(message);
+ this.status = status;
+ }
+}
+
+export class TidalResponse extends Response {
+ constructor(body: BodyInit, init?: ResponseInit) {
+ super(body, init);
+ }
+}
+
+export class HiFiClient {
+ private static token: string | null;
+ private countryCode: string;
+ private static appTokenExpiry = 0;
+ private static albumTracksMax = 20;
+ private static albumTracksActive = 0;
+ private static albumTracksQueue: Array<() => void> = [];
+
+ private static buildUrl(base: string, params?: Params) {
+ if (!params) return base;
+ const u = new URL(base);
+ Object.entries(params)
+ .filter(([, v]) => v !== undefined && v !== null && v !== '')
+ .forEach(([k, v]) => u.searchParams.set(k, String(v)));
+ return u.toString();
+ }
+
+ private encodeBasic(id: string, secret: string) {
+ if (typeof window !== 'undefined' && typeof window.btoa === 'function') {
+ return window.btoa(`${id}:${secret}`);
+ }
+ // Node fallback
+ return Buffer.from(`${id}:${secret}`).toString('base64');
+ }
+
+ private async fetchAppToken(signal: AbortSignal = new AbortController().signal): Promise {
+ const now = Date.now();
+ if (HiFiClient.token && now < HiFiClient.appTokenExpiry) return HiFiClient.token;
+
+ const res = await fetch('https://auth.tidal.com/v1/oauth2/token', {
+ method: 'POST',
+ headers: {
+ 'content-type': 'application/x-www-form-urlencoded',
+ authorization: `Basic ${this.encodeBasic(CLIENT_ID, CLIENT_SECRET)}`,
+ },
+ body: new URLSearchParams({
+ grant_type: 'client_credentials',
+ client_id: CLIENT_ID,
+ client_secret: CLIENT_SECRET,
+ }),
+ signal,
+ });
+
+ if (!res.ok) {
+ const txt = await res.text().catch(() => '');
+ throw new Error(`Failed to obtain app token: ${res.status} ${txt}`);
+ }
+
+ const json = await res.json();
+ const token = json.access_token;
+ const expires_in = json.expires_in ?? 3600;
+ HiFiClient.token = token;
+ HiFiClient.appTokenExpiry = Date.now() + expires_in * 1000 - 60_000;
+ return token;
+ }
+
+ constructor(countryCode = 'US') {
+ this.countryCode = countryCode;
+ }
+
+ private async fetchJson(url: string, params?: Params, signal: AbortSignal = new AbortController().signal) {
+ const final = HiFiClient.buildUrl(url, params);
+ const res = await fetch(final, {
+ headers: { authorization: `Bearer ${await this.fetchAppToken(signal)}` },
+ signal,
+ });
+
+ if (!res.ok) {
+ throw new ResponseError(res.status, res.statusText);
+ }
+
+ return res.json();
+ }
+
+ private static extractUuidFromTidalUrl(href?: string | null) {
+ if (!href) return null;
+ const parts = href.split('/');
+ return parts.length >= 9 ? parts.slice(4, 9).join('-') : null;
+ }
+
+ private async withAlbumTrackSlot(fn: () => Promise) {
+ if (HiFiClient.albumTracksActive >= HiFiClient.albumTracksMax) {
+ await new Promise((res) => HiFiClient.albumTracksQueue.push(res));
+ }
+ HiFiClient.albumTracksActive++;
+ try {
+ return await fn();
+ } finally {
+ HiFiClient.albumTracksActive--;
+ const next = HiFiClient.albumTracksQueue.shift();
+ if (next) next();
+ }
+ }
+
+ async getInfo(id: number, signal?: AbortSignal) {
+ const url = `https://api.tidal.com/v1/tracks/${id}/`;
+ const data = await this.fetchJson(url, { countryCode: this.countryCode }, signal);
+ return { version: API_VERSION, data };
+ }
+
+ async getTrack(id: number, quality = 'HI_RES_LOSSLESS', signal?: AbortSignal) {
+ const url = `https://api.tidal.com/v1/tracks/${id}/playbackinfo`;
+ const params = {
+ audioquality: quality,
+ playbackmode: 'STREAM',
+ assetpresentation: 'FULL',
+ countryCode: this.countryCode,
+ };
+ const data = await this.fetchJson(url, params, signal);
+ return { version: API_VERSION, data };
+ }
+
+ async getRecommendations(id: number, signal?: AbortSignal) {
+ const url = `https://api.tidal.com/v1/tracks/${id}/recommendations`;
+ const data = await this.fetchJson(url, { limit: '20', countryCode: this.countryCode }, signal);
+ return { version: API_VERSION, data };
+ }
+
+ async getSimilarArtists(id: number, cursor?: string | number | null, signal?: AbortSignal) {
+ const url = `https://openapi.tidal.com/v2/artists/${id}/relationships/similarArtists`;
+ const params: Params = {
+ 'page[cursor]': cursor ?? undefined,
+ countryCode: this.countryCode,
+ include: 'similarArtists,similarArtists.profileArt',
+ };
+
+ const payload = await this.fetchJson(url, params, signal);
+ const included = Array.isArray(payload?.included) ? payload.included : [];
+ const artists_map: Record = {};
+ const artworks_map: Record = {};
+ for (const i of included) {
+ if (i.type === 'artists') artists_map[i.id] = i;
+ if (i.type === 'artworks') artworks_map[i.id] = i;
+ }
+
+ const resolveArtist = (entry: any) => {
+ const aid = entry.id;
+ const inc = artists_map[aid] || {};
+ const attr = inc.attributes || {};
+
+ let pic_id: string | null = null;
+ const art_data = inc.relationships?.profileArt?.data;
+ if (Array.isArray(art_data) && art_data.length > 0) {
+ const artwork = artworks_map[art_data[0].id];
+ const files = artwork?.attributes?.files;
+ if (Array.isArray(files) && files[0]?.href) {
+ pic_id = HiFiClient.extractUuidFromTidalUrl(files[0].href);
+ }
+ }
+
+ return {
+ ...attr,
+ id: String(aid).match(/^\d+$/) ? Number(aid) : aid,
+ picture: pic_id || attr.selectedAlbumCoverFallback,
+ url: `http://www.tidal.com/artist/${aid}`,
+ relationType: 'SIMILAR_ARTIST',
+ };
+ };
+
+ return { version: API_VERSION, artists: (payload?.data || []).map(resolveArtist) };
+ }
+
+ async getSimilarAlbums(id: number, cursor?: string | number | null, signal?: AbortSignal) {
+ const url = `https://openapi.tidal.com/v2/albums/${id}/relationships/similarAlbums`;
+ const params: Params = {
+ 'page[cursor]': cursor ?? undefined,
+ countryCode: this.countryCode,
+ include: 'similarAlbums,similarAlbums.coverArt,similarAlbums.artists',
+ };
+
+ const payload = await this.fetchJson(url, params, signal);
+ const included = Array.isArray(payload?.included) ? payload.included : [];
+ const albums_map: Record = {};
+ const artworks_map: Record = {};
+ const artists_map: Record = {};
+ for (const i of included) {
+ if (i.type === 'albums') albums_map[i.id] = i;
+ if (i.type === 'artworks') artworks_map[i.id] = i;
+ if (i.type === 'artists') artists_map[i.id] = i;
+ }
+
+ const resolveAlbum = (entry: any) => {
+ const aid = entry.id;
+ const inc = albums_map[aid] || {};
+ const attr = inc.attributes || {};
+
+ let cover_id: string | null = null;
+ const art_data = inc.relationships?.coverArt?.data;
+ if (Array.isArray(art_data) && art_data.length > 0) {
+ const artwork = artworks_map[art_data[0].id];
+ const files = artwork?.attributes?.files;
+ if (Array.isArray(files) && files[0]?.href) {
+ cover_id = HiFiClient.extractUuidFromTidalUrl(files[0].href);
+ }
+ }
+
+ const artist_list: any[] = [];
+ const artists_data = inc.relationships?.artists?.data;
+ if (Array.isArray(artists_data)) {
+ for (const a_entry of artists_data) {
+ const a_obj = artists_map[a_entry.id];
+ if (a_obj) {
+ const a_id = a_obj.id;
+ artist_list.push({
+ id: String(a_id).match(/^\d+$/) ? Number(a_id) : a_id,
+ name: a_obj.attributes?.name,
+ });
+ }
+ }
+ }
+
+ return {
+ ...attr,
+ id: String(aid).match(/^\d+$/) ? Number(aid) : aid,
+ cover: cover_id,
+ artists: artist_list,
+ url: `http://www.tidal.com/album/${aid}`,
+ };
+ };
+
+ return { version: API_VERSION, albums: (payload?.data || []).map(resolveAlbum) };
+ }
+
+ async getArtist(id?: number | null, f?: number | null, skip_tracks = false, signal?: AbortSignal) {
+ if (!id && !f) throw new ResponseError(400, 'Provide id or f query param');
+
+ if (id) {
+ const artist_url = `https://api.tidal.com/v1/artists/${id}`;
+ const artist_data = await this.fetchJson(artist_url, { countryCode: this.countryCode }, signal);
+
+ let picture = artist_data.picture;
+ const fallback = artist_data.selectedAlbumCoverFallback;
+ if (!picture && fallback) {
+ artist_data.picture = fallback;
+ picture = fallback;
+ }
+
+ let cover = null;
+ if (picture) {
+ const slug = picture.replace(/-/g, '/');
+ cover = {
+ id: artist_data.id,
+ name: artist_data.name,
+ '750': `https://resources.tidal.com/images/${slug}/750x750.jpg`,
+ };
+ }
+
+ return { version: API_VERSION, artist: artist_data, cover };
+ }
+
+ // f provided -> gather albums and optionally tracks
+ const albums_url = `https://api.tidal.com/v1/artists/${f}/albums`;
+ const common_params: Params = { countryCode: this.countryCode, limit: 50 };
+
+ const tasks: Promise[] = [
+ this.fetchJson(albums_url, common_params, signal),
+ this.fetchJson(albums_url, { ...common_params, filter: 'EPSANDSINGLES' }, signal),
+ ];
+
+ if (skip_tracks) {
+ tasks.push(
+ this.fetchJson(
+ `https://api.tidal.com/v1/artists/${f}/toptracks`,
+ { countryCode: this.countryCode, limit: 15 },
+ signal
+ )
+ );
+ }
+
+ const results = await Promise.all(tasks.map((p) => p.catch((e) => e)));
+
+ const unique_releases: any[] = [];
+ const seen_ids = new Set();
+
+ for (const res of results.slice(0, 2)) {
+ if (res && !(res instanceof Error)) {
+ const data = res;
+ const items = Array.isArray(data?.items) ? data.items : data || [];
+ if (Array.isArray(items)) {
+ for (const item of items) {
+ if (item && item.id && !seen_ids.has(item.id)) {
+ unique_releases.push(item);
+ seen_ids.add(item.id);
+ }
+ }
+ }
+ }
+ }
+
+ const album_ids: number[] = unique_releases.map((i) => i.id).filter(Boolean);
+ const page_data = { items: unique_releases };
+
+ if (skip_tracks) {
+ let top_tracks: any[] = [];
+ if (results.length > 2) {
+ const res = results[2];
+ if (res && !(res instanceof Error)) {
+ top_tracks = Array.isArray(res.items) ? res.items : res || [];
+ }
+ }
+
+ return { version: API_VERSION, albums: page_data, tracks: top_tracks };
+ }
+
+ if (!album_ids.length) return { version: API_VERSION, albums: page_data, tracks: [] };
+
+ const fetchAlbumTracks = async (album_id: number) => {
+ return await this.withAlbumTrackSlot(async () => {
+ const album_data = await this.fetchJson(
+ 'https://api.tidal.com/v1/pages/album',
+ { albumId: album_id, countryCode: this.countryCode, deviceType: 'BROWSER' },
+ signal
+ );
+ const rows = Array.isArray(album_data?.rows) ? album_data.rows : [];
+ if (rows.length < 2) return [];
+ const modules = rows[1].modules || [];
+ if (!modules || modules.length === 0) return [];
+ const paged_list = modules[0].pagedList || {};
+ const items = paged_list.items || [];
+ const tracks = items.map((t: any) => (t.item ? t.item : t));
+ return tracks;
+ });
+ };
+
+ const trackResults = await Promise.all(album_ids.map((aid) => fetchAlbumTracks(aid).catch(() => [])));
+ const tracks: any[] = [];
+ for (const t of trackResults) {
+ if (Array.isArray(t)) tracks.push(...t);
+ }
+
+ return { version: API_VERSION, albums: page_data, tracks };
+ }
+
+ private buildCoverEntry(cover_slug: string, name?: string | null, track_id?: number | null) {
+ const slug = cover_slug.replace(/-/g, '/');
+ return {
+ id: track_id,
+ name,
+ '1280': `https://resources.tidal.com/images/${slug}/1280x1280.jpg`,
+ '640': `https://resources.tidal.com/images/${slug}/640x640.jpg`,
+ '80': `https://resources.tidal.com/images/${slug}/80x80.jpg`,
+ };
+ }
+
+ async getCover(id?: number | null, q?: string | null, signal?: AbortSignal) {
+ if (!id && !q) throw new ResponseError(400, 'Provide id or q query param');
+
+ if (id) {
+ const track_data = await this.fetchJson(
+ `https://api.tidal.com/v1/tracks/${id}/`,
+ { countryCode: this.countryCode },
+ signal
+ );
+ const album = track_data.album || {};
+ const cover_slug = album.cover;
+ if (!cover_slug) throw new ResponseError(404, 'Cover not found');
+ const entry = this.buildCoverEntry(cover_slug, album.title || track_data.title, album.id || id);
+ return { version: API_VERSION, covers: [entry] };
+ }
+
+ const search_data = await this.fetchJson(
+ 'https://api.tidal.com/v1/search/tracks',
+ { countryCode: this.countryCode, query: q, limit: 10 },
+ signal
+ );
+ const items = Array.isArray(search_data?.items) ? search_data.items.slice(0, 10) : [];
+ if (!items.length) throw new ResponseError(404, 'Cover not found');
+ const covers: any[] = [];
+ for (const track of items) {
+ const album = track.album || {};
+ const cover_slug = album.cover;
+ if (!cover_slug) continue;
+ covers.push(this.buildCoverEntry(cover_slug, track.title, track.id));
+ }
+ if (!covers.length) throw new ResponseError(404, 'Cover not found');
+ return { version: API_VERSION, covers };
+ }
+
+ async search(
+ options: {
+ s?: string;
+ a?: string;
+ al?: string;
+ v?: string;
+ p?: string;
+ i?: string;
+ offset?: number;
+ limit?: number;
+ },
+ signal?: AbortSignal
+ ) {
+ const { s, a, al, v, p, i, offset = 0, limit = 25 } = options;
+
+ if (i) {
+ // try filtered track search first
+ try {
+ const res = await this.fetchJson(
+ 'https://api.tidal.com/v1/tracks',
+ {
+ 'filter[isrc]': i,
+ limit,
+ offset,
+ countryCode: this.countryCode,
+ },
+ signal
+ );
+ return { version: API_VERSION, data: res };
+ } catch (err: any) {
+ if (err.status && ![400, 404].includes(err.status)) throw err;
+ // fallback to text search
+ }
+ const fallback = await this.fetchJson(
+ 'https://api.tidal.com/v1/search/tracks',
+ {
+ query: i,
+ limit,
+ offset,
+ countryCode: this.countryCode,
+ },
+ signal
+ );
+ return { version: API_VERSION, data: fallback };
+ }
+
+ const mapping: Array<[string | undefined, string, Params]> = [
+ [s, 'https://api.tidal.com/v1/search/tracks', { query: s, limit, offset, countryCode: this.countryCode }],
+ [
+ a,
+ 'https://api.tidal.com/v1/search/top-hits',
+ { query: a, limit, offset, types: 'ARTISTS,TRACKS', countryCode: this.countryCode },
+ ],
+ [
+ al,
+ 'https://api.tidal.com/v1/search/top-hits',
+ { query: al, limit, offset, types: 'ALBUMS', countryCode: this.countryCode },
+ ],
+ [
+ v,
+ 'https://api.tidal.com/v1/search/top-hits',
+ { query: v, limit, offset, types: 'VIDEOS', countryCode: this.countryCode },
+ ],
+ [
+ p,
+ 'https://api.tidal.com/v1/search/top-hits',
+ { query: p, limit, offset, types: 'PLAYLISTS', countryCode: this.countryCode },
+ ],
+ ];
+
+ for (const [val, url, params] of mapping) {
+ if (val) {
+ const data = await this.fetchJson(url, params, signal);
+ return { version: API_VERSION, data };
+ }
+ }
+
+ throw new Error('Provide one of s, a, al, v, p, or i');
+ }
+
+ async getAlbum(id: number, limit = 100, offset = 0, signal?: AbortSignal) {
+ const albumUrl = `https://api.tidal.com/v1/albums/${id}`;
+ const itemsUrl = `https://api.tidal.com/v1/albums/${id}/items`;
+ const tasks: Promise[] = [this.fetchJson(albumUrl, { countryCode: this.countryCode }, signal)];
+
+ let remaining = limit;
+ let currentOffset = offset;
+ const maxChunk = 100;
+ while (remaining > 0) {
+ const chunk = Math.min(remaining, maxChunk);
+ tasks.push(
+ this.fetchJson(itemsUrl, { countryCode: this.countryCode, limit: chunk, offset: currentOffset }, signal)
+ );
+ currentOffset += chunk;
+ remaining -= chunk;
+ }
+
+ const results = await Promise.all(tasks);
+ const albumData = results[0];
+ const pages = results.slice(1);
+ const allItems: any[] = [];
+ for (const p of pages) {
+ const pageItems = (p && p.items) || p;
+ if (Array.isArray(pageItems)) allItems.push(...pageItems);
+ }
+ albumData.items = allItems;
+ return { version: API_VERSION, data: albumData };
+ }
+
+ async getMix(id: string, signal?: AbortSignal) {
+ const url = 'https://api.tidal.com/v1/pages/mix';
+ const data = await this.fetchJson(
+ url,
+ { mixId: id, countryCode: this.countryCode, deviceType: 'BROWSER' },
+ signal
+ );
+ let header = {},
+ items: any[] = [];
+ const rows = data.rows || [];
+ for (const row of rows) {
+ for (const module of row.modules || []) {
+ if (module.type === 'MIX_HEADER') header = module.mix || {};
+ if (module.type === 'TRACK_LIST') items = (module.pagedList || {}).items || [];
+ }
+ }
+ return { version: API_VERSION, mix: header, items: items.map((it: any) => (it.item ? it.item : it)) };
+ }
+
+ async getPlaylist(id: string, limit = 100, offset = 0, signal?: AbortSignal) {
+ const playlistUrl = `https://api.tidal.com/v1/playlists/${id}`;
+ const itemsUrl = `https://api.tidal.com/v1/playlists/${id}/items`;
+ const [playlistData, itemsData] = await Promise.all([
+ this.fetchJson(playlistUrl, { countryCode: this.countryCode }, signal),
+ this.fetchJson(itemsUrl, { countryCode: this.countryCode, limit, offset }, signal),
+ ]);
+ const items = (itemsData && itemsData.items) || itemsData;
+ return { version: API_VERSION, playlist: playlistData, items };
+ }
+
+ // simplified artist/cover/lyrics/video/topvideos/similar methods (same pattern)
+ async getLyrics(id: number, signal?: AbortSignal) {
+ const url = `https://api.tidal.com/v1/tracks/${id}/lyrics`;
+ const data = await this.fetchJson(
+ url,
+ { countryCode: this.countryCode, locale: 'en_US', deviceType: 'BROWSER' },
+ signal
+ );
+ if (!data) {
+ const err: any = new Error('Lyrics not found');
+ err.status = 404;
+ throw err;
+ }
+ return { version: API_VERSION, lyrics: data };
+ }
+
+ async getVideo(id: number, quality = 'HIGH', mode = 'STREAM', presentation = 'FULL', signal?: AbortSignal) {
+ const url = `https://api.tidal.com/v1/videos/${id}/playbackinfo`;
+ const data = await this.fetchJson(
+ url,
+ { videoquality: quality, playbackmode: mode, assetpresentation: presentation },
+ signal
+ );
+ return { version: API_VERSION, video: data };
+ }
+
+ async getTopVideos(
+ { countryCode = 'US', locale = 'en_US', deviceType = 'BROWSER', limit = 25, offset = 0 } = {},
+ signal?: AbortSignal
+ ) {
+ const url = 'https://api.tidal.com/v1/pages/mymusic_recommended_videos';
+ const data = await this.fetchJson(url, { countryCode, locale, deviceType }, signal);
+ const rows = data.rows || [];
+ const videos: any[] = [];
+ for (const row of rows) {
+ for (const module of row.modules || []) {
+ const mt = module.type;
+ if (['VIDEO_PLAYLIST', 'VIDEO_ROW', 'PAGED_LIST'].includes(mt)) {
+ const items = (module.pagedList || {}).items || [];
+ for (const item of items) videos.push(item.item || item);
+ } else if (mt === 'VIDEO' || (mt && mt.toLowerCase().includes('video'))) {
+ const it = module.item || module;
+ if (typeof it === 'object') videos.push(it);
+ }
+ }
+ }
+ return { version: API_VERSION, videos: videos.slice(offset, offset + limit), total: videos.length };
+ }
+
+ async queryResponse(pathOrUrl: string, signal?: AbortSignal) {
+ try {
+ return new TidalResponse(JSON.stringify(await this.query(pathOrUrl, signal)), {
+ headers: { 'Content-Type': 'application/json' },
+ });
+ } catch (err: any) {
+ if (err instanceof ResponseError) {
+ return new TidalResponse(JSON.stringify({ error: err.message }), {
+ status: err.status,
+ headers: { 'Content-Type': 'application/json' },
+ });
+ }
+
+ throw err;
+ }
+ }
+
+ // generic helper that accepts local route strings like "/info/?id=123" or full URLs
+ async query(pathOrUrl: string, signal?: AbortSignal) {
+ // normalize: if starts with http use as-is, else treat as local route
+ try {
+ const u = new URL(pathOrUrl, 'http://localhost');
+ const pathname = u.pathname.replace(/\/+$/, '') || '/';
+ const qp: Record = {};
+ u.searchParams.forEach((v, k) => (qp[k] = v));
+
+ switch (pathname) {
+ case '/':
+ return { version: API_VERSION, Repo: 'https://github.com/binimum/hifi-api' };
+ case '/info':
+ return await this.getInfo(Number(qp.id));
+ case '/track':
+ return await this.getTrack(Number(qp.id), qp.quality || undefined);
+ case '/recommendations':
+ return await this.getRecommendations(Number(qp.id));
+ case '/artist/similar':
+ return await this.getSimilarArtists(Number(qp.id), qp.cursor ?? undefined, signal);
+ case '/album/similar':
+ return await this.getSimilarAlbums(Number(qp.id), qp.cursor ?? undefined, signal);
+ case '/artist':
+ return await this.getArtist(
+ qp.id ? Number(qp.id) : undefined,
+ qp.f ? Number(qp.f) : undefined,
+ qp.skip_tracks === 'true' || qp.skip_tracks === '1' || qp.skip_tracks === 'True',
+ signal
+ );
+ case '/cover':
+ return await this.getCover(qp.id ? Number(qp.id) : undefined, qp.q ?? undefined, signal);
+ case '/search':
+ return await this.search({
+ s: qp.s,
+ a: qp.a,
+ al: qp.al,
+ v: qp.v,
+ p: qp.p,
+ i: qp.i,
+ offset: qp.offset ? Number(qp.offset) : undefined,
+ limit: qp.limit ? Number(qp.limit) : undefined,
+ });
+ case '/album':
+ return await this.getAlbum(
+ Number(qp.id),
+ qp.limit ? Number(qp.limit) : undefined,
+ qp.offset ? Number(qp.offset) : undefined
+ );
+ case '/playlist':
+ return await this.getPlaylist(
+ qp.id || '',
+ qp.limit ? Number(qp.limit) : undefined,
+ qp.offset ? Number(qp.offset) : undefined
+ );
+ case '/mix':
+ return await this.getMix(qp.id || '');
+ case '/lyrics':
+ return await this.getLyrics(Number(qp.id));
+ case '/video':
+ return await this.getVideo(
+ Number(qp.id),
+ qp.quality || undefined,
+ qp.mode || undefined,
+ qp.presentation || undefined
+ );
+ case '/topvideos':
+ return await this.getTopVideos({
+ countryCode: qp.countryCode || undefined,
+ locale: qp.locale || undefined,
+ deviceType: qp.deviceType || undefined,
+ limit: qp.limit ? Number(qp.limit) : undefined,
+ offset: qp.offset ? Number(qp.offset) : undefined,
+ });
+ default:
+ // unknown local route => treat as raw upstream path (forward)
+ if (pathOrUrl.startsWith('http')) {
+ return await this.fetchJson(pathOrUrl);
+ }
+ throw new Error(`Unknown route: ${pathname}`);
+ }
+ } catch (err) {
+ throw err;
+ }
+ }
+}
diff --git a/js/api.js b/js/api.js
index b61b7b54d..383a2e73c 100644
--- a/js/api.js
+++ b/js/api.js
@@ -21,10 +21,12 @@ import { isCustomFormat } from './ffmpegFormats.ts';
import { DownloadProgress } from './progressEvents.js';
import { resolveDownloadTotalBytes } from './downloadProgressUtils.js';
import { readableStreamIterator } from './readableStreamIterator.js';
+import { HiFiClient } from './HiFi.ts';
export const DASH_MANIFEST_UNAVAILABLE_CODE = 'DASH_MANIFEST_UNAVAILABLE';
export { resolveDownloadTotalBytes };
const TIDAL_V2_TOKEN = 'txNoH4kkV41MfH25';
+const client = new HiFiClient();
export class LosslessAPI {
constructor(settings) {
@@ -54,6 +56,20 @@ export class LosslessAPI {
async fetchWithRetry(relativePath, options = {}) {
const type = options.type || 'api';
+ const instanceRoutes = ['/track', '/album/similar', '/artist/similar', '/video'];
+
+ if (!instanceRoutes.some((route) => relativePath.startsWith(route))) {
+ try {
+ console.log(relativePath);
+ return await client.queryResponse(relativePath);
+ } catch (err) {
+ console.warn(
+ `Direct fetch failed for ${relativePath}. Falling back to configured API instances...`,
+ err
+ );
+ }
+ }
+
let instances = await this.settings.getInstances(type);
if (instances.length === 0) {
throw new Error(`No API instances configured for type: ${type}`);
From b48ee588aa348845c49299e12006010dfc0b94a7 Mon Sep 17 00:00:00 2001
From: Daniel <790119+DanTheMan827@users.noreply.github.com>
Date: Fri, 20 Mar 2026 11:26:49 -0500
Subject: [PATCH 138/226] fix(api): don't cache direct queries
This also adds a global `window.allTidal` flag to force all queries to go through the API, which is useful for testing.
---
js/api.js | 76 ++++++++++++++++++++++++++++++++++++++++---------------
1 file changed, 56 insertions(+), 20 deletions(-)
diff --git a/js/api.js b/js/api.js
index 383a2e73c..2b6f394f5 100644
--- a/js/api.js
+++ b/js/api.js
@@ -21,7 +21,7 @@ import { isCustomFormat } from './ffmpegFormats.ts';
import { DownloadProgress } from './progressEvents.js';
import { resolveDownloadTotalBytes } from './downloadProgressUtils.js';
import { readableStreamIterator } from './readableStreamIterator.js';
-import { HiFiClient } from './HiFi.ts';
+import { HiFiClient, TidalResponse } from './HiFi.ts';
export const DASH_MANIFEST_UNAVAILABLE_CODE = 'DASH_MANIFEST_UNAVAILABLE';
export { resolveDownloadTotalBytes };
@@ -58,7 +58,7 @@ export class LosslessAPI {
const type = options.type || 'api';
const instanceRoutes = ['/track', '/album/similar', '/artist/similar', '/video'];
- if (!instanceRoutes.some((route) => relativePath.startsWith(route))) {
+ if (window.allTidal == true || !instanceRoutes.some((route) => relativePath.startsWith(route))) {
try {
console.log(relativePath);
return await client.queryResponse(relativePath);
@@ -435,7 +435,9 @@ export class LosslessAPI {
items: preparedTracks,
};
- await this.cache.set('search_tracks', query, result);
+ if (!(response instanceof TidalResponse)) {
+ await this.cache.set('search_tracks', query, result);
+ }
return result;
} catch (error) {
if (error.name === 'AbortError') throw error;
@@ -457,7 +459,9 @@ export class LosslessAPI {
items: normalized.items.map((a) => this.prepareArtist(a)),
};
- await this.cache.set('search_artists', query, result);
+ if (!(response instanceof TidalResponse)) {
+ await this.cache.set('search_artists', query, result);
+ }
return result;
} catch (error) {
if (error.name === 'AbortError') throw error;
@@ -480,7 +484,9 @@ export class LosslessAPI {
items: this.deduplicateAlbums(preparedItems),
};
- await this.cache.set('search_albums', query, result);
+ if (!(response instanceof TidalResponse)) {
+ await this.cache.set('search_albums', query, result);
+ }
return result;
} catch (error) {
if (error.name === 'AbortError') throw error;
@@ -502,7 +508,9 @@ export class LosslessAPI {
items: normalized.items.map((p) => this.preparePlaylist(p)),
};
- await this.cache.set('search_playlists', query, result);
+ if (!(response instanceof TidalResponse)) {
+ await this.cache.set('search_playlists', query, result);
+ }
return result;
} catch (error) {
if (error.name === 'AbortError') throw error;
@@ -527,7 +535,9 @@ export class LosslessAPI {
items: normalized.items.map((v) => this.prepareVideo(v)),
};
- await this.cache.set('search_videos', query, result);
+ if (!(response instanceof TidalResponse)) {
+ await this.cache.set('search_videos', query, result);
+ }
return result;
} catch (error) {
if (error.name === 'AbortError') throw error;
@@ -554,7 +564,9 @@ export class LosslessAPI {
originalTrackUrl: data.OriginalTrackUrl || null,
};
- await this.cache.set('video', id, result);
+ if (!(response instanceof TidalResponse)) {
+ await this.cache.set('video', id, result);
+ }
return result;
}
@@ -683,7 +695,9 @@ export class LosslessAPI {
const result = { album, tracks };
- await this.cache.set('album', id, result);
+ if (!(response instanceof TidalResponse)) {
+ await this.cache.set('album', id, result);
+ }
return result;
}
@@ -793,7 +807,9 @@ export class LosslessAPI {
const result = { playlist, tracks };
- await this.cache.set('playlist', id, result);
+ if (!(response instanceof TidalResponse)) {
+ await this.cache.set('playlist', id, result);
+ }
return result;
}
@@ -827,7 +843,9 @@ export class LosslessAPI {
};
const result = { mix, tracks };
- await this.cache.set('mix', id, result);
+ if (!(response instanceof TidalResponse)) {
+ await this.cache.set('mix', id, result);
+ }
return result;
}
@@ -984,7 +1002,9 @@ export class LosslessAPI {
const result = { ...artist, albums, eps, tracks, videos };
- await this.cache.set('artist', cacheKey, result);
+ if (!(primaryResponse instanceof TidalResponse) && !(contentResponse instanceof TidalResponse)) {
+ await this.cache.set('artist', cacheKey, result);
+ }
return result;
}
@@ -1004,7 +1024,9 @@ export class LosslessAPI {
const result = items.map((artist) => this.prepareArtist(artist));
- await this.cache.set('similar_artists', artistId, result);
+ if (!(response instanceof TidalResponse)) {
+ await this.cache.set('similar_artists', artistId, result);
+ }
return result;
} catch (e) {
console.warn('Failed to fetch similar artists:', e);
@@ -1032,7 +1054,9 @@ export class LosslessAPI {
text: data.text,
source: data.source || 'Tidal',
};
- await this.cache.set('artist', cacheKey, bio);
+ if (!(response instanceof TidalResponse)) {
+ await this.cache.set('artist', cacheKey, bio);
+ }
return bio;
}
}
@@ -1057,7 +1081,9 @@ export class LosslessAPI {
const result = items.map((album) => this.prepareAlbum(album));
- await this.cache.set('similar_albums', albumId, result);
+ if (!(response instanceof TidalResponse)) {
+ await this.cache.set('similar_albums', albumId, result);
+ }
return result;
} catch (e) {
console.warn('Failed to fetch similar albums:', e);
@@ -1197,7 +1223,9 @@ export class LosslessAPI {
if (found) {
track = this.prepareTrack(found.item || found);
- await this.cache.set('track', cacheKey, track);
+ if (!(response instanceof TidalResponse)) {
+ await this.cache.set('track', cacheKey, track);
+ }
return track;
}
@@ -1219,7 +1247,9 @@ export class LosslessAPI {
const items = data.items || [];
const tracks = items.map((item) => this.prepareTrack(item.track || item));
- await this.cache.set('recommendations', id, tracks);
+ if (!(response instanceof TidalResponse)) {
+ await this.cache.set('recommendations', id, tracks);
+ }
return tracks;
} catch (error) {
console.error('Failed to fetch recommendations:', error);
@@ -1236,7 +1266,9 @@ export class LosslessAPI {
const jsonResponse = await response.json();
const result = this.parseTrackLookup(this.normalizeTrackResponse(jsonResponse));
- await this.cache.set('track', cacheKey, result);
+ if (!(response instanceof TidalResponse)) {
+ await this.cache.set('track', cacheKey, result);
+ }
return result;
}
@@ -1259,7 +1291,9 @@ export class LosslessAPI {
}
}
- this.streamCache.set(cacheKey, streamUrl);
+ if (!(lookup instanceof TidalResponse)) {
+ this.streamCache.set(cacheKey, streamUrl);
+ }
return streamUrl;
}
@@ -1304,7 +1338,9 @@ export class LosslessAPI {
throw new Error(`Could not resolve video stream URL for ID: ${id}`);
}
- this.streamCache.set(cacheKey, streamUrl);
+ if (!(lookup instanceof TidalResponse)) {
+ this.streamCache.set(cacheKey, streamUrl);
+ }
return streamUrl;
}
From 8edca3678ddd1d5b8be2caf71b999de3df2eef89 Mon Sep 17 00:00:00 2001
From: Daniel <790119+DanTheMan827@users.noreply.github.com>
Date: Fri, 20 Mar 2026 11:57:38 -0500
Subject: [PATCH 139/226] fix(api): remove domain checks from searchVideos
---
js/api.js | 1 -
1 file changed, 1 deletion(-)
diff --git a/js/api.js b/js/api.js
index 2b6f394f5..70b9138ac 100644
--- a/js/api.js
+++ b/js/api.js
@@ -526,7 +526,6 @@ export class LosslessAPI {
try {
const response = await this.fetchWithRetry(`/search/?v=${encodeURIComponent(query)}`, {
...options,
- allowedDomains: ['api.monochrome.tf', 'arran.monochrome.tf'],
});
const data = await response.json();
const normalized = this.normalizeSearchResponse(data, 'videos');
From f2b8cdc812c2522eabe3c2619bb8e815e9129d1e Mon Sep 17 00:00:00 2001
From: Daniel <790119+DanTheMan827@users.noreply.github.com>
Date: Fri, 20 Mar 2026 12:52:07 -0500
Subject: [PATCH 140/226] feat(downloads): add metadata to videos
---
js/api.js | 94 ++++++++++++++++++++++++++------------------------
js/metadata.js | 6 ++--
2 files changed, 52 insertions(+), 48 deletions(-)
diff --git a/js/api.js b/js/api.js
index 70b9138ac..096435886 100644
--- a/js/api.js
+++ b/js/api.js
@@ -15,7 +15,7 @@ import { APICache } from './cache.js';
import { DashDownloader } from './dash-downloader.ts';
import { HlsDownloader } from './hls-downloader.js';
import { MP3EncodingError } from './mp3-encoder.js';
-import { loadFfmpeg, FfmpegError } from './ffmpeg.js';
+import { loadFfmpeg, FfmpegError, ffmpeg } from './ffmpeg.js';
import { triggerDownload, applyAudioPostProcessing } from './download-utils.ts';
import { isCustomFormat } from './ffmpegFormats.ts';
import { DownloadProgress } from './progressEvents.js';
@@ -1504,58 +1504,62 @@ export class LosslessAPI {
if (!isVideo) {
blob = await applyAudioPostProcessing(blob, quality, onProgress, options.signal);
+ }
- // Add metadata if track information is provided
- if (track) {
- onProgress?.({
- stage: 'processing',
- message: 'Adding metadata...',
- });
+ // Add metadata if track information is provided
+ if (track) {
+ onProgress?.({
+ stage: 'processing',
+ message: 'Adding metadata...',
+ });
- const enrichedTrack = { ...track };
- if (lookup.info) {
- enrichedTrack.replayGain = {
- trackReplayGain: lookup.info.trackReplayGain,
- trackPeakAmplitude: lookup.info.trackPeakAmplitude,
- albumReplayGain: lookup.info.albumReplayGain,
- albumPeakAmplitude: lookup.info.albumPeakAmplitude,
- };
- }
+ const enrichedTrack = { ...track };
+ if (lookup.info) {
+ enrichedTrack.replayGain = {
+ trackReplayGain: lookup.info.trackReplayGain,
+ trackPeakAmplitude: lookup.info.trackPeakAmplitude,
+ albumReplayGain: lookup.info.albumReplayGain,
+ albumPeakAmplitude: lookup.info.albumPeakAmplitude,
+ };
+ }
- if (
- track.album?.id &&
- (track.album?.totalDiscs == null || track.album?.numberOfTracksOnDisc == null)
- ) {
- try {
- const albumData = await this.getAlbum(track.album.id);
- if (albumData.tracks?.length > 0) {
- const discTrackCounts = new Map();
- let maxDiscNumber = 0;
- for (const t of albumData.tracks) {
- const dn = getTrackDiscNumber(t);
- discTrackCounts.set(dn, (discTrackCounts.get(dn) || 0) + 1);
- if (dn > maxDiscNumber) maxDiscNumber = dn;
- }
- const totalDiscs = maxDiscNumber || 1;
- const discNumber = getTrackDiscNumber(track);
- enrichedTrack.album = {
- ...(enrichedTrack.album || {}),
- totalDiscs: track.album?.totalDiscs ?? totalDiscs,
- numberOfTracksOnDisc:
- track.album?.numberOfTracksOnDisc ?? discTrackCounts.get(discNumber),
- };
+ if (track.album?.id && (track.album?.totalDiscs == null || track.album?.numberOfTracksOnDisc == null)) {
+ try {
+ const albumData = await this.getAlbum(track.album.id);
+ if (albumData.tracks?.length > 0) {
+ const discTrackCounts = new Map();
+ let maxDiscNumber = 0;
+ for (const t of albumData.tracks) {
+ const dn = getTrackDiscNumber(t);
+ discTrackCounts.set(dn, (discTrackCounts.get(dn) || 0) + 1);
+ if (dn > maxDiscNumber) maxDiscNumber = dn;
}
- } catch (e) {
- console.warn('Failed to fetch album for disc info:', e);
+ const totalDiscs = maxDiscNumber || 1;
+ const discNumber = getTrackDiscNumber(track);
+ enrichedTrack.album = {
+ ...(enrichedTrack.album || {}),
+ totalDiscs: track.album?.totalDiscs ?? totalDiscs,
+ numberOfTracksOnDisc:
+ track.album?.numberOfTracksOnDisc ?? discTrackCounts.get(discNumber),
+ };
}
+ } catch (e) {
+ console.warn('Failed to fetch album for disc info:', e);
}
+ }
- onProgress?.(new DownloadProgress('Adding metadata'));
- try {
- blob = await addMetadataToAudio(blob, enrichedTrack, this, quality, prefetchPromises);
- } catch (err) {
- console.error(err);
+ onProgress?.(new DownloadProgress('Adding metadata'));
+ try {
+ if (isVideo) {
+ blob = new File(
+ [await ffmpeg(blob, ['-c', 'copy'], 'output.mp4', 'video/mp4', onProgress, options.signal)],
+ 'output.mp4',
+ { type: 'video/mp4' }
+ );
}
+ blob = await addMetadataToAudio(blob, enrichedTrack, this, quality, prefetchPromises);
+ } catch (err) {
+ console.error(err);
}
}
diff --git a/js/metadata.js b/js/metadata.js
index c296c7c81..336030c12 100644
--- a/js/metadata.js
+++ b/js/metadata.js
@@ -34,12 +34,12 @@ export async function addMetadataToAudio(audioBlob, track, api, _quality, prefet
try {
data.title = getTrackTitle(track);
data.artist = getFullArtistString(track);
- data.albumTitle = track.album.title;
+ data.albumTitle = track.album?.title;
data.albumArtist = track.album?.artist?.name || track.artist?.name;
data.trackNumber = track.trackNumber;
data.discNumber = track.volumeNumber ?? track.discNumber;
- data.totalTracks = track.album.numberOfTracksOnDisc ?? track.album.numberOfTracks;
- data.totalDiscs = track.album.totalDiscs;
+ data.totalTracks = track.album?.numberOfTracksOnDisc ?? track.album?.numberOfTracks;
+ data.totalDiscs = track.album?.totalDiscs;
data.copyright = track.copyright;
data.isrc = track.isrc;
data.explicit = Boolean(track.explicit);
From a385cb558aad570f6c0be59eadaf4e20a0f3a747 Mon Sep 17 00:00:00 2001
From: Daniel <790119+DanTheMan827@users.noreply.github.com>
Date: Fri, 20 Mar 2026 13:02:48 -0500
Subject: [PATCH 141/226] fix(api): use an instance for `/recommendations`
---
js/api.js | 7 +++++--
1 file changed, 5 insertions(+), 2 deletions(-)
diff --git a/js/api.js b/js/api.js
index 096435886..4d906cf4b 100644
--- a/js/api.js
+++ b/js/api.js
@@ -56,11 +56,14 @@ export class LosslessAPI {
async fetchWithRetry(relativePath, options = {}) {
const type = options.type || 'api';
- const instanceRoutes = ['/track', '/album/similar', '/artist/similar', '/video'];
+ const instanceRoutes = ['/track', '/album/similar', '/artist/similar', '/video', '/recommendations'];
if (window.allTidal == true || !instanceRoutes.some((route) => relativePath.startsWith(route))) {
try {
- console.log(relativePath);
+ if (import.meta.env.DEV) {
+ console.log(relativePath);
+ }
+
return await client.queryResponse(relativePath);
} catch (err) {
console.warn(
From 7bcb9e1fb52aaff02ff39dac68b9859859ff497b Mon Sep 17 00:00:00 2001
From: edidealt
Date: Fri, 20 Mar 2026 18:07:06 +0000
Subject: [PATCH 142/226] listenbrainz love on like
---
index.html | 47 ++++++++++++++++-----
js/events.js | 5 ++-
js/listenbrainz.js | 96 +++++++++++++++++++++++++++++++++++++++----
js/multi-scrobbler.js | 3 +-
js/settings.js | 10 +++++
js/storage.js | 15 ++++++-
6 files changed, 153 insertions(+), 23 deletions(-)
diff --git a/index.html b/index.html
index b2d24ff00..0f6a9ba65 100644
--- a/index.html
+++ b/index.html
@@ -476,15 +476,12 @@ Create Playlist
Import from JSPF
- JSPF (JSON Shareable Playlist Format) is supported by const replayGainPreamp = document.getElementById('replay-gain-preamp');
- if (replayGainPreamp) {
- replayGainPreamp.value = replayGainSettings.getPreamp();
- replayGainPreamp.addEventListener('change', (e) => {
- const val = parseFloat(e.target.value);
- replayGainSettings.setPreamp(isNaN(val) ? 3 : val);
- player.applyReplayGain();
- });
- } Import playlists with rich metadata including MusicBrainz identifiers.
+ JSPF (JSON Shareable Playlist Format) is supported by const replayGainPreamp =
+ document.getElementById('replay-gain-preamp'); if (replayGainPreamp) {
+ replayGainPreamp.value = replayGainSettings.getPreamp();
+ replayGainPreamp.addEventListener('change', (e) => { const val = parseFloat(e.target.value);
+ replayGainSettings.setPreamp(isNaN(val) ? 3 : val); player.applyReplayGain(); }); } Import
+ playlists with rich metadata including MusicBrainz identifiers.
Popular Tracks
+
+
+ Love on Like
+ Automatically 'love' tracks on ListenBrainz when you like them
+
+
+
+
+
+
diff --git a/js/events.js b/js/events.js
index 11f1ffae1..dcd9b2f34 100644
--- a/js/events.js
+++ b/js/events.js
@@ -8,7 +8,7 @@ import {
getShareUrl,
escapeHtml,
} from './utils.js';
-import { lastFMStorage, libreFmSettings, waveformSettings } from './storage.js';
+import { lastFMStorage, libreFmSettings, listenBrainzSettings, waveformSettings } from './storage.js';
import { showNotification, downloadTrackWithMetadata, downloadAlbumAsZip, downloadPlaylistAsZip } from './downloads.js';
import { downloadQualitySettings } from './storage.js';
import { updateTabTitle, navigate } from './router.js';
@@ -1050,6 +1050,9 @@ export async function handleTrackAction(
if (libreFmSettings.isEnabled() && libreFmSettings.shouldLoveOnLike()) {
scrobbler.loveTrack(item);
}
+ if (listenBrainzSettings.isEnabled() && listenBrainzSettings.shouldLoveOnLike()) {
+ scrobbler.loveTrack(item);
+ }
}
// Update all instances of this item's like button on the page
diff --git a/js/listenbrainz.js b/js/listenbrainz.js
index 44ff52cda..e0dee3b66 100644
--- a/js/listenbrainz.js
+++ b/js/listenbrainz.js
@@ -2,7 +2,7 @@ import { listenBrainzSettings, lastFMStorage } from './storage.js';
export class ListenBrainzScrobbler {
constructor() {
- this.DEFAULT_API_URL = 'https://api.listenbrainz.org/1';
+ this.DEFAULT_API_URL = 'https://api.listenbrainz.org';
this.currentTrack = null;
this.scrobbleTimer = null;
this.scrobbleThreshold = 0;
@@ -12,7 +12,8 @@ export class ListenBrainzScrobbler {
getApiUrl() {
const customUrl = listenBrainzSettings.getCustomUrl();
- return customUrl || this.DEFAULT_API_URL;
+ const base = customUrl || this.DEFAULT_API_URL;
+ return base.replace(/\/1\/?$/, '');
}
isEnabled() {
@@ -26,7 +27,6 @@ export class ListenBrainzScrobbler {
_getMetadata(track) {
if (!track) return null;
- // Get the primary artist name
let artistName = 'Unknown Artist';
if (track.artist?.name) {
@@ -38,10 +38,9 @@ export class ListenBrainzScrobbler {
artistName = typeof first === 'string' ? first : first.name || 'Unknown Artist';
}
- // Clean artist name
if (typeof artistName === 'string') {
artistName = artistName
- .split(/\s*[&]\s*|\s+feat\.?\s+|\s+ft\.?\s+|\s+featuring\s+|\s+with\s+|\s+x\s+/i)[0]
+ .split(/\s*[&]\s*|\s+feat\.?\s*|\s+ft\.?\s*|\s+featuring\s+|\s+with\s+|\s+x\s+/i)[0]
.trim();
}
@@ -73,6 +72,54 @@ export class ListenBrainzScrobbler {
return payload;
}
+ async _lookupRecordingMbid(track) {
+ let artistName = 'Unknown Artist';
+ if (track.artist?.name) {
+ artistName = track.artist.name;
+ } else if (typeof track.artist === 'string') {
+ artistName = track.artist;
+ } else if (track.artists && track.artists.length > 0) {
+ const first = track.artists[0];
+ artistName = typeof first === 'string' ? first : first.name || 'Unknown Artist';
+ }
+ artistName = artistName
+ .split(/\s*[&]\s*|\s+feat\.?\s*|\s+ft\.?\s*|\s+featuring\s+|\s+with\s+|\s+x\s+/i)[0]
+ .trim();
+
+ const trackName = track.cleanTitle || track.title;
+ if (!artistName || !trackName) return null;
+
+ try {
+ const apiUrl = this.getApiUrl();
+ const params = new URLSearchParams({
+ recording_name: trackName,
+ artist_name: artistName,
+ });
+
+ const response = await fetch(`${apiUrl}/1/metadata/lookup/?${params}`, {
+ method: 'GET',
+ headers: {
+ Authorization: `Token ${this.getToken()}`,
+ },
+ });
+
+ if (!response.ok) {
+ console.warn(`[ListenBrainz] MBID lookup failed: ${response.status}`);
+ return null;
+ }
+
+ const data = await response.json();
+ if (data?.recording_mbid) {
+ console.log(`[ListenBrainz] Found MBID: ${data.recording_mbid}`);
+ return data.recording_mbid;
+ }
+ console.warn('[ListenBrainz] No recording_mbid found in lookup response');
+ } catch (error) {
+ console.error('[ListenBrainz] MBID lookup error:', error);
+ }
+ return null;
+ }
+
async submitListen(listenType, track, timestamp = null) {
if (!this.isEnabled()) return;
@@ -96,7 +143,7 @@ export class ListenBrainzScrobbler {
try {
const apiUrl = this.getApiUrl();
- const response = await fetch(`${apiUrl}/submit-listens`, {
+ const response = await fetch(`${apiUrl}/1/submit-listens`, {
method: 'POST',
headers: {
Authorization: `Token ${this.getToken()}`,
@@ -106,7 +153,6 @@ export class ListenBrainzScrobbler {
});
if (!response.ok) {
- // ListenBrainz doesn't always return JSON on error
const text = await response.text();
throw new Error(`ListenBrainz API Error ${response.status}: ${text}`);
}
@@ -121,8 +167,6 @@ export class ListenBrainzScrobbler {
if (!this.isEnabled()) return;
this.currentTrack = track;
- // Only reset hasScrobbled if we're not currently in the middle of scrobbling
- // to prevent race conditions that could cause double scrobbles
if (!this.isScrobbling) {
this.hasScrobbled = false;
}
@@ -175,4 +219,38 @@ export class ListenBrainzScrobbler {
this.clearScrobbleTimer();
this.currentTrack = null;
}
+
+ async loveTrack(track) {
+ if (!this.isEnabled()) return;
+
+ try {
+ const apiUrl = this.getApiUrl();
+ const mbid = await this._lookupRecordingMbid(track);
+
+ if (mbid) {
+ const response = await fetch(`${apiUrl}/1/feedback/recording-feedback`, {
+ method: 'POST',
+ headers: {
+ Authorization: `Token ${this.getToken()}`,
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify({
+ recording_mbid: mbid,
+ score: 1,
+ }),
+ });
+
+ if (!response.ok) {
+ const text = await response.text();
+ throw new Error(`ListenBrainz Feedback Error ${response.status}: ${text}`);
+ }
+
+ console.log('[ListenBrainz] Loved track:', track.title);
+ } else {
+ console.warn('[ListenBrainz] Could not find recording MBID for love feedback');
+ }
+ } catch (error) {
+ console.error('[ListenBrainz] Failed to love track:', error);
+ }
+ }
}
diff --git a/js/multi-scrobbler.js b/js/multi-scrobbler.js
index 0e057ab30..d4616acef 100644
--- a/js/multi-scrobbler.js
+++ b/js/multi-scrobbler.js
@@ -57,6 +57,7 @@ export class MultiScrobbler {
async loveTrack(track) {
await this.lastfm.loveTrack(track);
await this.librefm.loveTrack(track);
- // ListenBrainz and Maloja feedback could be added here when supported
+ await this.listenbrainz.loveTrack(track);
+ // Maloja feedback could be added here when supported
}
}
diff --git a/js/settings.js b/js/settings.js
index 8ec3c3ead..060b910cb 100644
--- a/js/settings.js
+++ b/js/settings.js
@@ -467,6 +467,8 @@ export async function initializeSettings(scrobbler, player, api, ui) {
const lbToggle = document.getElementById('listenbrainz-enabled-toggle');
const lbTokenSetting = document.getElementById('listenbrainz-token-setting');
const lbCustomUrlSetting = document.getElementById('listenbrainz-custom-url-setting');
+ const lbLoveSetting = document.getElementById('listenbrainz-love-setting');
+ const lbLoveToggle = document.getElementById('listenbrainz-love-toggle');
const lbTokenInput = document.getElementById('listenbrainz-token-input');
const lbCustomUrlInput = document.getElementById('listenbrainz-custom-url-input');
@@ -475,8 +477,10 @@ export async function initializeSettings(scrobbler, player, api, ui) {
if (lbToggle) lbToggle.checked = isEnabled;
if (lbTokenSetting) lbTokenSetting.style.display = isEnabled ? 'flex' : 'none';
if (lbCustomUrlSetting) lbCustomUrlSetting.style.display = isEnabled ? 'flex' : 'none';
+ if (lbLoveSetting) lbLoveSetting.style.display = isEnabled ? 'flex' : 'none';
if (lbTokenInput) lbTokenInput.value = listenBrainzSettings.getToken();
if (lbCustomUrlInput) lbCustomUrlInput.value = listenBrainzSettings.getCustomUrl();
+ if (lbLoveToggle) lbLoveToggle.checked = listenBrainzSettings.shouldLoveOnLike();
};
updateListenBrainzUI();
@@ -501,6 +505,12 @@ export async function initializeSettings(scrobbler, player, api, ui) {
});
}
+ if (lbLoveToggle) {
+ lbLoveToggle.addEventListener('change', (e) => {
+ listenBrainzSettings.setLoveOnLike(e.target.checked);
+ });
+ }
+
// ========================================
// Maloja Settings
// ========================================
diff --git a/js/storage.js b/js/storage.js
index 11082844a..dd4942a09 100644
--- a/js/storage.js
+++ b/js/storage.js
@@ -98,7 +98,7 @@ export const apiSettings = {
{ url: 'https://katze.qqdl.site', version: '2.2' },
{ url: 'https://hund.qqdl.site', version: '2.2' },
{ url: 'https://wolf.qqdl.site', version: '2.2' },
- { url: 'https://hifi.p1nkhamster.xyz/', version: '2.6'},
+ { url: 'https://hifi.p1nkhamster.xyz/', version: '2.6' },
],
};
this.instancesLoaded = true;
@@ -1592,6 +1592,7 @@ export const listenBrainzSettings = {
ENABLED_KEY: 'listenbrainz-enabled',
TOKEN_KEY: 'listenbrainz-token',
CUSTOM_URL_KEY: 'listenbrainz-custom-url',
+ LOVE_ON_LIKE_KEY: 'listenbrainz-love-on-like',
isEnabled() {
try {
@@ -1628,6 +1629,18 @@ export const listenBrainzSettings = {
setCustomUrl(url) {
localStorage.setItem(this.CUSTOM_URL_KEY, url);
},
+
+ shouldLoveOnLike() {
+ try {
+ return localStorage.getItem(this.LOVE_ON_LIKE_KEY) === 'true';
+ } catch {
+ return false;
+ }
+ },
+
+ setLoveOnLike(enabled) {
+ localStorage.setItem(this.LOVE_ON_LIKE_KEY, enabled ? 'true' : 'false');
+ },
};
export const malojaSettings = {
From 5ac4d23199c3297e269d177e5ce583ded1cff000 Mon Sep 17 00:00:00 2001
From: Daniel <790119+DanTheMan827@users.noreply.github.com>
Date: Fri, 20 Mar 2026 13:12:40 -0500
Subject: [PATCH 143/226] fix(HiFi.ts): ensure only one token is fetched
If multiple calls to the HiFi methods were called at once, you could potentially have ended up with multiple simultaneous token api calls
---
js/HiFi.ts | 61 ++++++++++++++++++++++++++++++------------------------
1 file changed, 34 insertions(+), 27 deletions(-)
diff --git a/js/HiFi.ts b/js/HiFi.ts
index 0cc222ae5..1a52e7930 100644
--- a/js/HiFi.ts
+++ b/js/HiFi.ts
@@ -20,8 +20,9 @@ export class TidalResponse extends Response {
export class HiFiClient {
private static token: string | null;
- private countryCode: string;
private static appTokenExpiry = 0;
+ private static tokenPromise: Promise
| null = null;
+ private countryCode: string;
private static albumTracksMax = 20;
private static albumTracksActive = 0;
private static albumTracksQueue: Array<() => void> = [];
@@ -35,7 +36,7 @@ export class HiFiClient {
return u.toString();
}
- private encodeBasic(id: string, secret: string) {
+ private static encodeBasic(id: string, secret: string) {
if (typeof window !== 'undefined' && typeof window.btoa === 'function') {
return window.btoa(`${id}:${secret}`);
}
@@ -43,35 +44,41 @@ export class HiFiClient {
return Buffer.from(`${id}:${secret}`).toString('base64');
}
- private async fetchAppToken(signal: AbortSignal = new AbortController().signal): Promise {
+ private static async fetchAppToken(signal: AbortSignal = new AbortController().signal) {
const now = Date.now();
if (HiFiClient.token && now < HiFiClient.appTokenExpiry) return HiFiClient.token;
- const res = await fetch('https://auth.tidal.com/v1/oauth2/token', {
- method: 'POST',
- headers: {
- 'content-type': 'application/x-www-form-urlencoded',
- authorization: `Basic ${this.encodeBasic(CLIENT_ID, CLIENT_SECRET)}`,
- },
- body: new URLSearchParams({
- grant_type: 'client_credentials',
- client_id: CLIENT_ID,
- client_secret: CLIENT_SECRET,
- }),
- signal,
- });
+ return await (HiFiClient.tokenPromise ??= (async () => {
+ try {
+ const res = await fetch('https://auth.tidal.com/v1/oauth2/token', {
+ method: 'POST',
+ headers: {
+ 'content-type': 'application/x-www-form-urlencoded',
+ authorization: `Basic ${this.encodeBasic(CLIENT_ID, CLIENT_SECRET)}`,
+ },
+ body: new URLSearchParams({
+ grant_type: 'client_credentials',
+ client_id: CLIENT_ID,
+ client_secret: CLIENT_SECRET,
+ }),
+ signal,
+ });
- if (!res.ok) {
- const txt = await res.text().catch(() => '');
- throw new Error(`Failed to obtain app token: ${res.status} ${txt}`);
- }
+ if (!res.ok) {
+ const txt = await res.text().catch(() => '');
+ throw new Error(`Failed to obtain app token: ${res.status} ${txt}`);
+ }
- const json = await res.json();
- const token = json.access_token;
- const expires_in = json.expires_in ?? 3600;
- HiFiClient.token = token;
- HiFiClient.appTokenExpiry = Date.now() + expires_in * 1000 - 60_000;
- return token;
+ const json = await res.json();
+ const token = json.access_token;
+ const expires_in = json.expires_in ?? 3600;
+ HiFiClient.token = token;
+ HiFiClient.appTokenExpiry = Date.now() + expires_in * 1000 - 60_000;
+ return token;
+ } finally {
+ HiFiClient.tokenPromise = null;
+ }
+ })());
}
constructor(countryCode = 'US') {
@@ -81,7 +88,7 @@ export class HiFiClient {
private async fetchJson(url: string, params?: Params, signal: AbortSignal = new AbortController().signal) {
const final = HiFiClient.buildUrl(url, params);
const res = await fetch(final, {
- headers: { authorization: `Bearer ${await this.fetchAppToken(signal)}` },
+ headers: { authorization: `Bearer ${await HiFiClient.fetchAppToken(signal)}` },
signal,
});
From 5d697760d09079c40da99ee244fbe7a9a16083e7 Mon Sep 17 00:00:00 2001
From: Daniel <790119+DanTheMan827@users.noreply.github.com>
Date: Fri, 20 Mar 2026 13:19:15 -0500
Subject: [PATCH 144/226] fix(HiFi.ts): cache token
---
js/HiFi.ts | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/js/HiFi.ts b/js/HiFi.ts
index 1a52e7930..a34dc4a87 100644
--- a/js/HiFi.ts
+++ b/js/HiFi.ts
@@ -46,6 +46,9 @@ export class HiFiClient {
private static async fetchAppToken(signal: AbortSignal = new AbortController().signal) {
const now = Date.now();
+ HiFiClient.token ??= localStorage.getItem('hifi_token') || null;
+ HiFiClient.appTokenExpiry = Number(localStorage.getItem('hifi_token_expiry') || '0');
+
if (HiFiClient.token && now < HiFiClient.appTokenExpiry) return HiFiClient.token;
return await (HiFiClient.tokenPromise ??= (async () => {
@@ -74,6 +77,9 @@ export class HiFiClient {
const expires_in = json.expires_in ?? 3600;
HiFiClient.token = token;
HiFiClient.appTokenExpiry = Date.now() + expires_in * 1000 - 60_000;
+ localStorage.setItem('hifi_token', token);
+ localStorage.setItem('hifi_token_expiry', HiFiClient.appTokenExpiry.toString());
+
return token;
} finally {
HiFiClient.tokenPromise = null;
From 1bd895093b41ca5d14137d21f9bcab950fb5caf4 Mon Sep 17 00:00:00 2001
From: Daniel <790119+DanTheMan827@users.noreply.github.com>
Date: Fri, 20 Mar 2026 14:02:02 -0500
Subject: [PATCH 145/226] fix(HiFi): enhance token fetching
---
js/HiFi.ts | 95 +++++++++++++++++++++++++++++++++++++++++++-----------
1 file changed, 77 insertions(+), 18 deletions(-)
diff --git a/js/HiFi.ts b/js/HiFi.ts
index a34dc4a87..265ed6c3f 100644
--- a/js/HiFi.ts
+++ b/js/HiFi.ts
@@ -1,6 +1,6 @@
const API_VERSION = '2.6';
-const CLIENT_ID = 'txNoH4kkV41MfH25';
-const CLIENT_SECRET = 'dQjy0MinCEvxi1O4UmxvxWnDjt4cgHBPw8ll6nYBk98=';
+const BROWSER_CLIENT_ID = 'txNoH4kkV41MfH25';
+const BROWSER_CLIENT_SECRET = 'dQjy0MinCEvxi1O4UmxvxWnDjt4cgHBPw8ll6nYBk98=';
type Params = Record;
@@ -18,14 +18,71 @@ export class TidalResponse extends Response {
}
}
+/** A container for the mock localStorage */
+
export class HiFiClient {
- private static token: string | null;
- private static appTokenExpiry = 0;
private static tokenPromise: Promise | null = null;
- private countryCode: string;
private static albumTracksMax = 20;
private static albumTracksActive = 0;
private static albumTracksQueue: Array<() => void> = [];
+ private countryCode: string;
+ private clientId: string;
+ private clientSecret: string;
+ private static _localStorage: Record = {};
+ private static get localStorage() {
+ return (
+ globalThis?.localStorage ??
+ window?.localStorage ?? {
+ getItem: (key) => HiFiClient._localStorage[key],
+ setItem: (key, value) => {
+ HiFiClient._localStorage[key] = String(value);
+ },
+ removeItem: (key) => {
+ delete HiFiClient._localStorage[key];
+ },
+ get length() {
+ return Object.keys(HiFiClient._localStorage).length;
+ },
+ clear() {
+ for (const key in HiFiClient._localStorage) {
+ delete HiFiClient._localStorage[key];
+ }
+ },
+ key(index) {
+ const keys = Object.keys(HiFiClient._localStorage);
+ return keys[index] || null;
+ },
+ }
+ );
+ }
+
+ private static get token(): string | null {
+ return HiFiClient.localStorage.getItem('hifi_token') || null;
+ }
+
+ private static set token(value: string | null) {
+ if (value) {
+ HiFiClient.localStorage.setItem('hifi_token', value);
+ } else {
+ HiFiClient.localStorage.removeItem('hifi_token');
+ }
+ }
+
+ private static get appTokenExpiry() {
+ return Number(HiFiClient.localStorage.getItem('hifi_token_expiry') || '0');
+ }
+
+ private static set appTokenExpiry(value: number) {
+ if (value) {
+ HiFiClient.localStorage.setItem('hifi_token_expiry', value.toString());
+ } else {
+ HiFiClient.localStorage.removeItem('hifi_token_expiry');
+ }
+
+ if (value < Date.now()) {
+ HiFiClient.localStorage.removeItem('hifi_token');
+ }
+ }
private static buildUrl(base: string, params?: Params) {
if (!params) return base;
@@ -44,12 +101,12 @@ export class HiFiClient {
return Buffer.from(`${id}:${secret}`).toString('base64');
}
- private static async fetchAppToken(signal: AbortSignal = new AbortController().signal) {
- const now = Date.now();
- HiFiClient.token ??= localStorage.getItem('hifi_token') || null;
- HiFiClient.appTokenExpiry = Number(localStorage.getItem('hifi_token_expiry') || '0');
-
- if (HiFiClient.token && now < HiFiClient.appTokenExpiry) return HiFiClient.token;
+ private static async fetchAppToken(
+ signal: AbortSignal = new AbortController().signal,
+ clientId: string,
+ clientSecret: string
+ ) {
+ if (HiFiClient.token && Date.now() < HiFiClient.appTokenExpiry) return HiFiClient.token;
return await (HiFiClient.tokenPromise ??= (async () => {
try {
@@ -57,12 +114,12 @@ export class HiFiClient {
method: 'POST',
headers: {
'content-type': 'application/x-www-form-urlencoded',
- authorization: `Basic ${this.encodeBasic(CLIENT_ID, CLIENT_SECRET)}`,
+ authorization: `Basic ${this.encodeBasic(clientId, clientSecret)}`,
},
body: new URLSearchParams({
grant_type: 'client_credentials',
- client_id: CLIENT_ID,
- client_secret: CLIENT_SECRET,
+ client_id: clientId,
+ client_secret: clientSecret,
}),
signal,
});
@@ -77,8 +134,6 @@ export class HiFiClient {
const expires_in = json.expires_in ?? 3600;
HiFiClient.token = token;
HiFiClient.appTokenExpiry = Date.now() + expires_in * 1000 - 60_000;
- localStorage.setItem('hifi_token', token);
- localStorage.setItem('hifi_token_expiry', HiFiClient.appTokenExpiry.toString());
return token;
} finally {
@@ -87,14 +142,18 @@ export class HiFiClient {
})());
}
- constructor(countryCode = 'US') {
+ constructor(clientId = BROWSER_CLIENT_ID, clientSecret = BROWSER_CLIENT_SECRET, countryCode = 'US') {
this.countryCode = countryCode;
+ this.clientId = clientId;
+ this.clientSecret = clientSecret;
}
private async fetchJson(url: string, params?: Params, signal: AbortSignal = new AbortController().signal) {
const final = HiFiClient.buildUrl(url, params);
const res = await fetch(final, {
- headers: { authorization: `Bearer ${await HiFiClient.fetchAppToken(signal)}` },
+ headers: {
+ authorization: `Bearer ${await HiFiClient.fetchAppToken(signal, this.clientId, this.clientSecret)}`,
+ },
signal,
});
From 8f1994d9d38c12246650694842b89e17a6a8047c Mon Sep 17 00:00:00 2001
From: matioku
Date: Fri, 20 Mar 2026 21:30:15 +0100
Subject: [PATCH 146/226] fix(events): fix share/open-in-new-tab URL
construction
---
js/events.js | 22 +++++++++++-----------
1 file changed, 11 insertions(+), 11 deletions(-)
diff --git a/js/events.js b/js/events.js
index dcd9b2f34..aebf437b6 100644
--- a/js/events.js
+++ b/js/events.js
@@ -1310,7 +1310,7 @@ export async function handleTrackAction(
// Use stored href from card if available, otherwise construct URL
const contextMenu = document.getElementById('context-menu');
const storedHref = contextMenu?._contextHref;
- const url = getShareUrl(storedHref ? storedHref : `/track/${item.id || item.uuid}`);
+ const url = getShareUrl(storedHref ? storedHref : `/${type}/${item.id || item.uuid}`);
trackCopyLink(type, item.id || item.uuid);
navigator.clipboard.writeText(url).then(() => {
@@ -1322,7 +1322,7 @@ export async function handleTrackAction(
const storedHref = contextMenu?._contextHref;
const url = storedHref
? `${window.location.origin}${storedHref}`
- : `${window.location.origin}/track/${item.id || item.uuid}`;
+ : `${window.location.origin}/${type}/${item.id || item.uuid}`;
trackOpenInNewTab(type, item.id || item.uuid);
window.open(url, '_blank');
@@ -1350,7 +1350,7 @@ export async function handleTrackAction(
-
+
${item.artists ? `
Artist: ${escapeHtml(Array.isArray(item.artists) ? item.artists.map((a) => a.name || a).join(', ') : item.artists)}
` : ''}
${item.trackerInfo.artist ? `
Tracked Artist: ${escapeHtml(item.trackerInfo.artist)}
` : ''}
@@ -1365,7 +1365,7 @@ export async function handleTrackAction(
${item.trackerInfo.leakedDate ? `
Leak Date: ${escapeHtml(new Date(item.trackerInfo.leakedDate).toLocaleDateString())}
` : ''}
${item.trackerInfo.recordingDate ? `
Recording Date: ${escapeHtml(new Date(item.trackerInfo.recordingDate).toLocaleDateString())}
` : ''}
-
+
${
item.trackerInfo.description
? `
@@ -1376,7 +1376,7 @@ export async function handleTrackAction(
`
: ''
}
-
+
${
item.trackerInfo.notes
? `
@@ -1387,7 +1387,7 @@ export async function handleTrackAction(
`
: ''
}
-
+
${
item.trackerInfo.sourceUrl
? `
@@ -1400,7 +1400,7 @@ export async function handleTrackAction(
`
: ''
}
-
+
${item.id ? `Track ID: ${escapeHtml(item.id)}
` : ''}
Close
@@ -1429,7 +1429,7 @@ export async function handleTrackAction(
${item.explicit ? `Explicit: Yes
` : ''}
Quality: ${escapeHtml(quality)} ${bitrate ? `(${escapeHtml(bitrate)})` : ''}
-
+
${
item.credits && item.credits.length > 0
? `
@@ -1442,7 +1442,7 @@ export async function handleTrackAction(
`
: ''
}
-
+
${
item.composers && item.composers.length > 0
? `
@@ -1450,7 +1450,7 @@ export async function handleTrackAction(
`
: ''
}
-
+
${
item.lyrics?.text
? `
@@ -1460,7 +1460,7 @@ export async function handleTrackAction(
`
: ''
}
-
+
${item.id ? `Track ID: ${escapeHtml(item.id)}
` : ''}
${item.album?.id ? `Album ID: ${escapeHtml(item.album.id)}
` : ''}
From cc2f28a79800c5f9d6d1bb2fe6e5060baa0a4047 Mon Sep 17 00:00:00 2001
From: edidealt
Date: Fri, 20 Mar 2026 20:51:05 +0000
Subject: [PATCH 147/226] fix track right clicks
---
js/events.js | 10 ++++++----
1 file changed, 6 insertions(+), 4 deletions(-)
diff --git a/js/events.js b/js/events.js
index dcd9b2f34..5103f43c2 100644
--- a/js/events.js
+++ b/js/events.js
@@ -1317,12 +1317,14 @@ export async function handleTrackAction(
showNotification('Link copied to clipboard!');
});
} else if (action === 'open-in-new-tab') {
- // Use stored href from card if available, otherwise construct URL
+ // Use stored href from card if available and not a track, otherwise construct URL
const contextMenu = document.getElementById('context-menu');
const storedHref = contextMenu?._contextHref;
- const url = storedHref
- ? `${window.location.origin}${storedHref}`
- : `${window.location.origin}/track/${item.id || item.uuid}`;
+ const contextType = contextMenu?._contextType;
+ const url =
+ storedHref && contextType !== 'track'
+ ? `${window.location.origin}${storedHref}`
+ : `${window.location.origin}/track/${item.id || item.uuid}`;
trackOpenInNewTab(type, item.id || item.uuid);
window.open(url, '_blank');
From 4f0d95bf42e2786e2d5ad09715c900db8673db69 Mon Sep 17 00:00:00 2001
From: Daniel <790119+DanTheMan827@users.noreply.github.com>
Date: Fri, 20 Mar 2026 17:13:23 -0500
Subject: [PATCH 148/226] refactor(hifi): update localStorage and token
handling
Modified token encoding to use globalThis and added a setToken method.
---
js/HiFi.ts | 18 ++++++++----------
1 file changed, 8 insertions(+), 10 deletions(-)
diff --git a/js/HiFi.ts b/js/HiFi.ts
index 265ed6c3f..5d855326a 100644
--- a/js/HiFi.ts
+++ b/js/HiFi.ts
@@ -18,8 +18,6 @@ export class TidalResponse extends Response {
}
}
-/** A container for the mock localStorage */
-
export class HiFiClient {
private static tokenPromise: Promise | null = null;
private static albumTracksMax = 20;
@@ -31,8 +29,7 @@ export class HiFiClient {
private static _localStorage: Record = {};
private static get localStorage() {
return (
- globalThis?.localStorage ??
- window?.localStorage ?? {
+ globalThis?.localStorage ?? {
getItem: (key) => HiFiClient._localStorage[key],
setItem: (key, value) => {
HiFiClient._localStorage[key] = String(value);
@@ -94,13 +91,18 @@ export class HiFiClient {
}
private static encodeBasic(id: string, secret: string) {
- if (typeof window !== 'undefined' && typeof window.btoa === 'function') {
- return window.btoa(`${id}:${secret}`);
+ if (typeof globalThis.btoa === 'function') {
+ return btoa(`${id}:${secret}`);
}
// Node fallback
return Buffer.from(`${id}:${secret}`).toString('base64');
}
+ static setToken(token: string, expiry: number = Date.now() + 60000) {
+ HiFiClient.token = token;
+ HiFiClient.appTokenExpiry = expiry
+ }
+
private static async fetchAppToken(
signal: AbortSignal = new AbortController().signal,
clientId: string,
@@ -747,10 +749,6 @@ export class HiFiClient {
offset: qp.offset ? Number(qp.offset) : undefined,
});
default:
- // unknown local route => treat as raw upstream path (forward)
- if (pathOrUrl.startsWith('http')) {
- return await this.fetchJson(pathOrUrl);
- }
throw new Error(`Unknown route: ${pathname}`);
}
} catch (err) {
From ab11ff6a3729c3265fffa2493571f10584614b5c Mon Sep 17 00:00:00 2001
From: edidealt
Date: Fri, 20 Mar 2026 22:28:08 +0000
Subject: [PATCH 149/226] multi-track selection
---
js/events.js | 403 +++++++++++++++++++++++++++++++++++++++++++++++++-
js/icons.ts | 3 +
js/storage.js | 8 +
js/ui.js | 3 +
styles.css | 88 +++++++++++
5 files changed, 499 insertions(+), 6 deletions(-)
diff --git a/js/events.js b/js/events.js
index 5103f43c2..c8e7ba389 100644
--- a/js/events.js
+++ b/js/events.js
@@ -8,7 +8,13 @@ import {
getShareUrl,
escapeHtml,
} from './utils.js';
-import { lastFMStorage, libreFmSettings, listenBrainzSettings, waveformSettings } from './storage.js';
+import {
+ lastFMStorage,
+ libreFmSettings,
+ listenBrainzSettings,
+ waveformSettings,
+ keyboardShortcuts,
+} from './storage.js';
import { showNotification, downloadTrackWithMetadata, downloadAlbumAsZip, downloadPlaylistAsZip } from './downloads.js';
import { downloadQualitySettings } from './storage.js';
import { updateTabTitle, navigate } from './router.js';
@@ -47,10 +53,191 @@ import {
trackStartMix,
trackEvent,
} from './analytics.js';
-import { SVG_BIN, SVG_MUTE, SVG_PAUSE, SVG_PLAY, SVG_VOLUME } from './icons.js';
+import { SVG_BIN, SVG_MUTE, SVG_PAUSE, SVG_PLAY, SVG_VOLUME, SVG_CHECKBOX, SVG_CHECKBOX_CHECKED } from './icons.js';
let currentTrackIdForWaveform = null;
+const trackSelection = {
+ selectedIds: new Set(),
+ lastClickedId: null,
+ isSelecting: false,
+};
+
+function isMultiSelectToggle(e) {
+ const shortcut = keyboardShortcuts.getShortcutForAction('multiSelectToggle');
+ if (!shortcut) return e.ctrlKey || e.metaKey;
+ const key = e.key?.toLowerCase();
+ const shortcutKey = shortcut.key?.toLowerCase();
+
+ if (['control', 'shift', 'alt', 'meta'].includes(shortcutKey)) {
+ if (shortcut.ctrl && !(e.ctrlKey || e.metaKey)) return false;
+ if (shortcut.shift && !e.shiftKey) return false;
+ if (shortcut.alt && !e.altKey) return false;
+ return true;
+ }
+
+ return (
+ (shortcut.ctrl ? e.ctrlKey || e.metaKey : !e.ctrlKey && !e.metaKey) &&
+ (shortcut.shift ? e.shiftKey : !e.shiftKey) &&
+ (shortcut.alt ? e.altKey : !e.altKey) &&
+ key === shortcutKey
+ );
+}
+
+function isMultiSelectRange(e) {
+ const shortcut = keyboardShortcuts.getShortcutForAction('multiSelectRange');
+ if (!shortcut) return e.shiftKey;
+ const key = e.key?.toLowerCase();
+ const shortcutKey = shortcut.key?.toLowerCase();
+
+ if (['control', 'shift', 'alt', 'meta'].includes(shortcutKey)) {
+ if (shortcut.ctrl && !(e.ctrlKey || e.metaKey)) return false;
+ if (shortcut.shift && !e.shiftKey) return false;
+ if (shortcut.alt && !e.altKey) return false;
+ return true;
+ }
+
+ return (
+ (shortcut.ctrl ? e.ctrlKey || e.metaKey : !e.ctrlKey && !e.metaKey) &&
+ (shortcut.shift ? e.shiftKey : !e.shiftKey) &&
+ (shortcut.alt ? e.altKey : !e.altKey) &&
+ key === shortcutKey
+ );
+}
+
+function getSelectedTracks() {
+ return Array.from(trackSelection.selectedIds);
+}
+
+function updateCheckbox(checkbox, checked) {
+ if (checkbox) {
+ checkbox.innerHTML = checked ? SVG_CHECKBOX_CHECKED(18) : SVG_CHECKBOX(18);
+ checkbox.classList.toggle('checked', checked);
+ }
+}
+
+function toggleTrackSelection(trackItem, ctrlHeld, shiftHeld) {
+ const trackId = trackItem.dataset.trackId;
+ const isSelected = trackSelection.selectedIds.has(trackId);
+
+ if (ctrlHeld) {
+ if (isSelected) {
+ trackSelection.selectedIds.delete(trackId);
+ trackItem.classList.remove('selected');
+ updateCheckbox(trackItem.querySelector('.track-checkbox'), false);
+ } else {
+ trackSelection.selectedIds.add(trackId);
+ trackItem.classList.add('selected');
+ updateCheckbox(trackItem.querySelector('.track-checkbox'), true);
+ }
+ trackSelection.lastClickedId = trackId;
+ } else if (shiftHeld && trackSelection.lastClickedId && trackSelection.lastClickedId !== trackId) {
+ const parentList = trackItem.closest('.track-list') || trackItem.closest('#main-content');
+ const allTrackElements = Array.from(parentList.querySelectorAll('.track-item'));
+ const lastIndex = allTrackElements.findIndex((el) => el.dataset.trackId === trackSelection.lastClickedId);
+ const currentIndex = allTrackElements.findIndex((el) => el.dataset.trackId === trackId);
+
+ if (lastIndex !== -1 && currentIndex !== -1) {
+ const start = Math.min(lastIndex, currentIndex);
+ const end = Math.max(lastIndex, currentIndex);
+ for (let i = start; i <= end; i++) {
+ const el = allTrackElements[i];
+ trackSelection.selectedIds.add(el.dataset.trackId);
+ el.classList.add('selected');
+ updateCheckbox(el.querySelector('.track-checkbox'), true);
+ }
+ }
+ } else {
+ if (!isSelected) {
+ trackSelection.selectedIds.add(trackId);
+ trackItem.classList.add('selected');
+ updateCheckbox(trackItem.querySelector('.track-checkbox'), true);
+ } else {
+ trackSelection.selectedIds.delete(trackId);
+ trackItem.classList.remove('selected');
+ updateCheckbox(trackItem.querySelector('.track-checkbox'), false);
+ }
+ trackSelection.lastClickedId = trackId;
+ }
+
+ trackSelection.isSelecting = trackSelection.selectedIds.size > 0;
+ document.body.classList.toggle('multi-select-mode', trackSelection.isSelecting);
+}
+
+function showMultiSelectPlaylistModal(tracks) {
+ const modal = document.createElement('div');
+ modal.className = 'modal-overlay';
+ modal.style.cssText =
+ 'position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0,0,0,0.8); display: flex; align-items: center; justify-content: center; z-index: 10000;';
+ modal.innerHTML = `
+
+
+
Add to Playlist
+ ×
+
+
+
+ + Create new playlist
+
+
+
+
+ `;
+
+ const closeModal = () => {
+ modal.remove();
+ document.body.style.overflow = '';
+ };
+
+ modal.querySelector('.modal-close').addEventListener('click', closeModal);
+ modal.addEventListener('click', (e) => {
+ if (e.target === modal) closeModal();
+ });
+
+ document.body.appendChild(modal);
+ document.body.style.overflow = 'hidden';
+
+ db.getPlaylists(true).then((playlists) => {
+ const listEl = modal.querySelector('.playlist-list');
+ if (playlists.length === 0) {
+ listEl.innerHTML = 'No playlists yet
';
+ } else {
+ listEl.innerHTML = playlists
+ .map(
+ (p) => `
+
+ ${escapeHtml(p.name)}
+ ${p.tracks?.length || 0} tracks
+
+ `
+ )
+ .join('');
+ }
+
+ listEl.querySelectorAll('.playlist-item').forEach((item) => {
+ item.addEventListener('click', async () => {
+ const playlistId = item.dataset.playlistId;
+ for (const track of tracks) {
+ await db.addTrackToPlaylist(playlistId, track);
+ }
+ syncManager.syncUserPlaylist(await db.getPlaylist(playlistId), 'update');
+ showNotification(`Added ${tracks.length} tracks to playlist`);
+ closeModal();
+ });
+ });
+ });
+
+ modal.querySelector('.create-new-playlist').addEventListener('click', () => {
+ const name = prompt('Playlist name:');
+ if (name) {
+ db.createPlaylist(name, tracks).then((playlist) => {
+ showNotification(`Created playlist "${name}" with ${tracks.length} tracks`);
+ closeModal();
+ });
+ }
+ });
+}
+
export function initializePlayerEvents(player, audioPlayer, scrobbler, ui) {
const playPauseBtn = document.querySelector('.now-playing-bar .play-pause-btn');
const nextBtn = document.getElementById('next-btn');
@@ -74,6 +261,104 @@ export function initializePlayerEvents(player, audioPlayer, scrobbler, ui) {
volumeFill.style.width = `${effectiveVolume}%`;
};
+ function clearSelection() {
+ trackSelection.selectedIds.clear();
+ trackSelection.lastClickedId = null;
+ trackSelection.isSelecting = false;
+ document.body.classList.remove('multi-select-mode');
+ document.querySelectorAll('.track-item.selected').forEach((el) => {
+ el.classList.remove('selected');
+ });
+ document.querySelectorAll('.track-checkbox').forEach((checkbox) => {
+ checkbox.innerHTML = SVG_CHECKBOX(18);
+ checkbox.classList.remove('checked');
+ });
+ updateSelectionBar();
+ }
+
+ function updateSelectionBar() {
+ let bar = document.getElementById('selection-bar');
+ if (!bar) {
+ bar = document.createElement('div');
+ bar.id = 'selection-bar';
+ bar.className = 'selection-bar';
+ bar.innerHTML = `
+ 0 selected
+
+ Play
+ Add to queue
+ Add to playlist
+ Download
+ Like
+
+ Clear
+ `;
+ document.body.appendChild(bar);
+
+ bar.querySelectorAll('button').forEach((btn) => {
+ btn.addEventListener('click', () => handleSelectionAction(btn.dataset.action));
+ });
+ }
+
+ const count = trackSelection.selectedIds.size;
+ bar.querySelector('.selection-count').textContent = `${count} selected`;
+ bar.classList.toggle('visible', count > 0);
+ }
+
+ function handleSelectionAction(action) {
+ const selectedIds = getSelectedTracks();
+ if (selectedIds.length === 0) return;
+
+ const mainContent = document.getElementById('main-content');
+ const selectedTracks = [];
+ mainContent.querySelectorAll('.track-item').forEach((item) => {
+ if (trackSelection.selectedIds.has(item.dataset.trackId)) {
+ const track = trackDataStore.get(item);
+ if (track) selectedTracks.push(track);
+ }
+ });
+
+ switch (action) {
+ case 'play-selected':
+ if (selectedTracks.length > 0) {
+ player.setQueue(selectedTracks, 0);
+ document.getElementById('shuffle-btn').classList.remove('active');
+ player.playTrackFromQueue();
+ }
+ break;
+ case 'add-to-queue-selected':
+ if (selectedTracks.length > 0) {
+ player.addToQueue(selectedTracks);
+ if (window.renderQueueFunction) window.renderQueueFunction();
+ showNotification(`Added ${selectedTracks.length} tracks to queue`);
+ }
+ break;
+ case 'add-to-playlist-selected':
+ if (selectedTracks.length > 0) {
+ showMultiSelectPlaylistModal(selectedTracks);
+ }
+ break;
+ case 'download-selected':
+ if (selectedTracks.length > 0) {
+ selectedTracks.forEach((track) => {
+ downloadTrackWithMetadata(track, downloadQualitySettings.getQuality(), api, lyricsManager);
+ });
+ showNotification(`Downloading ${selectedTracks.length} tracks`);
+ }
+ break;
+ case 'like-selected':
+ selectedTracks.forEach(async (track) => {
+ const added = await db.toggleFavorite('track', track);
+ syncManager.syncLibraryItem('track', track, added);
+ });
+ showNotification(`Liked ${selectedTracks.length} tracks`);
+ break;
+ case 'clear-selection':
+ clearSelection();
+ break;
+ }
+ }
+
if (homeStartRadioBtn) {
homeStartRadioBtn.addEventListener('click', async () => {
await player.enableRadio();
@@ -1774,6 +2059,14 @@ export function initializeTrackInteractions(player, api, mainContent, contextMen
}
contextMenu._contextTrack = contextTrack;
contextMenu._contextType = menuBtn.dataset.type || trackItem.dataset.type || 'track';
+ if (trackSelection.isSelecting && trackSelection.selectedIds.size > 0) {
+ const selectedTracks = [];
+ document.querySelectorAll('.track-item.selected').forEach((item) => {
+ const track = trackDataStore.get(item);
+ if (track) selectedTracks.push(track);
+ });
+ contextMenu._selectedTracks = selectedTracks;
+ }
await updateContextMenuLikeState(contextMenu, contextTrack);
const rect = menuBtn.getBoundingClientRect();
positionMenu(contextMenu, rect.left, rect.bottom + 5, rect);
@@ -1782,6 +2075,16 @@ export function initializeTrackInteractions(player, api, mainContent, contextMen
return;
}
+ const checkbox = e.target.closest('.track-checkbox');
+ if (checkbox) {
+ e.stopPropagation();
+ const trackItem = checkbox.closest('.track-item');
+ if (trackItem) {
+ toggleTrackSelection(trackItem, isMultiSelectToggle(e), isMultiSelectRange(e));
+ }
+ return;
+ }
+
const trackItem = e.target.closest('.track-item');
if (trackItem && (trackItem.classList.contains('unavailable') || trackItem.classList.contains('blocked'))) {
return;
@@ -1795,6 +2098,22 @@ export function initializeTrackInteractions(player, api, mainContent, contextMen
const clickedTrackId = trackItem.dataset.trackId;
const isSearch = window.location.pathname.startsWith('/search/');
+ if (isMultiSelectToggle(e)) {
+ e.preventDefault();
+ toggleTrackSelection(trackItem, true, isMultiSelectRange(e));
+ return;
+ }
+
+ if (isMultiSelectRange(e) && trackSelection.isSelecting) {
+ e.preventDefault();
+ toggleTrackSelection(trackItem, false, true);
+ return;
+ }
+
+ if (trackSelection.isSelecting) {
+ return;
+ }
+
if (isSearch) {
const clickedTrack = trackDataStore.get(trackItem);
if (clickedTrack) {
@@ -1886,6 +2205,15 @@ export function initializeTrackInteractions(player, api, mainContent, contextMen
contextMenu._originalHTML = null;
}
+ // Store selected tracks for multi-select actions
+ let selectedTracks = [];
+ if (trackSelection.isSelecting && trackSelection.selectedIds.size > 0) {
+ document.querySelectorAll('.track-item.selected').forEach((item) => {
+ const track = trackDataStore.get(item);
+ if (track) selectedTracks.push(track);
+ });
+ }
+
// Hide actions for unavailable tracks
const unavailableActions = ['play-next', 'add-to-queue', 'download', 'track-mix'];
contextMenu.querySelectorAll('[data-action]').forEach((btn) => {
@@ -1896,6 +2224,7 @@ export function initializeTrackInteractions(player, api, mainContent, contextMen
contextMenu._contextTrack = contextTrack;
contextMenu._contextType = contextTrack.type || 'track';
+ contextMenu._selectedTracks = selectedTracks;
await updateContextMenuLikeState(contextMenu, contextTrack);
positionMenu(contextMenu, e.clientX, e.clientY);
}
@@ -1933,7 +2262,7 @@ export function initializeTrackInteractions(player, api, mainContent, contextMen
}
});
- document.addEventListener('click', () => {
+ document.addEventListener('click', (e) => {
if (contextMenu.style.display === 'block') {
if (contextMenu._originalHTML) {
contextMenu.innerHTML = contextMenu._originalHTML;
@@ -1942,6 +2271,21 @@ export function initializeTrackInteractions(player, api, mainContent, contextMen
contextMenu._contextType = null;
contextMenu._originalHTML = null;
}
+
+ if (
+ trackSelection.isSelecting &&
+ !e.target.closest('.track-item') &&
+ !e.target.closest('.selection-bar') &&
+ !e.target.closest('.track-checkbox')
+ ) {
+ clearSelection();
+ }
+ });
+
+ document.addEventListener('keydown', (e) => {
+ if (e.key === 'Escape' && trackSelection.isSelecting) {
+ clearSelection();
+ }
});
contextMenu.addEventListener('click', async (e) => {
@@ -1983,9 +2327,55 @@ export function initializeTrackInteractions(player, api, mainContent, contextMen
}
if (action && track) {
- // Track context menu action
- trackContextMenuAction(action, type, track);
- await handleTrackAction(action, track, player, api, lyricsManager, type, ui, scrobbler, target.dataset);
+ const selectedTracks = contextMenu._selectedTracks || [];
+ const isMultiSelect = selectedTracks.length > 1;
+
+ if (isMultiSelect) {
+ // Handle multi-select actions
+ switch (action) {
+ case 'play-next':
+ selectedTracks.forEach((t) => {
+ trackPlayNext(t);
+ player.addNextToQueue(t);
+ });
+ if (window.renderQueueFunction) window.renderQueueFunction();
+ showNotification(`Playing next: ${selectedTracks.length} tracks`);
+ clearSelection();
+ break;
+ case 'add-to-queue':
+ player.addToQueue(selectedTracks);
+ if (window.renderQueueFunction) window.renderQueueFunction();
+ showNotification(`Added ${selectedTracks.length} tracks to queue`);
+ clearSelection();
+ break;
+ case 'toggle-like':
+ selectedTracks.forEach(async (t) => {
+ const added = await db.toggleFavorite('track', t);
+ syncManager.syncLibraryItem('track', t, added);
+ });
+ showNotification(`Liked ${selectedTracks.length} tracks`);
+ clearSelection();
+ break;
+ case 'add-to-playlist':
+ showMultiSelectPlaylistModal(selectedTracks);
+ clearSelection();
+ break;
+ case 'download':
+ selectedTracks.forEach((t) => {
+ downloadTrackWithMetadata(t, downloadQualitySettings.getQuality(), api, lyricsManager);
+ });
+ showNotification(`Downloading ${selectedTracks.length} tracks`);
+ clearSelection();
+ break;
+ default:
+ clearSelection();
+ break;
+ }
+ } else {
+ // Track context menu action
+ trackContextMenuAction(action, type, track);
+ await handleTrackAction(action, track, player, api, lyricsManager, type, ui, scrobbler, target.dataset);
+ }
}
// Reset menu state before closing
@@ -1995,6 +2385,7 @@ export function initializeTrackInteractions(player, api, mainContent, contextMen
}
contextMenu.style.display = 'none';
contextMenu._contextType = null;
+ contextMenu._selectedTracks = null;
});
// Now playing bar interactions
diff --git a/js/icons.ts b/js/icons.ts
index 0533f29c0..0c6d5e3e4 100644
--- a/js/icons.ts
+++ b/js/icons.ts
@@ -1,6 +1,9 @@
export { default as SVG_ANIMATE_SPIN } from '../images/animate-spin.svg?svg&icon';
export { default as SVG_APPLE } from '../images/apple.svg?svg&icon';
export { default as SVG_BIN } from '!lucide/trash-2.svg?svg&icon';
+export { default as SVG_CHECK } from '!lucide/check.svg?svg&icon';
+export { default as SVG_CHECKBOX } from '!lucide/square.svg?svg&icon';
+export { default as SVG_CHECKBOX_CHECKED } from '!lucide/check-square.svg?svg&icon';
export { default as SVG_CLOCK } from '!lucide/clock.svg?svg&icon';
export { default as SVG_CLOSE } from '!lucide/x.svg?svg&icon';
export { default as SVG_DOWNLOAD } from '!lucide/download.svg?svg&icon';
diff --git a/js/storage.js b/js/storage.js
index dd4942a09..8483a25aa 100644
--- a/js/storage.js
+++ b/js/storage.js
@@ -2747,6 +2747,14 @@ export const keyboardShortcuts = {
alt: false,
description: 'Toggle visualizer auto-cycle',
},
+ multiSelectToggle: {
+ key: 'control',
+ shift: false,
+ ctrl: true,
+ alt: false,
+ description: 'Toggle track selection (individual)',
+ },
+ multiSelectRange: { key: 'shift', shift: true, ctrl: false, alt: false, description: 'Select track range' },
},
getShortcuts() {
diff --git a/js/ui.js b/js/ui.js
index 0f625ab8d..770252a62 100644
--- a/js/ui.js
+++ b/js/ui.js
@@ -82,6 +82,7 @@ import {
SVG_CLOCK,
SVG_MOVE_UP,
SVG_MOVE_DOWN,
+ SVG_CHECKBOX,
} from './icons.js';
function sortTracks(tracks, sortType) {
@@ -397,6 +398,7 @@ export class UIRenderer {
? `${SVG_VIDEO(14)} `
: '';
const trackNumberHTML = `${showCover ? trackImageHTML : displayIndex}
`;
+ const checkboxHTML = `${SVG_CHECKBOX(18)}
`;
const explicitBadge = hasExplicitContent(track) ? this.createExplicitBadge() : '';
const qualityBadge = createQualityBadgeHTML(track);
const trackTitle = getTrackTitle(track);
@@ -437,6 +439,7 @@ export class UIRenderer {
${track.isLocal ? 'data-is-local="true"' : ''}
${isUnavailable ? 'title="This track is currently unavailable"' : ''}
${blockedTitle}>
+ ${checkboxHTML}
${trackNumberHTML}
diff --git a/styles.css b/styles.css
index 4917c8964..121499d2e 100644
--- a/styles.css
+++ b/styles.css
@@ -2156,6 +2156,7 @@ input[type='search']::-webkit-search-cancel-button {
transform var(--transition-fast);
cursor: pointer;
border: 1px solid transparent;
+ position: relative;
}
.track-item:hover {
@@ -2200,6 +2201,93 @@ input[type='search']::-webkit-search-cancel-button {
pointer-events: auto;
}
+.track-checkbox {
+ display: none;
+ width: 18px;
+ height: 18px;
+ cursor: pointer;
+ color: var(--muted-foreground);
+ flex-shrink: 0;
+ transition: color var(--transition-fast);
+ position: absolute;
+ left: 8px;
+ z-index: 1;
+}
+
+.multi-select-mode .track-checkbox,
+body.multi-select-mode .track-checkbox {
+ display: flex !important;
+ align-items: center;
+ justify-content: center;
+}
+
+.track-checkbox:hover {
+ color: var(--foreground);
+}
+
+.track-checkbox.checked {
+ color: var(--primary);
+}
+
+.track-item.selected {
+ background-color: rgb(var(--highlight-rgb), 0.1);
+}
+
+body.multi-select-mode .track-item {
+ cursor: default;
+ padding-left: 36px;
+}
+
+body.multi-select-mode .track-item:hover {
+ transform: none;
+}
+
+.selection-bar {
+ display: none;
+ position: fixed;
+ bottom: 100px;
+ left: 50%;
+ transform: translateX(-50%);
+ background: var(--primary);
+ color: var(--primary-foreground);
+ padding: 12px 20px;
+ border-radius: 24px;
+ font-size: 0.9rem;
+ font-weight: 500;
+ z-index: 1000;
+ box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
+ gap: 12px;
+ align-items: center;
+}
+
+.selection-bar.visible {
+ display: flex;
+}
+
+.selection-bar .selection-count {
+ color: var(--primary-foreground);
+}
+
+.selection-bar .selection-actions {
+ display: flex;
+ gap: 8px;
+}
+
+.selection-bar button {
+ background: transparent;
+ border: 1px solid var(--primary-foreground);
+ color: var(--primary-foreground);
+ padding: 6px 12px;
+ border-radius: 16px;
+ cursor: pointer;
+ font-size: 0.85rem;
+ transition: background var(--transition-fast);
+}
+
+.selection-bar button:hover {
+ background: rgba(255, 255, 255, 0.2);
+}
+
.track-number {
color: var(--muted-foreground);
text-align: center;
From 90f493063465d8d970e4d8890503b95a0983e0e6 Mon Sep 17 00:00:00 2001
From: edidealt
Date: Fri, 20 Mar 2026 22:36:38 +0000
Subject: [PATCH 150/226] whoops forgot mobile selection
---
js/events.js | 45 +++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 45 insertions(+)
diff --git a/js/events.js b/js/events.js
index c8e7ba389..ee0927ac0 100644
--- a/js/events.js
+++ b/js/events.js
@@ -63,6 +63,44 @@ const trackSelection = {
isSelecting: false,
};
+let longPressTimer = null;
+let isLongPress = false;
+let longPressTrackItem = null;
+const LONG_PRESS_DURATION = 500;
+
+function handleTrackTouchStart(e) {
+ if (!('ontouchstart' in window)) return;
+ const trackItem = e.target.closest('.track-item');
+ if (!trackItem || trackItem.classList.contains('unavailable') || trackItem.classList.contains('blocked')) return;
+
+ isLongPress = false;
+ longPressTrackItem = trackItem;
+
+ longPressTimer = setTimeout(() => {
+ isLongPress = true;
+ toggleTrackSelection(trackItem, true, false);
+ if (navigator.vibrate) navigator.vibrate(50);
+ }, LONG_PRESS_DURATION);
+}
+
+function handleTrackTouchMove(e) {
+ if (longPressTimer) {
+ clearTimeout(longPressTimer);
+ longPressTimer = null;
+ }
+}
+
+function handleTrackTouchEnd(e) {
+ if (longPressTimer) {
+ clearTimeout(longPressTimer);
+ longPressTimer = null;
+ }
+ setTimeout(() => {
+ isLongPress = false;
+ longPressTrackItem = null;
+ }, 100);
+}
+
function isMultiSelectToggle(e) {
const shortcut = keyboardShortcuts.getShortcutForAction('multiSelectToggle');
if (!shortcut) return e.ctrlKey || e.metaKey;
@@ -1950,6 +1988,10 @@ async function updateContextMenuLikeState(contextMenu, contextTrack) {
export function initializeTrackInteractions(player, api, mainContent, contextMenu, lyricsManager, ui, scrobbler) {
let contextTrack = null;
+ mainContent.addEventListener('touchstart', handleTrackTouchStart, { passive: true });
+ mainContent.addEventListener('touchmove', handleTrackTouchMove, { passive: true });
+ mainContent.addEventListener('touchend', handleTrackTouchEnd, { passive: true });
+
mainContent.addEventListener('click', async (e) => {
const actionBtn = e.target.closest('.track-action-btn, .like-btn, .play-btn');
if (actionBtn && actionBtn.dataset.action) {
@@ -2089,6 +2131,9 @@ export function initializeTrackInteractions(player, api, mainContent, contextMen
if (trackItem && (trackItem.classList.contains('unavailable') || trackItem.classList.contains('blocked'))) {
return;
}
+ if (isLongPress && longPressTrackItem === trackItem) {
+ return;
+ }
if (
trackItem &&
!trackItem.dataset.queueIndex &&
From 397fc53a467d7e104f1b28c077f258b924ec7ca9 Mon Sep 17 00:00:00 2001
From: Daniel <790119+DanTheMan827@users.noreply.github.com>
Date: Fri, 20 Mar 2026 17:59:13 -0500
Subject: [PATCH 151/226] feat(downloads): add local media folder bulk download
options and folder template paths
This also implements a ModernSettings class for a more streamlined settings API.
---
index.html | 39 ++++-
js/ModernSettings.ts | 289 ++++++++++++++++++++++++++++++++
js/api.js | 8 +-
js/app.js | 3 +
js/bulk-download-writer.ts | 85 +++++++++-
js/desktop/neutralino-bridge.js | 15 ++
js/download-utils.ts | 72 +++++++-
js/downloads.js | 226 ++++++++++++++++++++++---
js/settings.js | 143 ++++++++++++++--
js/storage.js | 52 ------
js/ui.js | 6 +
js/utils.js | 106 ++++++++++--
public/neutralino_loader.html | 20 +++
13 files changed, 947 insertions(+), 117 deletions(-)
create mode 100644 js/ModernSettings.ts
diff --git a/index.html b/index.html
index 0f6a9ba65..04b6aa1dd 100644
--- a/index.html
+++ b/index.html
@@ -4059,9 +4059,42 @@ Custom Theme
ZIP Archive
Folder Picker
+ Local Media Folder
Individual Files
+
+
+ Remember Last Folder
+ Re-use the last chosen directory for Folder Picker downloads
+
+
+
+
+
+
+
+
+ Reset Saved Folder
+ Clear the remembered Folder Picker directory
+
+
Reset
+
+
+
+ Single Downloads to Folder
+ Save individual track downloads directly to the configured folder instead
+ of triggering a browser download
+
+
+
+
+
+
Force ZIP as Blob
@@ -4160,10 +4193,10 @@
Custom Theme
- ZIP Folder Template
+ Folder Template
Customize album folder names. Available: {albumTitle}, {albumArtist},
- {year} Customize album folder names. Use / for nested folders.
+ Available: {albumTitle}, {albumArtist}, {year}
{
+ /** Internal map of pending async operations keyed by unique symbols. */
+ #pending: Record
> = {};
+
+ /** Whether new properties are prevented from being added. */
+ #finalized: boolean = false;
+
+ constructor() {}
+
+ /**
+ * Waits until all pending asynchronous operations complete.
+ *
+ * This includes:
+ * - Initial property loading
+ * - Any pending writes triggered by property setters
+ *
+ * This method loops until the pending operation list is empty, ensuring
+ * that operations scheduled during awaiting are also handled.
+ */
+ public async waitPending() {
+ while (true) {
+ const promises = Object.getOwnPropertySymbols(this.#pending).map((s) => this.#pending[s]);
+
+ if (promises.length) {
+ await Promise.all(promises);
+ } else {
+ break;
+ }
+ }
+ }
+
+ /**
+ * Registers a promise as a pending operation.
+ *
+ * The promise is automatically removed from the pending list once settled.
+ *
+ * @param callback Function producing the promise to track.
+ * @returns The created promise.
+ */
+ #addPending>(callback: () => C): C {
+ const sym = Symbol();
+
+ return (this.#pending[sym] = callback().finally(() => {
+ delete this.#pending[sym];
+ }) as C);
+ }
+
+ #checkKey(key: string) {
+ if (this.#finalized) {
+ throw new Error("Can't add a key after finalization.");
+ }
+
+ if (Object.keys(this).includes(key)) {
+ throw new Error("Can't add a key that already exists.");
+ }
+ }
+
+ /**
+ * Adds a new dynamically typed property to the settings instance.
+ *
+ * The property will:
+ * - Load its value asynchronously from the backing database.
+ * - Fall back to `defaultValue` if no value exists.
+ * - Persist any updates automatically when set.
+ *
+ * The method returns the same instance but **with the new property added to
+ * the TypeScript type**, allowing fluent chaining with full type safety.
+ *
+ * Example:
+ * ```ts
+ * const settings = new ModernSettings()
+ * .addProperty("darkMode", false)
+ * .addProperty("username", "")
+ * .finalize();
+ *
+ * await settings.waitPending();
+ *
+ * settings.darkMode = true;
+ * console.log(settings.username);
+ * ```
+ *
+ * @template T Property value type.
+ * @template K Property key name.
+ *
+ * @param key The property name to define on the settings object.
+ * @param defaultValue Value used if the setting is not present in storage.
+ * @param options Optional configuration.
+ *
+ * @param options.backingKey
+ * Optional storage key. Defaults to the property name.
+ *
+ * @param options.legacy
+ * Optional migration configuration for moving a value from `localStorage`
+ * into the database-backed settings store.
+ *
+ * @param options.legacy.key
+ * Legacy key to read from `localStorage`. Defaults to the same key used for storage.
+ *
+ * @param options.legacy.transformer
+ * Function used to convert the legacy string value into the correct type.
+ *
+ * @returns The same instance typed with the new property included.
+ *
+ * @throws If called after {@link finalize}.
+ * @throws If a property with the same name already exists.
+ */
+ public addProperty(
+ key: K,
+ defaultValue: T,
+ options?: {
+ backingKey?: string;
+ getter?: (value: T, settings: C & Record) => T;
+ setter?: (value: T, settings: C & Record) => T;
+ legacy?: {
+ key?: string;
+ transformer: (value: string) => T;
+ };
+ }
+ ) {
+ const { backingKey, legacy, getter, setter } = options ?? {};
+
+ this.#checkKey(key);
+
+ const typed = this as unknown as ModernSettings>;
+
+ let value: T;
+
+ this.#addPending(async () => {
+ if (legacy?.key != null || legacy?.transformer != null) {
+ {
+ const legacyValue = localStorage.getItem(legacy?.key ?? backingKey ?? key);
+
+ if (legacyValue !== null) {
+ db.saveSetting(backingKey ?? key, legacy.transformer!(legacyValue));
+ localStorage.removeItem(legacy?.key ?? backingKey ?? key);
+ }
+ }
+ }
+
+ try {
+ value = (await db.getSetting(backingKey ?? key)) ?? defaultValue;
+ } catch {
+ value = defaultValue;
+ }
+ }).catch(console.trace);
+
+ Object.defineProperty(this, key, {
+ get: () => (getter ? getter(value, typed as ModernSettings & C & Record) : value),
+ set: (newValue: T) => {
+ value = setter ? setter(newValue, typed as ModernSettings & C & Record) : newValue;
+ this.#addPending(() => db.saveSetting(backingKey ?? key, value));
+ },
+ enumerable: true,
+ });
+
+ return typed;
+ }
+
+ public addGetter(key: K, getter: (settings: ModernSettings) => R) {
+ this.#checkKey(key);
+ const typed = this as unknown as ModernSettings>> & C & Readonly>;
+
+ Object.defineProperty(this, key, {
+ get: () => getter(typed),
+ enumerable: true,
+ });
+
+ return typed;
+ }
+
+ /**
+ * Prevents further properties from being added.
+ *
+ * This is typically called once all `addProperty` calls are complete,
+ * ensuring the settings schema is fixed.
+ *
+ * @returns The settings instance.
+ */
+ public finalize() {
+ this.#finalized = true;
+ return this;
+ }
+}
+
+export enum BulkDownloadMethod {
+ Zip = 'zip',
+ Folder = 'folder',
+ Individual = 'individual',
+ LocalMedia = 'local',
+}
+
+export const modernSettings = new ModernSettings()
+ .addProperty('bulkDownloadFolder', null as FileSystemDirectoryHandle | null)
+ .addProperty('forceZipBlob', false, {
+ legacy: {
+ key: 'bulk-download-force-zip-blob',
+ transformer: Boolean,
+ },
+ })
+ .addProperty('rememberBulkDownloadFolder', false, {
+ legacy: {
+ key: 'bulk-download-remember-folder',
+ transformer: Boolean,
+ },
+ })
+ .addProperty('downloadSinglesToFolder', false, {
+ legacy: {
+ key: 'bulk-download-single-to-folder',
+ transformer: Boolean,
+ },
+ })
+ .addProperty('force-individual-downloads', false, {
+ legacy: {
+ transformer: Boolean,
+ },
+ })
+ .addProperty('bulkDownloadMethod', 'zip' as BulkDownloadMethod, {
+ getter: (stored, settings) => {
+ try {
+ if (stored && Object.values(BulkDownloadMethod).includes(stored)) {
+ return stored;
+ }
+
+ const legacy = settings['force-individual-downloads'];
+ if (legacy) {
+ settings['force-individual-downloads'] = false;
+ return (settings.bulkDownloadMethod = BulkDownloadMethod.Individual);
+ }
+
+ return BulkDownloadMethod.Zip;
+ } catch {
+ return BulkDownloadMethod.Zip;
+ }
+ },
+ })
+ .addProperty('folderTemplate', '', {
+ getter: (stored) => stored || '{albumTitle} - {albumArtist}',
+ legacy: {
+ key: 'zip-folder-template',
+ transformer: String,
+ },
+ })
+ .addProperty('filenameTemplate', '', {
+ getter: (stored) => stored || '{trackNumber} - {artist} - {title}',
+ legacy: {
+ key: 'filename-template',
+ transformer: String,
+ },
+ })
+ .finalize() as ModernSettings & {
+ /** The last used directory handle for bulk downloads */
+ bulkDownloadFolder: FileSystemDirectoryHandle | null;
+
+ /** Force ZIP blobs for bulk downloads even if file system APIs are available */
+ forceZipBlob: boolean;
+
+ /** Whether the Folder Picker should remember the last-used directory handle */
+ rememberBulkDownloadFolder: boolean;
+
+ /**
+ * Whether single-track downloads should be routed to the configured
+ * folder (saved Folder Picker handle or Local Media Folder path)
+ * instead of triggering a browser download.
+ */
+ downloadSinglesToFolder: boolean;
+
+ /** The selected bulk download method */
+ bulkDownloadMethod: BulkDownloadMethod;
+
+ /** Path template for bulk downloads */
+ folderTemplate: string;
+
+ /** Filename template for downloads */
+ filenameTemplate: string;
+};
diff --git a/js/api.js b/js/api.js
index 4d906cf4b..1712072b0 100644
--- a/js/api.js
+++ b/js/api.js
@@ -1506,7 +1506,13 @@ export class LosslessAPI {
}
if (!isVideo) {
- blob = await applyAudioPostProcessing(blob, quality, onProgress, options.signal);
+ blob = await applyAudioPostProcessing(
+ blob,
+ quality,
+ onProgress,
+ options.signal,
+ track?.audioQuality ?? null
+ );
}
// Add metadata if track information is provided
diff --git a/js/app.js b/js/app.js
index 11eb00fb2..eb44c95e8 100644
--- a/js/app.js
+++ b/js/app.js
@@ -61,6 +61,7 @@ import {
parseDynamicCSV,
importToLibrary,
} from './playlist-importer.js';
+import { modernSettings } from './ModernSettings.js';
import {
SVG_OFFLINE,
SVG_RIGHT_ARROW,
@@ -382,6 +383,8 @@ async function uploadCoverImage(file) {
}
document.addEventListener('DOMContentLoaded', async () => {
+ await modernSettings.waitPending();
+
// Initialize analytics
initAnalytics();
diff --git a/js/bulk-download-writer.ts b/js/bulk-download-writer.ts
index d0975f8c2..e5d755117 100644
--- a/js/bulk-download-writer.ts
+++ b/js/bulk-download-writer.ts
@@ -16,10 +16,12 @@ interface NeutralinoBridge {
title: string,
options: { defaultPath: string; filters: Array<{ name: string; extensions: string[] }> }
): Promise;
+ showFolderDialog(title: string, options?: Record): Promise;
};
filesystem: {
writeBinaryFile(path: string, buffer: ArrayBuffer): Promise;
appendBinaryFile(path: string, buffer: ArrayBuffer): Promise;
+ createDirectory(path: string): Promise;
};
}
@@ -156,13 +158,41 @@ export class ZipNeutralinoWriter implements IBulkDownloadWriter {
export class FolderPickerWriter implements IBulkDownloadWriter {
private constructor(private readonly dirHandle: FileSystemDirectoryHandle) {}
+ /** Returns the underlying directory handle (e.g. to persist it for later re-use). */
+ getDirHandle(): FileSystemDirectoryHandle {
+ return this.dirHandle;
+ }
+
+ /**
+ * Creates a {@link FolderPickerWriter} from an already-obtained handle
+ * without showing a directory picker. Useful when re-using a stored handle
+ * whose permission has already been verified by the caller.
+ */
+ static fromHandle(handle: FileSystemDirectoryHandle): FolderPickerWriter {
+ return new FolderPickerWriter(handle);
+ }
+
/**
- * Prompts the user to pick a writable directory.
+ * Prompts the user to pick a writable directory, or re-uses a previously
+ * saved handle when one is supplied and write permission can be obtained.
* Returns a new {@link FolderPickerWriter} bound to the chosen directory.
* If the user dismisses the picker, the promise rejects with a DOMException
* whose name is "AbortError".
*/
- static async create(): Promise {
+ static async create(savedHandle?: FileSystemDirectoryHandle | null): Promise {
+ // Try to re-use a saved handle first
+ if (savedHandle) {
+ try {
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ const permission = await (savedHandle as any).requestPermission({ mode: 'readwrite' });
+ if (permission === 'granted') {
+ return new FolderPickerWriter(savedHandle);
+ }
+ } catch {
+ // Fall through to show the picker
+ }
+ }
+
// showDirectoryPicker is part of the File System Access API (not yet in all TS DOM libs)
// eslint-disable-next-line @typescript-eslint/no-explicit-any
try {
@@ -214,3 +244,54 @@ export class FolderPickerWriter implements IBulkDownloadWriter {
}
}
}
+
+/**
+ * Writes files directly into a folder on the local filesystem via the
+ * Neutralino desktop bridge. Subdirectories are created automatically.
+ */
+export class NeutralinoFolderWriter implements IBulkDownloadWriter {
+ constructor(private readonly basePath: string) {}
+
+ async write(files: AsyncIterable): Promise {
+ // Import once per write() call; the module system caches the result.
+ const bridge = (await import('./desktop/neutralino-bridge.js')) as unknown as NeutralinoBridge;
+ const createdDirs = new Set();
+
+ for await (const file of files) {
+ const parts = file.name.split('/').filter(Boolean);
+ if (parts.length === 0) continue;
+
+ // Ensure all parent directories exist
+ for (let i = 1; i < parts.length; i++) {
+ const dirPath = this.basePath + '/' + parts.slice(0, i).join('/');
+ if (!createdDirs.has(dirPath)) {
+ try {
+ await bridge.filesystem.createDirectory(dirPath);
+ } catch {
+ // Directory may already exist; ignore
+ }
+ createdDirs.add(dirPath);
+ }
+ }
+
+ const filePath = this.basePath + '/' + file.name;
+ let buffer: ArrayBuffer;
+ const { input } = file;
+ if (input instanceof Blob) {
+ buffer = await input.arrayBuffer();
+ } else if (typeof input === 'string') {
+ const encoded = new TextEncoder().encode(input);
+ buffer = encoded.buffer.slice(
+ encoded.byteOffset,
+ encoded.byteOffset + encoded.byteLength
+ ) as ArrayBuffer;
+ } else if (input instanceof Uint8Array) {
+ buffer = input.buffer.slice(input.byteOffset, input.byteOffset + input.byteLength) as ArrayBuffer;
+ } else {
+ buffer = input;
+ }
+
+ await bridge.filesystem.writeBinaryFile(filePath, buffer);
+ }
+ }
+}
diff --git a/js/desktop/neutralino-bridge.js b/js/desktop/neutralino-bridge.js
index 85a686a29..eceee4aa5 100644
--- a/js/desktop/neutralino-bridge.js
+++ b/js/desktop/neutralino-bridge.js
@@ -177,6 +177,21 @@ export const filesystem = {
window.parent.postMessage({ type: 'NL_FS_APPEND_BINARY', id, path, buffer }, '*', [buffer]);
});
},
+ createDirectory: async (path) => {
+ if (!isNeutralino) return;
+ return new Promise((resolve, reject) => {
+ const id = Math.random().toString(36).substring(7);
+ const handler = (event) => {
+ if (event.data?.type === 'NL_RESPONSE' && event.data.id === id) {
+ window.removeEventListener('message', handler);
+ if (event.data.error) reject(event.data.error);
+ else resolve(event.data.result);
+ }
+ };
+ window.addEventListener('message', handler);
+ window.parent.postMessage({ type: 'NL_FS_CREATE_DIR', id, path }, '*');
+ });
+ },
};
export const updater = {
diff --git a/js/download-utils.ts b/js/download-utils.ts
index 61c7b6f36..51ed90c69 100644
--- a/js/download-utils.ts
+++ b/js/download-utils.ts
@@ -26,21 +26,68 @@ export function triggerDownload(blob: Blob, filename: string): void {
}
/**
- * Applies audio post-processing to a blob:
- * 1. Transcodes to a custom ffmpeg format if `quality` identifies one.
- * 2. Re-muxes to the user-selected lossless container when the quality is
- * a lossless tier (quality ends with "LOSSLESS").
+ * Apply post-processing to an audio Blob according to the requested quality.
*
- * Returns the (possibly transformed) blob.
+ * This function:
+ * - Detects the source container/extension via getExtensionFromBlob.
+ * - Determines whether the source is lossless:
+ * - FLAC is always lossless.
+ * - M4A is treated as lossless only when trackAudioQuality is "LOSSLESS" or "HI_RES_LOSSLESS".
+ * - If a custom lossy format is requested (isCustomFormat(quality)):
+ * - If the source is already lossy, returns the original Blob to avoid quality degradation.
+ * - Otherwise, obtains the custom format via getCustomFormat and transcodes using
+ * transcodeWithCustomFormat(...). Progress events are reported via onProgress.
+ * - If encoding fails, onProgress is notified with an error stage and the original error is rethrown.
+ * - If a lossless output is requested (quality ends with "LOSSLESS"):
+ * - Retrieves the configured lossless container and its format handler.
+ * - If the source is not lossless, logs a warning and returns the original Blob.
+ * - Otherwise:
+ * - If containerFmt.needsTranscode(blob) is true, transcodes via transcodeWithContainerFormat(...).
+ * - Else if the source is FLAC, calls rebuildFlacWithoutMetadata to strip/rebuild metadata safely.
+ * - Else remuxes into the desired container via ffmpegNewContainer (maps m4a -> mp4 where appropriate).
+ * - Any non-abort errors during lossless container conversion are caught and logged (conversion is best-effort).
+ *
+ * Progress and cancellation:
+ * - onProgress, if provided, will be called with progress/update/error events from the underlying encoding/transcode helpers.
+ * - An AbortSignal may be provided to cancel long-running transcode operations; abort-related errors (AbortError)
+ * are propagated.
+ *
+ * @param blob - The source audio Blob to process.
+ * @param quality - Requested output quality identifier (may indicate custom lossy format or lossless output).
+ * @param onProgress - Optional callback invoked with progress/update events (or error notifications).
+ * @param signal - Optional AbortSignal used to cancel asynchronous transcode operations.
+ * @param trackAudioQuality - Optional track audio quality information from the API (e.g. "LOSSLESS", "HI_RES_LOSSLESS")
+ * used to determine whether an m4a source should be treated as lossless.
+ * @returns A Promise that resolves to the resulting audio Blob (may be the original blob if no processing was needed
+ * or if processing was skipped due to source/quality constraints).
+ * @throws Throws underlying encoding/transcoding errors (including AbortError when aborted). Encoding errors during
+ * custom-format transcode are rethrown after reporting via onProgress. Non-abort errors during lossless
+ * container conversion are logged and do not necessarily propagate.
*/
export async function applyAudioPostProcessing(
blob: Blob,
quality: string,
onProgress: ((progress: ProgressEvent) => void) | null = null,
- signal: AbortSignal | null = null
+ signal: AbortSignal | null = null,
+ trackAudioQuality: string | null = null
): Promise {
- // Transcode to custom format if requested
+ const extension = await getExtensionFromBlob(blob);
+
+ // Determine whether the downloaded source is lossless.
+ // FLAC is always lossless. m4a is lossless only when the track's
+ // audio quality from the API is LOSSLESS or HI_RES_LOSSLESS; otherwise
+ // it is AAC (lossy).
+ const sourceIsLossless =
+ extension === 'flac' ||
+ (extension === 'm4a' && (trackAudioQuality === 'LOSSLESS' || trackAudioQuality === 'HI_RES_LOSSLESS'));
+
+ // Transcode to custom lossy format if requested
if (isCustomFormat(quality)) {
+ // If the source is already lossy, transcoding would degrade quality
+ // further (lossy → lossy). Return the blob as-is instead.
+ if (!sourceIsLossless) {
+ return blob;
+ }
const format = getCustomFormat(quality);
if (format) {
try {
@@ -59,8 +106,15 @@ export async function applyAudioPostProcessing(
if (quality.endsWith('LOSSLESS')) {
try {
- const containerFmt = getContainerFormat(losslessContainerSettings.getContainer());
- const extension = await getExtensionFromBlob(blob);
+ const containerName = losslessContainerSettings.getContainer();
+ const containerFmt = getContainerFormat(containerName);
+
+ if (!sourceIsLossless) {
+ console.warn(
+ `Requested lossless output but source is not lossless (quality: ${quality}, trackAudioQuality: ${trackAudioQuality}, extension: ${extension}).`
+ );
+ return blob;
+ }
if (await containerFmt?.needsTranscode(blob)) {
blob = await transcodeWithContainerFormat(blob, containerFmt, onProgress, signal);
diff --git a/js/downloads.js b/js/downloads.js
index 987a0b880..58749dd2c 100644
--- a/js/downloads.js
+++ b/js/downloads.js
@@ -5,6 +5,7 @@ import {
RATE_LIMIT_ERROR_MESSAGE,
getTrackArtists,
getTrackTitle,
+ formatPathTemplate,
getCoverBlob,
getExtensionFromBlob,
formatTemplate,
@@ -12,17 +13,20 @@ import {
getTrackDiscNumber,
} from './utils.js';
import { AbortError } from './errorTypes.ts';
-import { lyricsSettings, bulkDownloadSettings, playlistSettings } from './storage.js';
+import { lyricsSettings, playlistSettings } from './storage.js';
import { generateM3U, generateM3U8, generateCUE, generateNFO, generateJSON } from './playlist-generator.js';
import {
ZipStreamWriter,
ZipBlobWriter,
ZipNeutralinoWriter,
FolderPickerWriter,
+ NeutralinoFolderWriter,
SequentialFileWriter,
} from './bulk-download-writer.ts';
import { FfmpegProgress } from './ffmpeg.types.js';
import { DownloadProgress, ProgressMessage, SegmentedDownloadProgress } from './progressEvents.js';
+import { db } from './db.js';
+import { modernSettings } from './ModernSettings.js';
import { SVG_CLOSE } from './icons.ts';
const downloadTasks = new Map();
@@ -30,6 +34,11 @@ const bulkDownloadTasks = new Map();
const ongoingDownloads = new Set();
let downloadNotificationContainer = null;
+/** Wraps a single {@link WriterEntry}-like object as an AsyncIterable for use with IBulkDownloadWriter.write(). */
+async function* singleWriterEntry(entry) {
+ yield entry;
+}
+
async function createDiscLayoutContext(tracks, api) {
if (!playlistSettings.shouldSeparateDiscsInZip()) {
return { separateByDisc: false, resolveDiscNumber: () => 1 };
@@ -488,6 +497,71 @@ async function bulkDownload(
await writer.write(yieldFiles());
}
+/**
+ * Returns a writer that can be used to save a single-track download directly
+ * to the configured folder (Local Media Folder or saved Folder Picker handle),
+ * or `null` if the feature is not active / no folder is configured.
+ *
+ * In contrast to {@link createBulkWriter}, this never prompts the user – it
+ * only succeeds when the folder is already known.
+ */
+async function createSingleTrackFolderWriter() {
+ if (!modernSettings.downloadSinglesToFolder) return null;
+
+ const isNeutralino =
+ typeof window !== 'undefined' &&
+ (window.NL_MODE || window.location.search.includes('mode=neutralino') || window.parent !== window);
+ const method = modernSettings.bulkDownloadMethod;
+ const hasFolderPicker = 'showDirectoryPicker' in window;
+
+ if (method === 'local') {
+ const localHandle = await db.getSetting('local_folder_handle');
+ if (isNeutralino) {
+ if (localHandle?.path) return new NeutralinoFolderWriter(localHandle.path);
+ } else if (hasFolderPicker && localHandle && typeof localHandle.requestPermission === 'function') {
+ try {
+ const permission = await localHandle.requestPermission({ mode: 'readwrite' });
+ if (permission === 'granted') return FolderPickerWriter.fromHandle(localHandle);
+ } catch {
+ // no permission
+ }
+ }
+ return null;
+ }
+
+ if (method === 'folder' && hasFolderPicker) {
+ const rememberFolder = modernSettings.rememberBulkDownloadFolder;
+ const savedHandle = rememberFolder ? modernSettings.bulkDownloadFolder : null;
+ // Try to reuse the saved handle silently first.
+ if (savedHandle && typeof savedHandle.requestPermission === 'function') {
+ try {
+ const permission = await savedHandle.requestPermission({ mode: 'readwrite' });
+ if (permission === 'granted') return FolderPickerWriter.fromHandle(savedHandle);
+ } catch {
+ // fall through to picker
+ }
+ }
+ // No usable saved handle – open the picker so the user can choose a folder.
+ try {
+ const writer = await FolderPickerWriter.create();
+ if (rememberFolder) {
+ modernSettings.bulkDownloadFolder = writer.getDirHandle();
+ await modernSettings.waitPending();
+ }
+ return writer;
+ } catch (error) {
+ if (error instanceof DOMException && error.name === 'AbortError') {
+ // User cancelled the picker – return null so we fall back to the
+ // normal browser download instead of erroring out.
+ return null;
+ }
+ return null;
+ }
+ }
+
+ return null;
+}
+
/**
* Returns the appropriate bulk download writer for the current settings and environment,
* or null when individual sequential downloads should be used.
@@ -496,17 +570,75 @@ async function createBulkWriter(folderName) {
const isNeutralino =
typeof window !== 'undefined' &&
(window.NL_MODE || window.location.search.includes('mode=neutralino') || window.parent !== window);
- const method = bulkDownloadSettings.getMethod();
- const forceZipBlob = bulkDownloadSettings.shouldForceZipBlob();
+ const method = modernSettings.bulkDownloadMethod;
+ const forceZipBlob = modernSettings.forceZipBlob;
const hasFileSystemAccess = 'showSaveFilePicker' in window && 'createWritable' in FileSystemFileHandle.prototype;
const hasFolderPicker = 'showDirectoryPicker' in window;
+ // ── Local Media Folder method ────────────────────────────────────────────
+ if (method === 'local') {
+ const localHandle = await db.getSetting('local_folder_handle');
+ if (isNeutralino) {
+ if (localHandle?.path) {
+ return new NeutralinoFolderWriter(localHandle.path);
+ }
+ // No folder configured – prompt now
+ const bridge = await import('./desktop/neutralino-bridge.js');
+ const pickedPath = await bridge.os.showFolderDialog('Select Download Folder');
+ if (!pickedPath) return null; // user cancelled – fall back to default
+ // Persist as the local media folder so future downloads reuse it
+ const handle = {
+ name: pickedPath.split(/[/\\]/).pop() || pickedPath,
+ isNeutralino: true,
+ path: pickedPath,
+ };
+ await db.saveSetting('local_folder_handle', handle);
+ return new NeutralinoFolderWriter(pickedPath);
+ } else if (hasFolderPicker) {
+ // Browser mode: try to reuse the stored handle with write permission
+ if (localHandle && typeof localHandle.requestPermission === 'function') {
+ try {
+ const permission = await localHandle.requestPermission({ mode: 'readwrite' });
+ if (permission === 'granted') {
+ return FolderPickerWriter.fromHandle(localHandle);
+ }
+ } catch {
+ // fall through to picker
+ }
+ }
+ // No usable handle – prompt and persist
+ try {
+ const writer = await FolderPickerWriter.create();
+ await db.saveSetting('local_folder_handle', writer.getDirHandle());
+ return writer;
+ } catch (error) {
+ if (error instanceof DOMException && error.name === 'AbortError') {
+ throw error;
+ }
+ return null;
+ }
+ }
+ // Browser without File System Access API – fall through to ZIP
+ }
+
+ // ── Neutralino default (ZIP) ─────────────────────────────────────────────
if (isNeutralino) {
return new ZipNeutralinoWriter(folderName);
}
+
+ // ── Folder Picker method ─────────────────────────────────────────────────
if (method === 'folder' && hasFolderPicker) {
+ const rememberFolder = modernSettings.rememberBulkDownloadFolder;
+ const savedHandle = rememberFolder ? await db.getSetting('bulk_download_folder_handle') : null;
try {
- return await FolderPickerWriter.create();
+ const writer = await FolderPickerWriter.create(savedHandle);
+ if (rememberFolder) {
+ await db.saveSetting('bulk_download_folder_handle', writer.getDirHandle());
+ } else {
+ await db.saveSetting('bulk_download_folder_handle', null);
+ }
+
+ return writer;
} catch (error) {
if (error instanceof DOMException && error.name === 'AbortError') {
throw error;
@@ -514,6 +646,7 @@ async function createBulkWriter(folderName) {
return null;
}
}
+
if (method === 'individual') {
return new SequentialFileWriter();
}
@@ -556,6 +689,11 @@ async function startBulkDownload(
}
completeBulkDownload(notification, true);
+
+ // If the download went to the local media folder, refresh the local library.
+ if (modernSettings.bulkDownloadMethod === 'local') {
+ window.refreshLocalMediaFolder?.();
+ }
} catch (error) {
if (error.name === 'AbortError') {
removeBulkDownloadTask(notification);
@@ -579,7 +717,7 @@ export async function downloadAlbumAsZip(album, tracks, api, quality, lyricsMana
const releaseDate = releaseDateStr ? new Date(releaseDateStr) : null;
const year = releaseDate && !isNaN(releaseDate.getTime()) ? releaseDate.getFullYear() : '';
- const folderName = formatTemplate(localStorage.getItem('zip-folder-template') || '{albumTitle} - {albumArtist}', {
+ const folderName = formatPathTemplate(modernSettings.folderTemplate, {
albumTitle: album.title,
albumArtist: album.artist?.name,
year: year,
@@ -600,7 +738,7 @@ export async function downloadAlbumAsZip(album, tracks, api, quality, lyricsMana
}
export async function downloadPlaylistAsZip(playlist, tracks, api, quality, lyricsManager = null) {
- const folderName = formatTemplate(localStorage.getItem('zip-folder-template') || '{albumTitle} - {albumArtist}', {
+ const folderName = formatPathTemplate(modernSettings.folderTemplate, {
albumTitle: playlist.title,
albumArtist: 'Playlist',
year: new Date().getFullYear(),
@@ -643,14 +781,11 @@ export async function downloadDiscography(artist, selectedReleases, api, quality
const releaseDate = releaseDateStr ? new Date(releaseDateStr) : null;
const year = releaseDate && !isNaN(releaseDate.getTime()) ? releaseDate.getFullYear() : '';
- const albumFolder = formatTemplate(
- localStorage.getItem('zip-folder-template') || '{albumTitle} - {albumArtist}',
- {
- albumTitle: fullAlbum.title,
- albumArtist: fullAlbum.artist?.name,
- year: year,
- }
- );
+ const albumFolder = formatPathTemplate(modernSettings.folderTemplate, {
+ albumTitle: fullAlbum.title,
+ albumArtist: fullAlbum.artist?.name,
+ year: year,
+ });
const fullFolderPath = `${rootFolder}/${albumFolder}`;
if (coverBlob && playlistSettings.shouldIncludeCover())
@@ -942,16 +1077,63 @@ export async function downloadTrackWithMetadata(track, quality, api, lyricsManag
ongoingDownloads.add(downloadKey);
try {
+ // Resolve the folder writer before registering the download task so that
+ // any permission prompt (requestPermission) shows before the UI task appears.
+ const folderWriter = await createSingleTrackFolderWriter();
+
addDownloadTask(track.id, enrichedTrack, filename, api, controller);
- await api.downloadTrack(track.id, quality, filename, {
- signal: controller.signal,
- track: enrichedTrack,
- onProgress: (progress) => {
- updateDownloadProgress(track.id, progress);
- },
- calculateDashBytes: true,
- });
+ // Try to write directly to the configured folder when the feature is enabled.
+ if (folderWriter) {
+ // Download the blob (metadata already applied inside downloadTrack)
+ const blob = await api.downloadTrack(track.id, quality, filename, {
+ signal: controller.signal,
+ track: enrichedTrack,
+ onProgress: (progress) => {
+ updateDownloadProgress(track.id, progress);
+ },
+ calculateDashBytes: true,
+ triggerDownload: false,
+ });
+
+ const currentExtension = filename.split('.').pop()?.toLowerCase();
+ const finalFilename = buildTrackFilename(track, quality, await getExtensionFromBlob(blob))
+ .split('/')
+ .pop();
+
+ // Compute a subfolder path using the same template as bulk downloads so
+ // the track lands in e.g. "Album Title - Artist/" instead of the folder root.
+ const releaseDateStr =
+ enrichedTrack.album?.releaseDate ||
+ (enrichedTrack.streamStartDate ? enrichedTrack.streamStartDate.split('T')[0] : '');
+ const releaseDate = releaseDateStr ? new Date(releaseDateStr) : null;
+ const releaseYear = releaseDate && !isNaN(releaseDate.getTime()) ? releaseDate.getFullYear() : '';
+ const subFolder = formatPathTemplate(modernSettings.folderTemplate, {
+ albumTitle: enrichedTrack.album?.title,
+ albumArtist: enrichedTrack.album?.artist?.name || enrichedTrack.artist?.name,
+ year: releaseYear,
+ });
+ const entryName = subFolder ? `${subFolder}/${finalFilename}` : finalFilename;
+
+ // Write to folder using IBulkDownloadWriter.write() via singleWriterEntry().
+ await folderWriter.write(singleWriterEntry({ name: entryName, lastModified: new Date(), input: blob }));
+
+ // If the target is the local media folder, do a cheap partial update:
+ // pass the downloaded blob and base filename so only this one track's metadata
+ // is read and inserted into localFilesCache instead of re-walking the whole folder.
+ if (modernSettings.bulkDownloadMethod === 'local') {
+ window.refreshLocalMediaFolder?.(blob, finalFilename);
+ }
+ } else {
+ await api.downloadTrack(track.id, quality, filename, {
+ signal: controller.signal,
+ track: enrichedTrack,
+ onProgress: (progress) => {
+ updateDownloadProgress(track.id, progress);
+ },
+ calculateDashBytes: true,
+ });
+ }
completeDownloadTask(track.id, true);
diff --git a/js/settings.js b/js/settings.js
index 060b910cb..98119c3ae 100644
--- a/js/settings.js
+++ b/js/settings.js
@@ -16,7 +16,6 @@ import {
qualityBadgeSettings,
trackDateSettings,
visualizerSettings,
- bulkDownloadSettings,
playlistSettings,
equalizerSettings,
listenBrainzSettings,
@@ -41,6 +40,7 @@ import { db } from './db.js';
import { authManager } from './accounts/auth.js';
import { syncManager } from './accounts/pocketbase.js';
import { containerFormats, customFormats } from './ffmpegFormats.ts';
+import { modernSettings } from './ModernSettings.js';
async function getButterchurnPresets(...args) {
const butterchurnModule = await import('./visualizers/butterchurn.js');
@@ -949,44 +949,157 @@ export async function initializeSettings(scrobbler, player, api, ui) {
'showSaveFilePicker' in window &&
typeof FileSystemFileHandle !== 'undefined' &&
'createWritable' in FileSystemFileHandle.prototype;
+ const hasFolderPicker = 'showDirectoryPicker' in window;
+
+ const rememberFolderSetting = document.getElementById('remember-folder-setting');
+ const rememberFolderToggle = document.getElementById('remember-folder-toggle');
+ const resetSavedFolderSetting = document.getElementById('reset-saved-folder-setting');
+ const resetSavedFolderBtn = document.getElementById('reset-saved-folder-btn');
+ const singleToFolderSetting = document.getElementById('single-to-folder-setting');
+ const singleToFolderToggle = document.getElementById('single-to-folder-toggle');
/** Shows/hides the Force ZIP as Blob setting based on method and browser support */
function updateForceZipBlobVisibility() {
if (!forceZipBlobSettingItem) return;
- const method = bulkDownloadSettings.getMethod();
+ const method = modernSettings.bulkDownloadMethod;
// Only relevant when zip method is selected and the browser supports streaming
const visible = method === 'zip' && hasFileSystemAccess;
forceZipBlobSettingItem.style.display = visible ? '' : 'none';
}
+ /** Shows/hides folder-picker-specific and folder-method settings */
+ async function updateFolderMethodVisibility() {
+ const method = modernSettings.bulkDownloadMethod;
+ const isFolderMethod = method === 'folder';
+ const isFolderOrLocal = isFolderMethod || method === 'local';
+
+ if (rememberFolderSetting) {
+ rememberFolderSetting.style.display = isFolderMethod && hasFolderPicker ? '' : 'none';
+ }
+
+ // Reset button: only visible when folder method + remember enabled + valid saved handle exists
+ if (resetSavedFolderSetting) {
+ let showReset = false;
+ if (isFolderMethod && hasFolderPicker && modernSettings.rememberBulkDownloadFolder) {
+ const savedHandle = await db.getSetting('bulk_download_folder_handle');
+ showReset = !!savedHandle;
+ }
+ resetSavedFolderSetting.style.display = showReset ? '' : 'none';
+ }
+
+ if (singleToFolderSetting) {
+ singleToFolderSetting.style.display = isFolderOrLocal ? '' : 'none';
+ }
+ }
+
const bulkDownloadMethod = document.getElementById('bulk-download-method');
if (bulkDownloadMethod) {
// Remove the folder picker option if the browser doesn't support it
- if (!('showDirectoryPicker' in window)) {
+ if (!hasFolderPicker) {
const folderOption = bulkDownloadMethod.querySelector('option[value="folder"]');
if (folderOption) {
folderOption.remove();
}
- // If the stored method is 'folder', fall back to 'zip'
- if (bulkDownloadSettings.getMethod() === 'folder') {
- bulkDownloadSettings.setMethod('zip');
+ const localOption = bulkDownloadMethod.querySelector('option[value="local"]');
+ if (localOption) {
+ localOption.remove();
+ }
+ // If the stored method is 'folder' or 'local' without native support, fall back to 'zip'
+ const currentMethod = modernSettings.bulkDownloadMethod;
+ if (currentMethod === 'folder' || currentMethod === 'local') {
+ modernSettings.bulkDownloadMethod = 'zip';
}
}
- bulkDownloadMethod.value = bulkDownloadSettings.getMethod();
- bulkDownloadMethod.addEventListener('change', (e) => {
- bulkDownloadSettings.setMethod(e.target.value);
+ bulkDownloadMethod.value = modernSettings.bulkDownloadMethod;
+ bulkDownloadMethod.addEventListener('change', async (e) => {
+ const previousMethod = modernSettings.bulkDownloadMethod;
+ const newMethod = e.target.value;
+ modernSettings.bulkDownloadMethod = newMethod;
+
+ // When switching to 'local', prompt to select the local media folder if not yet configured
+ if (newMethod === 'local') {
+ const existingHandle = await db.getSetting('local_folder_handle');
+ if (!existingHandle) {
+ let picked = false;
+ try {
+ const isNeutralino =
+ window.Neutralino && (window.NL_MODE || window.location.search.includes('mode=neutralino'));
+ if (isNeutralino) {
+ const path = await window.Neutralino.os.showFolderDialog('Select Local Media Folder');
+ if (path) {
+ picked = true;
+ const handle = {
+ name: path.split(/[/\\]/).pop() || path,
+ isNeutralino: true,
+ path,
+ };
+ await db.saveSetting('local_folder_handle', handle);
+ }
+ } else if (hasFolderPicker) {
+ const handle = await window.showDirectoryPicker({ mode: 'readwrite' });
+ if (handle) {
+ picked = true;
+ await db.saveSetting('local_folder_handle', handle);
+ }
+ }
+ } catch {
+ // User cancelled the picker
+ }
+
+ if (!picked) {
+ // Revert to the previous method since no folder was selected.
+ // Guard against the edge case where the previousMethod option
+ // no longer exists in the dropdown (e.g. removed due to no API support).
+ if (bulkDownloadMethod.querySelector(`option[value="${previousMethod}"]`)) {
+ modernSettings.bulkDownloadMethod = previousMethod;
+ bulkDownloadMethod.value = previousMethod;
+ } else {
+ // Fall back to zip which is always present
+ modernSettings.bulkDownloadMethod = 'zip';
+ bulkDownloadMethod.value = 'zip';
+ }
+ }
+ }
+ }
+ await modernSettings.waitPending();
+
updateForceZipBlobVisibility();
+ await updateFolderMethodVisibility();
+ });
+ }
+
+ if (rememberFolderToggle) {
+ rememberFolderToggle.checked = modernSettings.rememberBulkDownloadFolder;
+ rememberFolderToggle.addEventListener('change', async (e) => {
+ modernSettings.rememberBulkDownloadFolder = !!e.target.checked;
+ await modernSettings.waitPending();
+ await updateFolderMethodVisibility();
+ });
+ }
+
+ if (resetSavedFolderBtn) {
+ resetSavedFolderBtn.addEventListener('click', async () => {
+ await db.saveSetting('bulk_download_folder_handle', null);
+ await updateFolderMethodVisibility();
+ });
+ }
+
+ if (singleToFolderToggle) {
+ singleToFolderToggle.checked = modernSettings.downloadSinglesToFolder;
+ singleToFolderToggle.addEventListener('change', (e) => {
+ modernSettings.downloadSinglesToFolder = !!e.target.checked;
});
}
if (forceZipBlobToggle) {
- forceZipBlobToggle.checked = bulkDownloadSettings.shouldForceZipBlob();
+ forceZipBlobToggle.checked = modernSettings.forceZipBlob;
forceZipBlobToggle.addEventListener('change', (e) => {
- bulkDownloadSettings.setForceZipBlob(e.target.checked);
+ modernSettings.forceZipBlob = !!e.target.checked;
});
}
updateForceZipBlobVisibility();
+ updateFolderMethodVisibility();
const includeCoverToggle = document.getElementById('include-cover-toggle');
if (includeCoverToggle) {
@@ -2745,18 +2858,18 @@ export async function initializeSettings(scrobbler, player, api, ui) {
// Filename template setting
const filenameTemplate = document.getElementById('filename-template');
if (filenameTemplate) {
- filenameTemplate.value = localStorage.getItem('filename-template') || '{trackNumber} - {artist} - {title}';
+ filenameTemplate.value = modernSettings.filenameTemplate;
filenameTemplate.addEventListener('change', (e) => {
- localStorage.setItem('filename-template', e.target.value);
+ modernSettings.filenameTemplate = String(e.target.value);
});
}
// ZIP folder template
const zipFolderTemplate = document.getElementById('zip-folder-template');
if (zipFolderTemplate) {
- zipFolderTemplate.value = localStorage.getItem('zip-folder-template') || '{albumTitle} - {albumArtist}';
+ zipFolderTemplate.value = modernSettings.folderTemplate;
zipFolderTemplate.addEventListener('change', (e) => {
- localStorage.setItem('zip-folder-template', e.target.value);
+ modernSettings.folderTemplate = String(e.target.value);
});
}
diff --git a/js/storage.js b/js/storage.js
index 8483a25aa..f4884a25a 100644
--- a/js/storage.js
+++ b/js/storage.js
@@ -721,58 +721,6 @@ export const trackDateSettings = {
},
};
-export const bulkDownloadSettings = {
- METHOD_KEY: 'bulk-download-method',
- FORCE_ZIP_BLOB_KEY: 'bulk-download-force-zip-blob',
- LEGACY_INDIVIDUAL_KEY: 'force-individual-downloads',
- VALID_METHODS: ['zip', 'folder', 'individual'],
-
- /** Returns the selected bulk download method: 'zip' | 'folder' | 'individual' */
- getMethod() {
- try {
- const stored = localStorage.getItem(this.METHOD_KEY);
- if (stored && this.VALID_METHODS.includes(stored)) {
- return stored;
- }
- const legacy = localStorage.getItem(this.LEGACY_INDIVIDUAL_KEY);
- if (legacy === 'true') {
- localStorage.setItem(this.METHOD_KEY, 'individual');
- localStorage.removeItem(this.LEGACY_INDIVIDUAL_KEY);
- return 'individual';
- }
- return 'zip';
- } catch {
- return 'zip';
- }
- },
-
- setMethod(method) {
- localStorage.setItem(this.METHOD_KEY, method);
- },
-
- /** When using ZIP mode, force in-memory blob download instead of streaming to disk */
- shouldForceZipBlob() {
- try {
- return localStorage.getItem(this.FORCE_ZIP_BLOB_KEY) === 'true';
- } catch {
- return false;
- }
- },
-
- setForceZipBlob(enabled) {
- localStorage.setItem(this.FORCE_ZIP_BLOB_KEY, enabled ? 'true' : 'false');
- },
-
- // Kept for backward compatibility
- shouldForceIndividual() {
- return this.getMethod() === 'individual';
- },
-
- setForceIndividual(enabled) {
- this.setMethod(enabled ? 'individual' : 'zip');
- },
-};
-
export const playlistSettings = {
M3U_KEY: 'playlist-generate-m3u',
M3U8_KEY: 'playlist-generate-m3u8',
diff --git a/js/ui.js b/js/ui.js
index 770252a62..5793b0b45 100644
--- a/js/ui.js
+++ b/js/ui.js
@@ -1852,6 +1852,12 @@ export class UIRenderer {
if (introDiv) introDiv.style.display = 'block';
if (headerDiv) headerDiv.style.display = 'none';
if (listContainer) listContainer.innerHTML = '';
+ // Kick off a background scan when there is a saved folder handle but
+ // the cache hasn't been populated yet (e.g. first visit after a page
+ // reload where the startup scan was silently denied permission).
+ if (!window.localFilesScanInProgress && !window.localFilesCache) {
+ window.refreshLocalMediaFolder?.();
+ }
}
} else {
if (selectBtnText) selectBtnText.textContent = 'Select Music Folder';
diff --git a/js/utils.js b/js/utils.js
index c244fc8ee..b7425ab7a 100644
--- a/js/utils.js
+++ b/js/utils.js
@@ -1,4 +1,5 @@
//js/utils.js
+import { modernSettings } from './ModernSettings.js';
import { qualityBadgeSettings, coverArtSizeSettings, trackDateSettings } from './storage.js';
export const QUALITY = 'HI_RES_LOSSLESS';
@@ -69,6 +70,45 @@ export const sanitizeForFilename = (value) => {
.trim();
};
+/**
+ * Sanitizes a single path component (no slashes allowed in the output).
+ * Invalid filesystem characters are replaced with underscores.
+ */
+export const sanitizeForPathComponent = (value) => {
+ if (!value) return 'Unknown';
+ return value
+ .replace(/[\\/:*?"<>|]/g, '_')
+ .replace(/\s+/g, ' ')
+ .trim();
+};
+
+/**
+ * Like {@link formatTemplate} but allows `/` in the template for nested
+ * directory structures. Each path component has invalid characters replaced,
+ * the path is normalised to forward-slash separators, and empty components,
+ * `.`, and `..` segments are stripped.
+ */
+export const formatPathTemplate = (template, data) => {
+ let result = replaceTokens(template, {
+ discNumber: String(Number(data.discNumber || 1)),
+ trackNumber: data.trackNumber ? String(data.trackNumber).padStart(2, '0') : '00',
+ artist: sanitizeForPathComponent(data.artist || 'Unknown Artist'),
+ title: sanitizeForPathComponent(data.title || 'Unknown Title'),
+ album: sanitizeForPathComponent(data.album || 'Unknown Album'),
+ albumArtist: sanitizeForPathComponent(data.albumArtist || 'Unknown Artist'),
+ albumTitle: sanitizeForPathComponent(data.albumTitle || 'Unknown Album'),
+ year: sanitizeForPathComponent(String(data.year || 'Unknown')),
+ });
+
+ // Normalise separators, collapse duplicates, strip . and ..
+ return result
+ .replace(/\\/g, '/')
+ .split('/')
+ .map((p) => p.trim())
+ .filter((p) => p !== '' && p !== '.' && p !== '..')
+ .join('/');
+};
+
/**
* Detects audio format from DataView of first bytes
* @param {DataView} view - DataView of first 12 bytes of audio file
@@ -203,7 +243,7 @@ export const getExtensionForQuality = (quality) => {
};
export const buildTrackFilename = (track, quality, extension = null) => {
- const template = localStorage.getItem('filename-template') || '{trackNumber} - {artist} - {title}';
+ const template = modernSettings.filenameTemplate;
const ext = extension || getExtensionForQuality(quality);
const artistName = track.artist?.name || track.artists?.[0]?.name || 'Unknown Artist';
@@ -366,18 +406,17 @@ export const getTrackArtistsHTML = (track = {}, { fallback = 'Unknown Artist' }
return fallback;
};
-export const formatTemplate = (template, data) => {
- let result = template;
- result = result.replace(/\{discNumber\}/g, String(Number(data.discNumber || 1)));
- result = result.replace(/\{trackNumber\}/g, data.trackNumber ? String(data.trackNumber).padStart(2, '0') : '00');
- result = result.replace(/\{artist\}/g, sanitizeForFilename(data.artist || 'Unknown Artist'));
- result = result.replace(/\{title\}/g, sanitizeForFilename(data.title || 'Unknown Title'));
- result = result.replace(/\{album\}/g, sanitizeForFilename(data.album || 'Unknown Album'));
- result = result.replace(/\{albumArtist\}/g, sanitizeForFilename(data.albumArtist || 'Unknown Artist'));
- result = result.replace(/\{albumTitle\}/g, sanitizeForFilename(data.albumTitle || 'Unknown Album'));
- result = result.replace(/\{year\}/g, data.year || 'Unknown');
- return result;
-};
+export const formatTemplate = (template, data) =>
+ replaceTokens(template, {
+ discNumber: String(Number(data.discNumber || 1)),
+ trackNumber: data.trackNumber ? String(data.trackNumber).padStart(2, '0') : '00',
+ artist: sanitizeForFilename(data.artist || 'Unknown Artist'),
+ title: sanitizeForFilename(data.title || 'Unknown Title'),
+ album: sanitizeForFilename(data.album || 'Unknown Album'),
+ albumArtist: sanitizeForFilename(data.albumArtist || 'Unknown Artist'),
+ albumTitle: sanitizeForFilename(data.albumTitle || 'Unknown Album'),
+ year: data.year || 'Unknown',
+ });
export const calculateTotalDuration = (tracks) => {
if (!Array.isArray(tracks) || tracks.length === 0) return 0;
@@ -678,3 +717,44 @@ export function getTrackDiscNumber(track) {
}
return null;
}
+
+/**
+ * Executes a function with a fallback error handler.
+ * Works with both synchronous and asynchronous callbacks.
+ *
+ * If the callback returns a Promise, the result will also be a Promise.
+ *
+ * @template T
+ * @param {() => T | Promise} fn Function to execute
+ * @param {(error: unknown) => T | Promise} onError Error handler
+ * @returns {T | Promise}
+ */
+export function tryCatch(fn, onError) {
+ try {
+ const result = fn();
+
+ if (result instanceof Promise) {
+ return result.catch(onError);
+ }
+
+ return result;
+ } catch (err) {
+ return onError(err);
+ }
+}
+
+/**
+ * Replace `{token}` placeholders in a template string.
+ *
+ * Replacement values are inserted verbatim and are NOT reprocessed,
+ * preventing cascading replacements if values contain token patterns.
+ *
+ * @param {string} template The input string containing tokens like `{tokenName}`
+ * @param {Record} tokens An object of tokens to replace and the replacement values.
+ * @returns {string} The string with valid tokens replaced
+ */
+export function replaceTokens(template, tokens) {
+ return template.replace(/{([^{}]+)}/g, (match, key) => {
+ return key in tokens ? tokens[key] : match;
+ });
+}
diff --git a/public/neutralino_loader.html b/public/neutralino_loader.html
index 50ef8fb17..0ee807dea 100644
--- a/public/neutralino_loader.html
+++ b/public/neutralino_loader.html
@@ -347,6 +347,26 @@
}
}
break;
+
+ case 'NL_FS_CREATE_DIR':
+ try {
+ await Neutralino.filesystem.createDirectory(event.data.path);
+ if (iframe && iframe.contentWindow) {
+ iframe.contentWindow.postMessage(
+ { type: 'NL_RESPONSE', id: event.data.id, result: 'success' },
+ '*'
+ );
+ }
+ } catch (e) {
+ console.error('[Shell] Create Directory failed:', e);
+ if (iframe && iframe.contentWindow) {
+ iframe.contentWindow.postMessage(
+ { type: 'NL_RESPONSE', id: event.data.id, error: e },
+ '*'
+ );
+ }
+ }
+ break;
}
});
From 5c5ea904c85577d836ee4d25aefa38e94e77eb29 Mon Sep 17 00:00:00 2001
From: DanTheMan827 <790119+DanTheMan827@users.noreply.github.com>
Date: Fri, 20 Mar 2026 23:00:41 +0000
Subject: [PATCH 152/226] style: auto-fix linting issues
---
js/HiFi.ts | 8 ++++----
styles.css | 4 ++--
2 files changed, 6 insertions(+), 6 deletions(-)
diff --git a/js/HiFi.ts b/js/HiFi.ts
index 5d855326a..7db70ac15 100644
--- a/js/HiFi.ts
+++ b/js/HiFi.ts
@@ -98,10 +98,10 @@ export class HiFiClient {
return Buffer.from(`${id}:${secret}`).toString('base64');
}
- static setToken(token: string, expiry: number = Date.now() + 60000) {
- HiFiClient.token = token;
- HiFiClient.appTokenExpiry = expiry
- }
+ static setToken(token: string, expiry: number = Date.now() + 60000) {
+ HiFiClient.token = token;
+ HiFiClient.appTokenExpiry = expiry;
+ }
private static async fetchAppToken(
signal: AbortSignal = new AbortController().signal,
diff --git a/styles.css b/styles.css
index 121499d2e..c50347cf0 100644
--- a/styles.css
+++ b/styles.css
@@ -2255,7 +2255,7 @@ body.multi-select-mode .track-item:hover {
font-size: 0.9rem;
font-weight: 500;
z-index: 1000;
- box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
+ box-shadow: 0 4px 20px rgb(0, 0, 0, 0.3);
gap: 12px;
align-items: center;
}
@@ -2285,7 +2285,7 @@ body.multi-select-mode .track-item:hover {
}
.selection-bar button:hover {
- background: rgba(255, 255, 255, 0.2);
+ background: rgb(255, 255, 255, 0.2);
}
.track-number {
From e22d2b96ade5f94b4af5a76f589e7d7d15781102 Mon Sep 17 00:00:00 2001
From: edidealt
Date: Sat, 21 Mar 2026 00:03:04 +0000
Subject: [PATCH 153/226] separate donate page
---
index.html | 89 ++++++++++++++++++----------------------------------
js/app.js | 27 ----------------
js/router.js | 3 ++
3 files changed, 34 insertions(+), 85 deletions(-)
diff --git a/index.html b/index.html
index 04b6aa1dd..68f693508 100644
--- a/index.html
+++ b/index.html
@@ -1457,52 +1457,6 @@
-
-
-
Support Monochrome
-
- If Monochrome has been useful to you and you're able to, consider making a donation. It helps pay
- for the server and domain, and you get to support us :)
-
-
-
-
- If you cannot financially support us, please consider starring the project on GitHub and sharing
- with friends!
-
-
- Star on GitHub
-
-
-
- Close
-
-
-
-
@@ -1609,7 +1563,7 @@
Importing Tracks from CSV
-
Donate to Monochrome
-
-
- If Monochrome has been useful to you and you're able to, consider making a donation.
- It helps pay for the domain, and you get to support us :)
+
+
Support Monochrome
+
+ If Monochrome has been useful to you and you're able to, consider making a donation. It
+ helps pay for the server and domain, and you get to support us :)
- Donate to Monochrome
+ Donate on Ko-fi
+
+
+ If you cannot financially support us, please consider starring the project on GitHub and
+ sharing with friends!
+
+
+ Star on GitHub
+
+
diff --git a/js/app.js b/js/app.js
index eb44c95e8..c616a4740 100644
--- a/js/app.js
+++ b/js/app.js
@@ -2710,33 +2710,6 @@ document.addEventListener('DOMContentLoaded', async () => {
});
}
- // Donate Modal Logic
- const donateModal = document.getElementById('donate-modal');
- const closeDonateModalBtn = document.getElementById('close-donate-modal-btn');
- const sidebarDonateLink = document.getElementById('sidebar-donate-link');
- const donateBtnAbout = document.getElementById('donate-btn');
- const donateBtnPage = document.getElementById('donate-btn-page');
-
- const openDonateModal = (e) => {
- if (e) e.preventDefault();
- trackOpenModal('Donate');
- donateModal.classList.add('active');
- };
-
- const closeDonateModal = () => {
- donateModal.classList.remove('active');
- trackCloseModal('Donate');
- };
-
- if (donateModal) {
- if (closeDonateModalBtn) closeDonateModalBtn.addEventListener('click', closeDonateModal);
- donateModal.querySelector('.modal-overlay')?.addEventListener('click', closeDonateModal);
-
- if (sidebarDonateLink) sidebarDonateLink.addEventListener('click', openDonateModal);
- if (donateBtnAbout) donateBtnAbout.addEventListener('click', openDonateModal);
- if (donateBtnPage) donateBtnPage.addEventListener('click', openDonateModal);
- }
-
// Listener for Pocketbase Sync updates
window.addEventListener('library-changed', () => {
const path = window.location.pathname;
diff --git a/js/router.js b/js/router.js
index 708557f69..0bc33e638 100644
--- a/js/router.js
+++ b/js/router.js
@@ -104,6 +104,9 @@ export function createRouter(ui) {
case 'home':
await ui.renderHomePage();
break;
+ case 'donate':
+ ui.showPage('donate');
+ break;
case 'user':
if (param && param.startsWith('@') && !param.includes('/')) {
await loadProfile(decodeURIComponent(param.slice(1)));
From c74389d330ffa2c802051546e1ea30d6a1c1fcdb Mon Sep 17 00:00:00 2001
From: Daniel <790119+DanTheMan827@users.noreply.github.com>
Date: Fri, 20 Mar 2026 19:44:21 -0500
Subject: [PATCH 154/226] fix(metadata): write xid to mp4 files
---
js/taglib.worker.ts | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/js/taglib.worker.ts b/js/taglib.worker.ts
index 5f0465f1f..87482b928 100644
--- a/js/taglib.worker.ts
+++ b/js/taglib.worker.ts
@@ -122,6 +122,10 @@ export async function addMetadataToAudio(message: _AddMetadataMessage): Promise<
if (copyright) props.replace('COPYRIGHT', [copyright]);
if (isrc) props.replace('ISRC', [isrc]);
+ if (isrc && isMp4) {
+ const mp4Tag = (underlying as Mp4File).tag() as Mp4Tag;
+ mp4Tag.setItem('xid ', Mp4Item.fromStringList([`:isrc:${isrc}`]));
+ }
if (lyrics) props.replace('LYRICS', [lyrics.replace(/\r/g, '').replace(/\n/g, '\r\n')]);
if (explicit !== undefined) {
From 3415901bdb797358f4b947a31d587acef17b00d8 Mon Sep 17 00:00:00 2001
From: akane <107654710+genericness@users.noreply.github.com>
Date: Sat, 21 Mar 2026 11:35:09 -0700
Subject: [PATCH 155/226] feat(ui): add cmdk-style command palette
---
index.html | 24 +-
js/commandPalette.js | 1640 ++++++++++++++++++++++++++----------------
styles.css | 270 ++++++-
3 files changed, 1278 insertions(+), 656 deletions(-)
diff --git a/index.html b/index.html
index 68f693508..4ef6d048b 100644
--- a/index.html
+++ b/index.html
@@ -1484,14 +1484,36 @@
Update Available
+
+ ↑↓ navigate
+ ↵ select
+ esc close
+
diff --git a/js/commandPalette.js b/js/commandPalette.js
index 154f47752..2fce066cb 100644
--- a/js/commandPalette.js
+++ b/js/commandPalette.js
@@ -3,6 +3,72 @@ import { db } from './db.js';
import Fuse from 'fuse.js';
import { navigate } from './router.js';
+const ICONS = {
+ search: '
',
+ house: '
',
+ library:
+ '
',
+ clock: '
',
+ calendar:
+ '
',
+ settings:
+ '
',
+ info: '
',
+ download:
+ '
',
+ heart: '
',
+ play: '
',
+ pause: '
',
+ skipForward:
+ '
',
+ skipBack:
+ '
',
+ shuffle:
+ '
',
+ repeat: '
',
+ volumeX:
+ '
',
+ volume: '
',
+ list: '
',
+ trash: '
',
+ text: '
',
+ maximize:
+ '
',
+ sparkles:
+ '
',
+ palette:
+ '
',
+ sun: '
',
+ moon: '
',
+ sliders:
+ '
',
+ plus: '
',
+ folderPlus:
+ '
',
+ user: '
',
+ logOut: '
',
+ logIn: '
',
+ keyboard:
+ '
',
+ music: '
',
+ disc: '
',
+ mic: '
',
+ upload: '
',
+ handHeart:
+ '
',
+ monitor:
+ '
',
+ pencil: '
',
+ radio: '
',
+ store: '
',
+};
+
+function escapeHtml(str) {
+ const div = document.createElement('div');
+ div.textContent = str;
+ return div.innerHTML;
+}
+
class CommandPalette {
constructor() {
this.overlay = document.getElementById('command-palette-overlay');
@@ -10,72 +76,603 @@ class CommandPalette {
this.resultsContainer = document.getElementById('command-palette-results');
this.isOpen = false;
this.selectedIndex = 0;
- this.results = [];
-
+ this.flatItems = [];
this.allSettings = [];
- this.debouncedSearch = debounce(this.performSearch.bind(this), 300);
+ this.musicSearchAbort = null;
+ this.debouncedMusicSearch = debounce(this.searchMusic.bind(this), 300);
+ this.commands = this.buildCommands();
+ this.fuse = new Fuse(this.commands, {
+ keys: [
+ { name: 'label', weight: 0.6 },
+ { name: 'keywords', weight: 0.3 },
+ { name: 'group', weight: 0.1 },
+ ],
+ threshold: 0.4,
+ ignoreLocation: true,
+ includeScore: true,
+ });
- this.commands = [
+ this.init();
+ }
+
+ buildCommands() {
+ return [
+ {
+ id: 'nav-home',
+ group: 'Navigation',
+ icon: 'house',
+ label: 'Go to Home',
+ keywords: ['home', 'main', 'start', 'landing'],
+ action: () => {
+ navigate('/');
+ },
+ },
+ {
+ id: 'nav-library',
+ group: 'Navigation',
+ icon: 'library',
+ label: 'Go to Library',
+ keywords: ['library', 'collection', 'playlists', 'favorites'],
+ action: () => {
+ navigate('/library');
+ },
+ },
+ {
+ id: 'nav-recent',
+ group: 'Navigation',
+ icon: 'clock',
+ label: 'Go to Recent',
+ keywords: ['recent', 'history', 'last played'],
+ action: () => {
+ navigate('/recent');
+ },
+ },
+ {
+ id: 'nav-unreleased',
+ group: 'Navigation',
+ icon: 'calendar',
+ label: 'Go to Unreleased',
+ keywords: ['unreleased', 'upcoming', 'tracker'],
+ action: () => {
+ navigate('/unreleased');
+ },
+ },
+ {
+ id: 'nav-settings',
+ group: 'Navigation',
+ icon: 'settings',
+ label: 'Go to Settings',
+ keywords: ['settings', 'preferences', 'config', 'options'],
+ shortcut: null,
+ action: () => {
+ navigate('/settings');
+ },
+ },
{
- name: 'theme',
- description: 'Change theme (white, dark, ocean, purple, forest, etc.)',
- action: (args) => this.handleTheme(args),
+ id: 'nav-about',
+ group: 'Navigation',
+ icon: 'info',
+ label: 'Go to About',
+ keywords: ['about', 'version', 'credits'],
+ action: () => {
+ navigate('/about');
+ },
},
{
- name: 'play',
- description: 'Search and play a track',
- action: (args, autoPick) => this.handlePlay(args, autoPick),
+ id: 'nav-download',
+ group: 'Navigation',
+ icon: 'download',
+ label: 'Go to Download',
+ keywords: ['download', 'desktop', 'app'],
+ action: () => {
+ navigate('/download');
+ },
},
{
- name: 'shuffle',
- description: 'Shuffle a playlist, artist, or album',
- action: (args, autoPick) => this.handleShuffle(args, autoPick),
+ id: 'nav-donate',
+ group: 'Navigation',
+ icon: 'handHeart',
+ label: 'Go to Donate',
+ keywords: ['donate', 'support', 'contribute'],
+ action: () => {
+ navigate('/donate');
+ },
},
+
{
- name: 'queue',
- description: 'Manage the queue (wipe, like all, download)',
- action: (args) => this.handleQueue(args),
+ id: 'play-pause',
+ group: 'Playback',
+ icon: 'play',
+ label: 'Play / Pause',
+ keywords: ['play', 'pause', 'toggle', 'resume', 'stop'],
+ shortcut: 'Space',
+ action: () => {
+ window.monochromePlayer?.handlePlayPause();
+ },
},
{
- name: 'setting',
- description: 'Search for a specific setting',
- action: (args) => this.handleSettingSearch(args),
+ id: 'play-next',
+ group: 'Playback',
+ icon: 'skipForward',
+ label: 'Next Track',
+ keywords: ['next', 'skip', 'forward'],
+ shortcut: 'Shift+→',
+ action: () => {
+ window.monochromePlayer?.playNext();
+ },
},
{
- name: 'sleep',
- description: 'Set sleep timer in minutes',
- action: (args) => this.handleSleepTimer(args),
+ id: 'play-prev',
+ group: 'Playback',
+ icon: 'skipBack',
+ label: 'Previous Track',
+ keywords: ['previous', 'back', 'rewind'],
+ shortcut: 'Shift+←',
+ action: () => {
+ window.monochromePlayer?.playPrev();
+ },
},
{
- name: 'quality',
- description: 'Set streaming & download quality',
- action: (args) => this.handleQuality(args),
+ id: 'play-shuffle',
+ group: 'Playback',
+ icon: 'shuffle',
+ label: 'Toggle Shuffle',
+ keywords: ['shuffle', 'random'],
+ shortcut: 'S',
+ action: () => {
+ document.getElementById('shuffle-btn')?.click();
+ },
},
{
- name: 'visualizer',
- description: 'Control visualizer (toggle, preset)',
- action: (args) => this.handleVisualizer(args),
+ id: 'play-repeat',
+ group: 'Playback',
+ icon: 'repeat',
+ label: 'Toggle Repeat',
+ keywords: ['repeat', 'loop', 'cycle'],
+ shortcut: 'R',
+ action: () => {
+ document.getElementById('repeat-btn')?.click();
+ },
},
{
- name: 'cache',
- description: 'Clear application cache',
- action: () => this.handleClearCache(),
+ id: 'play-mute',
+ group: 'Playback',
+ icon: 'volumeX',
+ label: 'Mute / Unmute',
+ keywords: ['mute', 'unmute', 'sound', 'volume', 'silent'],
+ shortcut: 'M',
+ action: () => {
+ const el = window.monochromePlayer?.activeElement;
+ if (el) el.muted = !el.muted;
+ },
+ },
+ {
+ id: 'play-vol-up',
+ group: 'Playback',
+ icon: 'volume',
+ label: 'Volume Up',
+ keywords: ['volume', 'louder'],
+ shortcut: '↑',
+ action: () => {
+ const p = window.monochromePlayer;
+ if (p) p.setVolume(p.userVolume + 0.1);
+ },
+ },
+ {
+ id: 'play-vol-down',
+ group: 'Playback',
+ icon: 'volume',
+ label: 'Volume Down',
+ keywords: ['volume', 'quieter', 'softer'],
+ shortcut: '↓',
+ action: () => {
+ const p = window.monochromePlayer;
+ if (p) p.setVolume(p.userVolume - 0.1);
+ },
},
- ];
- this.init();
+ {
+ id: 'like-current',
+ group: 'Now Playing',
+ icon: 'heart',
+ label: 'Like Current Track',
+ keywords: ['like', 'favorite', 'love', 'heart', 'save'],
+ action: () => {
+ document.querySelector('.now-playing-bar .like-btn')?.click();
+ },
+ },
+ {
+ id: 'download-current',
+ group: 'Now Playing',
+ icon: 'download',
+ label: 'Download Current Track',
+ keywords: ['download', 'save', 'current'],
+ action: () => {
+ document.querySelector('.now-playing-bar .download-btn')?.click();
+ },
+ },
+
+ {
+ id: 'queue-open',
+ group: 'Queue',
+ icon: 'list',
+ label: 'Open Queue',
+ keywords: ['queue', 'list', 'up next'],
+ shortcut: 'Q',
+ action: () => {
+ document.getElementById('queue-btn')?.click();
+ },
+ },
+ {
+ id: 'queue-wipe',
+ group: 'Queue',
+ icon: 'trash',
+ label: 'Clear Queue',
+ keywords: ['wipe', 'clear', 'empty', 'queue'],
+ action: () => {
+ window.monochromePlayer?.wipeQueue();
+ this.notify('Queue cleared');
+ },
+ },
+ {
+ id: 'queue-like-all',
+ group: 'Queue',
+ icon: 'heart',
+ label: 'Like All in Queue',
+ keywords: ['like', 'all', 'queue', 'heart', 'favorite'],
+ action: () => this.likeAllInQueue(),
+ },
+ {
+ id: 'queue-download',
+ group: 'Queue',
+ icon: 'download',
+ label: 'Download Queue',
+ keywords: ['download', 'queue', 'save', 'all'],
+ action: () => this.downloadQueue(),
+ },
+
+ {
+ id: 'lyrics-toggle',
+ group: 'View',
+ icon: 'text',
+ label: 'Toggle Lyrics',
+ keywords: ['lyrics', 'words', 'text', 'karaoke'],
+ shortcut: 'L',
+ action: () => {
+ document.querySelector('.now-playing-bar .cover')?.click();
+ },
+ },
+ {
+ id: 'fullscreen-open',
+ group: 'View',
+ icon: 'maximize',
+ label: 'Open Fullscreen View',
+ keywords: ['fullscreen', 'expand', 'immersive', 'cover'],
+ action: () => {
+ const cover = document.querySelector('.now-playing-bar .cover-art');
+ if (cover) cover.click();
+ },
+ },
+ {
+ id: 'vis-toggle',
+ group: 'View',
+ icon: 'sparkles',
+ label: 'Toggle Visualizer',
+ keywords: ['visualizer', 'visual', 'animation', 'effects'],
+ action: () => this.toggleVisualizer(),
+ },
+ {
+ id: 'vis-butterchurn',
+ group: 'View',
+ icon: 'sparkles',
+ label: 'Visualizer: Butterchurn',
+ keywords: ['butterchurn', 'milkdrop', 'preset', 'visualizer'],
+ action: () => this.setVisualizerPreset('butterchurn'),
+ },
+ {
+ id: 'vis-kawarp',
+ group: 'View',
+ icon: 'sparkles',
+ label: 'Visualizer: Kawarp',
+ keywords: ['kawarp', 'preset', 'visualizer'],
+ action: () => this.setVisualizerPreset('kawarp'),
+ },
+ {
+ id: 'vis-lcd',
+ group: 'View',
+ icon: 'sparkles',
+ label: 'Visualizer: LCD',
+ keywords: ['lcd', 'preset', 'visualizer'],
+ action: () => this.setVisualizerPreset('lcd'),
+ },
+ {
+ id: 'vis-particles',
+ group: 'View',
+ icon: 'sparkles',
+ label: 'Visualizer: Particles',
+ keywords: ['particles', 'preset', 'visualizer'],
+ action: () => this.setVisualizerPreset('particles'),
+ },
+ {
+ id: 'vis-unknown',
+ group: 'View',
+ icon: 'sparkles',
+ label: 'Visualizer: Unknown Pleasures',
+ keywords: ['unknown pleasures', 'preset', 'visualizer', 'joy division'],
+ action: () => this.setVisualizerPreset('unknown-pleasures'),
+ },
+
+ {
+ id: 'theme-system',
+ group: 'Theme',
+ icon: 'monitor',
+ label: 'Theme: System',
+ keywords: ['theme', 'system', 'auto', 'default'],
+ action: () => this.setTheme('system'),
+ },
+ {
+ id: 'theme-black',
+ group: 'Theme',
+ icon: 'moon',
+ label: 'Theme: Monochrome',
+ keywords: ['theme', 'monochrome', 'black', 'dark', 'amoled'],
+ action: () => this.setTheme('monochrome'),
+ },
+ {
+ id: 'theme-dark',
+ group: 'Theme',
+ icon: 'moon',
+ label: 'Theme: Dark',
+ keywords: ['theme', 'dark'],
+ action: () => this.setTheme('dark'),
+ },
+ {
+ id: 'theme-white',
+ group: 'Theme',
+ icon: 'sun',
+ label: 'Theme: White',
+ keywords: ['theme', 'white', 'light'],
+ action: () => this.setTheme('white'),
+ },
+ {
+ id: 'theme-ocean',
+ group: 'Theme',
+ icon: 'palette',
+ label: 'Theme: Ocean',
+ keywords: ['theme', 'ocean', 'blue', 'sea'],
+ action: () => this.setTheme('ocean'),
+ },
+ {
+ id: 'theme-purple',
+ group: 'Theme',
+ icon: 'palette',
+ label: 'Theme: Purple',
+ keywords: ['theme', 'purple', 'violet'],
+ action: () => this.setTheme('purple'),
+ },
+ {
+ id: 'theme-forest',
+ group: 'Theme',
+ icon: 'palette',
+ label: 'Theme: Forest',
+ keywords: ['theme', 'forest', 'green', 'nature'],
+ action: () => this.setTheme('forest'),
+ },
+ {
+ id: 'theme-mocha',
+ group: 'Theme',
+ icon: 'palette',
+ label: 'Theme: Mocha',
+ keywords: ['theme', 'mocha', 'catppuccin', 'brown', 'warm'],
+ action: () => this.setTheme('mocha'),
+ },
+ {
+ id: 'theme-macchiato',
+ group: 'Theme',
+ icon: 'palette',
+ label: 'Theme: Macchiato',
+ keywords: ['theme', 'macchiato', 'catppuccin'],
+ action: () => this.setTheme('machiatto'),
+ },
+ {
+ id: 'theme-frappe',
+ group: 'Theme',
+ icon: 'palette',
+ label: 'Theme: Frappé',
+ keywords: ['theme', 'frappe', 'catppuccin'],
+ action: () => this.setTheme('frappe'),
+ },
+ {
+ id: 'theme-latte',
+ group: 'Theme',
+ icon: 'palette',
+ label: 'Theme: Latte',
+ keywords: ['theme', 'latte', 'catppuccin', 'light'],
+ action: () => this.setTheme('latte'),
+ },
+ {
+ id: 'theme-store',
+ group: 'Theme',
+ icon: 'store',
+ label: 'Open Theme Store',
+ keywords: ['theme', 'store', 'browse', 'community', 'custom'],
+ action: () => {
+ document.getElementById('open-theme-store')?.click();
+ },
+ },
+
+ {
+ id: 'quality-low',
+ group: 'Audio',
+ icon: 'sliders',
+ label: 'Quality: Low',
+ keywords: ['quality', 'low', 'streaming', 'bitrate'],
+ action: () => this.setQuality('LOW'),
+ },
+ {
+ id: 'quality-high',
+ group: 'Audio',
+ icon: 'sliders',
+ label: 'Quality: High',
+ keywords: ['quality', 'high', 'streaming', 'bitrate'],
+ action: () => this.setQuality('HIGH'),
+ },
+ {
+ id: 'quality-lossless',
+ group: 'Audio',
+ icon: 'sliders',
+ label: 'Quality: Lossless',
+ keywords: ['quality', 'lossless', 'flac', 'cd', 'streaming'],
+ action: () => this.setQuality('LOSSLESS'),
+ },
+ {
+ id: 'quality-hires',
+ group: 'Audio',
+ icon: 'sliders',
+ label: 'Quality: Hi-Res',
+ keywords: ['quality', 'hires', 'hi-res', 'master', 'mqa', 'streaming'],
+ action: () => this.setQuality('HI_RES_LOSSLESS'),
+ },
+ {
+ id: 'sleep-15',
+ group: 'Audio',
+ icon: 'clock',
+ label: 'Sleep Timer: 15 min',
+ keywords: ['sleep', 'timer', '15', 'minutes'],
+ action: () => this.setSleepTimer(15),
+ },
+ {
+ id: 'sleep-30',
+ group: 'Audio',
+ icon: 'clock',
+ label: 'Sleep Timer: 30 min',
+ keywords: ['sleep', 'timer', '30', 'minutes'],
+ action: () => this.setSleepTimer(30),
+ },
+ {
+ id: 'sleep-60',
+ group: 'Audio',
+ icon: 'clock',
+ label: 'Sleep Timer: 60 min',
+ keywords: ['sleep', 'timer', '60', 'minutes', 'hour'],
+ action: () => this.setSleepTimer(60),
+ },
+ {
+ id: 'sleep-120',
+ group: 'Audio',
+ icon: 'clock',
+ label: 'Sleep Timer: 120 min',
+ keywords: ['sleep', 'timer', '120', 'minutes', 'hours'],
+ action: () => this.setSleepTimer(120),
+ },
+
+ {
+ id: 'lib-create-playlist',
+ group: 'Library',
+ icon: 'plus',
+ label: 'Create Playlist',
+ keywords: ['create', 'new', 'playlist', 'add'],
+ action: () => this.createPlaylist(),
+ },
+ {
+ id: 'lib-create-folder',
+ group: 'Library',
+ icon: 'folderPlus',
+ label: 'Create Folder',
+ keywords: ['create', 'new', 'folder', 'add', 'organize'],
+ action: () => this.createFolder(),
+ },
+
+ {
+ id: 'sys-cache',
+ group: 'System',
+ icon: 'trash',
+ label: 'Clear Cache',
+ keywords: ['cache', 'clear', 'reset', 'clean'],
+ action: () => this.clearCache(),
+ },
+ {
+ id: 'sys-shortcuts',
+ group: 'System',
+ icon: 'keyboard',
+ label: 'View Keyboard Shortcuts',
+ keywords: ['keyboard', 'shortcuts', 'keys', 'hotkeys', 'bindings'],
+ action: () => {
+ document.getElementById('shortcuts-modal')?.style.setProperty('display', 'flex');
+ },
+ },
+ {
+ id: 'sys-export',
+ group: 'System',
+ icon: 'upload',
+ label: 'Export Data',
+ keywords: ['export', 'backup', 'data', 'save'],
+ action: () => this.navigateToSetting({ tab: 'system', id: 'export-data-btn' }),
+ },
+ {
+ id: 'sys-search-setting',
+ group: 'System',
+ icon: 'search',
+ label: 'Search Settings...',
+ keywords: ['setting', 'find', 'search', 'preference', 'option', 'configure'],
+ action: () => this.enterSettingsMode(),
+ },
+
+ {
+ id: 'acc-profile',
+ group: 'Account',
+ icon: 'user',
+ label: 'View Profile',
+ keywords: ['profile', 'account', 'user', 'me'],
+ action: () => {
+ document.querySelector('.user-avatar-btn')?.click();
+ },
+ },
+ {
+ id: 'acc-edit-profile',
+ group: 'Account',
+ icon: 'pencil',
+ label: 'Edit Profile',
+ keywords: ['edit', 'profile', 'username', 'avatar', 'display name'],
+ action: async () => {
+ const { openEditProfile } = await import('./profile.js');
+ openEditProfile();
+ },
+ },
+ {
+ id: 'acc-sign-out',
+ group: 'Account',
+ icon: 'logOut',
+ label: 'Sign Out',
+ keywords: ['sign out', 'log out', 'logout', 'disconnect'],
+ action: async () => {
+ const { authManager } = await import('./accounts/auth.js');
+ await authManager.signOut();
+ },
+ },
+ {
+ id: 'acc-sign-in',
+ group: 'Account',
+ icon: 'logIn',
+ label: 'Sign In',
+ keywords: ['sign in', 'log in', 'login', 'account', 'connect'],
+ action: () => {
+ navigate('/account');
+ },
+ },
+ ];
}
init() {
- document.addEventListener('keydown', async (e) => {
+ document.addEventListener('keydown', (e) => {
if ((e.ctrlKey || e.metaKey) && e.key === 'k') {
e.preventDefault();
- await this.toggle();
+ this.toggle();
}
});
this.input.addEventListener('input', () => this.handleInput());
- this.input.addEventListener('keydown', async (e) => await this.handleKeydown(e));
+ this.input.addEventListener('keydown', (e) => this.handleKeydown(e));
this.overlay.addEventListener('click', (e) => {
if (e.target === this.overlay) this.close();
@@ -84,84 +681,60 @@ class CommandPalette {
this.cacheAllSettings();
}
- async toggle() {
+ toggle() {
if (this.isOpen) this.close();
- else await this.open();
+ else this.open();
}
- async open() {
+ open() {
this.isOpen = true;
+ this.settingsMode = false;
this.overlay.style.display = 'flex';
- this.input.value = '>';
+ this.input.value = '';
+ this.input.placeholder = 'Search commands, music, settings...';
this.input.focus();
- await this.handleInput();
+ this.showDefaultCommands();
}
close() {
this.isOpen = false;
+ this.settingsMode = false;
this.overlay.style.display = 'none';
+ this.cancelMusicSearch();
}
- async handleInput() {
- const value = this.input.value;
+ enterSettingsMode() {
+ this.settingsMode = true;
+ this.input.value = '';
+ this.input.placeholder = 'Search settings...';
+ this.input.focus();
+ this.cacheAllSettings();
+ this.renderSettingsResults('');
+ }
+
+ handleInput() {
+ const query = this.input.value.trim();
this.selectedIndex = 0;
- if (!value.startsWith('>')) {
- await this.renderResults([
- {
- name: 'Type > to use commands',
- description: 'e.g. >theme White, >play The Whole World Is Free',
- action: async () => {
- this.input.value = '>';
- await this.handleInput();
- },
- type: 'hint',
- },
- ]);
+ if (this.settingsMode) {
+ this.renderSettingsResults(query);
return;
}
- const fullQuery = value.slice(1);
- const match = fullQuery.match(/^(\S+)(?:\s+(.*))?$/);
-
- if (!match) {
- await this.renderDefaultCommands();
+ if (!query) {
+ this.cancelMusicSearch();
+ this.showDefaultCommands();
return;
}
- const cmdName = match[1].toLowerCase();
- const args = match[2] || '';
-
- const command = this.commands.find((c) => c.name === cmdName);
-
- if (command) {
- const commandsWithSubmenus = ['queue', 'go', 'visualizer', 'quality', 'sleep', 'setting'];
- if (commandsWithSubmenus.includes(command.name) && !args.trim()) {
- command.action(args);
- return;
- }
-
- await this.renderResults([
- {
- name: `Execute: ${command.name} ${args}`,
- description: args ? `Run ${command.name} for "${args}"` : command.description,
- action: () => command.action(args, ['play', 'shuffle', 'setting'].includes(command.name)),
- type: 'execution',
- },
- ]);
-
- if (args.trim().length > 0 && (cmdName === 'play' || cmdName === 'shuffle')) {
- this.debouncedSearch(cmdName, args.trim());
- }
- } else {
- await this.renderDefaultCommands(cmdName);
- }
+ this.searchCommands(query);
+ this.debouncedMusicSearch(query);
}
- async handleKeydown(e) {
+ handleKeydown(e) {
if (e.key === 'ArrowDown') {
e.preventDefault();
- this.selectedIndex = Math.min(this.selectedIndex + 1, this.results.length - 1);
+ this.selectedIndex = Math.min(this.selectedIndex + 1, this.flatItems.length - 1);
this.updateSelection();
} else if (e.key === 'ArrowUp') {
e.preventDefault();
@@ -169,433 +742,319 @@ class CommandPalette {
this.updateSelection();
} else if (e.key === 'Enter') {
e.preventDefault();
- await this.executeSelected();
+ this.executeSelected();
} else if (e.key === 'Escape') {
- this.close();
- }
- }
-
- async renderDefaultCommands(filter = '') {
- let cmds = this.commands;
- if (filter) {
- if (Fuse) {
- const fuse = new Fuse(this.commands, { keys: ['name', 'description'] });
- cmds = fuse.search(filter).map((r) => r.item);
+ if (this.settingsMode) {
+ this.settingsMode = false;
+ this.input.value = '';
+ this.input.placeholder = 'Search commands, music, settings...';
+ this.showDefaultCommands();
} else {
- cmds = this.commands.filter((c) => c.name.includes(filter));
+ this.close();
}
+ } else if (e.key === 'Backspace' && this.settingsMode && !this.input.value) {
+ this.settingsMode = false;
+ this.input.placeholder = 'Search commands, music, settings...';
+ this.showDefaultCommands();
}
+ }
- await this.renderResults(
- cmds.map((c) => ({
- name: c.name,
- description: c.description,
- action: async () => {
- this.input.value = `>${c.name} `;
- await this.handleInput();
- },
- type: 'command',
- }))
+ showDefaultCommands() {
+ const groups = this.groupBy(
+ this.commands.filter((c) => {
+ const priority = [
+ 'nav-home',
+ 'nav-library',
+ 'nav-settings',
+ 'play-pause',
+ 'play-next',
+ 'play-prev',
+ 'play-shuffle',
+ 'queue-open',
+ 'lyrics-toggle',
+ 'fullscreen-open',
+ 'sys-search-setting',
+ ];
+ return priority.includes(c.id);
+ }),
+ 'group'
);
+
+ this.renderGroups(groups);
}
- async renderResults(results) {
- this.results = results;
- this.resultsContainer.innerHTML = '';
+ searchCommands(query) {
+ const fuseResults = this.fuse.search(query).slice(0, 12);
+ const matched = fuseResults.map((r) => r.item);
- if (results.length === 0) {
- this.resultsContainer.innerHTML =
- '
No results found
';
+ if (matched.length === 0) {
+ this.renderGroups({});
return;
}
- results.forEach((result, index) => {
- const div = document.createElement('div');
- div.className = `command-result-item ${index === this.selectedIndex ? 'selected' : ''}`;
-
- const imgHtml = result.image
- ? `
`
- : '';
-
- div.innerHTML = `
-
- ${imgHtml}
-
${result.name} ${result.description || ''}
-
- `;
- div.addEventListener('click', async () => {
- this.selectedIndex = index;
- await this.executeSelected();
- });
- this.resultsContainer.appendChild(div);
- });
+ const groups = this.groupBy(matched, 'group');
+ this.renderGroups(groups);
}
- updateSelection() {
- const items = this.resultsContainer.querySelectorAll('.command-result-item');
- items.forEach((item, index) => {
- if (index === this.selectedIndex) {
- item.classList.add('selected');
- item.scrollIntoView({ block: 'nearest' });
- } else {
- item.classList.remove('selected');
- }
- });
- }
+ async searchMusic(query) {
+ if (!query || query.length < 2) return;
- async executeSelected() {
- const result = this.results[this.selectedIndex];
- if (result && result.action) {
- await result.action();
- if (result.type !== 'hint') {
- this.close();
- }
- } else if (result && result.type === 'command') {
- this.input.value = `>${result.name} `;
- await this.handleInput();
- }
- }
+ const api = window.monochromeUi?.api;
+ if (!api) return;
- handleTheme(args) {
- if (!args) return;
- const theme = args.trim().toLowerCase();
- document.documentElement.setAttribute('data-theme', theme);
- localStorage.setItem('theme', theme);
+ this.cancelMusicSearch();
+ const controller = new AbortController();
+ this.musicSearchAbort = controller;
- const themeOptions = document.querySelectorAll('.theme-option');
- themeOptions.forEach((opt) => {
- if (opt.dataset.theme === theme) opt.classList.add('active');
- else opt.classList.remove('active');
- });
- }
+ this.showMusicLoading();
- async showNotification(message) {
- const { showNotification } = await import('./downloads.js');
- showNotification(message);
- }
+ try {
+ const [tracks, albums, artists] = await Promise.all([
+ api.searchTracks(query, { limit: 4 }),
+ api.searchAlbums(query, { limit: 3 }),
+ api.searchArtists(query, { limit: 3 }),
+ ]);
- async handleQueue(args) {
- const player = window.monochromePlayer;
- const ui = window.monochromeUi;
+ if (controller.signal.aborted || !this.isOpen) return;
- if (!player || !ui) {
- console.error('Player or UI not available for queue command');
- return;
- }
+ const musicGroups = {};
- if (!args || !args.trim()) {
- await this.renderResults(
- [
- { name: '>queue wipe', description: 'Clear the queue and stop playback' },
- { name: '>queue like all', description: 'Like all tracks in the current queue' },
- { name: '>queue download', description: 'Download all tracks in the current queue' },
- ].map((c) => ({
- ...c,
- type: 'command',
+ if (tracks?.items?.length) {
+ musicGroups['Tracks'] = tracks.items.map((track) => ({
+ id: `track-${track.id}`,
+ group: 'Tracks',
+ icon: null,
+ image: api.getCoverUrl(track.album?.cover, 80),
+ label: track.title,
+ description: `${track.artist?.name || 'Unknown'} \u2022 ${track.album?.title || ''}`,
action: async () => {
- this.input.value = c.name;
- await this.handleInput();
+ window.monochromePlayer.setQueue([track], 0);
+ await window.monochromePlayer.playTrackFromQueue();
},
- }))
- );
- return;
- }
-
- const subCommand = args.trim().toLowerCase();
+ }));
+ }
- switch (subCommand) {
- case 'wipe':
- player.wipeQueue();
- this.showNotification('Queue wiped.');
- this.close();
- break;
- case 'like all':
- this.likeAllInQueue(player, ui);
- break;
- case 'download':
- this.downloadQueue(player, ui);
- break;
- default:
- this.showNotification(`Unknown queue command: ${subCommand}`);
- break;
- }
- }
+ if (albums?.items?.length) {
+ musicGroups['Albums'] = albums.items.map((album) => ({
+ id: `album-${album.id}`,
+ group: 'Albums',
+ icon: null,
+ image: api.getCoverUrl(album.cover, 80),
+ label: album.title,
+ description: album.artist?.name || 'Unknown',
+ action: () => {
+ navigate(`/album/${album.id}`);
+ },
+ }));
+ }
- async likeAllInQueue(player, ui) {
- const queue = player.getCurrentQueue();
- if (queue.length === 0) {
- this.showNotification('Queue is empty.');
- return;
- }
+ if (artists?.items?.length) {
+ musicGroups['Artists'] = artists.items.map((artist) => ({
+ id: `artist-${artist.id}`,
+ group: 'Artists',
+ icon: null,
+ image: api.getArtistPictureUrl(artist.picture, 80),
+ label: artist.name,
+ description: 'Artist',
+ action: () => {
+ navigate(`/artist/${artist.id}`);
+ },
+ }));
+ }
- const { handleTrackAction } = await import('./events.js');
- const scrobbler = window.monochromeScrobbler;
+ if (Object.keys(musicGroups).length > 0) {
+ this.appendMusicGroups(musicGroups);
+ }
- let likedCount = 0;
- this.showNotification('Liking all tracks in queue...');
- for (const track of queue) {
- const isLiked = await db.isFavorite('track', track.id);
- if (!isLiked) {
- await handleTrackAction('toggle-like', track, player, ui.api, ui.lyricsManager, 'track', ui, scrobbler);
- likedCount++;
+ this.removeMusicLoading();
+ } catch (e) {
+ if (e.name !== 'AbortError') {
+ this.removeMusicLoading();
}
}
- this.showNotification(`Liked ${likedCount} new track(s) in the queue.`);
- this.close();
}
- async downloadQueue(player, ui) {
- const queue = player.getCurrentQueue();
- if (queue.length === 0) {
- this.showNotification('Queue is empty.');
- return;
+ cancelMusicSearch() {
+ if (this.musicSearchAbort) {
+ this.musicSearchAbort.abort();
+ this.musicSearchAbort = null;
}
+ }
- const { downloadTracks } = await import('./downloads.js');
- const { downloadQualitySettings } = await import('./storage.js');
- const lyricsManager = ui.lyricsManager;
+ showMusicLoading() {
+ this.removeMusicLoading();
+ const loading = document.createElement('div');
+ loading.className = 'cmdk-loading';
+ loading.setAttribute('data-music-loading', '');
+ loading.innerHTML = '
Searching music...';
+ this.resultsContainer.appendChild(loading);
+ }
- downloadTracks(queue, ui.api, downloadQualitySettings.getQuality(), lyricsManager);
- this.close();
+ removeMusicLoading() {
+ this.resultsContainer.querySelector('[data-music-loading]')?.remove();
}
- async handleNavigation(args) {
- const validPages = ['home', 'library', 'recent', 'settings', 'unreleased', 'about', 'download'];
+ appendMusicGroups(musicGroups) {
+ this.removeMusicLoading();
+ this.resultsContainer.querySelectorAll('[data-music-group]').forEach((el) => el.remove());
- if (!args || !args.trim()) {
- await this.renderResults(
- validPages.map((p) => ({
- name: `>go ${p}`,
- description: `Navigate to ${p}`,
- action: () => {
- this.close();
- navigate(p === 'home' ? '/' : `/${p}`);
- },
- type: 'command',
- }))
- );
- return;
- }
+ const startIndex = this.flatItems.length;
+ let index = startIndex;
- const page = args.trim().toLowerCase();
+ for (const [heading, items] of Object.entries(musicGroups)) {
+ const groupEl = document.createElement('div');
+ groupEl.className = 'cmdk-group';
+ groupEl.setAttribute('data-music-group', '');
- if (validPages.includes(page)) {
- this.close();
- navigate(page === 'home' ? '/' : `/${page}`);
- } else {
- this.showNotification(`Unknown page: ${page}`);
- }
- }
+ const headingEl = document.createElement('div');
+ headingEl.className = 'cmdk-group-heading';
+ headingEl.textContent = heading;
+ groupEl.appendChild(headingEl);
- async handleSleepTimer(args) {
- if (!args || !args.trim()) {
- await this.renderResults(
- [15, 30, 45, 60, 120].map((m) => ({
- name: `>sleep ${m}`,
- description: `Set sleep timer for ${m} minutes`,
- action: () => {
- this.setSleepTimer(m);
- this.close();
- },
- type: 'command',
- }))
- );
- return;
- }
+ for (const item of items) {
+ const itemEl = this.createItemElement(item, index);
+ groupEl.appendChild(itemEl);
+ this.flatItems.push(item);
+ index++;
+ }
- const minutes = parseInt(args.trim());
- if (!isNaN(minutes) && minutes > 0) {
- this.setSleepTimer(minutes);
- this.close();
- } else {
- this.showNotification('Invalid duration');
+ this.resultsContainer.appendChild(groupEl);
}
}
- setSleepTimer(minutes) {
- if (window.monochromePlayer) {
- window.monochromePlayer.setSleepTimer(minutes);
- this.showNotification(`Sleep timer set for ${minutes} minutes`);
+ groupBy(items, key) {
+ const groups = {};
+ for (const item of items) {
+ const group = item[key] || 'Other';
+ if (!groups[group]) groups[group] = [];
+ groups[group].push(item);
}
+ return groups;
}
- async handleQuality(args) {
- const qualityMap = {
- low: 'LOW',
- high: 'HIGH',
- lossless: 'LOSSLESS',
- hires: 'HI_RES_LOSSLESS',
- 'hi-res': 'HI_RES_LOSSLESS',
- master: 'HI_RES_LOSSLESS',
- };
-
- const displayQualities = [
- { id: 'low', name: 'Low', code: 'LOW' },
- { id: 'high', name: 'High', code: 'HIGH' },
- { id: 'lossless', name: 'Lossless', code: 'LOSSLESS' },
- { id: 'hi-res', name: 'Hi-Res', code: 'HI_RES_LOSSLESS' },
- ];
-
- if (!args || !args.trim()) {
- const results = displayQualities.map((q) => ({
- name: `>quality ${q.id}`,
- description: `Set quality to ${q.name}`,
- action: () => {
- this.setQuality(q.code, true, true);
- this.close();
- },
- type: 'command',
- }));
-
- results.push({
- name: 'Usage: >quality [level] [-S] [-D]',
- description: '-S for Streaming only, -D for Download only',
- action: () => {},
- type: 'hint',
- });
- await this.renderResults(results);
+ renderGroups(groups) {
+ this.resultsContainer.innerHTML = '';
+ this.flatItems = [];
+ let index = 0;
+
+ const groupEntries = Object.entries(groups);
+ if (groupEntries.length === 0) {
+ const query = this.input.value.trim();
+ if (query) {
+ const empty = document.createElement('div');
+ empty.className = 'cmdk-empty';
+ empty.textContent = 'No commands found';
+ this.resultsContainer.appendChild(empty);
+ }
return;
}
- const parts = args.trim().split(/\s+/);
- const qualityKey = parts.find((p) => !p.startsWith('-'))?.toLowerCase();
- const flags = parts.filter((p) => p.startsWith('-')).map((f) => f.toLowerCase());
+ for (const [heading, items] of groupEntries) {
+ const groupEl = document.createElement('div');
+ groupEl.className = 'cmdk-group';
- if (!qualityKey || !qualityMap[qualityKey]) {
- this.showNotification('Invalid quality setting');
- return;
- }
+ const headingEl = document.createElement('div');
+ headingEl.className = 'cmdk-group-heading';
+ headingEl.textContent = heading;
+ groupEl.appendChild(headingEl);
- const qualityCode = qualityMap[qualityKey];
- let setStreaming = true;
- let setDownload = true;
+ for (const item of items) {
+ const itemEl = this.createItemElement(item, index);
+ groupEl.appendChild(itemEl);
+ this.flatItems.push(item);
+ index++;
+ }
- if (flags.includes('-d') && !flags.includes('-s')) {
- setStreaming = false;
- } else if (flags.includes('-s') && !flags.includes('-d')) {
- setDownload = false;
+ this.resultsContainer.appendChild(groupEl);
}
- this.setQuality(qualityCode, setStreaming, setDownload);
- this.close();
+ this.updateSelection();
}
- async setQuality(quality, setStreaming, setDownload) {
- const messages = [];
- const qualityName = this.getQualityName(quality);
-
- if (setStreaming) {
- if (window.monochromePlayer) {
- window.monochromePlayer.setQuality(quality);
- localStorage.setItem('playback-quality', quality);
- messages.push('Streaming');
+ createItemElement(item, index) {
+ const el = document.createElement('div');
+ el.className = 'cmdk-item';
+ el.setAttribute('data-index', index);
+ if (index === this.selectedIndex) el.setAttribute('data-selected', 'true');
+
+ let iconHtml = '';
+ if (item.image) {
+ iconHtml = `
`;
+ } else if (item.icon && ICONS[item.icon]) {
+ iconHtml = `
${ICONS[item.icon]}
`;
+ }
- const streamingSelect = document.getElementById('streaming-quality-setting');
- if (streamingSelect) {
- streamingSelect.value = quality;
- }
- }
+ let shortcutHtml = '';
+ if (item.shortcut) {
+ const keys = item.shortcut.split('+');
+ shortcutHtml = `
${keys.map((k) => `${escapeHtml(k)} `).join('')}
`;
}
- if (setDownload) {
- const { downloadQualitySettings } = await import('./storage.js');
- downloadQualitySettings.setQuality(quality);
- messages.push('Download');
+ const descHtml = item.description
+ ? `
${escapeHtml(item.description)} `
+ : '';
- const downloadSelect = document.getElementById('download-quality-setting');
- if (downloadSelect) {
- downloadSelect.value = quality;
- }
- }
+ el.innerHTML = `${iconHtml}
${escapeHtml(item.label)} ${descHtml}
${shortcutHtml}`;
- if (messages.length > 0) {
- this.showNotification(`${messages.join(' & ')} quality set to ${qualityName}`);
- }
- }
+ el.addEventListener('click', () => {
+ this.selectedIndex = index;
+ this.executeSelected();
+ });
- getQualityName(code) {
- const names = {
- LOW: 'Low',
- HIGH: 'High',
- LOSSLESS: 'Lossless',
- HI_RES_LOSSLESS: 'Hi-Res',
- };
- return names[code] || code;
- }
-
- async handleVisualizer(args) {
- if (!args || !args.trim()) {
- await this.renderResults(
- [
- { name: '>visualizer toggle', description: 'Toggle visualizer on/off', cmd: 'toggle' },
- { name: '>visualizer butterchurn', description: 'Set preset to Butterchurn', cmd: 'butterchurn' },
- { name: '>visualizer kawarp', description: 'Set preset to Kawarp', cmd: 'kawarp' },
- { name: '>visualizer lcd', description: 'Set preset to LCD', cmd: 'lcd' },
- { name: '>visualizer particles', description: 'Set preset to Particles', cmd: 'particles' },
- {
- name: '>visualizer unknown-pleasures',
- description: 'Set preset to Unknown Pleasures',
- cmd: 'unknown-pleasures',
- },
- ].map((c) => ({
- ...c,
- action: () => {
- if (c.cmd === 'toggle') {
- this.toggleVisualizer();
- } else {
- this.setVisualizerPreset(c.cmd);
- }
- this.close();
- },
- type: 'command',
- }))
- );
- return;
- }
+ el.addEventListener('mouseenter', () => {
+ this.selectedIndex = index;
+ this.updateSelection();
+ });
- const subCmd = args.trim().toLowerCase();
- if (subCmd === 'toggle') {
- this.toggleVisualizer();
- this.close();
- } else {
- const presets = ['butterchurn', 'kawarp', 'lcd', 'particles', 'unknown-pleasures'];
- if (presets.includes(subCmd)) {
- this.setVisualizerPreset(subCmd);
- this.close();
+ return el;
+ }
+
+ updateSelection() {
+ const items = this.resultsContainer.querySelectorAll('.cmdk-item');
+ items.forEach((item) => {
+ const idx = parseInt(item.getAttribute('data-index'));
+ if (idx === this.selectedIndex) {
+ item.setAttribute('data-selected', 'true');
+ item.scrollIntoView({ block: 'nearest' });
} else {
- this.showNotification('Unknown visualizer command');
+ item.removeAttribute('data-selected');
}
- }
+ });
}
- async toggleVisualizer() {
- const { visualizerSettings } = await import('./storage.js');
- const current = visualizerSettings.isEnabled();
- visualizerSettings.setEnabled(!current);
- this.showNotification(`Visualizer ${!current ? 'enabled' : 'disabled'}`);
+ executeSelected() {
+ const item = this.flatItems[this.selectedIndex];
+ if (!item || !item.action) return;
- const overlay = document.getElementById('fullscreen-cover-overlay');
- if (overlay && getComputedStyle(overlay).display !== 'none') {
- window.monochromeUi?.closeFullscreenCover();
- }
+ item.action();
+ this.close();
}
- async setVisualizerPreset(preset) {
- const { visualizerSettings } = await import('./storage.js');
- visualizerSettings.setPreset(preset);
- if (window.monochromeUi?.visualizer) {
- window.monochromeUi.visualizer.setPreset(preset);
- }
- this.showNotification(`Visualizer preset set to ${preset}`);
- }
+ renderSettingsResults(query) {
+ if (this.allSettings.length === 0) this.cacheAllSettings();
- async handleClearCache() {
- const api = window.monochromeUi?.api;
- if (api) {
- await api.clearCache();
- this.showNotification('Cache cleared');
- this.close();
+ let results = this.allSettings;
+ if (query) {
+ const fuse = new Fuse(this.allSettings, {
+ keys: ['label', 'description'],
+ includeScore: true,
+ threshold: 0.4,
+ ignoreLocation: true,
+ });
+ results = fuse.search(query).map((r) => r.item);
}
+
+ const items = results.map((setting) => ({
+ id: `setting-${setting.id}`,
+ group: `Settings \u2022 ${setting.tab}`,
+ icon: 'settings',
+ label: setting.label,
+ description: setting.description,
+ action: () => this.navigateToSetting(setting),
+ }));
+
+ const groups = this.groupBy(items, 'group');
+ this.renderGroups(groups);
}
cacheAllSettings() {
@@ -617,62 +1076,11 @@ class CommandPalette {
: `setting-item-${Math.random().toString(36).substr(2, 9)}`;
}
- return {
- id: item.id,
- label,
- description,
- tab,
- };
+ return { id: item.id, label, description, tab };
})
.filter((s) => s.label);
}
- async handleSettingSearch(args, autoPick = false) {
- const query = args.trim().toLowerCase();
-
- if (!query) {
- await this.renderResults(
- this.allSettings.map((setting) => ({
- name: setting.label,
- description: `[${setting.tab}] ${setting.description}`,
- action: () => {
- this.navigateToSetting(setting);
- this.close();
- },
- type: 'setting',
- }))
- );
- return;
- }
-
- const fuse = new Fuse(this.allSettings, {
- keys: ['label', 'description'],
- includeScore: true,
- threshold: 0.4,
- ignoreLocation: true,
- });
-
- const results = fuse.search(query).map((r) => r.item);
-
- if (autoPick && results.length > 0) {
- this.navigateToSetting(results[0]);
- this.close();
- return;
- }
-
- await this.renderResults(
- results.map((setting) => ({
- name: setting.label,
- description: `[${setting.tab}] ${setting.description}`,
- action: () => {
- this.navigateToSetting(setting);
- this.close();
- },
- type: 'setting',
- }))
- );
- }
-
async navigateToSetting(setting) {
navigate('/settings');
@@ -698,167 +1106,131 @@ class CommandPalette {
}
}
- async performSearch(cmdName, query) {
- if (!this.isOpen) return;
+ setTheme(theme) {
+ document.documentElement.setAttribute('data-theme', theme);
+ localStorage.setItem('theme', theme);
+ const themeOptions = document.querySelectorAll('.theme-option');
+ themeOptions.forEach((opt) => {
+ if (opt.dataset.theme === theme) opt.classList.add('active');
+ else opt.classList.remove('active');
+ });
+ this.notify(`Theme set to ${theme}`);
+ }
- const api = window.monochromeUi?.api;
- if (!api) return;
+ async toggleVisualizer() {
+ const { visualizerSettings } = await import('./storage.js');
+ const current = visualizerSettings.isEnabled();
+ visualizerSettings.setEnabled(!current);
+ this.notify(`Visualizer ${!current ? 'enabled' : 'disabled'}`);
- let results = [];
+ const overlay = document.getElementById('fullscreen-cover-overlay');
+ if (overlay && getComputedStyle(overlay).display !== 'none') {
+ window.monochromeUi?.closeFullscreenCover();
+ }
+ }
- try {
- if (cmdName === 'play') {
- const data = await api.searchTracks(query);
- results = data.items.map((track) => ({
- name: track.title,
- description: `${track.artist?.name || 'Unknown'} • ${track.album?.title || 'Unknown'}`,
- image: api.getCoverUrl(track.album?.cover, 80),
- action: async () => {
- window.monochromePlayer.setQueue([track], 0);
- await window.monochromePlayer.playTrackFromQueue();
- this.close();
- },
- type: 'result',
- }));
- } else if (cmdName === 'shuffle') {
- const [albums, artists, playlists, userPlaylists] = await Promise.all([
- api.searchAlbums(query),
- api.searchArtists(query),
- api.searchPlaylists(query),
- db.getPlaylists(true),
- ]);
-
- let matchedUserPlaylists = [];
- if (Fuse) {
- const fuse = new Fuse(userPlaylists, { keys: ['name'] });
- matchedUserPlaylists = fuse.search(query).map((r) => r.item);
- } else {
- matchedUserPlaylists = userPlaylists.filter((p) =>
- p.name.toLowerCase().includes(query.toLowerCase())
- );
- }
+ async setVisualizerPreset(preset) {
+ const { visualizerSettings } = await import('./storage.js');
+ visualizerSettings.setPreset(preset);
+ if (window.monochromeUi?.visualizer) {
+ window.monochromeUi.visualizer.setPreset(preset);
+ }
+ this.notify(`Visualizer preset: ${preset}`);
+ }
- const formatResult = (item, type, subtitle, image) => ({
- name: item.title || item.name,
- description: `${type} • ${subtitle}`,
- image: image,
- action: () => this.playCollection(item, type, true),
- type: 'result',
- });
-
- results = [
- ...matchedUserPlaylists.map((p) =>
- formatResult(
- p,
- 'User Playlist',
- `${p.tracks?.length || 0} tracks`,
- p.cover || (p.images && p.images[0])
- )
- ),
- ...artists.items.map((a) =>
- formatResult(a, 'Artist', 'Artist', api.getArtistPictureUrl(a.picture, 80))
- ),
- ...albums.items.map((a) => formatResult(a, 'Album', a.artist?.name, api.getCoverUrl(a.cover, 80))),
- ...playlists.items.map((p) =>
- formatResult(p, 'Playlist', p.creator?.name || 'Tidal', api.getCoverUrl(p.image, 80))
- ),
- ];
- }
- } catch (e) {
- console.error('Command palette search error:', e);
+ async setQuality(quality) {
+ const qualityNames = { LOW: 'Low', HIGH: 'High', LOSSLESS: 'Lossless', HI_RES_LOSSLESS: 'Hi-Res' };
+
+ if (window.monochromePlayer) {
+ window.monochromePlayer.setQuality(quality);
+ localStorage.setItem('playback-quality', quality);
+ const streamingSelect = document.getElementById('streaming-quality-setting');
+ if (streamingSelect) streamingSelect.value = quality;
}
- if (this.isOpen && results.length > 0) {
- await this.renderResults(results);
+ const { downloadQualitySettings } = await import('./storage.js');
+ downloadQualitySettings.setQuality(quality);
+ const downloadSelect = document.getElementById('download-quality-setting');
+ if (downloadSelect) downloadSelect.value = quality;
+
+ this.notify(`Quality set to ${qualityNames[quality] || quality}`);
+ }
+
+ setSleepTimer(minutes) {
+ if (window.monochromePlayer) {
+ window.monochromePlayer.setSleepTimer(minutes);
+ this.notify(`Sleep timer: ${minutes} minutes`);
}
}
- async handlePlay(args, autoPick) {
- if (!args) return;
+ async likeAllInQueue() {
+ const player = window.monochromePlayer;
+ const ui = window.monochromeUi;
+ if (!player || !ui) return;
- if (autoPick) {
- const api = window.monochromeUi?.api;
- const results = await api.searchTracks(args);
- if (results.items.length > 0) {
- const track = results.items[0];
- window.monochromePlayer.setQueue([track], 0);
- await window.monochromePlayer.playTrackFromQueue();
- this.close();
- }
+ const queue = player.getCurrentQueue();
+ if (queue.length === 0) {
+ this.notify('Queue is empty');
+ return;
}
- }
- async handleShuffle(args, autoPick) {
- if (!args) return;
+ const { handleTrackAction } = await import('./events.js');
+ const scrobbler = window.monochromeScrobbler;
- if (autoPick) {
- this.performSearch('shuffle', args).then(() => {
- if (this.results.length > 0 && this.results[0].action) {
- this.results[0].action();
- }
- });
+ let likedCount = 0;
+ this.notify('Liking all tracks in queue...');
+ for (const track of queue) {
+ const isLiked = await db.isFavorite('track', track.id);
+ if (!isLiked) {
+ await handleTrackAction('toggle-like', track, player, ui.api, ui.lyricsManager, 'track', ui, scrobbler);
+ likedCount++;
+ }
}
+ this.notify(`Liked ${likedCount} new track(s)`);
}
- async playCollection(item, type, shuffle) {
+ async downloadQueue() {
const player = window.monochromePlayer;
- const api = window.monochromeUi.api;
- let tracks = [];
+ const ui = window.monochromeUi;
+ if (!player || !ui) return;
- try {
- if (type === 'User Playlist') {
- tracks = item.tracks;
- } else if (type === 'Artist') {
- const artist = await api.getArtist(item.id);
- const allReleases = [...(artist.albums || []), ...(artist.eps || [])];
- const trackSet = new Set();
- const allTracks = [];
-
- const chunkSize = 8;
- for (let i = 0; i < allReleases.length; i += chunkSize) {
- const chunk = allReleases.slice(i, i + chunkSize);
- await Promise.all(
- chunk.map(async (album) => {
- try {
- const { tracks: albumTracks } = await api.getAlbum(album.id);
- albumTracks.forEach((track) => {
- if (!trackSet.has(track.id)) {
- trackSet.add(track.id);
- allTracks.push(track);
- }
- });
- } catch (err) {
- console.warn(`Failed to fetch tracks for album ${album.title}:`, err);
- }
- })
- );
- }
+ const queue = player.getCurrentQueue();
+ if (queue.length === 0) {
+ this.notify('Queue is empty');
+ return;
+ }
- if (allTracks.length > 0) {
- tracks = allTracks;
- } else {
- tracks = artist.tracks || [];
- }
- } else if (type === 'Album') {
- tracks = (await api.getAlbum(item.id)).tracks;
- } else if (type === 'Playlist') {
- tracks = (await api.getPlaylist(item.uuid)).tracks;
- }
+ const { downloadTracks } = await import('./downloads.js');
+ const { downloadQualitySettings } = await import('./storage.js');
+ downloadTracks(queue, ui.api, downloadQualitySettings.getQuality(), ui.lyricsManager);
+ }
- if (tracks && tracks.length > 0) {
- if (shuffle) {
- tracks = [...tracks].sort(() => Math.random() - 0.5);
- player.shuffleActive = true;
- document.getElementById('shuffle-btn')?.classList.add('active');
- }
- player.setQueue(tracks, 0);
- player.playTrackFromQueue();
- this.close();
- }
- } catch (e) {
- console.error('Failed to play collection:', e);
+ async createPlaylist() {
+ const name = `New Playlist ${new Date().toLocaleDateString()}`;
+ await db.createPlaylist(name);
+ navigate('/library');
+ this.notify('Playlist created');
+ }
+
+ async createFolder() {
+ const name = `New Folder ${new Date().toLocaleDateString()}`;
+ await db.createFolder(name);
+ navigate('/library');
+ this.notify('Folder created');
+ }
+
+ async clearCache() {
+ const api = window.monochromeUi?.api;
+ if (api) {
+ await api.clearCache();
+ this.notify('Cache cleared');
}
}
+
+ async notify(message) {
+ const { showNotification } = await import('./downloads.js');
+ showNotification(message);
+ }
}
new CommandPalette();
diff --git a/styles.css b/styles.css
index c50347cf0..95d3844da 100644
--- a/styles.css
+++ b/styles.css
@@ -8381,67 +8381,295 @@ body:has(#side-panel.active) #close-fullscreen-cover-btn {
#command-palette-overlay {
position: fixed;
inset: 0;
- background: rgb(0, 0, 0, 0.5);
- backdrop-filter: blur(4px);
+ background: rgb(0 0 0 / 60%);
+ backdrop-filter: blur(8px);
z-index: 10000;
display: flex;
justify-content: center;
align-items: flex-start;
- padding-top: 10vh;
- animation: fade-in 0.2s ease-out;
+ padding-top: min(10vh, 120px);
+ animation: cmdk-overlay-in 150ms ease-out;
+}
+
+@keyframes cmdk-overlay-in {
+ from {
+ opacity: 0;
+ }
+
+ to {
+ opacity: 1;
+ }
+}
+
+@keyframes cmdk-scale-in {
+ from {
+ opacity: 0;
+ transform: scale(0.96);
+ }
+
+ to {
+ opacity: 1;
+ transform: scale(1);
+ }
}
.command-palette {
width: 100%;
- max-width: 600px;
+ max-width: 640px;
+ margin: 0 1rem;
background: var(--card);
border: 1px solid var(--border);
border-radius: 16px;
- box-shadow: var(--shadow-2xl);
+ box-shadow:
+ 0 16px 70px rgb(0 0 0 / 50%),
+ 0 0 0 1px rgb(255 255 255 / 5%) inset;
display: flex;
flex-direction: column;
overflow: hidden;
- max-height: 60vh;
+ max-height: min(60vh, 480px);
+ animation: cmdk-scale-in 150ms ease-out;
}
.command-palette-header {
- padding: 1.25rem 1.5rem;
+ display: flex;
+ align-items: center;
+ gap: 12px;
+ padding: 14px 16px;
border-bottom: 1px solid var(--border);
}
+.command-palette-search-icon {
+ flex-shrink: 0;
+ color: var(--muted-foreground);
+}
+
#command-palette-input {
- width: 100%;
+ flex: 1;
+ min-width: 0;
background: transparent;
border: none;
- font-size: 1.2rem;
- font-weight: 500;
+ font-size: 1rem;
+ font-weight: 400;
color: var(--foreground);
outline: none;
}
+#command-palette-input::placeholder {
+ color: var(--muted-foreground);
+}
+
+.command-palette-kbd {
+ flex-shrink: 0;
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ padding: 2px 6px;
+ font-size: 11px;
+ font-family: inherit;
+ font-weight: 500;
+ color: var(--muted-foreground);
+ background: var(--secondary);
+ border: 1px solid var(--border);
+ border-radius: 6px;
+ line-height: 1.4;
+}
+
.command-palette-results {
+ flex: 1;
overflow-y: auto;
- padding: 0.5rem;
+ overscroll-behavior: contain;
+ padding: 6px 8px;
}
-.command-result-item {
- padding: 0.75rem 1rem;
- border-radius: var(--radius);
- cursor: pointer;
+.cmdk-group + .cmdk-group {
+ margin-top: 4px;
+}
+
+.cmdk-group-heading {
+ padding: 6px 10px 4px;
+ font-size: 11px;
+ font-weight: 600;
+ color: var(--muted-foreground);
+ text-transform: uppercase;
+ letter-spacing: 0.04em;
+ user-select: none;
+}
+
+.cmdk-item {
display: flex;
- justify-content: space-between;
align-items: center;
+ gap: 10px;
+ padding: 8px 10px;
+ border-radius: var(--radius, 8px);
+ cursor: pointer;
+ font-size: 0.875rem;
color: var(--foreground);
+ transition: background 80ms ease;
+ user-select: none;
+ position: relative;
}
-.command-result-item:hover,
-.command-result-item.selected {
+.cmdk-item:hover {
background: var(--secondary);
}
-.command-result-desc {
- font-size: 0.85rem;
+.cmdk-item[data-selected='true'] {
+ background: var(--secondary);
+}
+
+.cmdk-item-icon {
+ flex-shrink: 0;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ min-width: 20px;
+ min-height: 20px;
+ color: var(--muted-foreground);
+}
+
+.cmdk-item-icon img {
+ width: 28px;
+ height: 28px;
+ border-radius: 4px;
+ object-fit: cover;
+ aspect-ratio: 1 / 1;
+}
+
+.cmdk-item-content {
+ flex: 1;
+ min-width: 0;
+ display: flex;
+ flex-direction: column;
+ gap: 1px;
+}
+
+.cmdk-item-label {
+ font-weight: 500;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+}
+
+.cmdk-item-description {
+ font-size: 0.75rem;
+ color: var(--muted-foreground);
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+}
+
+.cmdk-item-shortcut {
+ flex-shrink: 0;
+ display: flex;
+ align-items: center;
+ gap: 4px;
+ margin-left: auto;
+}
+
+.cmdk-item-shortcut kbd {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ min-width: 20px;
+ height: 20px;
+ padding: 0 5px;
+ font-size: 10px;
+ font-family: inherit;
+ font-weight: 500;
+ color: var(--muted-foreground);
+ background: var(--secondary);
+ border: 1px solid var(--border);
+ border-radius: 4px;
+ line-height: 1;
+}
+
+.cmdk-empty {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ height: 64px;
+ color: var(--muted-foreground);
+ font-size: 0.875rem;
+}
+
+.cmdk-loading {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ gap: 8px;
+ padding: 12px;
color: var(--muted-foreground);
+ font-size: 0.8rem;
+}
+
+.cmdk-loading-spinner {
+ width: 14px;
+ height: 14px;
+ border: 2px solid var(--border);
+ border-top-color: var(--foreground);
+ border-radius: 50%;
+ animation: cmdk-spin 600ms linear infinite;
+}
+
+@keyframes cmdk-spin {
+ to {
+ transform: rotate(360deg);
+ }
+}
+
+.cmdk-separator {
+ height: 1px;
+ background: var(--border);
+ margin: 4px 8px;
+}
+
+.command-palette-footer {
+ display: flex;
+ align-items: center;
+ gap: 16px;
+ padding: 8px 16px;
+ border-top: 1px solid var(--border);
+ font-size: 0.7rem;
+ color: var(--muted-foreground);
+}
+
+.command-palette-hint {
+ display: flex;
+ align-items: center;
+ gap: 4px;
+}
+
+.command-palette-hint kbd {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ padding: 1px 4px;
+ font-size: 10px;
+ font-family: inherit;
+ font-weight: 500;
+ color: var(--muted-foreground);
+ background: var(--secondary);
+ border: 1px solid var(--border);
+ border-radius: 4px;
+}
+
+@media (width <= 640px) {
+ #command-palette-overlay {
+ padding-top: 0;
+ align-items: flex-start;
+ }
+
+ .command-palette {
+ max-width: 100%;
+ max-height: 100dvh;
+ margin: 0;
+ border-radius: 0;
+ border: none;
+ }
+
+ .command-palette-footer {
+ display: none;
+ }
}
.video-card .card-image-container {
From d75f0e3196d318e2852e6987feed5379ce4799fb Mon Sep 17 00:00:00 2001
From: akane <107654710+genericness@users.noreply.github.com>
Date: Sat, 21 Mar 2026 11:53:16 -0700
Subject: [PATCH 156/226] refactor(ui): use icons.ts for command palette icons
---
js/commandPalette.js | 162 +++++++++++++++++++++++++------------------
js/icons.ts | 31 +++++++++
2 files changed, 125 insertions(+), 68 deletions(-)
diff --git a/js/commandPalette.js b/js/commandPalette.js
index 2fce066cb..5ea9dd564 100644
--- a/js/commandPalette.js
+++ b/js/commandPalette.js
@@ -2,65 +2,92 @@ import { debounce } from './utils.js';
import { db } from './db.js';
import Fuse from 'fuse.js';
import { navigate } from './router.js';
+import {
+ SVG_SEARCH,
+ SVG_HOUSE,
+ SVG_LIBRARY,
+ SVG_CLOCK,
+ SVG_CALENDAR,
+ SVG_SETTINGS,
+ SVG_INFO,
+ SVG_DOWNLOAD,
+ SVG_HAND_HEART,
+ SVG_PLAY,
+ SVG_SKIP_FORWARD,
+ SVG_SKIP_BACK,
+ SVG_SHUFFLE,
+ SVG_REPEAT,
+ SVG_MUTE,
+ SVG_VOLUME_1,
+ SVG_HEART,
+ SVG_LIST,
+ SVG_TRASH,
+ SVG_ALIGN_LEFT,
+ SVG_MAXIMIZE,
+ SVG_SPARKLES,
+ SVG_MONITOR,
+ SVG_MOON,
+ SVG_SUN,
+ SVG_PALETTE,
+ SVG_STORE,
+ SVG_SLIDERS,
+ SVG_PLUS,
+ SVG_FOLDER_PLUS,
+ SVG_KEYBOARD,
+ SVG_UPLOAD,
+ SVG_USER,
+ SVG_PENCIL,
+ SVG_LOG_OUT,
+ SVG_LOG_IN,
+ SVG_MUSIC,
+ SVG_DISC,
+ SVG_MIC,
+ SVG_RADIO,
+} from './icons.js';
+
+const ICON_SIZE = 16;
const ICONS = {
- search: '
',
- house: '
',
- library:
- '
',
- clock: '
',
- calendar:
- '
',
- settings:
- '
',
- info: '
',
- download:
- '
',
- heart: '
',
- play: '
',
- pause: '
',
- skipForward:
- '
',
- skipBack:
- '
',
- shuffle:
- '
',
- repeat: '
',
- volumeX:
- '
',
- volume: '
',
- list: '
',
- trash: '
',
- text: '
',
- maximize:
- '
',
- sparkles:
- '
',
- palette:
- '
',
- sun: '
',
- moon: '
',
- sliders:
- '
',
- plus: '
',
- folderPlus:
- '
',
- user: '
',
- logOut: '
',
- logIn: '
',
- keyboard:
- '
',
- music: '
',
- disc: '
',
- mic: '
',
- upload: '
',
- handHeart:
- '
',
- monitor:
- '
',
- pencil: '
',
- radio: '
',
- store: '
',
+ search: SVG_SEARCH,
+ house: SVG_HOUSE,
+ library: SVG_LIBRARY,
+ clock: SVG_CLOCK,
+ calendar: SVG_CALENDAR,
+ settings: SVG_SETTINGS,
+ info: SVG_INFO,
+ download: SVG_DOWNLOAD,
+ handHeart: SVG_HAND_HEART,
+ play: SVG_PLAY,
+ skipForward: SVG_SKIP_FORWARD,
+ skipBack: SVG_SKIP_BACK,
+ shuffle: SVG_SHUFFLE,
+ repeat: SVG_REPEAT,
+ volumeX: SVG_MUTE,
+ volume: SVG_VOLUME_1,
+ heart: SVG_HEART,
+ list: SVG_LIST,
+ trash: SVG_TRASH,
+ text: SVG_ALIGN_LEFT,
+ maximize: SVG_MAXIMIZE,
+ sparkles: SVG_SPARKLES,
+ monitor: SVG_MONITOR,
+ moon: SVG_MOON,
+ sun: SVG_SUN,
+ palette: SVG_PALETTE,
+ store: SVG_STORE,
+ sliders: SVG_SLIDERS,
+ plus: SVG_PLUS,
+ folderPlus: SVG_FOLDER_PLUS,
+ keyboard: SVG_KEYBOARD,
+ upload: SVG_UPLOAD,
+ user: SVG_USER,
+ pencil: SVG_PENCIL,
+ logOut: SVG_LOG_OUT,
+ logIn: SVG_LOG_IN,
+ music: SVG_MUSIC,
+ disc: SVG_DISC,
+ mic: SVG_MIC,
+ radio: SVG_RADIO,
};
function escapeHtml(str) {
@@ -196,7 +223,7 @@ class CommandPalette {
icon: 'skipForward',
label: 'Next Track',
keywords: ['next', 'skip', 'forward'],
- shortcut: 'Shift+→',
+ shortcut: 'Shift+\u2192',
action: () => {
window.monochromePlayer?.playNext();
},
@@ -207,7 +234,7 @@ class CommandPalette {
icon: 'skipBack',
label: 'Previous Track',
keywords: ['previous', 'back', 'rewind'],
- shortcut: 'Shift+←',
+ shortcut: 'Shift+\u2190',
action: () => {
window.monochromePlayer?.playPrev();
},
@@ -252,7 +279,7 @@ class CommandPalette {
icon: 'volume',
label: 'Volume Up',
keywords: ['volume', 'louder'],
- shortcut: '↑',
+ shortcut: '\u2191',
action: () => {
const p = window.monochromePlayer;
if (p) p.setVolume(p.userVolume + 0.1);
@@ -264,7 +291,7 @@ class CommandPalette {
icon: 'volume',
label: 'Volume Down',
keywords: ['volume', 'quieter', 'softer'],
- shortcut: '↓',
+ shortcut: '\u2193',
action: () => {
const p = window.monochromePlayer;
if (p) p.setVolume(p.userVolume - 0.1);
@@ -478,7 +505,7 @@ class CommandPalette {
id: 'theme-frappe',
group: 'Theme',
icon: 'palette',
- label: 'Theme: Frappé',
+ label: 'Theme: Frapp\u00e9',
keywords: ['theme', 'frappe', 'catppuccin'],
action: () => this.setTheme('frappe'),
},
@@ -823,7 +850,7 @@ class CommandPalette {
musicGroups['Tracks'] = tracks.items.map((track) => ({
id: `track-${track.id}`,
group: 'Tracks',
- icon: null,
+ icon: 'music',
image: api.getCoverUrl(track.album?.cover, 80),
label: track.title,
description: `${track.artist?.name || 'Unknown'} \u2022 ${track.album?.title || ''}`,
@@ -838,7 +865,7 @@ class CommandPalette {
musicGroups['Albums'] = albums.items.map((album) => ({
id: `album-${album.id}`,
group: 'Albums',
- icon: null,
+ icon: 'disc',
image: api.getCoverUrl(album.cover, 80),
label: album.title,
description: album.artist?.name || 'Unknown',
@@ -852,7 +879,7 @@ class CommandPalette {
musicGroups['Artists'] = artists.items.map((artist) => ({
id: `artist-${artist.id}`,
group: 'Artists',
- icon: null,
+ icon: 'mic',
image: api.getArtistPictureUrl(artist.picture, 80),
label: artist.name,
description: 'Artist',
@@ -898,8 +925,7 @@ class CommandPalette {
this.removeMusicLoading();
this.resultsContainer.querySelectorAll('[data-music-group]').forEach((el) => el.remove());
- const startIndex = this.flatItems.length;
- let index = startIndex;
+ let index = this.flatItems.length;
for (const [heading, items] of Object.entries(musicGroups)) {
const groupEl = document.createElement('div');
@@ -981,7 +1007,7 @@ class CommandPalette {
if (item.image) {
iconHtml = `
`;
} else if (item.icon && ICONS[item.icon]) {
- iconHtml = `
${ICONS[item.icon]}
`;
+ iconHtml = `
${ICONS[item.icon](ICON_SIZE)}
`;
}
let shortcutHtml = '';
diff --git a/js/icons.ts b/js/icons.ts
index 0c6d5e3e4..29ba9ee04 100644
--- a/js/icons.ts
+++ b/js/icons.ts
@@ -1,45 +1,76 @@
+export { default as SVG_ALIGN_LEFT } from '!lucide/align-left.svg?svg&icon';
export { default as SVG_ANIMATE_SPIN } from '../images/animate-spin.svg?svg&icon';
export { default as SVG_APPLE } from '../images/apple.svg?svg&icon';
export { default as SVG_BIN } from '!lucide/trash-2.svg?svg&icon';
+export { default as SVG_CALENDAR } from '!lucide/calendar.svg?svg&icon';
export { default as SVG_CHECK } from '!lucide/check.svg?svg&icon';
export { default as SVG_CHECKBOX } from '!lucide/square.svg?svg&icon';
export { default as SVG_CHECKBOX_CHECKED } from '!lucide/check-square.svg?svg&icon';
export { default as SVG_CLOCK } from '!lucide/clock.svg?svg&icon';
export { default as SVG_CLOSE } from '!lucide/x.svg?svg&icon';
+export { default as SVG_DISC } from '!lucide/disc.svg?svg&icon';
export { default as SVG_DOWNLOAD } from '!lucide/download.svg?svg&icon';
export { default as SVG_EQUAL } from '!lucide/equal.svg?svg&icon';
export { default as SVG_FACEBOOK } from '../images/facebook.svg?svg&icon';
+export { default as SVG_FOLDER_PLUS } from '!lucide/folder-plus.svg?svg&icon';
export { default as SVG_GENIUS_ACTIVE } from '../images/genius-active.svg?svg&icon';
export { default as SVG_GENIUS_INACTIVE } from '../images/genius-inactive.svg?svg&icon';
export { default as SVG_GLOBE } from '!lucide/globe.svg?svg&icon';
+export { default as SVG_HAND_HEART } from '!lucide/hand-heart.svg?svg&icon';
export { default as SVG_HEART } from '!lucide/heart.svg?svg&icon&class=heart-icon';
export { default as SVG_HEART_FILLED } from '!lucide/heart.svg?svg&icon&class=heart-icon+filled';
+export { default as SVG_HOUSE } from '!lucide/house.svg?svg&icon';
+export { default as SVG_INFO } from '!lucide/info.svg?svg&icon';
export { default as SVG_INSTAGRAM } from '../images/instagram.svg?svg&icon';
+export { default as SVG_KEYBOARD } from '!lucide/keyboard.svg?svg&icon';
export { default as SVG_LEFT_ARROW } from '!lucide/chevron-left.svg?svg&icon';
+export { default as SVG_LIBRARY } from '!lucide/library.svg?svg&icon';
export { default as SVG_LINK } from '!lucide/link.svg?svg&icon';
+export { default as SVG_LIST } from '!lucide/list.svg?svg&icon';
+export { default as SVG_LOG_IN } from '!lucide/log-in.svg?svg&icon';
+export { default as SVG_LOG_OUT } from '!lucide/log-out.svg?svg&icon';
+export { default as SVG_MAXIMIZE } from '!lucide/maximize.svg?svg&icon';
export { default as SVG_MENU } from '!lucide/ellipsis-vertical.svg?svg&icon';
+export { default as SVG_MIC } from '!lucide/mic.svg?svg&icon';
export { default as SVG_MINUS } from '!lucide/minus.svg?svg&icon';
export { default as SVG_MIX } from '../images/mix.svg?svg&icon';
+export { default as SVG_MONITOR } from '!lucide/monitor.svg?svg&icon';
+export { default as SVG_MOON } from '!lucide/moon.svg?svg&icon';
export { default as SVG_MOVE_DOWN } from '!lucide/move-down.svg?svg&icon';
export { default as SVG_MOVE_UP } from '!lucide/move-up.svg?svg&icon';
+export { default as SVG_MUSIC } from '!lucide/music.svg?svg&icon';
export { default as SVG_MUTE } from '!lucide/volume-x.svg?svg&icon';
export { default as SVG_OFFLINE } from '!lucide/triangle-alert.svg?svg&icon';
+export { default as SVG_PALETTE } from '!lucide/palette.svg?svg&icon';
export { default as SVG_PAUSE } from '../images/pause.svg?svg&icon';
export { default as SVG_PAUSE_LARGE } from '../images/pause-large.svg?svg&icon';
+export { default as SVG_PENCIL } from '!lucide/pencil.svg?svg&icon';
export { default as SVG_PLAY } from '../images/play.svg?svg&icon';
export { default as SVG_PLAY_LARGE } from '../images/play-large.svg?svg&icon';
export { default as SVG_PLUS } from '!lucide/plus.svg?svg&icon';
+export { default as SVG_RADIO } from '!lucide/radio.svg?svg&icon';
export { default as SVG_REPEAT } from '!lucide/repeat.svg?svg&icon';
export { default as SVG_REPEAT_ONE } from '!lucide/repeat-1.svg?svg&icon';
export { default as SVG_RESET } from '!lucide/rotate-ccw.svg?svg&icon';
export { default as SVG_RIGHT_ARROW } from '!lucide/chevron-right.svg?svg&icon';
+export { default as SVG_SEARCH } from '!lucide/search.svg?svg&icon';
+export { default as SVG_SETTINGS } from '!lucide/settings.svg?svg&icon';
export { default as SVG_SHARE } from '!lucide/share.svg?svg&icon';
export { default as SVG_SHUFFLE } from '!lucide/shuffle.svg?svg&icon';
+export { default as SVG_SKIP_BACK } from '!lucide/skip-back.svg?svg&icon';
+export { default as SVG_SKIP_FORWARD } from '!lucide/skip-forward.svg?svg&icon';
+export { default as SVG_SLIDERS } from '!lucide/sliders-horizontal.svg?svg&icon';
export { default as SVG_SORT } from '../images/sort.svg?svg&icon';
export { default as SVG_SOUNDCLOUD } from '../images/soundcloud.svg?svg&icon';
+export { default as SVG_SPARKLES } from '!lucide/sparkles.svg?svg&icon';
export { default as SVG_SQUARE_PEN } from '!lucide/square-pen.svg?svg&icon';
+export { default as SVG_STORE } from '!lucide/store.svg?svg&icon';
+export { default as SVG_SUN } from '!lucide/sun.svg?svg&icon';
export { default as SVG_TRASH } from '!lucide/trash.svg?svg&icon';
export { default as SVG_TWITTER } from '../images/twitter.svg?svg&icon';
+export { default as SVG_UPLOAD } from '!lucide/upload.svg?svg&icon';
+export { default as SVG_USER } from '!lucide/user.svg?svg&icon';
export { default as SVG_VIDEO } from '!lucide/video.svg?svg&icon';
export { default as SVG_VOLUME } from '!lucide/volume-2.svg?svg&icon';
+export { default as SVG_VOLUME_1 } from '!lucide/volume-1.svg?svg&icon';
export { default as SVG_YOUTUBE } from '../images/youtube.svg?svg&icon';
From 7e56fc50301c441540941491ab4657976099d7fa Mon Sep 17 00:00:00 2001
From: akane <107654710+genericness@users.noreply.github.com>
Date: Sat, 21 Mar 2026 11:56:34 -0700
Subject: [PATCH 157/226] fix(ui): command palette accessibility, theme
handling, and edge cases
---
index.html | 7 ++++++-
js/commandPalette.js | 31 ++++++++++++++++++++++++++-----
styles.css | 1 +
3 files changed, 33 insertions(+), 6 deletions(-)
diff --git a/index.html b/index.html
index 4ef6d048b..9af8e98db 100644
--- a/index.html
+++ b/index.html
@@ -1505,10 +1505,15 @@
Update Available
placeholder="Search commands, music, settings..."
autocomplete="off"
spellcheck="false"
+ aria-label="Command palette search"
+ role="combobox"
+ aria-expanded="true"
+ aria-controls="command-palette-results"
+ aria-autocomplete="list"
/>
ESC
-
+
↑↓ navigate
↵ select
diff --git a/js/commandPalette.js b/js/commandPalette.js
index 5ea9dd564..c99d204f4 100644
--- a/js/commandPalette.js
+++ b/js/commandPalette.js
@@ -642,6 +642,7 @@ class CommandPalette {
icon: 'search',
label: 'Search Settings...',
keywords: ['setting', 'find', 'search', 'preference', 'option', 'configure'],
+ keepOpen: true,
action: () => this.enterSettingsMode(),
},
@@ -923,6 +924,7 @@ class CommandPalette {
appendMusicGroups(musicGroups) {
this.removeMusicLoading();
+ this.resultsContainer.querySelector('.cmdk-empty')?.remove();
this.resultsContainer.querySelectorAll('[data-music-group]').forEach((el) => el.remove());
let index = this.flatItems.length;
@@ -1000,7 +1002,10 @@ class CommandPalette {
createItemElement(item, index) {
const el = document.createElement('div');
el.className = 'cmdk-item';
+ el.id = `cmdk-item-${index}`;
+ el.setAttribute('role', 'option');
el.setAttribute('data-index', index);
+ el.setAttribute('aria-selected', index === this.selectedIndex ? 'true' : 'false');
if (index === this.selectedIndex) el.setAttribute('data-selected', 'true');
let iconHtml = '';
@@ -1041,18 +1046,34 @@ class CommandPalette {
const idx = parseInt(item.getAttribute('data-index'));
if (idx === this.selectedIndex) {
item.setAttribute('data-selected', 'true');
+ item.setAttribute('aria-selected', 'true');
item.scrollIntoView({ block: 'nearest' });
} else {
item.removeAttribute('data-selected');
+ item.setAttribute('aria-selected', 'false');
}
});
+ this.input.setAttribute('aria-activedescendant', `cmdk-item-${this.selectedIndex}`);
}
- executeSelected() {
+ async executeSelected() {
const item = this.flatItems[this.selectedIndex];
if (!item || !item.action) return;
- item.action();
+ if (item.keepOpen) {
+ try {
+ await item.action();
+ } catch (e) {
+ console.error('Command palette action error:', e);
+ }
+ return;
+ }
+
+ try {
+ await item.action();
+ } catch (e) {
+ console.error('Command palette action error:', e);
+ }
this.close();
}
@@ -1132,9 +1153,9 @@ class CommandPalette {
}
}
- setTheme(theme) {
- document.documentElement.setAttribute('data-theme', theme);
- localStorage.setItem('theme', theme);
+ async setTheme(theme) {
+ const { themeManager } = await import('./storage.js');
+ themeManager.setTheme(theme);
const themeOptions = document.querySelectorAll('.theme-option');
themeOptions.forEach((opt) => {
if (opt.dataset.theme === theme) opt.classList.add('active');
diff --git a/styles.css b/styles.css
index 95d3844da..9e9620971 100644
--- a/styles.css
+++ b/styles.css
@@ -8527,6 +8527,7 @@ body:has(#side-panel.active) #close-fullscreen-cover-btn {
color: var(--muted-foreground);
}
+/* stylelint-disable-next-line no-descending-specificity */
.cmdk-item-icon img {
width: 28px;
height: 28px;
From da5ade79a77fcb2e030cb0be6828e171ae5f5d02 Mon Sep 17 00:00:00 2001
From: akane <107654710+genericness@users.noreply.github.com>
Date: Sat, 21 Mar 2026 12:04:25 -0700
Subject: [PATCH 158/226] perf(ui): cache settings Fuse instance in command
palette
---
js/commandPalette.js | 15 ++++++++-------
1 file changed, 8 insertions(+), 7 deletions(-)
diff --git a/js/commandPalette.js b/js/commandPalette.js
index c99d204f4..bbc088ea5 100644
--- a/js/commandPalette.js
+++ b/js/commandPalette.js
@@ -1082,13 +1082,7 @@ class CommandPalette {
let results = this.allSettings;
if (query) {
- const fuse = new Fuse(this.allSettings, {
- keys: ['label', 'description'],
- includeScore: true,
- threshold: 0.4,
- ignoreLocation: true,
- });
- results = fuse.search(query).map((r) => r.item);
+ results = this.settingsFuse.search(query).map((r) => r.item);
}
const items = results.map((setting) => ({
@@ -1126,6 +1120,13 @@ class CommandPalette {
return { id: item.id, label, description, tab };
})
.filter((s) => s.label);
+
+ this.settingsFuse = new Fuse(this.allSettings, {
+ keys: ['label', 'description'],
+ includeScore: true,
+ threshold: 0.4,
+ ignoreLocation: true,
+ });
}
async navigateToSetting(setting) {
From 446b6fff559568219ac11b079f19a989262c64ab Mon Sep 17 00:00:00 2001
From: akane <107654710+genericness@users.noreply.github.com>
Date: Sat, 21 Mar 2026 12:30:38 -0700
Subject: [PATCH 159/226] fix(ui): deduplicate recommended songs on homepage
---
js/api.js | 8 +++++---
js/db.js | 10 ++++------
js/ui.js | 9 ++++++++-
3 files changed, 17 insertions(+), 10 deletions(-)
diff --git a/js/api.js b/js/api.js
index 1712072b0..318619919 100644
--- a/js/api.js
+++ b/js/api.js
@@ -1182,9 +1182,11 @@ export class LosslessAPI {
const results = await Promise.all(artistPromises);
results.forEach((tracks) => {
- if (tracks.length > 0) {
- recommendedTracks.push(...tracks);
- tracks.forEach((t) => seenTrackIds.add(t.id));
+ for (const t of tracks) {
+ if (!seenTrackIds.has(t.id)) {
+ seenTrackIds.add(t.id);
+ recommendedTracks.push(t);
+ }
}
});
diff --git a/js/db.js b/js/db.js
index 1924e49ad..588292fd2 100644
--- a/js/db.js
+++ b/js/db.js
@@ -107,20 +107,18 @@ export class MusicDatabase {
const store = transaction.objectStore(storeName);
const index = store.index('timestamp');
- // Check the most recent entry
const cursorReq = index.openCursor(null, 'prev');
cursorReq.onsuccess = (e) => {
const cursor = e.target.result;
if (cursor) {
- const lastTrack = cursor.value;
- if (lastTrack.id === track.id) {
- // If same track, delete the old entry so we just update the timestamp
+ if (cursor.value.id === track.id) {
store.delete(cursor.primaryKey);
}
+ cursor.continue();
+ } else {
+ store.put(entry);
}
- // Add the new entry
- store.put(entry);
};
cursorReq.onerror = (_e) => {
diff --git a/js/ui.js b/js/ui.js
index 5793b0b45..7e3646a32 100644
--- a/js/ui.js
+++ b/js/ui.js
@@ -2187,12 +2187,19 @@ export class UIRenderer {
// Take random samples from each to form seeds
const shuffle = (arr) => [...arr].sort(() => Math.random() - 0.5);
- const seeds = [
+ const combined = [
...shuffle(playlistTracks).slice(0, 20),
...shuffle(favorites).slice(0, 20),
...shuffle(history).slice(0, 10),
];
+ const seenIds = new Set();
+ const seeds = combined.filter((t) => {
+ if (seenIds.has(t.id)) return false;
+ seenIds.add(t.id);
+ return true;
+ });
+
return shuffle(seeds);
}
From 7008de33b0990b4cee3fe9aba91f9070e3c6e7c6 Mon Sep 17 00:00:00 2001
From: akane <107654710+genericness@users.noreply.github.com>
Date: Sat, 21 Mar 2026 12:33:25 -0700
Subject: [PATCH 160/226] fix(ui): revert history dedup to preserve play
frequency
---
js/db.js | 7 +++----
1 file changed, 3 insertions(+), 4 deletions(-)
diff --git a/js/db.js b/js/db.js
index 588292fd2..5f015643f 100644
--- a/js/db.js
+++ b/js/db.js
@@ -112,13 +112,12 @@ export class MusicDatabase {
cursorReq.onsuccess = (e) => {
const cursor = e.target.result;
if (cursor) {
- if (cursor.value.id === track.id) {
+ const lastTrack = cursor.value;
+ if (lastTrack.id === track.id) {
store.delete(cursor.primaryKey);
}
- cursor.continue();
- } else {
- store.put(entry);
}
+ store.put(entry);
};
cursorReq.onerror = (_e) => {
From 6728b17efefee10ebe70f23033d5a56c7b96a4f6 Mon Sep 17 00:00:00 2001
From: Daniel <790119+DanTheMan827@users.noreply.github.com>
Date: Sat, 21 Mar 2026 16:56:05 -0500
Subject: [PATCH 161/226] fix: bun format
---
.github/dependabot.yml | 8 +-
.github/pull_request_template.md | 6 +-
.github/workflows/desktop-build.yml | 236 +-
.github/workflows/update-lockfile.yml | 72 +-
INSTANCES.md | 30 +-
README.md | 2 -
functions/user/@[username].js | 6 +-
js/accounts/pocketbase.js | 3 +-
js/lyrics.js | 9 +-
public/lib/butterchurnPresets.min.js | 8351 ++++++++++++++++++++++++-
vite-plugin-blob.ts | 8 +-
11 files changed, 8543 insertions(+), 188 deletions(-)
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
index f33a02cd1..f6401cb08 100644
--- a/.github/dependabot.yml
+++ b/.github/dependabot.yml
@@ -6,7 +6,7 @@
version: 2
updates:
- - package-ecosystem: "devcontainers"
- directory: "/"
- schedule:
- interval: weekly
+ - package-ecosystem: 'devcontainers'
+ directory: '/'
+ schedule:
+ interval: weekly
diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md
index a7b191901..d748fcf52 100644
--- a/.github/pull_request_template.md
+++ b/.github/pull_request_template.md
@@ -1,14 +1,18 @@
### Description
+
### Type of Change
+
- [ ] Bug fix
- [ ] New feature
- [ ] Style/UI update
- [ ] Docs only
### Checklist
+
- [ ] **I have read the [Contributing Guidelines](https://github.com/monochrome-music/monochrome/blob/main/CONTRIBUTING.md).**
- [ ] **I understand every line of code I am submitting.**
- [ ] I have tested these changes locally, and they work as expected.
---
-*By submitting this PR, I agree to follow the guidelines. I understand that the final decision to merge rests with the maintainers and that not all contributions can be accepted.*
\ No newline at end of file
+
+_By submitting this PR, I agree to follow the guidelines. I understand that the final decision to merge rests with the maintainers and that not all contributions can be accepted._
diff --git a/.github/workflows/desktop-build.yml b/.github/workflows/desktop-build.yml
index d8c7b2e5d..ef5bc2e0f 100644
--- a/.github/workflows/desktop-build.yml
+++ b/.github/workflows/desktop-build.yml
@@ -1,125 +1,125 @@
name: Desktop Build
on:
- push:
- branches: [main, neutralino]
- workflow_dispatch:
+ push:
+ branches: [main, neutralino]
+ workflow_dispatch:
permissions:
- contents: write
+ contents: write
jobs:
- build:
- runs-on: ${{ matrix.os }}
-
- strategy:
- fail-fast: false
- matrix:
- include:
- - os: windows-latest
- platform: windows
- binary_source: Monochrome-win_x64.exe
- binary_dest: Monochrome.exe
- archive_name: monochrome-windows.zip
- - os: ubuntu-latest
- platform: linux
- binary_source: Monochrome-linux_x64
- binary_dest: Monochrome
- archive_name: monochrome-linux.zip
- - os: macos-latest
- platform: macos
- binary_source: Monochrome-mac_universal
- binary_dest: Monochrome
- archive_name: monochrome-mac.zip
-
- steps:
- - name: Checkout code
- uses: actions/checkout@v4
- with:
- fetch-depth: 1
-
- - name: Setup Bun
- uses: oven-sh/setup-bun@v2
- with:
- bun-version: latest
-
- - name: Install dependencies
- run: bun install
-
- - name: Download Neutralino binaries
- run: bun x neu update
-
- - name: Auto-Bump Version
- run: |
- $json = Get-Content neutralino.config.json -Raw | ConvertFrom-Json
- $v = $json.version.Split('.')
- if ($v.Count -lt 2) { $v += "0" }
- $newVersion = "{0}.{1}.{2}" -f $v[0], $v[1], ${{ github.run_number }}
- $json.version = $newVersion
- $json | ConvertTo-Json -Depth 5 | Set-Content neutralino.config.json
- shell: pwsh
-
- - name: Build application
- run: bun run build
-
- - name: Prepare Release Folder
- run: |
- mkdir release
- cp dist/Monochrome/resources.neu release/
- cp neutralino.config.json release/
- cp -r dist/Monochrome/extensions release/
- cp dist/Monochrome/${{ matrix.binary_source }} release/${{ matrix.binary_dest }}
- shell: bash
-
- - name: Set Permissions (Linux/macOS)
- if: matrix.platform != 'windows'
- run: chmod +x release/${{ matrix.binary_dest }}
-
- - name: Zip for R2 (Windows)
- if: matrix.platform == 'windows'
- run: Compress-Archive -Path release/* -DestinationPath ${{ matrix.archive_name }} -Force
- shell: pwsh
-
- - name: Zip for R2 (Linux/macOS)
- if: matrix.platform != 'windows'
- run: |
- cd release
- zip -r ../${{ matrix.archive_name }} .
- shell: bash
-
- - name: Isolate Zip File
- run: |
- mkdir out_delivery
- mv ${{ matrix.archive_name }} out_delivery/
- shell: bash
-
- - name: Generate Update Manifest
- run: |
- $config = Get-Content neutralino.config.json | ConvertFrom-Json
- $version = $config.version
- $platform = "${{ matrix.platform }}"
- $archive = "${{ matrix.archive_name }}"
- $json = @{
- version = $version
- url = "https://downloads.samidy.com/out_delivery/$archive"
- notes = "Update $version is available."
- }
- $json | ConvertTo-Json | Set-Content "out_delivery/update-$platform.json"
- shell: pwsh
-
- - name: Upload to R2
- uses: ryand56/r2-upload-action@latest
- with:
- r2-account-id: ${{ secrets.R2_ACCOUNT_ID }}
- r2-access-key-id: ${{ secrets.R2_ACCESS_KEY_ID }}
- r2-secret-access-key: ${{ secrets.R2_SECRET_ACCESS_KEY }}
- r2-bucket: ${{ secrets.R2_BUCKET }}
- source-dir: 'out_delivery'
- destination-dir: 'out_delivery'
-
- - name: Upload Artifact (GH Internal)
- uses: actions/upload-artifact@v4
- with:
- name: Monochrome-${{ matrix.platform }}
- path: release/
- retention-days: 30
\ No newline at end of file
+ build:
+ runs-on: ${{ matrix.os }}
+
+ strategy:
+ fail-fast: false
+ matrix:
+ include:
+ - os: windows-latest
+ platform: windows
+ binary_source: Monochrome-win_x64.exe
+ binary_dest: Monochrome.exe
+ archive_name: monochrome-windows.zip
+ - os: ubuntu-latest
+ platform: linux
+ binary_source: Monochrome-linux_x64
+ binary_dest: Monochrome
+ archive_name: monochrome-linux.zip
+ - os: macos-latest
+ platform: macos
+ binary_source: Monochrome-mac_universal
+ binary_dest: Monochrome
+ archive_name: monochrome-mac.zip
+
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v4
+ with:
+ fetch-depth: 1
+
+ - name: Setup Bun
+ uses: oven-sh/setup-bun@v2
+ with:
+ bun-version: latest
+
+ - name: Install dependencies
+ run: bun install
+
+ - name: Download Neutralino binaries
+ run: bun x neu update
+
+ - name: Auto-Bump Version
+ run: |
+ $json = Get-Content neutralino.config.json -Raw | ConvertFrom-Json
+ $v = $json.version.Split('.')
+ if ($v.Count -lt 2) { $v += "0" }
+ $newVersion = "{0}.{1}.{2}" -f $v[0], $v[1], ${{ github.run_number }}
+ $json.version = $newVersion
+ $json | ConvertTo-Json -Depth 5 | Set-Content neutralino.config.json
+ shell: pwsh
+
+ - name: Build application
+ run: bun run build
+
+ - name: Prepare Release Folder
+ run: |
+ mkdir release
+ cp dist/Monochrome/resources.neu release/
+ cp neutralino.config.json release/
+ cp -r dist/Monochrome/extensions release/
+ cp dist/Monochrome/${{ matrix.binary_source }} release/${{ matrix.binary_dest }}
+ shell: bash
+
+ - name: Set Permissions (Linux/macOS)
+ if: matrix.platform != 'windows'
+ run: chmod +x release/${{ matrix.binary_dest }}
+
+ - name: Zip for R2 (Windows)
+ if: matrix.platform == 'windows'
+ run: Compress-Archive -Path release/* -DestinationPath ${{ matrix.archive_name }} -Force
+ shell: pwsh
+
+ - name: Zip for R2 (Linux/macOS)
+ if: matrix.platform != 'windows'
+ run: |
+ cd release
+ zip -r ../${{ matrix.archive_name }} .
+ shell: bash
+
+ - name: Isolate Zip File
+ run: |
+ mkdir out_delivery
+ mv ${{ matrix.archive_name }} out_delivery/
+ shell: bash
+
+ - name: Generate Update Manifest
+ run: |
+ $config = Get-Content neutralino.config.json | ConvertFrom-Json
+ $version = $config.version
+ $platform = "${{ matrix.platform }}"
+ $archive = "${{ matrix.archive_name }}"
+ $json = @{
+ version = $version
+ url = "https://downloads.samidy.com/out_delivery/$archive"
+ notes = "Update $version is available."
+ }
+ $json | ConvertTo-Json | Set-Content "out_delivery/update-$platform.json"
+ shell: pwsh
+
+ - name: Upload to R2
+ uses: ryand56/r2-upload-action@latest
+ with:
+ r2-account-id: ${{ secrets.R2_ACCOUNT_ID }}
+ r2-access-key-id: ${{ secrets.R2_ACCESS_KEY_ID }}
+ r2-secret-access-key: ${{ secrets.R2_SECRET_ACCESS_KEY }}
+ r2-bucket: ${{ secrets.R2_BUCKET }}
+ source-dir: 'out_delivery'
+ destination-dir: 'out_delivery'
+
+ - name: Upload Artifact (GH Internal)
+ uses: actions/upload-artifact@v4
+ with:
+ name: Monochrome-${{ matrix.platform }}
+ path: release/
+ retention-days: 30
diff --git a/.github/workflows/update-lockfile.yml b/.github/workflows/update-lockfile.yml
index db5d39f5d..73be2b4bd 100644
--- a/.github/workflows/update-lockfile.yml
+++ b/.github/workflows/update-lockfile.yml
@@ -1,43 +1,43 @@
name: Update Lock File
on:
- workflow_dispatch:
+ workflow_dispatch:
permissions:
- contents: write
+ contents: write
jobs:
- update-lock:
- runs-on: ubuntu-latest
-
- steps:
- - name: Checkout code
- uses: actions/checkout@v4
- with:
- fetch-depth: 1
-
- - name: Configure Git
- uses: DanTheMan827/config-git-user-action@v1
-
- - name: Setup Bun
- uses: oven-sh/setup-bun@v1
- with:
- bun-version: latest
-
- - name: Cache dependencies
- uses: actions/cache@v3
- with:
- path: |
- ./bun_modules
- ./node_modules
- ./bun.lock
- key: ${{ runner.os }}-bun-${{ hashFiles('bun.lock') }}
-
- - name: Install dependencies
- run: bun install
-
- - name: Commit changes
- run: |
- git add -A . || true
- git commit "update lockfile" || true
- git push || true
+ update-lock:
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v4
+ with:
+ fetch-depth: 1
+
+ - name: Configure Git
+ uses: DanTheMan827/config-git-user-action@v1
+
+ - name: Setup Bun
+ uses: oven-sh/setup-bun@v1
+ with:
+ bun-version: latest
+
+ - name: Cache dependencies
+ uses: actions/cache@v3
+ with:
+ path: |
+ ./bun_modules
+ ./node_modules
+ ./bun.lock
+ key: ${{ runner.os }}-bun-${{ hashFiles('bun.lock') }}
+
+ - name: Install dependencies
+ run: bun install
+
+ - name: Commit changes
+ run: |
+ git add -A . || true
+ git commit "update lockfile" || true
+ git push || true
diff --git a/INSTANCES.md b/INSTANCES.md
index 71d847916..399ff12a5 100644
--- a/INSTANCES.md
+++ b/INSTANCES.md
@@ -30,10 +30,10 @@ These instances are community instances of Monochrome & its WebUI:
These instances provide the tidal-ui web interface, not monochrome:
-| Provider | URL | Status |
-| ------------------- | ---------------------------------------------- | --------- |
-| **squid.wtf** | [tidal.squid.wtf](https://tidal.squid.wtf) | Community |
-| **QQDL** | [tidal.qqdl.site](https://tidal.qqdl.site/) | Community |
+| Provider | URL | Status |
+| ------------- | ------------------------------------------- | --------- |
+| **squid.wtf** | [tidal.squid.wtf](https://tidal.squid.wtf) | Community |
+| **QQDL** | [tidal.qqdl.site](https://tidal.qqdl.site/) | Community |
---
@@ -48,17 +48,17 @@ These are available API endpoints that can be used with Monochrome or other Hi-F
### Official & Community APIs
-| Provider | URL | Notes |
-| ----------------- | ----------------------------------- | ---------------- |
-| **Monochrome** | `https://monochrome-api.samidy.com` | Official API |
-| | `https://api.monochrome.tf` | Official API |
-| | `https://arran.monochrome.tf` | Official API |
-| **squid.wtf** | `https://triton.squid.wtf` | Community hosted |
-| **Lucida (QQDL)** | `https://wolf.qqdl.site` | Community hosted |
-| | `https://maus.qqdl.site` | Community hosted |
-| | `https://vogel.qqdl.site` | Community hosted |
-| | `https://katze.qqdl.site` | Community hosted |
-| | `https://hund.qqdl.site` | Community hosted |
+| Provider | URL | Notes |
+| ----------------- | ----------------------------------- | ----------------------------------------------------------------------- |
+| **Monochrome** | `https://monochrome-api.samidy.com` | Official API |
+| | `https://api.monochrome.tf` | Official API |
+| | `https://arran.monochrome.tf` | Official API |
+| **squid.wtf** | `https://triton.squid.wtf` | Community hosted |
+| **Lucida (QQDL)** | `https://wolf.qqdl.site` | Community hosted |
+| | `https://maus.qqdl.site` | Community hosted |
+| | `https://vogel.qqdl.site` | Community hosted |
+| | `https://katze.qqdl.site` | Community hosted |
+| | `https://hund.qqdl.site` | Community hosted |
| **Kinoplus** | `https://tidal.kinoplus.online` | Community hosted - [Limited/No-Sub](https://rentry.co/limitedtidalaccs) |
---
diff --git a/README.md b/README.md
index 1cc0c76bb..132e40f14 100644
--- a/README.md
+++ b/README.md
@@ -110,7 +110,6 @@ Our Recommended way to use monochrome is through our official instance:
**[monochrome.tf](https://monochrome.tf)** / **[monochrome.samidy.com](https://monochrome.samidy.com)**
-
For alternative instances, check [INSTANCES.md](INSTANCES.md).
---
@@ -282,4 +281,3 @@ We welcome contributions from the community! Please see our [Contributing Guide]
-
diff --git a/functions/user/@[username].js b/functions/user/@[username].js
index 77ccda645..507d9cdfc 100644
--- a/functions/user/@[username].js
+++ b/functions/user/@[username].js
@@ -13,10 +13,10 @@ export async function onRequest(context) {
const POCKETBASE_URL = 'https://data.samidy.xyz';
const filter = `username="${username}"`;
const profileUrl = `${POCKETBASE_URL}/api/collections/DB_users/records?filter=${encodeURIComponent(filter)}&fields=username,display_name,avatar_url,banner,about,status`;
-
+
const response = await fetch(profileUrl);
if (!response.ok) throw new Error(`PocketBase error: ${response.status}`);
-
+
const data = await response.json();
const profile = data.items && data.items.length > 0 ? data.items[0] : null;
@@ -24,7 +24,7 @@ export async function onRequest(context) {
const displayName = profile.display_name || profile.username;
const title = `${displayName} (@${profile.username})`;
let description = profile.about || `View ${displayName}'s profile on Monochrome.`;
-
+
if (profile.status) {
try {
const statusObj = JSON.parse(profile.status);
diff --git a/js/accounts/pocketbase.js b/js/accounts/pocketbase.js
index 3867f9ac5..d96f8acbf 100644
--- a/js/accounts/pocketbase.js
+++ b/js/accounts/pocketbase.js
@@ -5,7 +5,8 @@ import { authManager } from './auth.js';
const PUBLIC_COLLECTION = 'public_playlists';
const DEFAULT_POCKETBASE_URL = 'https://data.samidy.xyz';
-const POCKETBASE_URL = window.__POCKETBASE_URL__ || localStorage.getItem('monochrome-pocketbase-url') || DEFAULT_POCKETBASE_URL;
+const POCKETBASE_URL =
+ window.__POCKETBASE_URL__ || localStorage.getItem('monochrome-pocketbase-url') || DEFAULT_POCKETBASE_URL;
console.log('[PocketBase] Using URL:', POCKETBASE_URL);
diff --git a/js/lyrics.js b/js/lyrics.js
index b5d5378c7..b567f7908 100644
--- a/js/lyrics.js
+++ b/js/lyrics.js
@@ -33,14 +33,17 @@ function trackHasAsianText(track) {
function cleanTrackerSearch(text) {
if (!text) return '';
// chud emojis will NOT be tolerated in my precious genius lyrics worker
- let cleaned = text.replace(/[\p{Extended_Pictographic}\p{Emoji_Component}\p{Emoji_Presentation}\p{Emoji_Modifier}\p{Emoji_Modifier_Base}\p{Symbol}]/gu, '');
-
+ let cleaned = text.replace(
+ /[\p{Extended_Pictographic}\p{Emoji_Component}\p{Emoji_Presentation}\p{Emoji_Modifier}\p{Emoji_Modifier_Base}\p{Symbol}]/gu,
+ ''
+ );
+
cleaned = cleaned.replace(/[\u2600-\u27BF\u2B50\u2B06\u2194\u21AA\u2934\u203C\u2049\u3030\u303D\u3297\u3299]/g, '');
cleaned = cleaned.replace(/\[v\s*\d+\s*\]/gi, '');
cleaned = cleaned.replace(/\s+/g, ' ');
-
+
return cleaned.trim();
}
diff --git a/public/lib/butterchurnPresets.min.js b/public/lib/butterchurnPresets.min.js
index a16d7e477..e7fcf2c31 100644
--- a/public/lib/butterchurnPresets.min.js
+++ b/public/lib/butterchurnPresets.min.js
@@ -1 +1,8350 @@
-!function(a,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define("butterchurnPresets",[],e):"object"==typeof exports?exports.butterchurnPresets=e():a.butterchurnPresets=e()}("undefined"!=typeof self?self:this,function(){return function(a){var e={};function t(r){if(e[r])return e[r].exports;var n=e[r]={i:r,l:!1,exports:{}};return a[r].call(n.exports,n,n.exports,t),n.l=!0,n.exports}return t.m=a,t.c=e,t.d=function(a,e,r){t.o(a,e)||Object.defineProperty(a,e,{configurable:!1,enumerable:!0,get:r})},t.n=function(a){var e=a&&a.__esModule?function(){return a.default}:function(){return a};return t.d(e,"a",e),e},t.o=function(a,e){return Object.prototype.hasOwnProperty.call(a,e)},t.p="",t(t.s=165)}([function(a,e,t){a.exports=!t(5)(function(){return 7!=Object.defineProperty({},"a",{get:function(){return 7}}).a})},function(a,e){a.exports=function(a){return"object"==typeof a?null!==a:"function"==typeof a}},function(a,e){var t=a.exports="undefined"!=typeof window&&window.Math==Math?window:"undefined"!=typeof self&&self.Math==Math?self:Function("return this")();"number"==typeof __g&&(__g=t)},function(a,e){var t=a.exports={version:"2.5.3"};"number"==typeof __e&&(__e=t)},function(a,e,t){var r=t(15),n=t(16),_=t(18),s=Object.defineProperty;e.f=t(0)?Object.defineProperty:function(a,e,t){if(r(a),e=_(e,!0),r(t),n)try{return s(a,e,t)}catch(a){}if("get"in t||"set"in t)throw TypeError("Accessors not supported!");return"value"in t&&(a[e]=t.value),a}},function(a,e){a.exports=function(a){try{return!!a()}catch(a){return!0}}},function(a,e,t){"use strict";e.__esModule=!0,e.default=function(a,e){if(!(a instanceof e))throw new TypeError("Cannot call a class as a function")}},function(a,e,t){"use strict";e.__esModule=!0;var r,n=t(8),_=(r=n)&&r.__esModule?r:{default:r};e.default=function(){function a(a,e){for(var t=0;t
1.0)\n ) * (\n (tmpvar_8 * -2.0)\n + 1.570796)));\n tmpvar_6 = (tmpvar_8 * sign((uv_1.y / uv_1.x)));\n if ((abs(uv_1.x) > (1e-08 * abs(uv_1.y)))) {\n if ((uv_1.x < 0.0)) {\n if ((uv_1.y >= 0.0)) {\n tmpvar_6 += 3.141593;\n } else {\n tmpvar_6 = (tmpvar_6 - 3.141593);\n };\n };\n } else {\n tmpvar_6 = (sign(uv_1.y) * 1.570796);\n };\n xlat_mutablers0.x = ((tmpvar_6 / 3.1416) * 2.0);\n xlat_mutablers0.y = (0.03 / sqrt(dot (uv_1, uv_1)));\n ret1_3 = vec3(0.0, 0.0, 0.0);\n for (int n_2 = 0; n_2 <= 10; n_2++) {\n float tmpvar_9;\n tmpvar_9 = fract((-(q9) + (\n float(n_2)\n / 10.0)));\n xlat_mutableang2 = (((q1 * 3.14) * float(n_2)) / 10.0);\n float tmpvar_10;\n tmpvar_10 = cos(xlat_mutableang2);\n float tmpvar_11;\n tmpvar_11 = sin(xlat_mutableang2);\n mat2 tmpvar_12;\n tmpvar_12[uint(0)].x = tmpvar_10;\n tmpvar_12[uint(0)].y = -(tmpvar_11);\n tmpvar_12[1u].x = tmpvar_11;\n tmpvar_12[1u].y = tmpvar_10;\n xlat_mutableuv2 = (uv_1 * ((q13 * tmpvar_9) * tmpvar_12));\n ret1_3 = max (ret1_3, (texture (sampler_main, (xlat_mutableuv2 + 0.5)).xyz * (1.0 - tmpvar_9)));\n };\n vec4 tmpvar_13;\n tmpvar_13.w = 1.0;\n tmpvar_13.xyz = ((ret1_3 * 2.0) + ((\n (bass_att * xlat_mutablers0.y)\n * texture (sampler_main, \n ((uv_1 * q12) + vec2(0.5, 0.0))\n ).yzx) * clamp (\n (1.0 - (ret1_3 * 32.0))\n , 0.0, 1.0)));\n ret = tmpvar_13.xyz;\n }"}},function(a,e){a.exports={baseVals:{rating:5,gammaadj:1,decay:.965,echo_zoom:1.483827,echo_alpha:.5,echo_orient:3,wave_mode:7,additivewave:1,wave_brighten:0,wrap:0,darken_center:1,darken:1,wave_a:.001,wave_scale:1.285751,wave_smoothing:.63,modwavealphastart:.71,modwavealphaend:1.3,warpanimspeed:.01,warpscale:1.470245,zoomexp:4.778023,zoom:.998162,warp:.01,sx:1.001828,wave_r:.65,wave_g:.65,wave_b:.65,ob_size:.005,ob_r:1,ob_g:.5,ob_b:.5,ob_a:1,ib_size:.5,ib_r:0,ib_g:0,ib_b:0,ib_a:1,mv_x:64,mv_y:4.800001,mv_dx:.4,mv_l:1,mv_r:0,mv_g:.5,mv_a:.1},shapes:[{baseVals:{enabled:1,sides:3,additive:1,thickoutline:1,textured:1,x:1,y:.59,rad:.559231,ang:3.39292,tex_zoom:100,r:0,g:1,b:1,g2:0,border_r:0,border_g:0,border_b:0,border_a:1},init_eqs_str:"",frame_eqs_str:"a.x=.1*Math.sin(div(a.time,10))+.5+.1*a.treb_att;"},{baseVals:{enabled:0}},{baseVals:{enabled:1,sides:6,textured:1,x:.3,y:.7,rad:1.089252,ang:.816814,tex_ang:3.141592,tex_zoom:.504215,g:1,b:1,r2:1,b2:1,border_a:0},init_eqs_str:"",frame_eqs_str:""},{baseVals:{enabled:1,sides:3,textured:1,rad:.284278,ang:3.141593,tex_ang:4.900885,tex_zoom:2.987755,g:1,b:1,r2:.95,b2:1,a2:1,border_r:0,border_g:0,border_b:0,border_a:1},init_eqs_str:'a["var"]=0;',frame_eqs_str:'a.ang=div(a.time,10);a.tex_zoom=3.4+.03*a.bass;a["var"]=above(a.bass_att,.7);a.a=a["var"];a.a2=a["var"];a.border_a=a["var"];'}],waves:[{baseVals:{enabled:1,usedots:1,thick:1,additive:1,r:0,a:.06},init_eqs_str:"a.px=0;a.xoffset2=0;a.py=0;a.xoffset1=0;a.pheight=0;a.pphase=0;a.yspout=0;a.pphase2=0;a.xspout=0;a.lrorient=0;a.yheight=0;",frame_eqs_str:"",point_eqs_str:"a.xspout=.5;a.yspout=-.01;a.pphase=9999*a.sample*a.sample*.0001;a.pphase2=.1+.01*mod(3349*a.sample*a.sample,100);a.pheight=.002*mod(9893*a.sample,100);a.yheight=.01*mod(1231*a.sample*a.sample,100);a.r=.01*mod(5454*a.sample,100)*Math.abs(Math.sin(.25*a.time));a.g=.01*mod(9954*a.sample,100);a.xoffset1=Math.cos(a.time*a.pphase2+a.pphase)*a.pheight;a.xoffset2=-1*Math.cos(a.time*a.pphase2+a.pphase)*a.pheight;a.lrorient=.00001 1.0)\n ) * (\n (tmpvar_8 * -2.0)\n + 1.570796)));\n tmpvar_6 = (tmpvar_8 * sign((uv_1.y / uv_1.x)));\n if ((abs(uv_1.x) > (1e-08 * abs(uv_1.y)))) {\n if ((uv_1.x < 0.0)) {\n if ((uv_1.y >= 0.0)) {\n tmpvar_6 += 3.141593;\n } else {\n tmpvar_6 = (tmpvar_6 - 3.141593);\n };\n };\n } else {\n tmpvar_6 = (sign(uv_1.y) * 1.570796);\n };\n xlat_mutablers0.x = (((tmpvar_6 / 3.1416) * 6.0) * q28);\n xlat_mutablers0.y = inversesqrt(dot (uv_1, uv_1));\n vec2 tmpvar_9;\n tmpvar_9.x = (xlat_mutablers0.x + (q9 * 8.0));\n tmpvar_9.y = (xlat_mutablers0.y + ((q9 * q28) * 4.0));\n xlat_mutablerss = (tmpvar_9 / 12.0);\n vec2 tmpvar_10;\n tmpvar_10.x = q5;\n tmpvar_10.y = q6;\n ofs_2 = (0.1 * tmpvar_10.yx);\n float tmpvar_11;\n float tmpvar_12;\n tmpvar_12 = -(q9);\n tmpvar_11 = fract(tmpvar_12);\n mat2 tmpvar_13;\n tmpvar_13[uint(0)].x = 1.0;\n tmpvar_13[uint(0)].y = -0.0;\n tmpvar_13[1u].x = 0.0;\n tmpvar_13[1u].y = 1.0;\n xlat_mutableuv2 = ((uv_1 * (\n (q13 * tmpvar_11)\n * tmpvar_13)) * aspect.yx);\n vec2 tmpvar_14;\n tmpvar_14 = fract(((xlat_mutableuv2 + 0.5) + ofs_2));\n xlat_mutableneu = (texture (sampler_main, tmpvar_14).xyz + ((texture (sampler_blur1, tmpvar_14).xyz * scale1) + bias1));\n ret1_3 = max (vec3(0.0, 0.0, 0.0), ((xlat_mutableneu * \n (1.0 - (tmpvar_11 * tmpvar_11))\n ) * 2.0));\n float tmpvar_15;\n tmpvar_15 = fract((tmpvar_12 + 0.3333333));\n mat2 tmpvar_16;\n tmpvar_16[uint(0)].x = -0.4990803;\n tmpvar_16[uint(0)].y = -0.8665558;\n tmpvar_16[1u].x = 0.8665558;\n tmpvar_16[1u].y = -0.4990803;\n xlat_mutableuv2 = ((uv_1 * (\n (q13 * tmpvar_15)\n * tmpvar_16)) * aspect.yx);\n vec2 tmpvar_17;\n tmpvar_17 = fract(((xlat_mutableuv2 + 0.5) + ofs_2));\n xlat_mutableneu = (texture (sampler_main, tmpvar_17).xyz + ((texture (sampler_blur1, tmpvar_17).xyz * scale1) + bias1));\n ret1_3 = max (ret1_3, ((xlat_mutableneu * \n (1.0 - (tmpvar_15 * tmpvar_15))\n ) * 2.0));\n float tmpvar_18;\n tmpvar_18 = fract((tmpvar_12 + 0.6666667));\n mat2 tmpvar_19;\n tmpvar_19[uint(0)].x = -0.5018377;\n tmpvar_19[uint(0)].y = 0.8649619;\n tmpvar_19[1u].x = -0.8649619;\n tmpvar_19[1u].y = -0.5018377;\n xlat_mutableuv2 = ((uv_1 * (\n (q13 * tmpvar_18)\n * tmpvar_19)) * aspect.yx);\n vec2 tmpvar_20;\n tmpvar_20 = fract(((xlat_mutableuv2 + 0.5) + ofs_2));\n xlat_mutableneu = (texture (sampler_main, tmpvar_20).xyz + ((texture (sampler_blur1, tmpvar_20).xyz * scale1) + bias1));\n ret1_3 = max (ret1_3, ((xlat_mutableneu * \n (1.0 - (tmpvar_18 * tmpvar_18))\n ) * 2.0));\n float tmpvar_21;\n tmpvar_21 = fract((tmpvar_12 + 1.0));\n mat2 tmpvar_22;\n tmpvar_22[uint(0)].x = 0.9999949;\n tmpvar_22[uint(0)].y = 0.003185092;\n tmpvar_22[1u].x = -0.003185092;\n tmpvar_22[1u].y = 0.9999949;\n xlat_mutableuv2 = ((uv_1 * (\n (q13 * tmpvar_21)\n * tmpvar_22)) * aspect.yx);\n vec2 tmpvar_23;\n tmpvar_23 = fract(((xlat_mutableuv2 + 0.5) + ofs_2));\n xlat_mutableneu = (texture (sampler_main, tmpvar_23).xyz + ((texture (sampler_blur1, tmpvar_23).xyz * scale1) + bias1));\n ret1_3 = max (ret1_3, ((xlat_mutableneu * \n (1.0 - (tmpvar_21 * tmpvar_21))\n ) * 2.0));\n vec2 tmpvar_24;\n tmpvar_24.x = (ret1_3.x + ret1_3.z);\n tmpvar_24.y = (ret1_3.x - ret1_3.y);\n vec4 tmpvar_25;\n tmpvar_25.w = 1.0;\n tmpvar_25.xyz = ((ret1_3 + (\n ((bass_att * 0.004) / sqrt(dot (uv_1, uv_1)))\n * roam_sin).xyz) + ((2.0 * \n (bass_att * ((texture (sampler_blur1, fract(\n (xlat_mutablerss + (tmpvar_24 / 2.0))\n )).xyz * scale1) + bias1).zxy)\n ) * clamp (\n (1.0 - (ret1_3 * 4.0))\n , 0.0, 1.0)));\n ret = tmpvar_25.xyz;\n }"}},function(a,e){a.exports={baseVals:{rating:0,gammaadj:1.14,decay:1,echo_zoom:1,wave_mode:6,wave_thick:1,wave_brighten:0,wrap:0,darken:1,wave_a:1.17,wave_scale:.797,wave_smoothing:0,modwavealphastart:.71,modwavealphaend:1.3,warpscale:1.331,zoomexp:.9995,zoom:.9998,rot:.02,dy:-.008,warp:.01,sx:1.0098,wave_r:.5,wave_g:.5,wave_b:.5,wave_x:.9,ob_size:.005,ob_a:.8,ib_size:0,ib_r:0,ib_g:0,ib_b:0,ib_a:1,mv_x:44.8,mv_y:38.4,mv_l:5,mv_g:.91,mv_b:.71,mv_a:0},shapes:[{baseVals:{enabled:0}},{baseVals:{enabled:0}},{baseVals:{enabled:0}},{baseVals:{enabled:0}}],waves:[{baseVals:{enabled:0}},{baseVals:{enabled:0}},{baseVals:{enabled:0}},{baseVals:{enabled:0}}],init_eqs_str:"a.q12=0;a.q18=0;a.q6=0;a.bass_thresh=0;a.wg=0;a.q11=0;a.q10=0;a.wb=0;a.q17=0;a.vol=0;a.q2=0;a.q3=0;a.wr=0;a.q7=0;a.q8=0;",frame_eqs_str:"a.wave_r+=.3*Math.sin(50*a.vol);a.wave_b+=.3*Math.sin(20*a.vol);a.wave_g+=.5*Math.sin(35*a.vol);a.q8=a.wave_r;a.q7=a.wave_b;a.q6=a.wave_g;a.wr=.5+.4*(.6*Math.sin(1.1*a.time)+.4*Math.sin(.8*a.time));a.wb=.5+.4*(.6*Math.sin(1.6*a.time)+.4*Math.sin(.5*a.time));a.wg=.5+.4*(.6*Math.sin(1.34*a.time)+.4*Math.sin(.4*a.time));a.monitor=a.wg;a.q10=a.wr;a.q11=a.wb;a.q12=a.wg;a.q18=.007*Math.sin(.1*a.time);a.q17=-.007*Math.sin(.254*a.time);a.q2=a.bass_thresh;a.vol=.25*(a.bass+a.mid+a.treb);\na.vol*=a.vol;a.q3=a.vol;a.warp=0;",pixel_eqs_str:"",pixel_eqs:"",warp:" shader_body { \n vec3 noise3_1;\n vec3 tmpvar_2;\n tmpvar_2 = (texture (sampler_main, uv).xyz + ((texture (sampler_blur1, uv).xyz * scale1) + bias1));\n vec2 tmpvar_3;\n tmpvar_3 = (0.5 + ((\n (uv - vec2(0.0, 1.0))\n - 0.5) * (1.0 + \n (tmpvar_2.y * 0.03)\n )));\n vec2 tmpvar_4;\n tmpvar_4.x = (tmpvar_3.x + pow (tmpvar_2.x, 0.0));\n tmpvar_4.y = (tmpvar_3.y + pow (tmpvar_2.x, 0.005));\n noise3_1 = (texture (sampler_noise_lq, ((\n (uv_orig * texsize.xy)\n * texsize_noise_lq.zw) + rand_frame.xy)).xyz * fract(q15));\n vec3 tmpvar_5;\n tmpvar_5 = (noise3_1 * (vec3(1.0, 1.0, 1.0) - vec3(fract(\n (q3 * 0.5)\n ))));\n noise3_1 = tmpvar_5;\n vec4 tmpvar_6;\n tmpvar_6 = texture (sampler_main, fract(tmpvar_4));\n vec3 tmpvar_7;\n tmpvar_7.x = q10;\n tmpvar_7.y = q11;\n tmpvar_7.z = q12;\n vec3 tmpvar_8;\n tmpvar_8 = mix (tmpvar_5, tmpvar_7, tmpvar_6.xxx);\n vec4 tmpvar_9;\n tmpvar_9.w = 1.0;\n tmpvar_9.xyz = (tmpvar_6.xyz + clamp ((\n (tmpvar_6.yzx * tmpvar_8.zxy)\n - \n (tmpvar_6.zxy * tmpvar_8.yzx)\n ), 0.0, 1.0));\n ret = tmpvar_9.xyz;\n }",comp:" shader_body { \n vec2 uv1_1;\n vec2 tmpvar_2;\n tmpvar_2.y = 0.0;\n tmpvar_2.x = texsize.z;\n vec2 tmpvar_3;\n tmpvar_3.x = 0.0;\n tmpvar_3.y = texsize.w;\n vec2 tmpvar_4;\n tmpvar_4.x = (texture (sampler_main, (uv - tmpvar_2)).xyz - texture (sampler_main, (uv + tmpvar_2)).xyz).x;\n tmpvar_4.y = (texture (sampler_main, (uv - tmpvar_3)).xyz - texture (sampler_main, (uv + tmpvar_3)).xyz).x;\n uv1_1 = ((0.3 * cos(\n ((uv - 0.5) * 2.0)\n )) - tmpvar_4);\n float tmpvar_5;\n tmpvar_5 = clamp ((0.04 / sqrt(\n dot (uv1_1, uv1_1)\n )), 0.0, 1.0);\n uv1_1 = ((0.3 * cos(\n (uv1_1 * 12.0)\n )) - (9.0 * tmpvar_4));\n vec4 tmpvar_6;\n tmpvar_6.w = 1.0;\n tmpvar_6.xyz = (tmpvar_5 + ((texture (sampler_main, uv).xyz * 12.0) * vec3(clamp (\n (0.04 / sqrt(dot (uv1_1, uv1_1)))\n , 0.0, 1.0))));\n ret = tmpvar_6.xyz;\n }"}},function(a,e){a.exports={baseVals:{rating:0,gammaadj:1.14,decay:1,echo_zoom:1,wave_mode:1,wave_thick:1,modwavealphabyvolume:1,wave_brighten:0,wrap:0,darken:1,wave_a:.001,wave_scale:.179,wave_smoothing:0,wave_mystery:.3,modwavealphastart:.71,modwavealphaend:1.3,warpscale:1.331,zoomexp:.8195,zoom:1.0697,dy:.006,warp:.01,sx:.9996,wave_g:0,wave_b:0,ob_a:.8,ib_size:0,ib_r:0,ib_g:0,ib_b:0,ib_a:1,mv_x:0,mv_y:0,mv_l:1,mv_g:.91,mv_b:.71,mv_a:0},shapes:[{baseVals:{enabled:0}},{baseVals:{enabled:0}},{baseVals:{enabled:0}},{baseVals:{enabled:0}}],waves:[{baseVals:{enabled:0}},{baseVals:{enabled:0}},{baseVals:{enabled:0}},{baseVals:{enabled:0}}],init_eqs_str:"a.q12=0;a.q18=0;a.q6=0;a.q5=0;a.bass_thresh=0;a.wg=0;a.q11=0;a.q10=0;a.wb=0;a.q17=0;a.vol=0;a.q2=0;a.q3=0;a.wr=0;a.q7=0;a.mtime=0;a.q8=0;",frame_eqs_str:"a.bass_thresh=2*above(a.bass_att,a.bass_thresh)+(1-above(a.bass_att,a.bass_thresh))*(.91*(a.bass_thresh-1.3)+1.3);a.wave_r=.5+.5*(.6*Math.sin(1.3*a.time)+.4*Math.sin(.98*a.time));a.wave_b=.5+.5*(.6*Math.sin(1.1*a.time)+.4*Math.sin(.78*a.time));a.wave_g=.5+.5*(.6*Math.sin(1.2*a.time)+.4*Math.sin(.6*a.time));a.q8=a.wave_r;a.q7=a.wave_b;a.q6=a.wave_g;a.wr=.5+.4*(.6*Math.sin(.2*a.time)+.4*Math.sin(.8*a.time));a.wb=.5+.4*(.6*Math.sin(.377*a.time)+.4*Math.sin(.5*a.time));a.wg=.5+\n.4*(.6*Math.sin(.7*a.time)+.4*Math.sin(.4*a.time));a.q10=a.wr;a.q11=a.wb;a.q12=a.wg;a.q10=.8;a.q11=.2;a.q12=.1;a.q18=.01*Math.sin(.1*a.mtime);a.q17=-.01*Math.sin(.254*a.mtime);a.q2=a.bass_thresh;a.vol=.25*(a.bass+a.mid+a.treb);a.vol*=a.vol;a.q3=a.vol;a.q5=.5*a.vol;a.mtime+=.01*a.vol;a.q2=.25*a.mtime;",pixel_eqs_str:"",pixel_eqs:"",warp:" shader_body { \n vec3 noise2_1;\n vec3 ret_2;\n vec3 tmpvar_3;\n tmpvar_3 = (((texture (sampler_blur1, uv).xyz * scale1) + bias1) + texture (sampler_main, uv).xyz);\n vec2 tmpvar_4;\n tmpvar_4 = (0.5 + ((uv - 0.5) * (1.0 + \n (tmpvar_3.y * 0.05)\n )));\n vec2 tmpvar_5;\n tmpvar_5.x = (tmpvar_4.x + pow (tmpvar_3.x, q17));\n tmpvar_5.y = (tmpvar_4.y + pow (tmpvar_3.x, q18));\n vec4 tmpvar_6;\n tmpvar_6 = texture (sampler_fc_main, fract(tmpvar_5));\n vec3 tmpvar_7;\n tmpvar_7.x = q10;\n tmpvar_7.y = q11;\n tmpvar_7.z = q12;\n noise2_1 = (texture (sampler_noise_lq, ((\n (uv_orig * texsize.xy)\n * texsize_noise_lq.zw) + rand_frame.xy)).xyz + ((tmpvar_7 * vec3(rad)) * vol));\n vec3 a_8;\n a_8 = (1.0 - tmpvar_6.xyz);\n ret_2 = (tmpvar_6.xyz + (0.3 * clamp (\n ((a_8.yzx * noise2_1.zxy) - (a_8.zxy * noise2_1.yzx))\n , 0.0, 1.0)));\n ret_2 = (ret_2 * 0.97);\n vec4 tmpvar_9;\n tmpvar_9.w = 1.0;\n tmpvar_9.xyz = ret_2;\n ret = tmpvar_9.xyz;\n }",comp:" shader_body { \n vec3 ret_1;\n ret_1 = (texture (sampler_main, uv).xyz * vec3(0.9, 0.3, 0.5));\n ret_1 = (ret_1 * 1.34);\n ret_1 = (ret_1 * ret_1);\n vec4 tmpvar_2;\n tmpvar_2.w = 1.0;\n tmpvar_2.xyz = ret_1;\n ret = tmpvar_2.xyz;\n }"}},function(a,e){a.exports={baseVals:{rating:5,gammaadj:1.28,decay:.8,echo_zoom:1,echo_orient:3,wave_mode:7,additivewave:1,modwavealphabyvolume:1,wave_brighten:0,brighten:1,wave_a:.001,wave_scale:1.286,wave_smoothing:.63,modwavealphastart:.71,modwavealphaend:1.3,zoomexp:3.04777,zoom:1.0173,warp:.01605,wave_g:.65,wave_b:.65,ob_size:0,ob_a:1,mv_x:64,mv_y:48,mv_l:0,mv_a:0,b1ed:0},shapes:[{baseVals:{enabled:0}},{baseVals:{enabled:0}},{baseVals:{enabled:0}},{baseVals:{enabled:0}}],waves:[{baseVals:{enabled:1,thick:1,additive:1,g:0,b:0},init_eqs_str:"a.ma=0;a.mx=0;a.my=0;",frame_eqs_str:"",point_eqs_str:"a.ma+=3.1415*above(a.bass,1)*.01*a.bass;a.ma-=3.1415*above(a.treb,1)*.01*a.treb;a.mx+=.0002*Math.cos(a.ma);a.my+=.0002*Math.sin(a.ma);a.mx=.00001b;b++)a.gmegabuf[Math.floor(a.n)]=0,a.n+=1;for(b=a.n=0;1E4>b;b++)a.megabuf[Math.floor(a.n)]=0,a.n+=1;a.trelx=0;a.trely=0;a.trelz=0;a.reg20=1;a.reg21=0;a.reg22=0;a.reg23=0;a.reg24=1;a.reg25=0;a.reg26=0;a.reg27=0;a.reg28=1;b=0;do{b+=1;var c;a.ran1=div(randint(800),100);a.ran2=\ndiv(randint(800),100);a.ran3=div(randint(800),100);a.posx=randint(5)-2;a.posy=randint(5)-2;a.posz=randint(5)-2;a.c1=Math.cos(a.ran1);a.c2=Math.cos(a.ran2);a.c3=Math.cos(a.ran3);a.s1=Math.sin(a.ran1);a.s2=Math.sin(a.ran2);a.s3=Math.sin(a.ran3);a.reg20=a.c2*a.c1;a.reg21=a.c2*a.s1;a.reg22=-a.s2;a.reg23=a.s3*a.s2*a.c1-a.c3*a.s1;a.reg24=a.s3*a.s2*a.s1+a.c3*a.c1;a.reg25=a.s3*a.c2;a.reg26=a.c3*a.s2*a.c1+a.s3*a.s1;a.reg27=a.c3*a.s2*a.s1-a.s3*a.c1;a.reg28=a.c3*a.c2;a.dist=.001;var d=0;do{d+=1;a.uvx=div(a.reg26*\na.dist,a.q7);a.uvy=div(a.reg27*a.dist,a.q7);a.uvz=div(a.reg28*a.dist,a.q7);a.uvx+=a.posx;a.uvy+=a.posy;a.uvz+=a.posz;a.uvx=8*(div(a.uvx,8)+30.5-Math.floor(div(a.uvx,8)+30.5)-.5);a.uvy=8*(div(a.uvy,8)+30.5-Math.floor(div(a.uvy,8)+30.5)-.5);a.uvz=8*(div(a.uvz,8)+30.5-Math.floor(div(a.uvz,8)+30.5)-.5);a.uvx0=a.uvx+a.q8;a.uvy0=a.uvy+a.q8;a.uvz0=a.uvz+a.q8;for(c=0;8>c;c++)a.uvx=.00001a.uvx?1:0)?-2-a.uvx:a.uvx,a.uvy=.00001a.uvy?1:0)?-2-a.uvy:a.uvy,a.uvz=.00001a.uvz?1:0)?-2-a.uvz:a.uvz,a.slen=a.uvx*a.uvx+a.uvy*a.uvy+a.uvz*a.uvz,a.uvx=2.6*(.00001a.slen?1:0)?4*a.uvx:.00001a.slen?1:0)?div(a.uvx,a.slen):a.uvx)+a.uvx0,a.uvy=2.6*(.00001a.slen?1:0)?4*a.uvy:.00001a.slen?1:0)?div(a.uvy,a.slen):a.uvy)+a.uvy0,a.uvz=2.6*(.00001a.slen?1:0)?4*a.uvz:.00001a.slen?1:0)?div(a.uvz,a.slen):\na.uvz)+a.uvz0;a.len=sqrt(a.uvx*a.uvx+a.uvy*a.uvy+a.uvz*a.uvz);a.dist*=1.05;c=(.6>a.dist?1:0)*(30d);d=.06>a.dist?1:0}while(.00001b);",frame_eqs_str:"a.fps_=0*a.fps_+1*(.00001=a.fps?1:0)?a.fps:25+.5*(a.fps-25));a.dec_s=1-div(.06*30,a.fps_);a.beat=a.time>a.t0+3?1:0;a.t0=.00001Math.abs(a.rotz-0)?1:0)?a.beat*(randint(100)<20*a.travel?1:0)*(div(randint(10),10)-.3):bnot(a.beat*(30>randint(100)?1:0))*a.rotz;a.slow=.00001randint(1E3*a.avg)?1:0):bnot(a.beat*(50>randint(100)?1:0));a.look=.00001randint(1E3*a.speed)?1:0):bnot(a.beat*(50>randint(100)?1:0));a.lx=.00001a.dist_?1:0)*2;a.travel=.00001b;b++){a.n+=1;a.ran1=div(randint(100),100);a.ran2=div(randint(100),200)-.25;a.tx=Math.cos(1.57*a.n+a.ran2)*(4>=a.n?1:0)*a.ran1;a.ty=Math.sin(1.57*a.n+a.ran2)*(4>=a.n?1:0)*a.ran1;a.c1=Math.cos(a.v1);a.c2=Math.cos(a.v2+a.ty);a.c3=Math.cos(a.v3+a.tx);a.s1=Math.sin(a.v1);a.s2=Math.sin(a.v2+a.ty);a.s3=Math.sin(a.v3+a.tx);a.reg10=a.c2*a.c1;a.reg11=a.c2*a.s1;a.reg12=-a.s2;a.reg13=a.s3*a.s2*a.c1-a.c3*a.s1;a.reg14=a.s3*a.s2*a.s1+a.c3*a.c1;a.reg15=a.s3*\na.c2;a.reg16=a.c3*a.s2*a.c1+a.s3*a.s1;a.reg17=a.c3*a.s2*a.s1-a.s3*a.c1;a.reg18=a.c3*a.c2;a.reg20=a.reg30;a.reg21=a.reg31;a.reg22=a.reg32;a.reg23=a.reg33;a.reg24=a.reg34;a.reg25=a.reg35;a.reg26=a.reg36;a.reg27=a.reg37;a.reg28=a.reg38;a.q20=a.reg10*a.reg20+a.reg11*a.reg23+a.reg12*a.reg26;a.q21=a.reg10*a.reg21+a.reg11*a.reg24+a.reg12*a.reg27;a.q22=a.reg10*a.reg22+a.reg11*a.reg25+a.reg12*a.reg28;a.q23=a.reg13*a.reg20+a.reg14*a.reg23+a.reg15*a.reg26;a.q24=a.reg13*a.reg21+a.reg14*a.reg24+a.reg15*a.reg27;\na.q25=a.reg13*a.reg22+a.reg14*a.reg25+a.reg15*a.reg28;a.q26=a.reg16*a.reg20+a.reg17*a.reg23+a.reg18*a.reg26;a.q27=a.reg16*a.reg21+a.reg17*a.reg24+a.reg18*a.reg27;a.q28=a.reg16*a.reg22+a.reg17*a.reg25+a.reg18*a.reg28;a.reg20=a.q20;a.reg21=a.q21;a.reg22=a.q22;a.reg23=a.q23;a.reg24=a.q24;a.reg25=a.q25;a.reg26=a.q26;a.reg27=a.q27;a.reg28=a.q28;a.dist=.002;var c,d=0;do{d+=1;a.uvx=div(a.reg26*a.dist,a.q7);a.uvy=div(a.reg27*a.dist,a.q7);a.uvz=div(a.reg28*a.dist,a.q7);a.uvx+=a.posx;a.uvy+=a.posy;a.uvz+=a.posz;\na.uvx=8*(div(a.uvx,8)+30.5-Math.floor(div(a.uvx,8)+30.5)-.5);a.uvy=8*(div(a.uvy,8)+30.5-Math.floor(div(a.uvy,8)+30.5)-.5);a.uvz=8*(div(a.uvz,8)+30.5-Math.floor(div(a.uvz,8)+30.5)-.5);a.uvx0=a.uvx+a.q8;a.uvy0=a.uvy+a.q8;a.uvz0=a.uvz+a.q8;for(c=0;8>c;c++)a.uvx=.00001a.uvx?1:0)?-2-a.uvx:a.uvx,a.uvy=.00001a.uvy?1:0)?-2-a.uvy:a.uvy,a.uvz=.00001a.uvz?1:0)?\n-2-a.uvz:a.uvz,a.slen=a.uvx*a.uvx+a.uvy*a.uvy+a.uvz*a.uvz,a.uvx=2.6*(.00001a.slen?1:0)?4*a.uvx:.00001a.slen?1:0)?div(a.uvx,a.slen):a.uvx)+a.uvx0,a.uvy=2.6*(.00001a.slen?1:0)?4*a.uvy:.00001a.slen?1:0)?div(a.uvy,a.slen):a.uvy)+a.uvy0,a.uvz=2.6*(.00001a.slen?1:0)?4*a.uvz:.00001a.slen?1:0)?div(a.uvz,a.slen):a.uvz)+a.uvz0;a.len=sqrt(a.uvx*a.uvx+a.uvy*a.uvy+a.uvz*a.uvz);a.dist*=1.1;c=(.6>a.dist?1:0)*(30d);a.megabuf[Math.floor(a.n)]=a.megabuf[Math.floor(a.n)]*a.dec_s+(1-a.dec_s)*a.dist;a.avg+=Math.abs(div(a.megabuf[Math.floor(a.n)],5))}a.n=0;for(b=a.avg=0;5>b;b++)a.n+=1,a.avg+=Math.abs(div(a.megabuf[Math.floor(a.n)],5));a.xslope=Math.min(Math.max(div(2,a.avg)*(a.megabuf[1]-a.megabuf[3]),-3),3);a.yslope=Math.min(Math.max(div(2,a.avg)*(a.megabuf[4]-a.megabuf[2]),-3),3);a.monitor=a.avg;a.dist_=a.dist_*a.dec_s+(1-a.dec_s)*a.dist;a.q10=a.ds*a.q7;a.q14=Math.abs(a.ds)+2*(Math.abs(a.v1)+\nMath.abs(a.v2)+Math.abs(a.v3))+div(1,255)+.05*a.start;a.q19=.6+.4*Math.sin(.02*a.time+6*a.cran0);a.start*=.9;a.q11=a.v1;a.q12=a.v2;a.q13=a.v3;a.monitor=a.q16;a.bb=.99*a.bb+.02*a.bass;a.mm=.99*a.mm+.02*a.mid;a.tt=.99*a.tt+.02*a.treb;a.mx=Math.max(Math.max(a.bb,a.mm),a.tt);a.mn=Math.min(Math.min(a.bb,a.mm),a.tt);a.h1=div(a.bb-a.mn,a.mx-a.mn);a.h2=div(a.mm-a.mn,a.mx-a.mn);a.h3=div(a.tt-a.mn,a.mx-a.mn);a.v=div(.1333,a.fps);a.bm+=(a.h1-a.h2)*a.v;a.mt+=(a.h2-a.h3)*a.v;a.bt+=(a.h1-a.h3)*a.v;a.w=2*a.bm;a.q3=\nMath.sin(a.w);a.q9=Math.cos(a.w);a.q17=a.bm;a.q18=a.mt;a.q19=a.bt;",pixel_eqs_str:"a.warp=0;a.zoom=1;a.dx=div(-a.q12,a.q16)*(1+0*pow(a.x-.5,2));a.dy=div(a.q13,a.q16)*(1+0*pow(a.y-.5,2));a.rot=a.q11;",warp:"float sustain;\nfloat xlat_mutabledist;\nfloat xlat_mutablestruc;\nvec2 xlat_mutableuv1;\nvec3 xlat_mutableuv2;\n shader_body { \n mat3 tmpvar_1;\n tmpvar_1[uint(0)].x = q20;\n tmpvar_1[uint(0)].y = q23;\n tmpvar_1[uint(0)].z = q26;\n tmpvar_1[1u].x = q21;\n tmpvar_1[1u].y = q24;\n tmpvar_1[1u].z = q27;\n tmpvar_1[2u].x = q22;\n tmpvar_1[2u].y = q25;\n tmpvar_1[2u].z = q28;\n vec3 tmpvar_2;\n tmpvar_2.x = q4;\n tmpvar_2.y = q5;\n tmpvar_2.z = q6;\n sustain = (1.0123 - q14);\n vec2 uv_3;\n vec3 ret_4;\n vec2 tmpvar_5;\n tmpvar_5 = (uv - 0.5);\n xlat_mutableuv1 = ((tmpvar_5 * aspect.xy) * q16);\n vec4 tmpvar_6;\n tmpvar_6 = texture (sampler_pc_main, uv);\n uv_3 = ((tmpvar_5 * (1.0 - \n (q10 / (1.0 - ((tmpvar_6.z + \n (0.003921569 * tmpvar_6.y)\n ) + (q10 * 0.7))))\n )) + 0.5);\n vec4 tmpvar_7;\n tmpvar_7 = fract((8.0 * texture (sampler_noise_lq, (uv_3 + rand_frame.yz))));\n xlat_mutabledist = tmpvar_7.x;\n if ((tmpvar_7.y > 0.2)) {\n vec3 tmpvar_8;\n tmpvar_8 = (tmpvar_7.xyz - vec3(0.4, 0.5, 0.5));\n vec2 uvi_9;\n uvi_9 = ((tmpvar_8.zy * 0.003) + uv_3);\n vec2 pix_10;\n vec4 nb2_11;\n vec4 nb_12;\n vec2 x_13;\n x_13 = (uvi_9 - 0.5);\n pix_10 = (texsize.zw * (1.0 + (\n sqrt(dot (x_13, x_13))\n * 8.0)));\n float tmpvar_14;\n tmpvar_14 = (q10 * 0.7);\n vec4 tmpvar_15;\n tmpvar_15 = texture (sampler_pc_main, (uvi_9 - pix_10));\n nb_12.x = (1.0 - ((tmpvar_15.z + \n (0.003921569 * tmpvar_15.y)\n ) + tmpvar_14));\n vec4 tmpvar_16;\n tmpvar_16 = texture (sampler_pc_main, (uvi_9 + (pix_10 * vec2(1.0, -1.0))));\n nb_12.y = (1.0 - ((tmpvar_16.z + \n (0.003921569 * tmpvar_16.y)\n ) + tmpvar_14));\n vec4 tmpvar_17;\n tmpvar_17 = texture (sampler_pc_main, (uvi_9 + pix_10));\n nb_12.z = (1.0 - ((tmpvar_17.z + \n (0.003921569 * tmpvar_17.y)\n ) + tmpvar_14));\n vec4 tmpvar_18;\n tmpvar_18 = texture (sampler_pc_main, (uvi_9 + (pix_10 * vec2(-1.0, 1.0))));\n nb_12.w = (1.0 - ((tmpvar_18.z + \n (0.003921569 * tmpvar_18.y)\n ) + tmpvar_14));\n vec4 tmpvar_19;\n tmpvar_19 = texture (sampler_pc_main, (uvi_9 + (pix_10 * vec2(0.0, -1.0))));\n nb2_11.x = (1.0 - ((tmpvar_19.z + \n (0.003921569 * tmpvar_19.y)\n ) + tmpvar_14));\n vec4 tmpvar_20;\n tmpvar_20 = texture (sampler_pc_main, (uvi_9 + (pix_10 * vec2(1.0, 0.0))));\n nb2_11.y = (1.0 - ((tmpvar_20.z + \n (0.003921569 * tmpvar_20.y)\n ) + tmpvar_14));\n vec4 tmpvar_21;\n tmpvar_21 = texture (sampler_pc_main, (uvi_9 + (pix_10 * vec2(0.0, 1.0))));\n nb2_11.z = (1.0 - ((tmpvar_21.z + \n (0.003921569 * tmpvar_21.y)\n ) + tmpvar_14));\n vec4 tmpvar_22;\n tmpvar_22 = texture (sampler_pc_main, (uvi_9 + (pix_10 * vec2(-1.0, 0.0))));\n nb2_11.w = (1.0 - ((tmpvar_22.z + \n (0.003921569 * tmpvar_22.y)\n ) + tmpvar_14));\n vec4 tmpvar_23;\n tmpvar_23 = min (nb_12, nb2_11);\n nb_12.zw = tmpvar_23.zw;\n nb_12.xy = min (tmpvar_23.xy, tmpvar_23.zw);\n xlat_mutabledist = (min (nb_12.x, nb_12.y) + ((0.008 * tmpvar_8.x) * abs(tmpvar_8.y)));\n };\n vec4 tmpvar_24;\n tmpvar_24 = texture (sampler_pc_main, uv_3);\n float tmpvar_25;\n tmpvar_25 = min (xlat_mutabledist, (1.0 - (\n (tmpvar_24.z + (0.003921569 * tmpvar_24.y))\n + \n (q10 * 0.7)\n )));\n xlat_mutabledist = tmpvar_25;\n float tmpvar_26;\n tmpvar_26 = (tmpvar_25 + pow (tmpvar_25, 3.0));\n vec3 tmpvar_27;\n tmpvar_27.xy = (xlat_mutableuv1 * tmpvar_26);\n tmpvar_27.z = tmpvar_26;\n xlat_mutableuv2 = (((tmpvar_27 / q7) * tmpvar_1) + tmpvar_2);\n xlat_mutableuv2 = ((fract(\n ((xlat_mutableuv2 / 8.0) + 0.5)\n ) - 0.5) * 8.0);\n float li_28;\n vec3 zz0_29;\n vec3 zz_30;\n zz0_29 = (xlat_mutableuv2 + q8);\n li_28 = 0.0;\n zz_30 = ((2.0 * clamp (xlat_mutableuv2, vec3(-1.0, -1.0, -1.0), vec3(1.0, 1.0, 1.0))) - xlat_mutableuv2);\n float tmpvar_31;\n tmpvar_31 = dot (zz_30, zz_30);\n if ((tmpvar_31 <= 0.25)) {\n zz_30 = (zz_30 * 4.0);\n li_28 = 24.0;\n } else {\n if ((tmpvar_31 <= 1.0)) {\n zz_30 = (zz_30 / tmpvar_31);\n };\n };\n zz_30 = ((2.6 * zz_30) + zz0_29);\n zz_30 = ((2.0 * clamp (zz_30, vec3(-1.0, -1.0, -1.0), vec3(1.0, 1.0, 1.0))) - zz_30);\n float tmpvar_32;\n tmpvar_32 = dot (zz_30, zz_30);\n if ((tmpvar_32 <= 0.25)) {\n zz_30 = (zz_30 * 4.0);\n li_28 = 24.0;\n } else {\n if ((tmpvar_32 <= 1.0)) {\n zz_30 = (zz_30 / tmpvar_32);\n };\n };\n zz_30 = ((2.6 * zz_30) + zz0_29);\n zz_30 = ((2.0 * clamp (zz_30, vec3(-1.0, -1.0, -1.0), vec3(1.0, 1.0, 1.0))) - zz_30);\n float tmpvar_33;\n tmpvar_33 = dot (zz_30, zz_30);\n if ((tmpvar_33 <= 0.25)) {\n zz_30 = (zz_30 * 4.0);\n li_28 = 24.0;\n } else {\n if ((tmpvar_33 <= 1.0)) {\n zz_30 = (zz_30 / tmpvar_33);\n };\n };\n zz_30 = ((2.6 * zz_30) + zz0_29);\n zz_30 = ((2.0 * clamp (zz_30, vec3(-1.0, -1.0, -1.0), vec3(1.0, 1.0, 1.0))) - zz_30);\n float tmpvar_34;\n tmpvar_34 = dot (zz_30, zz_30);\n if ((tmpvar_34 <= 0.25)) {\n zz_30 = (zz_30 * 4.0);\n li_28 = 24.0;\n } else {\n if ((tmpvar_34 <= 1.0)) {\n zz_30 = (zz_30 / tmpvar_34);\n };\n };\n zz_30 = ((2.6 * zz_30) + zz0_29);\n zz_30 = ((2.0 * clamp (zz_30, vec3(-1.0, -1.0, -1.0), vec3(1.0, 1.0, 1.0))) - zz_30);\n float tmpvar_35;\n tmpvar_35 = dot (zz_30, zz_30);\n if ((tmpvar_35 <= 0.25)) {\n zz_30 = (zz_30 * 4.0);\n li_28 = 24.0;\n } else {\n if ((tmpvar_35 <= 1.0)) {\n zz_30 = (zz_30 / tmpvar_35);\n };\n };\n zz_30 = ((2.6 * zz_30) + zz0_29);\n zz_30 = ((2.0 * clamp (zz_30, vec3(-1.0, -1.0, -1.0), vec3(1.0, 1.0, 1.0))) - zz_30);\n float tmpvar_36;\n tmpvar_36 = dot (zz_30, zz_30);\n if ((tmpvar_36 <= 0.25)) {\n zz_30 = (zz_30 * 4.0);\n li_28 = 24.0;\n } else {\n if ((tmpvar_36 <= 1.0)) {\n zz_30 = (zz_30 / tmpvar_36);\n };\n };\n zz_30 = ((2.6 * zz_30) + zz0_29);\n zz_30 = ((2.0 * clamp (zz_30, vec3(-1.0, -1.0, -1.0), vec3(1.0, 1.0, 1.0))) - zz_30);\n float tmpvar_37;\n tmpvar_37 = dot (zz_30, zz_30);\n if ((tmpvar_37 <= 0.25)) {\n zz_30 = (zz_30 * 4.0);\n li_28 = 24.0;\n } else {\n if ((tmpvar_37 <= 1.0)) {\n zz_30 = (zz_30 / tmpvar_37);\n };\n };\n zz_30 = ((2.6 * zz_30) + zz0_29);\n zz_30 = ((2.0 * clamp (zz_30, vec3(-1.0, -1.0, -1.0), vec3(1.0, 1.0, 1.0))) - zz_30);\n float tmpvar_38;\n tmpvar_38 = dot (zz_30, zz_30);\n if ((tmpvar_38 <= 0.25)) {\n zz_30 = (zz_30 * 4.0);\n li_28 = 24.0;\n } else {\n if ((tmpvar_38 <= 1.0)) {\n zz_30 = (zz_30 / tmpvar_38);\n };\n };\n zz_30 = ((2.6 * zz_30) + zz0_29);\n vec4 tmpvar_39;\n tmpvar_39.xyz = zz_30;\n tmpvar_39.w = li_28;\n float tmpvar_40;\n tmpvar_40 = sqrt(dot (zz_30, zz_30));\n xlat_mutablestruc = (sqrt(dot (tmpvar_39.xyw, tmpvar_39.xyw)) / 24.0);\n vec4 tmpvar_41;\n tmpvar_41 = texture (sampler_pc_main, uv_3);\n float tmpvar_42;\n float tmpvar_43;\n tmpvar_43 = (q10 * 0.7);\n tmpvar_42 = ((log(\n (1.0 + (tmpvar_40 / 24.0))\n ) * 0.02) * (1.0 - (1.0 - \n ((tmpvar_41.z + (0.003921569 * tmpvar_41.y)) + tmpvar_43)\n )));\n float tmpvar_44;\n vec4 tmpvar_45;\n tmpvar_45 = texture (sampler_pc_main, uv_3);\n tmpvar_44 = (1.0 - ((tmpvar_45.z + \n (0.003921569 * tmpvar_45.y)\n ) + tmpvar_43));\n if ((((tmpvar_25 <= tmpvar_44) && (tmpvar_40 < 24.0)) && (tmpvar_25 > 0.005))) {\n ret_4.x = (((1.0 - sustain) * xlat_mutablestruc) + (sustain * mix (texture (sampler_main, uv_3).xyz, \n ((texture (sampler_blur1, uv_3).xyz * scale1) + bias1)\n , vec3(\n (q14 * 4.0)\n )).x));\n float x_46;\n x_46 = ((1.0 - tmpvar_25) * 255.0);\n float ip_47;\n ip_47 = float(int(x_46));\n vec2 tmpvar_48;\n tmpvar_48.x = (x_46 - ip_47);\n tmpvar_48.y = (ip_47 / 255.0);\n ret_4.yz = tmpvar_48;\n } else {\n vec3 tmpvar_49;\n tmpvar_49.y = 0.0;\n tmpvar_49.x = sustain;\n tmpvar_49.z = (1.0 - tmpvar_42);\n vec3 tmpvar_50;\n tmpvar_50.xy = vec2(0.003921569, 0.0);\n tmpvar_50.z = (q14 / 6.0);\n ret_4 = ((texture (sampler_fc_main, uv_3).xyz * tmpvar_49) - tmpvar_50);\n };\n vec4 tmpvar_51;\n tmpvar_51.w = 1.0;\n tmpvar_51.xyz = ret_4;\n ret = tmpvar_51.xyz;\n }",comp:"vec2 xlat_mutabled;\nvec3 xlat_mutabledx;\nvec3 xlat_mutabledy;\n shader_body { \n vec3 ret_1;\n xlat_mutabled = (texsize.zw * 1.5);\n xlat_mutabledx = (texture (sampler_main, (uv_orig + (vec2(1.0, 0.0) * xlat_mutabled))).xyz - texture (sampler_main, (uv_orig - (vec2(1.0, 0.0) * xlat_mutabled))).xyz);\n xlat_mutabledy = (texture (sampler_main, (uv_orig + (vec2(0.0, 1.0) * xlat_mutabled))).xyz - texture (sampler_main, (uv_orig - (vec2(0.0, 1.0) * xlat_mutabled))).xyz);\n vec2 tmpvar_2;\n tmpvar_2.x = xlat_mutabledx.y;\n tmpvar_2.y = xlat_mutabledy.y;\n vec2 x_3;\n x_3 = (tmpvar_2 * 8.0);\n ret_1 = (((texture (sampler_main, uv).x * \n (1.0 - sqrt(dot (x_3, x_3)))\n ) * pow (hue_shader, vec3(6.0, 6.0, 6.0))) * 1.4);\n vec2 tmpvar_4;\n tmpvar_4.x = xlat_mutabledx.z;\n tmpvar_4.y = xlat_mutabledy.z;\n vec2 x_5;\n x_5 = (tmpvar_4 * 4.0);\n vec3 tmpvar_6;\n tmpvar_6 = mix (ret_1, vec3(1.0, 1.0, 1.0), vec3(sqrt(dot (x_5, x_5))));\n ret_1 = tmpvar_6;\n vec4 tmpvar_7;\n tmpvar_7.w = 1.0;\n tmpvar_7.xyz = tmpvar_6;\n ret = tmpvar_7.xyz;\n }"}},function(a,e){a.exports={baseVals:{rating:4,gammaadj:1.9,echo_zoom:1.169,echo_orient:1,wave_mode:5,additivewave:1,wave_a:0,wave_scale:.9,wave_smoothing:.63,wave_mystery:1,modwavealphastart:2,modwavealphaend:2,warpscale:2.853,rot:.006,warp:0,wave_r:.65,wave_g:.65,wave_b:.65,ob_size:.005,mv_x:0,mv_y:48,mv_dx:-.941,mv_dy:.426,mv_l:5,mv_r:.316,mv_g:.078,mv_b:.942,mv_a:0,b1ed:0},shapes:[{baseVals:{enabled:1,sides:100,textured:1,rad:.78903,ang:.62832,tex_zoom:1.02009,r:0,g:1,b:1,r2:.7,b2:1,border_a:0},init_eqs_str:"a.vx=0;a.vy=0;",frame_eqs_str:""},{baseVals:{enabled:0}},{baseVals:{enabled:0}},{baseVals:{enabled:0}}],waves:[{baseVals:{enabled:0}},{baseVals:{enabled:0}},{baseVals:{enabled:0}},{baseVals:{enabled:0}}],init_eqs_str:"a.d=0;a.y3=0;a.y1=0;a.xx=0;a.res=0;a.q12=0;a.x1=0;a.vx3=0;a.q13=0;a.q6=0;a.dt=0;a.q1=0;a.q5=0;a.c_x=0;a.c_y=0;a.q9=0;a.d1=0;a.v=0;a.si1=0;a.vx4=0;a.diff=0;a.x3=0;a.q23=0;a.q24=0;a.d2=0;a.q11=0;a.q10=0;a.xx2=0;a.q4=0;a.yy1=0;a.vy4=0;a.dir=0;a.x4=0;a.r=0;a.x2=0;a.beat=0;a.vol=0;a.vy2=0;a.y2=0;a.size=0;a.q2=0;a.q14=0;a.si2=0;a.vx2=0;a.q3=0;a.yy=0;a.y4=0;a.q7=0;a.vy3=0;a.strength=0;a.xx1=0;a.velocity=0;a.q8=0;a.c_x=.5;a.c_y=.5;",frame_eqs_str:"a.sx=1+.01*mod(8*a.bass,8)*equal(mod(a.time,Math.floor(24-2*a.bass)),0);a.sy=1+.01*mod(8*a.mid,8)*equal(mod(a.time,12+Math.floor(24-2*a.bass)),0);a.q1=a.aspectx;a.q2=a.aspecty;a.rot=0;a.zoom=1;a.warp=0;a.vol=8*a.bass+4*a.mid+2*a.treb;a.vol*=above(a.vol,17);a.monitor=a.vol;a.beat=above(a.vol,a.res);a.diff=(1-a.beat)*a.diff+a.beat*(a.vol-a.res);a.res=a.beat*(a.vol+2*a.diff)+(1-a.beat)*(a.res-div(60*(.04*a.diff+.12),a.fps));a.res=Math.max(0,a.res);a.monitor=a.res;a.r=.00001 3.1)) {\n ret_2 = (ret_2 - 0.4);\n };\n bool tmpvar_4;\n if ((ret_2.x < 0.1)) {\n tmpvar_4 = (abs((n_1 - 3.0)) < 0.5);\n } else {\n tmpvar_4 = bool(0);\n };\n if (tmpvar_4) {\n ret_2 = (ret_2 + 0.4);\n };\n vec4 tmpvar_5;\n tmpvar_5.w = 1.0;\n tmpvar_5.xyz = ret_2;\n ret = tmpvar_5.xyz;\n }",comp:" shader_body { \n vec3 ret2_1;\n vec3 ret_2;\n vec2 tmpvar_3;\n tmpvar_3 = (texsize.zw * 8.0);\n vec2 tmpvar_4;\n tmpvar_4.x = (((texture (sampler_blur1, \n (uv + (vec2(1.0, 0.0) * tmpvar_3))\n ).xyz * scale1) + bias1) - ((texture (sampler_blur1, \n (uv - (vec2(1.0, 0.0) * tmpvar_3))\n ).xyz * scale1) + bias1)).y;\n tmpvar_4.y = (((texture (sampler_blur1, \n (uv + (vec2(0.0, 1.0) * tmpvar_3))\n ).xyz * scale1) + bias1) - ((texture (sampler_blur1, \n (uv - (vec2(0.0, 1.0) * tmpvar_3))\n ).xyz * scale1) + bias1)).y;\n vec2 tmpvar_5;\n tmpvar_5 = (uv + (tmpvar_4 * 0.55));\n vec4 tmpvar_6;\n tmpvar_6 = texture (sampler_blur3, uv);\n ret_2 = (vec3((0.25 * dot (\n clamp ((2.0 * ((tmpvar_6.xyz * scale3) + bias3)), 0.0, 1.0)\n , vec3(0.32, 0.49, 0.29)))) - (0.8 * dot (\n clamp (((20.0 * (\n (0.6 * ((texture (sampler_blur2, uv).xyz * scale2) + bias2))\n - 0.01)) - 2.0), 0.0, 1.0)\n , vec3(0.32, 0.49, 0.29))));\n vec4 tmpvar_7;\n tmpvar_7 = texture (sampler_blur1, uv);\n ret_2 = (ret_2 + dot (clamp (\n ((30.0 * ((texture (sampler_main, uv).xyz + \n (((tmpvar_7.xyz * scale1) + bias1) * 0.15)\n ) - 0.01)) - 2.0)\n , 0.0, 1.0), vec3(0.32, 0.49, 0.29)));\n ret_2 = (ret_2 + 1.0);\n vec3 tmpvar_8;\n tmpvar_8 = mix (ret_2, (ret_2 * (\n ((texture (sampler_blur3, tmpvar_5).xyz * scale3) + bias3)\n - \n ((texture (sampler_blur1, tmpvar_5).xyz * scale1) + bias1)\n )), pow (hue_shader, ret_2));\n ret2_1 = (vec3((-0.5 * dot (\n ((texture (sampler_blur3, tmpvar_5).xyz * scale3) + bias3)\n , vec3(0.32, 0.49, 0.29)))) + (0.8 * (\n (texture (sampler_blur1, tmpvar_5).xyz * scale1)\n + bias1)));\n ret2_1 = (ret2_1 - (0.9 * texture (sampler_main, tmpvar_5).xyz));\n ret2_1 = (ret2_1 - 1.5);\n vec3 tmpvar_9;\n tmpvar_9 = mix (ret2_1, (ret2_1 * (\n ((tmpvar_6.xyz * scale3) + bias3)\n - \n ((tmpvar_7.xyz * scale1) + bias1)\n )), pow (hue_shader.zxy, tmpvar_8));\n ret2_1 = tmpvar_9;\n vec3 tmpvar_10;\n tmpvar_10 = abs((tmpvar_8 - tmpvar_9));\n ret_2 = (tmpvar_10 * tmpvar_10);\n ret_2 = (ret_2 * 1.15);\n vec4 tmpvar_11;\n tmpvar_11.w = 1.0;\n tmpvar_11.xyz = ret_2;\n ret = tmpvar_11.xyz;\n }"}},function(a,e){a.exports={baseVals:{rating:4,gammaadj:1,decay:.9,echo_zoom:1,wave_brighten:0,wrap:0,wave_a:.001,wave_scale:5.715,wave_smoothing:.9,modwavealphastart:1,modwavealphaend:1,warpanimspeed:.162,warpscale:5.582,zoomexp:.32104,zoom:.9901,warp:.11563,wave_r:0,wave_g:0,wave_b:0,ob_size:0,ib_size:0,ib_r:0,ib_g:0,ib_b:0,ib_a:1,mv_x:64,mv_y:48,mv_l:0,mv_b:0,mv_a:0},shapes:[{baseVals:{enabled:1,sides:100,thickoutline:1,textured:1,rad:.05343,tex_zoom:12.77228,g:1,b:1,a:0,r2:1,b2:1,a2:1,border_g:0,border_a:0},init_eqs_str:"a.vx=0;a.vy=0;",frame_eqs_str:""},{baseVals:{enabled:1,sides:100,textured:1,y:.75,rad:.46753,g:1,b:1,r2:1,b2:1,border_a:0},init_eqs_str:"a.w=0;a.q1=0;",frame_eqs_str:"a.w=4*-Math.atan2(.5,a.q1)+4*Math.asin(1);a.ang=a.w;a.x=.5+.19*Math.sin(a.w);a.y=.5+.26*Math.cos(a.w);"},{baseVals:{enabled:1,sides:100,textured:1,y:.75,rad:.46753,g:1,b:1,r2:1,b2:1,border_a:0},init_eqs_str:"a.w=0;a.q1=0;",frame_eqs_str:"a.w=4*-Math.atan2(.5,a.q1)+4*Math.asin(1)+div(2*Math.asin(1),3);a.ang=a.w;a.x=.5+.19*Math.sin(a.w);a.y=.5+.26*Math.cos(a.w);"},{baseVals:{enabled:1,sides:100,textured:1,y:.75,rad:.46753,g:1,b:1,r2:1,b2:1,border_a:0},init_eqs_str:"a.w=0;a.q1=0;",frame_eqs_str:"a.w=4*-Math.atan2(.5,a.q1)+4*Math.asin(1)-div(2*Math.asin(1),3);a.ang=a.w;a.x=.5+.19*Math.sin(a.w);a.y=.5+.26*Math.cos(a.w);"}],waves:[{baseVals:{enabled:1,usedots:1,thick:1,additive:1,scaling:2.44415,smoothing:0},init_eqs_str:"a.d=0;a.n=0;a.y1=0;a.xx=0;a.z=0;a.w=0;a.t5=0;a.t1=0;a.x1=0;a.cl3=0;a.j3=0;a.cl2=0;a.zoom=0;a.j=0;a.cl1=0;a.t8=0;a.v=0;a.t3=0;a.t6=0;a.pi3=0;a.t7=0;a.c2=0;a.j2=0;a.s3=0;a.t=0;a.k=0;a.zz=0;a.c3=0;a.t2=0;a.bb=0;a.s1=0;a.s2=0;a.t4=0;a.yy=0;a.c=0;a.c1=0;a.t2=0;a.t3=0;a.t4=0;a.cl=0;",frame_eqs_str:"a.t1=0;a.v=.01;a.j+=.01*a.bass;a.j2+=.01*a.mid_att;a.j3+=.01*a.treb_att;a.t2=a.j;a.t3=a.j2;a.t4=a.j3;a.k=.99*a.k+div(10*a.mid,a.fps);a.t5=-a.k;a.cl1=a.cl1-.0005-.003*a.bass;a.cl1=.00001 0.0))))\n .xy * 0.025))));\n vec4 tmpvar_6;\n tmpvar_6.w = 1.0;\n tmpvar_6.xyz = (((tmpvar_5.xyz * tmpvar_5.xyz) * 1.4) - 0.04);\n ret = tmpvar_6.xyz;\n }"}},function(a,e){a.exports={baseVals:{rating:5,gammaadj:1.56,decay:1,echo_zoom:.362,echo_orient:1,wave_thick:1,wave_brighten:0,darken:1,wave_a:.001,wave_scale:1.599,wave_smoothing:0,wave_mystery:-.5,modwavealphastart:2,modwavealphaend:2,warpscale:.107,zoomexp:.1584,fshader:1,warp:.01,wave_r:.51,wave_g:.5,ob_size:0,ob_a:1,ib_r:0,ib_g:0,ib_b:0,ib_a:1,mv_x:64,mv_y:48,mv_l:.5,mv_r:0,mv_g:0,mv_b:0,mv_a:0},shapes:[{baseVals:{enabled:0}},{baseVals:{enabled:0}},{baseVals:{enabled:0}},{baseVals:{enabled:0}}],waves:[{baseVals:{enabled:0}},{baseVals:{enabled:0}},{baseVals:{enabled:0}},{baseVals:{enabled:0}}],init_eqs_str:"a.d=0;a.y3=0;a.y1=0;a.xx=0;a.s=0;a.t1=0;a.x1=0;a.vx3=0;a.q6=0;a.dt=0;a.q1=0;a.q5=0;a.v=0;a.vx4=0;a.mm=0;a.tt=0;a.grav=0;a.x3=0;a.xx2=0;a.q4=0;a.a=0;a.yy1=0;a.vy4=0;a.dir=0;a.bounce=0;a.x4=0;a.r=0;a.x2=0;a.mx=0;a.mn=0;a.vy2=0;a.y2=0;a.bb=0;a.q2=0;a.m1=0;a.spring=0;a.vx2=0;a.q3=0;a.resist=0;a.yy=0;a.y4=0;a.vy3=0;a.xx1=0;a.b1=0;a.q8=0;",frame_eqs_str:"a.ib_r=.3*Math.sin(5*a.time)+.7;a.ib_g=.3*Math.sin(4*a.time)+.3;a.ib_b=.5*Math.sin(4*div(a.time,3))+.5;a.xx1=.9*a.xx1+.01*a.bass;a.xx2=.9*a.xx2+.01*a.treb;a.yy1=.94*a.yy1+.0075*(a.treb+a.bass);a.x1=.5+2*(a.xx1-a.xx2);a.y1=.4+a.yy1;a.x1=Math.max(0,Math.min(1,a.x1));a.y1=Math.max(0,Math.min(1,a.y1));a.spring=10;a.grav=.5;a.resist=1;a.bounce=.75;a.dt=.0002*div(60,a.fps);a.vx2=a.vx2*(1-a.resist*a.dt)+a.dt*(a.x1+a.x3-2*a.x2)*a.spring;a.vy2=a.vy2*(1-a.resist*a.dt)+a.dt*((a.y1+a.y3-\n2*a.y2)*a.spring-a.grav);a.vx3=a.vx3*(1-a.resist*a.dt)+a.dt*(a.x2+a.x4-2*a.x3)*a.spring;a.vy3=a.vy3*(1-a.resist*a.dt)+a.dt*((a.y2+a.y4-2*a.y3)*a.spring-a.grav);a.vx4=a.vx4*(1-a.resist*a.dt)+a.dt*(a.x3-a.x4)*a.spring;a.vy4=a.vy4*(1-a.resist*a.dt)+a.dt*((a.y3-a.y4)*a.spring-a.grav);a.x2+=a.vx2;a.y2+=a.vy2;a.x3+=a.vx3;a.y3+=a.vy3;a.x4+=a.vx4;a.y4+=a.vy4;a.vx2=.00001b;b++)a.gmegabuf[Math.floor(a.i)]=0,a.i+=1;a.count=50;a.attributes=32;a.nliststart=24;a.minradius=.004;a.maxradius=.04;a.v=0;for(b=a.index=0;b 1.0)\n ) * (\n (tmpvar_15 * -2.0)\n + 1.570796)));\n tmpvar_13 = (tmpvar_15 * sign((tmpvar_11.x / tmpvar_11.y)));\n if ((abs(tmpvar_11.y) > (1e-08 * abs(tmpvar_11.x)))) {\n if ((tmpvar_11.y < 0.0)) {\n if ((tmpvar_11.x >= 0.0)) {\n tmpvar_13 += 3.141593;\n } else {\n tmpvar_13 = (tmpvar_13 - 3.141593);\n };\n };\n } else {\n tmpvar_13 = (sign(tmpvar_11.x) * 1.570796);\n };\n vec2 tmpvar_16;\n tmpvar_16.x = (tmpvar_13 * 0.1591549);\n tmpvar_16.y = tmpvar_12;\n vec2 tmpvar_17;\n tmpvar_17.x = ((tmpvar_16.x * 2.0) + q11);\n tmpvar_17.y = ((0.3 * log(tmpvar_12)) + q12);\n vec2 tmpvar_18;\n tmpvar_18 = (0.5 + (0.5 - abs(\n ((fract((tmpvar_17 * 0.5)) * 2.0) - 1.0)\n )));\n vec2 tmpvar_19;\n tmpvar_19 = (texsize.zw * 3.0);\n vec3 tmpvar_20;\n tmpvar_20 = ((2.0 * (\n (texture (sampler_blur1, (tmpvar_18 + (vec2(1.0, 0.0) * tmpvar_19))).xyz * scale1)\n + bias1)) - (2.0 * (\n (texture (sampler_blur1, (tmpvar_18 - (vec2(1.0, 0.0) * tmpvar_19))).xyz * scale1)\n + bias1)));\n vec3 tmpvar_21;\n tmpvar_21 = ((2.0 * (\n (texture (sampler_blur1, (tmpvar_18 + (vec2(0.0, 1.0) * tmpvar_19))).xyz * scale1)\n + bias1)) - (2.0 * (\n (texture (sampler_blur1, (tmpvar_18 - (vec2(0.0, 1.0) * tmpvar_19))).xyz * scale1)\n + bias1)));\n vec2 tmpvar_22;\n tmpvar_22.x = tmpvar_20.x;\n tmpvar_22.y = tmpvar_21.x;\n mirror_uv_3 = (tmpvar_18 + ((tmpvar_22 * texsize.zw) * 4.0));\n ret_4 = ((mix (ret_4, vec3(1.0, 1.0, 1.0), \n ((((texture (sampler_blur1, mirror_uv_3).xyz * scale1) + bias1).x * (1.0 - (\n (texture (sampler_blur2, mirror_uv_3).xyz * scale2)\n + bias2).x)) * (pow (hue_shader, vec3(4.0, 4.0, 4.0)) * 1.4))\n ) * texture (sampler_main, mirror_uv_3).xxx) + ((\n (1.0 - texture (sampler_main, mirror_uv_3).x)\n * \n ((texture (sampler_blur1, mirror_uv_3).xyz * scale1) + bias1)\n .x) * vec3(3.0, 3.0, 3.0)));\n vec2 tmpvar_23;\n tmpvar_23.x = tmpvar_20.x;\n tmpvar_23.y = tmpvar_21.x;\n mirror_uv_3 = (mirror_uv_3 - ((tmpvar_23 * texsize.zw) * 24.0));\n dx_2 = ((2.0 * (\n (texture (sampler_blur1, (mirror_uv_3 + (vec2(1.0, 0.0) * tmpvar_19))).xyz * scale1)\n + bias1)) - (2.0 * (\n (texture (sampler_blur1, (mirror_uv_3 - (vec2(1.0, 0.0) * tmpvar_19))).xyz * scale1)\n + bias1)));\n dy_1 = ((2.0 * (\n (texture (sampler_blur1, (mirror_uv_3 + (vec2(0.0, 1.0) * tmpvar_19))).xyz * scale1)\n + bias1)) - (2.0 * (\n (texture (sampler_blur1, (mirror_uv_3 - (vec2(0.0, 1.0) * tmpvar_19))).xyz * scale1)\n + bias1)));\n vec2 tmpvar_24;\n tmpvar_24.x = -(dx_2.y);\n tmpvar_24.y = dy_1.y;\n vec2 tmpvar_25;\n tmpvar_25 = (tmpvar_24 * 0.25);\n vec3 tmpvar_26;\n tmpvar_26 = mix (ret_4, vec3(1.0, 1.0, 1.0), (mix (vec3(1.0, 0.7, 0.2), vec3(0.15, 0.0, 0.5), vec3(\n ((((\n ((mirror_uv_3.x * 0.8) - mirror_uv_3.y)\n + 0.75) + tmpvar_25.x) + tmpvar_25.y) - 0.1)\n )) * texture (sampler_main, mirror_uv_3).y));\n vec2 tmpvar_27;\n vec2 tmpvar_28;\n tmpvar_28 = ((0.5 + (\n (uv - 0.5)\n * aspect.wz)) - vec2(0.5, 0.5));\n vec2 tmpvar_29;\n tmpvar_29.x = ((q4 * tmpvar_28.x) - (q3 * tmpvar_28.y));\n tmpvar_29.y = ((q3 * tmpvar_28.x) + (q4 * tmpvar_28.y));\n tmpvar_27 = (vec2(0.5, 0.5) + tmpvar_29);\n mirror_uv_3 = tmpvar_27.yx;\n vec2 tmpvar_30;\n tmpvar_30 = (vec2(1.0, 0.0) * texsize.zw);\n dx_2 = ((2.0 * texture (sampler_main, (tmpvar_27.yx + tmpvar_30)).xyz) - (2.0 * texture (sampler_main, (tmpvar_27.yx - tmpvar_30)).xyz));\n vec2 tmpvar_31;\n tmpvar_31 = (vec2(0.0, 1.0) * texsize.zw);\n dy_1 = ((2.0 * texture (sampler_main, (tmpvar_27.yx + tmpvar_31)).xyz) - (2.0 * texture (sampler_main, (tmpvar_27.yx - tmpvar_31)).xyz));\n vec2 tmpvar_32;\n tmpvar_32.x = dx_2.z;\n tmpvar_32.y = dy_1.z;\n vec3 tmpvar_33;\n tmpvar_33 = mix (tmpvar_26, vec3(0.9, 0.9, 1.0), vec3((1.0 - texture (sampler_main, (tmpvar_27.yx - tmpvar_32)).z)));\n ret_4 = tmpvar_33;\n vec4 tmpvar_34;\n tmpvar_34.w = 1.0;\n tmpvar_34.xyz = tmpvar_33;\n ret = tmpvar_34.xyz;\n }"}},function(a,e){a.exports={baseVals:{rating:5,gammaadj:1,decay:.995,echo_zoom:1.007,echo_orient:3,additivewave:1,modwavealphabyvolume:1,wave_brighten:0,wrap:0,wave_a:1.413,wave_scale:.418,wave_smoothing:0,wave_mystery:-.66,modwavealphastart:2,modwavealphaend:2,warpanimspeed:.626,warpscale:8.642,zoomexp:7.10084,zoom:.99951,warp:.09014,wave_r:0,wave_g:0,wave_x:.24,wave_y:.44,ob_size:0,ob_a:1,ib_size:.26,mv_x:64,mv_y:48,mv_l:0,mv_a:0},shapes:[{baseVals:{enabled:1,sides:48,additive:1,rad:.0277,ang:6.03186,tex_ang:6.03186,tex_zoom:.6839,r:0,g:1,a2:1,border_r:0,border_g:0,border_b:0,border_a:0},init_eqs_str:"a.q3=0;a.q4=0;a.q5=0;a.q2=0;a.q1=0;",frame_eqs_str:"a.x=a.q3;a.y=a.q4;a.rad=a.q5;a.x=.5+div(a.x-.5,a.q2);a.y=.5+div(a.y-.5,a.q1);"},{baseVals:{enabled:1,sides:48,additive:1,rad:.0277,ang:6.03186,tex_ang:6.03186,tex_zoom:.6839,r:0,b:1,g2:0,b2:1,a2:1,border_r:0,border_g:0,border_b:0,border_a:0},init_eqs_str:"a.q6=0;a.q7=0;a.q8=0;a.q2=0;a.q1=0;",frame_eqs_str:"a.x=a.q6;a.y=a.q7;a.rad=a.q8;a.x=.5+div(a.x-.5,a.q2);a.y=.5+div(a.y-.5,a.q1);"},{baseVals:{enabled:0}},{baseVals:{enabled:0}}],waves:[{baseVals:{enabled:1,thick:1,additive:1,scaling:2.0231,smoothing:0,g:0,b:0},init_eqs_str:"a.d=0;a.tt2=0;a.res=0;a.tt1=0;a.diff=0;a.tt3=0;a.beat=0;a.vol=0;a.m=0;a.monitor=0;a.t2=0;a.t3=0;a.t4=0;a.cl=0;",frame_eqs_str:"a.vol=8*a.bass+5*a.mid+3*a.treb;a.m=.97*a.m+.08*a.vol;a.monitor=a.vol;a.beat=above(a.vol,a.res)*above(a.vol,a.m)*above(a.vol,16);a.diff=(1-a.beat)*a.diff+a.beat*(a.vol-a.res);a.res=a.beat*(a.vol+.04*a.m)+(1-a.beat)*(a.res-div(60*(.1+.02*a.diff),a.fps));a.res=Math.max(0,a.res);a.a=a.beat;",point_eqs_str:"a.tt3=.6*a.tt3+1*a.value1;a.tt2=.7*a.tt2+.2*a.tt3;a.tt1=.8*a.tt1+.1*a.tt2;a.d=.9*a.d+.2*a.tt1;a.y=.5+a.d*a.sample*(1-a.sample)*2;a.x=-.05+1.1*a.sample;"},{baseVals:{enabled:0}},{baseVals:{enabled:0}},{baseVals:{enabled:0}}],init_eqs_str:"a.y3=0;a.y1=0;a.xx=0;a.q12=0;a.w2=0;a.ref_ang=0;a.q18=0;a.x1=0;a.vx3=0;a.q13=0;a.q15=0;a.q6=0;a.q1=0;a.q5=0;a.q9=0;a.d1=0;a.si1=0;a.vx1=0;a.vx4=0;a.x3=0;a.d2=0;a.q11=0;a.q10=0;a.q4=0;a.vy4=0;a.dir=0;a.bounce=0;a.q16=0;a.x4=0;a.w1=0;a.r=0;a.x2=0;a.q17=0;a.vy2=0;a.y2=0;a.vy1=0;a.q2=0;a.m1=0;a.q14=0;a.si2=0;a.v1=0;a.vx2=0;a.q3=0;a.yy=0;a.y4=0;a.q7=0;a.vy3=0;a.v2=0;a.b1=0;a.q8=0;a.x1=.5;a.x2=.51;a.y2=.9;a.y1=.7;a.x3=.8;a.y3=.5;a.x4=.2;a.y4=.5;a.ax1=0;a.ay1=0;a.ax2=0;a.ay2=0;a.ax3=\n0;a.ay3=0;a.vx1=0;a.vx2=0;",frame_eqs_str:"a.zoom=1.002;a.warp=.2;a.wave_a=0;a.r=.04+.008*Math.max(a.bass_att,a.treb_att);a.bounce=below(a.y1,a.r);a.y1+=a.vy1;a.vy1=.00001 1.0)\n ) * (\n (tmpvar_7 * -2.0)\n + 1.570796)));\n tmpvar_5 = (tmpvar_7 * sign((tmpvar_4.x / tmpvar_4.y)));\n if ((abs(tmpvar_4.y) > (1e-08 * abs(tmpvar_4.x)))) {\n if ((tmpvar_4.y < 0.0)) {\n if ((tmpvar_4.x >= 0.0)) {\n tmpvar_5 += 3.141593;\n } else {\n tmpvar_5 = (tmpvar_5 - 3.141593);\n };\n };\n } else {\n tmpvar_5 = (sign(tmpvar_4.x) * 1.570796);\n };\n vec2 tmpvar_8;\n tmpvar_8.x = ((tmpvar_5 * q11) - tmpvar_3);\n tmpvar_8.y = (((q13 * \n log(sqrt(dot (tmpvar_4, tmpvar_4)))\n ) + (tmpvar_5 * q11)) + tmpvar_3);\n uv_1 = (0.5 + (0.5 - abs(\n ((fract((tmpvar_8 * 0.5)) * 2.0) - 1.0)\n )));\n vec4 tmpvar_9;\n tmpvar_9 = texture (sampler_main, uv_1);\n ret_2 = (vec3(dot (tmpvar_9.xyz, vec3(0.32, 0.49, 0.29))) * mix (vec3(1.0, 1.0, 1.0), vec3(0.2, 0.5, 1.0), tmpvar_9.xxx));\n ret_2 = (ret_2 * 2.0);\n vec4 tmpvar_10;\n tmpvar_10.w = 1.0;\n tmpvar_10.xyz = ret_2;\n ret = tmpvar_10.xyz;\n }"}},function(a,e){a.exports={baseVals:{rating:5,gammaadj:1,decay:1,echo_zoom:1,echo_alpha:.5,wave_thick:1,wave_brighten:0,wrap:0,wave_a:.004,wave_scale:.242,wave_smoothing:0,wave_mystery:-.44,modwavealphastart:1,modwavealphaend:1,warpanimspeed:.88,warpscale:9.181,zoomexp:.65309,zoom:.87866,warp:.04914,wave_y:.04,ob_size:.05,ob_a:1,ib_size:0,ib_r:0,ib_g:0,ib_b:0,ib_a:1,mv_x:64,mv_y:48,mv_l:0,mv_b:0,mv_a:0},shapes:[{baseVals:{enabled:1,sides:100,thickoutline:1,y:.04,rad:.01,tex_ang:.12566,tex_zoom:1.51878,r:0,a:0,g2:0,b2:.01,border_a:0},init_eqs_str:"",frame_eqs_str:""},{baseVals:{enabled:0}},{baseVals:{enabled:0}},{baseVals:{enabled:0}}],waves:[{baseVals:{enabled:1,thick:1,additive:1,scaling:2.0231,smoothing:0,r:0,b:0},init_eqs_str:"a.tt3=0;a.tt2=0;a.tt1=0;a.d=0;a.t2=0;a.t3=0;a.t4=0;a.cl=0;",frame_eqs_str:"a.r=1;a.g=0;a.b=1;",point_eqs_str:"a.tt3=.6*a.tt3+1*a.value1;a.tt2=.7*a.tt2+.2*a.tt3;a.tt1=.8*a.tt1+.1*a.tt2;a.d=.9*a.d+.2*a.tt1;a.y=.6+a.d*a.sample*(1-a.sample)*2;a.x=-.05+1.1*a.sample;"},{baseVals:{enabled:0}},{baseVals:{enabled:0}},{baseVals:{enabled:0}}],init_eqs_str:"a.c_inv_i=0;a.translation_x=0;a.q12=0;a.a_i=0;a.a_r=0;a.q18=0;a.q13=0;a.scale=0;a.q15=0;a.c_inv_r=0;a.angle=0;a.q11=0;a.bcad_r=0;a.b_r=0;a.q16=0;a.bcad_i=0;a.q17=0;a.q14=0;a.translation_y=0;a.translation_v=0;a.b_i=0;a.translation_u=0;a.x1=.9;a.y1=.5;a.x2=.5;a.y2=.5;a.x3=.5;a.y3=.5;a.x4=.5;a.y4=.5;",frame_eqs_str:"a.zoom=1;a.scale=1;a.angle=.1*a.time;a.translation_x=0;a.translation_y=.12;a.a_r=Math.cos(a.angle)*a.scale;a.a_i=Math.sin(a.angle)*a.scale;a.b_r=a.translation_x;a.b_i=a.translation_y;a.scale=1.6;a.angle=0;a.translation_u=0;a.translation_v=0;a.q15=Math.cos(a.angle)*a.scale;a.q16=Math.sin(a.angle)*a.scale;a.q17=a.translation_u;a.q18=a.translation_v;a.c_inv_r=div(a.q15,a.q15*a.q15+a.q16*a.q16);a.c_inv_i=div(a.q16,a.q15*a.q15+a.q16*a.q16);a.q11=a.a_r*a.c_inv_r-a.a_i*a.c_inv_i;a.q12=\na.a_r*a.c_inv_i-a.a_i*a.c_inv_r;a.bcad_r=a.b_r*a.q15-a.b_i*a.q16-(a.a_r*a.q17-a.a_i*a.q18);a.bcad_i=a.b_r*a.q16-a.b_i*a.q15-(a.a_r*a.q18-a.a_i*a.q17);a.q13=a.bcad_r*a.c_inv_r-a.bcad_i*a.c_inv_i;a.q14=a.bcad_r*a.c_inv_i-a.bcad_i*a.c_inv_r;",pixel_eqs_str:"",pixel_eqs:"",warp:" shader_body { \n float conway_1;\n vec3 ret_2;\n vec2 tmpvar_3;\n tmpvar_3.x = (((\n ((texture (sampler_blur2, (uv + vec2(0.02, 0.0))).xyz * scale2) + bias2)\n - \n ((texture (sampler_blur2, (uv - vec2(0.02, 0.0))).xyz * scale2) + bias2)\n ).y * 1280.0) * texsize.z);\n tmpvar_3.y = (((\n ((texture (sampler_blur2, (uv + vec2(0.0, 0.02))).xyz * scale2) + bias2)\n - \n ((texture (sampler_blur2, (uv - vec2(0.0, 0.02))).xyz * scale2) + bias2)\n ).y * 1024.0) * texsize.w);\n ret_2.y = texture (sampler_pc_main, (uv - (tmpvar_3 * 0.004))).y;\n ret_2.y = (ret_2.y + ((\n ((ret_2.y - ((texture (sampler_blur1, uv).xyz * scale1) + bias1).y) - 0.1)\n * 0.1) + 0.02));\n vec2 tmpvar_4;\n tmpvar_4 = (vec2(0.0, 1.0) * texsize.zw);\n ret_2.z = (texture (sampler_fc_main, (uv - tmpvar_4)).z - 0.004);\n conway_1 = (texture (sampler_pw_main, (uv_orig - texsize.zw)).x + texture (sampler_pw_main, (uv_orig + (vec2(0.0, -1.0) * texsize.zw))).x);\n conway_1 = (conway_1 + texture (sampler_pw_main, (uv_orig + (vec2(1.0, -1.0) * texsize.zw))).x);\n conway_1 = (conway_1 + texture (sampler_pw_main, (uv_orig + (vec2(-1.0, 0.0) * texsize.zw))).x);\n conway_1 = (conway_1 + texture (sampler_pw_main, (uv_orig + (vec2(1.0, 0.0) * texsize.zw))).x);\n conway_1 = (conway_1 + texture (sampler_pw_main, (uv_orig + (vec2(-1.0, 1.0) * texsize.zw))).x);\n conway_1 = (conway_1 + texture (sampler_pw_main, (uv_orig + tmpvar_4)).x);\n conway_1 = (conway_1 + texture (sampler_pw_main, (uv_orig + texsize.zw)).x);\n conway_1 = (conway_1 - fract(conway_1));\n float tmpvar_5;\n tmpvar_5 = clamp (texture (sampler_pc_main, uv_orig).x, 0.0, 1.0);\n ret_2.x = (clamp ((1.0 - \n abs((conway_1 - 3.0))\n ), 0.0, 1.0) * (1.0 - tmpvar_5));\n ret_2.x = (ret_2.x + (clamp (\n max ((1.0 - abs((conway_1 - 2.0))), (1.0 - abs((conway_1 - 3.0))))\n , 0.0, 1.0) * tmpvar_5));\n vec4 tmpvar_6;\n tmpvar_6.w = 1.0;\n tmpvar_6.xyz = ret_2;\n ret = tmpvar_6.xyz;\n }",comp:" shader_body { \n vec2 moebius_1;\n vec3 ret_2;\n vec2 tmpvar_3;\n tmpvar_3.x = q11;\n tmpvar_3.y = q12;\n vec2 tmpvar_4;\n tmpvar_4.x = q17;\n tmpvar_4.y = q18;\n vec2 tmpvar_5;\n vec2 tmpvar_6;\n tmpvar_6 = (uv - 0.5);\n tmpvar_5 = (tmpvar_6 * aspect.xy);\n vec2 tmpvar_7;\n tmpvar_7.x = ((tmpvar_5.x * q15) - (tmpvar_5.y * q16));\n tmpvar_7.y = ((tmpvar_5.x * q16) - (tmpvar_5.y * q15));\n vec2 tmpvar_8;\n tmpvar_8 = (tmpvar_7 + tmpvar_4);\n vec2 tmpvar_9;\n tmpvar_9.x = ((q13 * tmpvar_8.x) + (q14 * tmpvar_8.y));\n tmpvar_9.y = ((q14 * tmpvar_8.x) - (q13 * tmpvar_8.y));\n moebius_1 = (((tmpvar_9 / \n ((tmpvar_8.x * tmpvar_8.x) + (tmpvar_8.y * tmpvar_8.y))\n ) + tmpvar_3) * 0.5);\n float tmpvar_10;\n tmpvar_10 = sqrt(dot (moebius_1, moebius_1));\n moebius_1 = (0.5 + ((\n (1.0 - abs(((\n fract((moebius_1 * 0.5))\n * 2.0) - 1.0)))\n - 0.5) * 0.95));\n vec2 tmpvar_11;\n tmpvar_11 = (0.5 + (tmpvar_6 * 0.2));\n ret_2 = (texture (sampler_main, tmpvar_11).z * vec3(0.4, 0.0, 0.7));\n ret_2 = (mix (ret_2, vec3(0.0, 1.0, 1.0), vec3(clamp (texture (sampler_fc_main, moebius_1).y, 0.0, 1.0))) * (1.4 - pow (\n (tmpvar_10 * 0.8)\n , 0.3)));\n vec3 tmpvar_12;\n tmpvar_12 = mix (mix (mix (ret_2, vec3(4.0, 1.0, 0.0), vec3(\n ((clamp ((texture (sampler_fc_main, tmpvar_11).y - texture (sampler_pc_main, tmpvar_11).y), 0.0, 1.0) * 4.0) * (tmpvar_10 * tmpvar_10))\n )), vec3(-4.0, -4.0, -4.0), texture (sampler_main, tmpvar_11).xxx), vec3(2.0, 2.0, 2.0), vec3((texture (sampler_pc_main, tmpvar_11).x * 0.75)));\n ret_2 = tmpvar_12;\n vec4 tmpvar_13;\n tmpvar_13.w = 1.0;\n tmpvar_13.xyz = tmpvar_12;\n ret = tmpvar_13.xyz;\n }"}},function(a,e){a.exports={baseVals:{rating:1,gammaadj:1.28,decay:.8,echo_zoom:1,echo_orient:3,wave_mode:7,additivewave:1,modwavealphabyvolume:1,wave_brighten:0,wrap:0,brighten:1,wave_a:.001,wave_scale:1.286,wave_smoothing:.63,modwavealphastart:.71,modwavealphaend:1.3,warpanimspeed:.01,warpscale:100,zoomexp:.92178,zoom:.9901,warp:.01,wave_r:.65,wave_g:.65,wave_b:.65,ob_size:.005,ob_g:1,ob_a:1,ib_size:0,ib_r:0,ib_g:0,ib_b:0,ib_a:1,mv_x:64,mv_y:48,mv_l:0,mv_a:0,b1ed:0},shapes:[{baseVals:{enabled:0}},{baseVals:{enabled:0}},{baseVals:{enabled:0}},{baseVals:{enabled:0}}],waves:[{baseVals:{enabled:0}},{baseVals:{enabled:0}},{baseVals:{enabled:0}},{baseVals:{enabled:0}}],init_eqs_str:"a.d=0;a.y3=0;a.y1=0;a.xx=0;a.s=0;a.x1=0;a.vx3=0;a.q6=0;a.dt=0;a.q1=0;a.q5=0;a.v=0;a.vx4=0;a.grav=0;a.x3=0;a.q11=0;a.q10=0;a.xx2=0;a.q4=0;a.a=0;a.yy1=0;a.vy4=0;a.dir=0;a.bounce=0;a.x4=0;a.r=0;a.x2=0;a.vy2=0;a.y2=0;a.q2=0;a.spring=0;a.vx2=0;a.q3=0;a.resist=0;a.yy=0;a.y4=0;a.vy3=0;a.xx1=0;a.v2=0;a.q8=0;a.x1=.9;a.y1=.5;a.x2=.5;a.y2=.5;a.x3=.5;a.y3=.5;a.x4=.5;a.y4=.5;",frame_eqs_str:"a.xx1=.9*a.xx1+.01*a.bass;a.xx2=.9*a.xx2+.01*a.treb;a.yy1=.94*a.yy1+.0075*(a.treb+a.bass);a.x1=.5+2*(a.xx1-a.xx2);a.y1=.4+a.yy1;a.x1=Math.max(0,Math.min(1,a.x1));a.y1=Math.max(0,Math.min(1,a.y1));a.spring=10;a.grav=.5;a.resist=1;a.bounce=.75;a.dt=.0001*div(60,a.fps);a.vx2=a.vx2*(1-a.resist*a.dt)+a.dt*(a.x1+a.x3-2*a.x2)*a.spring;a.vy2=a.vy2*(1-a.resist*a.dt)+a.dt*((a.y1+a.y3-2*a.y2)*a.spring-a.grav);a.vx3=a.vx3*(1-a.resist*a.dt)+a.dt*(a.x2+a.x4-2*a.x3)*a.spring;a.vy3=a.vy3*(1-\na.resist*a.dt)+a.dt*((a.y2+a.y4-2*a.y3)*a.spring-a.grav);a.vx4=a.vx4*(1-a.resist*a.dt)+a.dt*(a.x3-a.x4)*a.spring;a.vy4=a.vy4*(1-a.resist*a.dt)+a.dt*((a.y3-a.y4)*a.spring-a.grav);a.x2+=a.vx2;a.y2+=a.vy2;a.x3+=a.vx3;a.y3+=a.vy3;a.x4+=a.vx4;a.y4+=a.vy4;a.vx2=.00001 1.0)\n ) * (\n (tmpvar_4 * -2.0)\n + 1.570796)));\n tmpvar_2 = (tmpvar_4 * sign((tmpvar_1.x / tmpvar_1.y)));\n if ((abs(tmpvar_1.y) > (1e-08 * abs(tmpvar_1.x)))) {\n if ((tmpvar_1.y < 0.0)) {\n if ((tmpvar_1.x >= 0.0)) {\n tmpvar_2 += 3.141593;\n } else {\n tmpvar_2 = (tmpvar_2 - 3.141593);\n };\n };\n } else {\n tmpvar_2 = (sign(tmpvar_1.x) * 1.570796);\n };\n vec2 tmpvar_5;\n tmpvar_5.x = (tmpvar_2 * q11);\n tmpvar_5.y = (((0.5 * \n log(sqrt(dot (tmpvar_1, tmpvar_1)))\n ) - (tmpvar_2 * q11)) + q12);\n vec4 tmpvar_6;\n tmpvar_6.w = 1.0;\n tmpvar_6.xyz = pow (texture (sampler_main, (0.5 + (\n (0.5 - abs(((\n fract((tmpvar_5 * 0.5))\n * 2.0) - 1.0)))\n * vec2(0.96, 1.0)))).xyz, vec3(0.618034, 0.618034, 0.618034));\n ret = tmpvar_6.xyz;\n }"}},function(a,e){a.exports={baseVals:{rating:4,gammaadj:1,decay:1,echo_zoom:1,echo_alpha:.5,wave_thick:1,wave_brighten:0,wave_a:.004,wave_scale:.01,wave_smoothing:0,wave_mystery:-.44,modwavealphastart:1,modwavealphaend:1,warpanimspeed:.01,warpscale:100,zoomexp:.24298,zoom:.9901,warp:.01,wave_y:.04,ob_size:0,ob_g:1,ib_size:0,ib_r:0,ib_g:0,ib_b:0,mv_x:64,mv_y:48,mv_l:0,mv_b:0,mv_a:0,b1ed:0},shapes:[{baseVals:{enabled:1,sides:100,thickoutline:1,rad:.01,tex_ang:.12566,tex_zoom:1.51878,r:.05,a:.1,g2:0,border_a:0},init_eqs_str:"",frame_eqs_str:""},{baseVals:{enabled:0}},{baseVals:{enabled:0}},{baseVals:{enabled:0}}],waves:[{baseVals:{enabled:0}},{baseVals:{enabled:0}},{baseVals:{enabled:0}},{baseVals:{enabled:0}}],init_eqs_str:"a.x1=.9;a.y1=.5;a.x2=.5;a.y2=.5;a.x3=.5;a.y3=.5;a.x4=.5;a.y4=.5;",frame_eqs_str:"a.zoom=1;a.warp=0;",pixel_eqs_str:"",pixel_eqs:"",warp:" shader_body { \n vec2 my_uv_1;\n vec3 ret_2;\n vec2 tmpvar_3;\n tmpvar_3 = (0.02 * aspect.zw);\n vec3 tmpvar_4;\n tmpvar_4 = (((texture (sampler_blur2, \n ((uv + (vec2(1.0, 0.0) * tmpvar_3)) - floor((uv + (vec2(1.0, 0.0) * tmpvar_3))))\n ).xyz * scale2) + bias2) - ((texture (sampler_blur2, \n ((uv - (vec2(1.0, 0.0) * tmpvar_3)) - floor((uv - (vec2(1.0, 0.0) * tmpvar_3))))\n ).xyz * scale2) + bias2));\n vec3 tmpvar_5;\n tmpvar_5 = (((texture (sampler_blur2, \n ((uv + (vec2(0.0, 1.0) * tmpvar_3)) - floor((uv + (vec2(0.0, 1.0) * tmpvar_3))))\n ).xyz * scale2) + bias2) - ((texture (sampler_blur2, \n ((uv - (vec2(0.0, 1.0) * tmpvar_3)) - floor((uv - (vec2(0.0, 1.0) * tmpvar_3))))\n ).xyz * scale2) + bias2));\n vec3 tmpvar_6;\n tmpvar_6 = ((texture (sampler_blur1, uv).xyz * scale1) + bias1);\n vec2 tmpvar_7;\n tmpvar_7.x = tmpvar_4.y;\n tmpvar_7.y = tmpvar_5.y;\n vec2 tmpvar_8;\n tmpvar_8.x = tmpvar_5.x;\n tmpvar_8.y = -(tmpvar_4.x);\n vec2 tmpvar_9;\n tmpvar_9 = ((uv - (tmpvar_7 * vec2(0.01, 0.01))) - (tmpvar_8 * -0.02));\n ret_2.y = texture (sampler_fc_main, (tmpvar_9 - floor(tmpvar_9))).y;\n ret_2.y = (ret_2.y + ((\n (ret_2.y - tmpvar_6.y)\n * 0.02) + 0.005));\n vec2 tmpvar_10;\n tmpvar_10.x = tmpvar_4.x;\n tmpvar_10.y = tmpvar_5.x;\n vec2 tmpvar_11;\n tmpvar_11.x = tmpvar_5.z;\n tmpvar_11.y = -(tmpvar_4.z);\n my_uv_1 = ((uv - (tmpvar_10 * vec2(0.01, 0.01))) - (tmpvar_11 * -0.02));\n ret_2.x = texture (sampler_fc_main, (my_uv_1 - floor(my_uv_1))).x;\n ret_2.x = (ret_2.x + ((\n (ret_2.x - tmpvar_6.x)\n * 0.02) + 0.005));\n vec2 tmpvar_12;\n tmpvar_12.x = tmpvar_4.z;\n tmpvar_12.y = tmpvar_5.z;\n vec2 tmpvar_13;\n tmpvar_13.x = tmpvar_5.y;\n tmpvar_13.y = -(tmpvar_4.y);\n my_uv_1 = ((uv - (tmpvar_12 * vec2(0.01, 0.01))) - (tmpvar_13 * -0.02));\n ret_2.z = texture (sampler_fc_main, (my_uv_1 - floor(my_uv_1))).z;\n ret_2.z = (ret_2.z + ((\n (ret_2.z - tmpvar_6.z)\n * 0.02) + 0.005));\n vec4 tmpvar_14;\n tmpvar_14.w = 1.0;\n tmpvar_14.xyz = ret_2;\n ret = tmpvar_14.xyz;\n }",comp:" shader_body { \n vec2 uv2_1;\n vec3 ret_2;\n uv2_1 = (uv + (vec2(1.0, 0.0) * texsize.zw));\n float tmpvar_3;\n tmpvar_3 = (((texture (sampler_main, uv2_1).xyz + \n (((texture (sampler_blur1, uv2_1).xyz * scale1) + bias1) * 0.4)\n ) + (\n ((texture (sampler_blur2, uv2_1).xyz * scale2) + bias2)\n * 0.15)) + ((\n (texture (sampler_blur3, uv2_1).xyz * scale3)\n + bias3) * 0.1)).x;\n uv2_1 = (uv + (vec2(-1.0, 0.0) * texsize.zw));\n float tmpvar_4;\n tmpvar_4 = (((texture (sampler_main, uv2_1).xyz + \n (((texture (sampler_blur1, uv2_1).xyz * scale1) + bias1) * 0.4)\n ) + (\n ((texture (sampler_blur2, uv2_1).xyz * scale2) + bias2)\n * 0.15)) + ((\n (texture (sampler_blur3, uv2_1).xyz * scale3)\n + bias3) * 0.1)).x;\n uv2_1 = (uv + (vec2(0.0, 1.0) * texsize.zw));\n float tmpvar_5;\n tmpvar_5 = (((texture (sampler_main, uv2_1).xyz + \n (((texture (sampler_blur1, uv2_1).xyz * scale1) + bias1) * 0.4)\n ) + (\n ((texture (sampler_blur2, uv2_1).xyz * scale2) + bias2)\n * 0.15)) + ((\n (texture (sampler_blur3, uv2_1).xyz * scale3)\n + bias3) * 0.1)).x;\n uv2_1 = (uv + (vec2(0.0, -1.0) * texsize.zw));\n vec3 tmpvar_6;\n tmpvar_6.z = 0.14;\n tmpvar_6.x = (tmpvar_3 - tmpvar_4);\n tmpvar_6.y = (tmpvar_5 - ((\n (texture (sampler_main, uv2_1).xyz + (((texture (sampler_blur1, uv2_1).xyz * scale1) + bias1) * 0.4))\n + \n (((texture (sampler_blur2, uv2_1).xyz * scale2) + bias2) * 0.15)\n ) + (\n ((texture (sampler_blur3, uv2_1).xyz * scale3) + bias3)\n * 0.1)).x);\n ret_2 = (0.5 + (0.5 * normalize(tmpvar_6)));\n vec2 x_7;\n x_7 = (ret_2.xy - 0.5);\n ret_2 = (ret_2 * clamp ((\n sqrt(dot (x_7, x_7))\n * 5.0), 0.0, 1.0));\n ret_2 = ret_2.xxy;\n ret_2 = (ret_2 + 1.15);\n ret_2 = (ret_2 * mix (ret_2, (ret_2 * \n (((texture (sampler_blur3, uv).xyz * scale3) + bias3) - ((texture (sampler_blur1, uv).xyz * scale1) + bias1))\n ), pow (hue_shader.zxy, ret_2)));\n ret_2 = (ret_2 * ret_2);\n vec4 tmpvar_8;\n tmpvar_8.w = 1.0;\n tmpvar_8.xyz = ret_2;\n ret = tmpvar_8.xyz;\n }"}},function(a,e){a.exports={baseVals:{rating:5,gammaadj:1,decay:1,echo_zoom:1,echo_alpha:.5,wave_thick:1,wave_brighten:0,wrap:0,wave_a:.004,wave_scale:.242,wave_smoothing:0,wave_mystery:-.44,modwavealphastart:1,modwavealphaend:1,warpanimspeed:.397,warpscale:15.099,zoomexp:.65309,zoom:.87866,warp:.04027,wave_y:.04,ob_size:0,ob_a:1,ib_size:0,ib_r:0,ib_g:0,ib_b:0,ib_a:1,mv_x:64,mv_y:48,mv_l:0,mv_b:0,mv_a:0},shapes:[{baseVals:{enabled:1,sides:100,thickoutline:1,y:.04,rad:.01,tex_ang:.12566,tex_zoom:1.51878,r:0,a:0,g2:0,b2:.01,border_a:0},init_eqs_str:"",frame_eqs_str:""},{baseVals:{enabled:0}},{baseVals:{enabled:0}},{baseVals:{enabled:0}}],waves:[{baseVals:{enabled:1,thick:1,additive:1,scaling:2.0231,smoothing:0,r:0,b:0},init_eqs_str:"a.tt3=0;a.tt2=0;a.tt1=0;a.d=0;a.t2=0;a.t3=0;a.t4=0;a.cl=0;",frame_eqs_str:"a.r=1;a.g=0;a.b=1;",point_eqs_str:"a.tt3=.6*a.tt3+1*a.value1;a.tt2=.7*a.tt2+.2*a.tt3;a.tt1=.8*a.tt1+.1*a.tt2;a.d=.9*a.d+.2*a.tt1;a.y=.6+a.d*a.sample*(1-a.sample)*2;a.x=-.05+1.1*a.sample;"},{baseVals:{enabled:0}},{baseVals:{enabled:0}},{baseVals:{enabled:0}}],init_eqs_str:"a.c_inv_i=0;a.translation_x=0;a.q12=0;a.a_i=0;a.a_r=0;a.q18=0;a.q13=0;a.scale=0;a.q15=0;a.c_inv_r=0;a.angle=0;a.q11=0;a.bcad_r=0;a.b_r=0;a.q16=0;a.bcad_i=0;a.q17=0;a.q14=0;a.translation_y=0;a.translation_v=0;a.b_i=0;a.translation_u=0;a.x1=.9;a.y1=.5;a.x2=.5;a.y2=.5;a.x3=.5;a.y3=.5;a.x4=.5;a.y4=.5;",frame_eqs_str:"a.zoom=.998;a.scale=1;a.angle=.02*a.time;a.translation_x=0;a.translation_y=.12;a.a_r=Math.cos(a.angle)*a.scale;a.a_i=Math.sin(a.angle)*a.scale;a.b_r=a.translation_x;a.b_i=a.translation_y;a.scale=1;a.angle=0*Math.sin(.1337*a.time);a.translation_u=0;a.translation_v=-.2;a.q15=Math.cos(a.angle)*a.scale;a.q16=Math.sin(a.angle)*a.scale;a.q17=a.translation_u;a.q18=a.translation_v;a.c_inv_r=div(a.q15,a.q15*a.q15+a.q16*a.q16);a.c_inv_i=div(a.q16,a.q15*a.q15+a.q16*a.q16);a.q11=a.a_r*\na.c_inv_r-a.a_i*a.c_inv_i;a.q12=a.a_r*a.c_inv_i-a.a_i*a.c_inv_r;a.bcad_r=a.b_r*a.q15-a.b_i*a.q16-(a.a_r*a.q17-a.a_i*a.q18);a.bcad_i=a.b_r*a.q16-a.b_i*a.q15-(a.a_r*a.q18-a.a_i*a.q17);a.q13=a.bcad_r*a.c_inv_r-a.bcad_i*a.c_inv_i;a.q14=a.bcad_r*a.c_inv_i-a.bcad_i*a.c_inv_r;",pixel_eqs_str:"",pixel_eqs:"",warp:" shader_body { \n float conway_1;\n vec3 ret_2;\n vec2 tmpvar_3;\n tmpvar_3.x = (((\n ((texture (sampler_blur2, (uv + vec2(0.02, 0.0))).xyz * scale2) + bias2)\n - \n ((texture (sampler_blur2, (uv - vec2(0.02, 0.0))).xyz * scale2) + bias2)\n ).y * 1280.0) * texsize.z);\n tmpvar_3.y = (((\n ((texture (sampler_blur2, (uv + vec2(0.0, 0.02))).xyz * scale2) + bias2)\n - \n ((texture (sampler_blur2, (uv - vec2(0.0, 0.02))).xyz * scale2) + bias2)\n ).y * 1024.0) * texsize.w);\n ret_2.y = texture (sampler_pc_main, (uv - (tmpvar_3 * 0.004))).y;\n ret_2.y = (ret_2.y + ((\n ((ret_2.y - ((texture (sampler_blur1, uv).xyz * scale1) + bias1).y) - 0.1)\n * 0.1) + 0.02));\n ret_2.z = (texture (sampler_fc_main, (0.5 + (\n (uv - 0.5)\n * 0.992))).z - 0.004);\n conway_1 = (texture (sampler_pw_main, (uv_orig - texsize.zw)).x + texture (sampler_pw_main, (uv_orig + (vec2(0.0, -1.0) * texsize.zw))).x);\n conway_1 = (conway_1 + texture (sampler_pw_main, (uv_orig + (vec2(1.0, -1.0) * texsize.zw))).x);\n conway_1 = (conway_1 + texture (sampler_pw_main, (uv_orig + (vec2(-1.0, 0.0) * texsize.zw))).x);\n conway_1 = (conway_1 + texture (sampler_pw_main, (uv_orig + (vec2(1.0, 0.0) * texsize.zw))).x);\n conway_1 = (conway_1 + texture (sampler_pw_main, (uv_orig + (vec2(-1.0, 1.0) * texsize.zw))).x);\n conway_1 = (conway_1 + texture (sampler_pw_main, (uv_orig + (vec2(0.0, 1.0) * texsize.zw))).x);\n conway_1 = (conway_1 + texture (sampler_pw_main, (uv_orig + texsize.zw)).x);\n conway_1 = (conway_1 - fract(conway_1));\n float tmpvar_4;\n tmpvar_4 = clamp (texture (sampler_pc_main, uv_orig).x, 0.0, 1.0);\n ret_2.x = (clamp ((1.0 - \n abs((conway_1 - 3.0))\n ), 0.0, 1.0) * (1.0 - tmpvar_4));\n ret_2.x = (ret_2.x + (clamp (\n max ((1.0 - abs((conway_1 - 2.0))), (1.0 - abs((conway_1 - 3.0))))\n , 0.0, 1.0) * tmpvar_4));\n vec4 tmpvar_5;\n tmpvar_5.w = 1.0;\n tmpvar_5.xyz = ret_2;\n ret = tmpvar_5.xyz;\n }",comp:" shader_body { \n vec2 moebius_1;\n vec3 ret_2;\n vec2 tmpvar_3;\n tmpvar_3.x = q11;\n tmpvar_3.y = q12;\n vec2 tmpvar_4;\n tmpvar_4.x = q17;\n tmpvar_4.y = q18;\n vec2 tmpvar_5;\n vec2 tmpvar_6;\n tmpvar_6 = (uv - 0.5);\n tmpvar_5 = (tmpvar_6 * aspect.xy);\n vec2 tmpvar_7;\n tmpvar_7.x = ((tmpvar_5.x * q15) - (tmpvar_5.y * q16));\n tmpvar_7.y = ((tmpvar_5.x * q16) - (tmpvar_5.y * q15));\n vec2 tmpvar_8;\n tmpvar_8 = (tmpvar_7 + tmpvar_4);\n vec2 tmpvar_9;\n tmpvar_9.x = ((q13 * tmpvar_8.x) + (q14 * tmpvar_8.y));\n tmpvar_9.y = ((q14 * tmpvar_8.x) - (q13 * tmpvar_8.y));\n moebius_1 = (((tmpvar_9 / \n ((tmpvar_8.x * tmpvar_8.x) + (tmpvar_8.y * tmpvar_8.y))\n ) + tmpvar_3) * 0.5);\n float tmpvar_10;\n tmpvar_10 = sqrt(dot (moebius_1, moebius_1));\n moebius_1 = (0.5 + ((\n (1.0 - abs(((\n fract((moebius_1 * 0.5))\n * 2.0) - 1.0)))\n - 0.5) * 0.99));\n vec3 tmpvar_11;\n tmpvar_11 = mix (mix (mix (\n mix (mix ((mix (ret_2, vec3(0.2, 0.6, 1.0), vec3(\n (texture (sampler_pc_main, moebius_1).y * 2.0)\n )) * (vec3(1.0, 1.0, 1.0) - vec3(\n ((((texture (sampler_blur1, \n (0.5 + (tmpvar_6 * 0.5))\n ).xyz * scale1) + bias1).y * 2.0) * tmpvar_10)\n ))), vec3(1.0, 1.0, 1.0), texture (sampler_pc_main, moebius_1).xxx), vec3(4.0, 1.0, 0.0), vec3(clamp (((texture (sampler_pc_main, \n (0.5 + (tmpvar_6 * 0.2))\n ).y * 2.0) * (\n (tmpvar_10 * tmpvar_10)\n * tmpvar_10)), 0.0, 1.0)))\n , vec3(0.1, 0.0, 0.0), vec3(\n clamp ((((texture (sampler_blur1, \n (0.5 + (tmpvar_6 * 0.2))\n ).xyz * scale1) + bias1).x * 12.0), 0.0, 1.0)\n )), (vec3(0.5, 0.8, 1.0) * texture (sampler_pc_main, uv).z), vec3((\n clamp ((((texture (sampler_blur1, \n (0.5 + (tmpvar_6 * 0.2))\n ).xyz * scale1) + bias1).x * 4.0), 0.0, 1.0)\n * 1.4))), vec3(1.0, 1.0, 1.0), texture (sampler_pc_main, (0.5 + (tmpvar_6 * 0.2))).xxx);\n ret_2 = tmpvar_11;\n vec4 tmpvar_12;\n tmpvar_12.w = 1.0;\n tmpvar_12.xyz = tmpvar_11;\n ret = tmpvar_12.xyz;\n }"}},function(a,e){a.exports={baseVals:{rating:5,gammaadj:1.56,decay:1,echo_zoom:.362,echo_orient:1,wave_mode:7,additivewave:1,modwavealphabyvolume:1,wave_brighten:0,darken:1,wave_a:.001,wave_scale:1.286,wave_smoothing:.63,modwavealphastart:.71,modwavealphaend:1.3,warpscale:1.331,fshader:1,zoom:1.004,warp:.19788,sx:.99967,sy:.9999,wave_g:.65,wave_b:.65,ob_size:0,ob_a:1,mv_x:64,mv_y:48,mv_l:0,mv_a:0},shapes:[{baseVals:{enabled:0}},{baseVals:{enabled:0}},{baseVals:{enabled:0}},{baseVals:{enabled:0}}],waves:[{baseVals:{enabled:1,thick:1,r:0,g:.3,b:.75},init_eqs_str:"a.ma=0;a.mx=0;a.my=0;",frame_eqs_str:"",point_eqs_str:"a.ma+=3.1415*above(a.bass,1)*.01*a.bass;a.ma-=3.1415*above(a.treb,1)*.01*a.treb;a.mx+=.0002*Math.cos(a.ma);a.my+=.0002*Math.sin(a.ma);a.mx=.00001b;b++)a.megabuf[Math.floor(a.index)]=0,a.gmegabuf[Math.floor(a.index)]=0,a.index+=1;a.tstart=\na.time;a.bd_recsz=8;a.bd_oct=4;a.bd_spo=30;a.bd_finc=pow(2,div(1,a.bd_spo));a.bd_nres=a.bd_oct*a.bd_spo;a.bd_minbpm=20;a.bd_maxbpm=pow(2,a.bd_oct)*a.bd_minbpm;a.bd_dat0=1E5;a.bd_tab0=a.bd_dat0+a.bd_nres*a.bd_recsz*3;a.reg00=a.bd_minbpm;a.reg01=a.bd_maxbpm;a.reg02=a.bd_recsz;a.reg03=a.bd_nres;a.reg05=a.bd_finc;a.reg06=a.bd_dat0;a.reg07=a.bd_mp0;a.time_st=0;a.timediff=1;",frame_eqs_str:"a.dt=Math.min(div(1,a.fps),.1);a.dec_m=1-4*a.dt;a.dec_s=1-a.dt;a.dec_xs=1-div(a.dt,6);a.t0=a.time-a.tstart;a.bd_b=a.bass;a.bd_bass_=a.bd_bass_*a.dec_m+(1-a.dec_m)*a.bd_b;a.bd_bass=a.bd_b-a.bd_bass_;a.bd_m=a.mid;a.bd_mid_=a.bd_mid_*a.dec_m+(1-a.dec_m)*a.bd_m;a.bd_mid=a.bd_m-a.bd_mid_;a.bd_t=a.treb;a.bd_treb_=a.bd_treb_*a.dec_m+(1-a.dec_m)*a.bd_t;a.bd_treb=a.bd_t-a.bd_treb_;a.n=0;a.bpm=a.bd_minbpm;for(var b=0;bb;b++){a.bd_exc=a.bd_bass*(.00001>Math.abs(a.bd_src-0)?1:0)+a.bd_mid*(.00001>Math.abs(a.bd_src-1)?1:0)+a.bd_treb*(.00001>Math.abs(a.bd_src-2)?1:0);for(var c=a.oct=0;ca.bd_pk?1:0)?a.bd_pk=a.bd_ampl:0;a.bd_slot+=1}a.bd_slot=0;a.bd_qual=pow(div(a.bd_pk,a.bd_mean)*a.bd_spo-1,1);a.gmegabuf[Math.floor(2*(a.bd_src*a.bd_oct+a.oct))]=a.bd_qual;for(d=0;da.bd_pk?1:0)?(a.bd_pk=a.gmegabuf[Math.floor(a.m)],a.maxind=a.bd_slot):0,a.bd_slot+=1;\na.quali=div(a.bd_pk,a.bd_mean)*a.bd_spo-1;for(b=a.n=0;150>b;b++)a.gmegabuf[Math.floor(50+a.n)]*=0,a.n+=1;a.bd_src=0;a.tsin=0;for(b=a.tcos=0;3>b;b++){for(c=a.oct=0;cb;b++){a.oct=1;for(c=0;ca.maxa?1:0)?(a.maxa=a.ampl,a.maxs=a.bd_src,a.maxo=a.oct):0,a.oct+=1;a.bd_src+=1}a.q28=pow(4*a.bd_qual,1.5);a.q31=a.q28;.00001Math.abs(a.prog-0)?1:0,2>Math.abs(a.testi-a.test0)?1:0))?a.test0=a.testi:0;a.n=a.bd_dat0+a.test0*a.bd_recsz;a.creep=a.gmegabuf[Math.floor(a.n+1)];a.beat=(0a.creepo?1:0);a.beatct=.000011.2*sqrt(a.bass_att)?1:0)?(a.rot=.00001 1.333)))).xyz;\n ret_1 = tmpvar_12;\n vec4 tmpvar_13;\n tmpvar_13.w = 1.0;\n tmpvar_13.xyz = tmpvar_12;\n ret = tmpvar_13.xyz;\n }",comp:" shader_body { \n vec3 ret_1;\n ret_1 = mix (texture (sampler_main, uv).xyz, texture (sampler_main, ((0.5 - uv) + 0.5)).xyz, vec3(0.5, 0.5, 0.5));\n ret_1 = (1.0 - ((ret_1 * \n (1.0 - ret_1)\n ) * 4.0));\n vec4 tmpvar_2;\n tmpvar_2.w = 1.0;\n tmpvar_2.xyz = ret_1;\n ret = tmpvar_2.xyz;\n }"}},function(a,e){a.exports={baseVals:{rating:5,gammaadj:1,decay:.925,echo_zoom:1.007,echo_orient:3,wave_brighten:0,brighten:1,darken:1,solarize:1,wave_a:.001,wave_scale:.01,wave_smoothing:0,modwavealphastart:1,modwavealphaend:1,warpanimspeed:1.459,warpscale:2.007,fshader:1,zoom:.9999,warp:.01,sx:.9999,wave_r:0,wave_g:0,wave_b:0,ob_size:.015,ob_b:1,ib_size:.26,mv_x:64,mv_y:48,mv_l:5,mv_a:0},shapes:[{baseVals:{enabled:1,sides:100,textured:1,rad:.789,ang:.6283,tex_zoom:1.17257,r:0,g:1,border_a:0},init_eqs_str:"a.an=0;a.vx=0;a.vy=0;",frame_eqs_str:"a.rad=.65+.1*a.bass;a.an=.99*a.an+.1*(a.bass-a.treb);a.ang=.1*a.an+.6;"},{baseVals:{enabled:0}},{baseVals:{enabled:0}},{baseVals:{enabled:0}}],waves:[{baseVals:{enabled:1,samples:495,sep:4,spectrum:1,thick:1,additive:1,scaling:100,smoothing:1,r:0,g:.04,b:0,a:.99},init_eqs_str:"a.my_z=0;a.d=0;a.n=0;a.y3=0;a.z2=0;a.y1=0;a.w=0;a.t5=0;a.w2=0;a.t1=0;a.x1=0;a.q6=0;a.zoom=0;a.p=0;a.q1=0;a.q5=0;a.t8=0;a.z3=0;a.w3=0;a.t3=0;a.my_x=0;a.x3=0;a.t6=0;a.my_y=0;a.pi=0;a.q4=0;a.t7=0;a.rd=0;a.w1=0;a.x2=0;a.t2=0;a.l=0;a.y2=0;a.q2=0;a.z1=0;a.q3=0;a.t4=0;a.t2=0;a.t3=0;a.t4=0;a.ab=1;",frame_eqs_str:"a.t1=a.q1;a.t2=a.q2;a.t3=a.q3;a.t4=a.q4;a.t5=a.q5;a.t6=a.q6;a.t8=.07;a.t7=1;",point_eqs_str:"a.t7=-a.t7;a.pi=Math.asin(1);a.n=180;a.rd=.075;a.my_x=.5*Math.sin(a.sample*a.pi*4+(a.t7+1)*a.t8)+Math.cos(a.sample*a.pi*a.n)*a.rd*Math.sin(a.sample*a.pi*4+(a.t7+1)*a.t8);a.my_y=.5*Math.cos(a.sample*a.pi*4+(a.t7+1)*a.t8)+Math.cos(a.sample*a.pi*a.n)*a.rd*Math.cos(a.sample*a.pi*4+(a.t7+1)*a.t8);a.my_z=Math.sin(a.sample*a.pi*a.n)*a.rd;a.d=1.4;a.zoom=.65;a.w1=a.q2;a.w2=a.q3;a.w3=a.q4;a.x1=Math.cos(a.w1)*a.my_x+Math.sin(a.w1)*a.my_y;a.y1=-Math.sin(a.w1)*a.my_x+Math.cos(a.w1)*a.my_y;\na.z1=a.my_z;a.x2=Math.cos(a.w2)*a.x1+Math.sin(a.w2)*a.z1;a.z2=-Math.sin(a.w2)*a.x1+Math.cos(a.w2)*a.z1;a.y2=a.y1;a.y3=Math.cos(a.w3)*a.y2+Math.sin(a.w3)*a.z2;a.z3=-Math.sin(a.w3)*a.y2+Math.cos(a.w3)*a.z2;a.x3=a.x2;a.l=sqrt(a.x3*a.x3+a.y3*a.y3);a.w=Math.atan2(a.x3,a.y3);a.p=Math.tan(Math.asin(1)+Math.atan2(a.d+a.z3,a.l));a.d=sqrt(a.x3*a.x3+a.y3*a.y3+(a.z3+a.d)*(a.z3+a.d));a.my_x=a.zoom*Math.sin(a.w)*a.p;a.my_y=a.zoom*Math.cos(a.w)*a.p;a.x=.5+a.my_x;a.y=.5+a.my_y;a.b=-a.z3+.5;a.b=.5*Math.min(1,Math.max(0,\na.b));a.r=1-2*a.b;"},{baseVals:{enabled:0}},{baseVals:{enabled:0}},{baseVals:{enabled:0}}],init_eqs_str:"a.index2=0;a.index=0;a.q12=0;a.q22=0;a.q21=0;a.q29=0;a.q1=0;a.dec_med=0;a.rott=0;a.is_beat=0;a.q31=0;a.q23=0;a.k1=0;a.q24=0;a.dec_slow=0;a.q4=0;a.q26=0;a.p2=0;a.avg=0;a.beat=0;a.p1=0;a.peak=0;a.q2=0;a.q27=0;a.p3=0;a.q3=0;a.t0=0;a.q28=0;a.q30=0;a.q20=0;a.p4=0;a.step=0;a.step=0;",frame_eqs_str:"a.dec_med=pow(.7,div(30,a.fps));a.dec_slow=pow(.99,div(30,a.fps));a.beat=Math.max(Math.max(a.bass,a.mid),a.treb);a.avg=a.avg*a.dec_slow+a.beat*(1-a.dec_slow);a.is_beat=above(a.beat,-2+a.avg+a.peak)*above(a.time,a.t0+.1);a.t0=a.is_beat*a.time+(1-a.is_beat)*a.t0;a.peak=a.is_beat*a.beat+(1-a.is_beat)*a.peak*a.dec_med;a.index=mod(a.index+a.is_beat,16);a.index2=mod(a.index2+a.is_beat*bnot(a.index),2);a.q20=a.avg;a.q21=a.beat;a.q22=a.peak;a.q23=a.index;a.q24=a.is_beat;a.q26=a.bass_att+\na.mid_att+a.treb_att;a.q27=a.index+1;a.q28=a.index2;a.q29=2*(mod(a.index,2)-.5);a.k1=a.is_beat*equal(mod(a.index,2),0);a.p1=a.k1*(a.p1+1)+(1-a.k1)*a.p1;a.p2=a.dec_med*a.p2+(1-a.dec_med)*a.p1;a.rott=div(3.1416*a.p2,8);a.q1=Math.cos(a.rott);a.q2=Math.sin(a.rott);a.q3=-a.q2;a.q4=a.q1;a.step+=a.q24;a.p3=a.p3*a.dec_slow+(1-a.dec_slow)*a.step;a.q30=a.step;a.p4=a.dec_slow*a.p4+(1-a.dec_slow)*a.q27;a.q31=a.p4;a.q12=a.time-a.t0;a.monitor=a.q12;a.zoom=1;a.rot=-0;a.dx=0;",pixel_eqs_str:"a.zoom=1.3;",warp:" shader_body { \n vec2 uv_1;\n vec2 uv6_2;\n vec2 tmpvar_3;\n tmpvar_3 = ((uv - 0.5) * aspect.xy);\n float tmpvar_4;\n tmpvar_4 = (((q29 * 2.0) * sqrt(\n dot (tmpvar_3, tmpvar_3)\n )) + (rand_frame * 64.0)).x;\n uv_1 = (uv + (clamp (\n ((sin(tmpvar_4) / cos(tmpvar_4)) * normalize(tmpvar_3))\n , vec2(-2.0, -2.0), vec2(2.0, 2.0)) / 20.0));\n uv6_2 = (0.4 * sin((tmpvar_3 * 22.0)));\n vec4 tmpvar_5;\n tmpvar_5.w = 1.0;\n tmpvar_5.xyz = (((q24 * \n (((texture (sampler_main, uv_1).xyz - (\n ((texture (sampler_blur1, fract(uv_1)).xyz * scale1) + bias1)\n * 0.04)) + (0.15 * (vec3(\n (0.1 / sqrt(dot (uv6_2, uv6_2)))\n ) * roam_cos.xyz))) - 0.02)\n ) * 0.98) + ((1.0 - q24) * texture (sampler_main, uv_orig).xyz));\n ret = tmpvar_5.xyz;\n }",comp:"uniform sampler2D sampler_rand00;\n shader_body { \n vec4 tmpvar_1;\n tmpvar_1 = texture (sampler_main, uv);\n vec4 tmpvar_2;\n tmpvar_2.w = 1.0;\n tmpvar_2.xyz = ((texture (sampler_rand00, (0.4 + \n (0.6 * tmpvar_1.xy)\n )) * tmpvar_1.z) * 3.0).xyz;\n ret = tmpvar_2.xyz;\n }"}},function(a,e){a.exports={baseVals:{rating:2,gammaadj:2.7,wave_mode:1,modwavealphabyvolume:1,wave_a:2.707,wave_scale:1.025,wave_smoothing:.1,modwavealphastart:.77,modwavealphaend:1.01,warpscale:1.331,zoom:1.014,warp:.21786,wave_r:.65,wave_g:.65,wave_b:.65,mv_a:0},shapes:[{baseVals:{enabled:0}},{baseVals:{enabled:0}},{baseVals:{enabled:0}},{baseVals:{enabled:0}}],waves:[{baseVals:{enabled:0}},{baseVals:{enabled:0}},{baseVals:{enabled:0}},{baseVals:{enabled:0}}],init_eqs_str:"",frame_eqs_str:"a.wave_r+=.35*(.6*Math.sin(3.98*a.time)+.4*Math.sin(11.047*a.time));a.wave_g+=.35*(.6*Math.sin(.835*a.time)+.4*Math.sin(1.081*a.time));a.wave_b+=.35*(.6*Math.sin(.814*a.time)+.4*Math.sin(1.011*a.time));a.cx+=.11*(.6*Math.sin(.374*a.time)+.4*Math.sin(.294*a.time));a.cy+=.11*(.6*Math.sin(.393*a.time)+.4*Math.sin(.223*a.time));a.dx+=.005*(.6*Math.sin(.173*a.time)+.4*Math.sin(.223*a.time));a.decay-=.01*equal(mod(a.frame,20),0);",pixel_eqs_str:"",pixel_eqs:"",warp:" shader_body { \n vec4 tmpvar_1;\n tmpvar_1.w = 1.0;\n tmpvar_1.xyz = (texture (sampler_main, (uv + (\n (texture (sampler_main, (mix (uv, uv_orig, vec2(-1.0, -1.0)) + texsize.zw)).xy - 0.37)\n * 0.01))).xyz - 0.004);\n ret = tmpvar_1.xyz;\n }",comp:" shader_body { \n vec4 tmpvar_1;\n tmpvar_1 = texture (sampler_main, uv);\n vec4 tmpvar_2;\n tmpvar_2.w = 1.0;\n tmpvar_2.xyz = mix (vec3(dot (tmpvar_1.xyz, vec3(0.3333, 0.3333, 0.3333))), tmpvar_1.xyz, vec3(3.0, 3.0, 3.0));\n ret = tmpvar_2.xyz;\n }"}},function(a,e){a.exports={baseVals:{rating:0,gammaadj:1.9,echo_zoom:1.16936,wave_mode:7,modwavealphabyvolume:1,wave_a:0,wave_scale:1.015009,wave_smoothing:.522,modwavealphastart:.83,modwavealphaend:1.31,warpscale:3.138,zoom:1.009006,warp:536e-6,wave_r:.5,wave_g:.5,wave_b:.5,mv_a:0},shapes:[{baseVals:{enabled:0}},{baseVals:{enabled:0}},{baseVals:{enabled:0}},{baseVals:{enabled:0}}],waves:[{baseVals:{enabled:0}},{baseVals:{enabled:0}},{baseVals:{enabled:0}},{baseVals:{enabled:0}}],init_eqs_str:"a.dx_residual=0;a.dy_residual=0;a.bass_thresh=0;",frame_eqs_str:"a.wave_r=.85+.25*Math.sin(.437*a.time+1);a.wave_g=.85+.25*Math.sin(.544*a.time+2);a.wave_b=.85+.25*Math.sin(.751*a.time+3);a.rot+=.01*(.6*Math.sin(.381*a.time)+.4*Math.sin(.579*a.time));a.cx+=.21*(.6*Math.sin(.374*a.time)+.4*Math.sin(.294*a.time));a.cy+=.21*(.6*Math.sin(.393*a.time)+.4*Math.sin(.223*a.time));a.dx+=.003*(.6*Math.sin(.234*a.time)+.4*Math.sin(.277*a.time));a.dy+=.003*(.6*Math.sin(.284*a.time)+.4*Math.sin(.247*a.time));a.decay-=.01*equal(mod(a.frame,6),0);a.dx+=\na.dx_residual;a.dy+=a.dy_residual;a.bass_thresh=2*above(a.bass_att,a.bass_thresh)+(1-above(a.bass_att,a.bass_thresh))*(.96*(a.bass_thresh-1.3)+1.3);a.dx_residual=.016*equal(a.bass_thresh,2.13)*Math.sin(7*a.time)+(1-equal(a.bass_thresh,2.13))*a.dx_residual;a.dy_residual=.012*equal(a.bass_thresh,2.13)*Math.sin(9*a.time)+(1-equal(a.bass_thresh,2.13))*a.dy_residual;a.wave_x-=7*a.dx_residual;a.wave_y-=7*a.dy_residual;a.wave_mystery=.03*a.time;a.zoom+=.005*(.6*Math.sin(.1934*a.time+3)+.4*Math.sin(.307*\na.time+9));a.zoom+=.4*Math.max(0,a.bass_att-1.1);",pixel_eqs_str:"",pixel_eqs:"",warp:" shader_body { \n vec3 ret_1;\n ret_1 = texture (sampler_main, uv).xyz;\n ret_1 = (ret_1 + ((ret_1 - \n ((texture (sampler_blur2, uv).xyz * scale2) + bias2)\n ) * 0.3));\n ret_1 = (ret_1 * 0.9);\n ret_1 = (ret_1 + ((\n ((texture (sampler_noise_lq, ((\n (uv_orig * texsize.xy)\n * \n (texsize_noise_lq.zw * 0.4)\n ) + rand_frame.xy)).xyz - 0.5) / 256.0)\n * 122.0) * clamp (\n (treb_att - 1.0)\n , 0.0, 1.0)));\n ret_1 = mix (ret_1, vec3(dot (ret_1, vec3(0.32, 0.49, 0.29))), vec3(0.2, 0.2, 0.2));\n vec4 tmpvar_2;\n tmpvar_2.w = 1.0;\n tmpvar_2.xyz = ret_1;\n ret = tmpvar_2.xyz;\n }",comp:" shader_body { \n vec3 ret_1;\n ret_1 = (texture (sampler_main, uv).xyz + ((\n (texture (sampler_blur1, uv).xyz * scale1)\n + bias1) * 0.4));\n vec3 tmpvar_2;\n tmpvar_2 = pow (ret_1, vec3(0.5, 0.8, 1.7));\n ret_1 = tmpvar_2;\n vec4 tmpvar_3;\n tmpvar_3.w = 1.0;\n tmpvar_3.xyz = tmpvar_2;\n ret = tmpvar_3.xyz;\n }"}},function(a,e){a.exports={baseVals:{rating:5,gammaadj:1,decay:1,wave_mode:6,additivewave:1,wave_dots:1,wave_thick:1,wave_brighten:0,wave_a:100,wave_scale:3.63,wave_smoothing:.9,wave_mystery:-.3,modwavealphastart:1.15,modwavealphaend:1.55,warpscale:1.331,zoom:.96971,warp:0,wave_r:.6,wave_g:.6,wave_b:.6,mv_x:64,mv_y:48,mv_l:0,mv_r:.35,mv_g:.35,mv_b:.35,mv_a:0},shapes:[{baseVals:{enabled:0}},{baseVals:{enabled:0}},{baseVals:{enabled:0}},{baseVals:{enabled:0}}],waves:[{baseVals:{enabled:0}},{baseVals:{enabled:0}},{baseVals:{enabled:0}},{baseVals:{enabled:0}}],init_eqs_str:"a.decay_rate=0;a.q6=0;a.rot_sum=0;a.q1=0;a.q5=0;a.prev_beat=0;a.is_beat=0;a.min_att=0;a.beat=0;a.decay_to=0;a.rot_sim=0;a.q2=0;a.q3=0;a.beat_level=0;a.rot_sum=0;a.q2=.07+.00004*randint(1E3)+.00003*randint(1E3);a.q3=1.035+.06*(randint(1E3)+randint(1E3)+randint(1E3))*.000333;",frame_eqs_str:"a.wave_r+=.5*(.6*Math.sin(1.98*a.time)+.4*Math.sin(3.047*a.time));a.wave_g+=.5*(.6*Math.sin(2.835*a.time)+.4*Math.sin(2.081*a.time));a.wave_b+=.5*(.6*Math.sin(3.814*a.time)+.4*Math.sin(1.011*a.time));a.cx=.5;a.cy=.5;a.rot=a.q2;a.zoom=a.zoom-1+a.q3;a.rot_sum+=a.rot;a.q1=-a.rot_sum;a.q5=Math.cos(a.rot_sum);a.q6=Math.sin(a.rot_sim);a.monitor=a.q2;a.min_att=2.5;a.decay_to=.8;a.decay_rate=pow(.999,a.fps);a.beat=div(a.bass,Math.max(a.min_att,a.bass_att));a.beat=Math.max(a.beat,div(a.mid,\nMath.max(a.min_att,a.mid_att)));a.beat=Math.max(a.beat,div(a.treb,Math.max(a.min_att,a.treb_att)));a.beat=Math.max(a.beat,(a.prev_beat-a.decay_to)*a.decay_rate+a.decay_to);a.beat_level=24*(a.beat-a.prev_beat-.02);a.is_beat=above(a.beat_level,.5);a.prev_beat=a.beat;a.wave_a=a.beat_level;",pixel_eqs_str:"",pixel_eqs:"",warp:" shader_body { \n vec4 tmpvar_1;\n tmpvar_1.w = 1.0;\n tmpvar_1.xyz = (texture (sampler_pw_main, uv).xyz - 0.004);\n ret = tmpvar_1.xyz;\n }",comp:" shader_body { \n vec2 uv_1;\n vec2 uv2_2;\n uv_1 = (uv - 0.5);\n uv_1 = (uv_1 * (min (aspect.x, aspect.y) * 0.8));\n uv_1 = (uv_1 * aspect.xy);\n float tmpvar_3;\n tmpvar_3 = sin(q1);\n float tmpvar_4;\n tmpvar_4 = cos(q1);\n uv2_2.x = ((uv_1.x * tmpvar_4) - (uv_1.y * tmpvar_3));\n uv2_2.y = ((uv_1.x * tmpvar_3) + (uv_1.y * tmpvar_4));\n uv2_2 = (uv2_2 * aspect.zw);\n uv2_2 = (uv2_2 + 0.5);\n vec4 tmpvar_5;\n tmpvar_5.w = 1.0;\n tmpvar_5.xyz = (abs((\n (texture (sampler_main, uv2_2).xyz * 2.65)\n + \n (((texture (sampler_blur1, uv2_2).xyz * scale1) + bias1) * -2.0)\n )) * 1.5);\n ret = tmpvar_5.xyz;\n }"}},function(a,e){a.exports={baseVals:{rating:5,gammaadj:1.9,echo_zoom:1.16936,wave_mode:5,additivewave:1,wave_a:0,wave_scale:.899719,wave_smoothing:.63,wave_mystery:1,modwavealphastart:2,modwavealphaend:2,warpscale:2.593743,zoom:1.00496,warp:.278033,sx:.999666,sy:.9999,wave_r:.65,wave_g:.65,wave_b:.65,mv_x:0,mv_y:48,mv_dx:-.941273,mv_dy:.426319,mv_l:5,mv_r:.315997,mv_g:.078173,mv_b:.941976,mv_a:0},shapes:[{baseVals:{enabled:0}},{baseVals:{enabled:0}},{baseVals:{enabled:0}},{baseVals:{enabled:0}}],waves:[{baseVals:{enabled:0}},{baseVals:{enabled:0}},{baseVals:{enabled:0}},{baseVals:{enabled:0}}],init_eqs_str:"a.du=0;a.q1=0;a.rg=0;a.q9=0;a.mult=0;a.ang2=0;a.dv=0;a.q4=0;a.q2=0;a.dist=0;a.q3=0;a.rg=0;",frame_eqs_str:"a.wave_r+=.35*(.6*Math.sin(.98*a.time)+.4*Math.sin(1.047*a.time));a.wave_g+=.35*(.6*Math.sin(.835*a.time)+.4*Math.sin(1.081*a.time));a.wave_b+=.35*(.6*Math.sin(.814*a.time)+.4*Math.sin(1.011*a.time));a.q1=2*a.cx-1+.52*(.6*Math.sin(.374*a.time)+.4*Math.sin(.294*a.time));a.q2=2*a.cy-1+.52*(.6*Math.sin(.393*a.time)+.4*Math.sin(.223*a.time));a.q3=2*a.cx-1+.52*(.6*Math.sin(.174*-a.time)+.4*Math.sin(.364*a.time));a.q4=2*a.cy-1+.52*(.6*Math.sin(.234*a.time)+.4*Math.sin(.271*-a.time));\na.decay-=.01*equal(mod(a.frame,5),0);a.rg=Math.max(.95*a.rg,.3+.5*Math.min(2,1.3*Math.max(0,a.mid_att-1)));a.q9=a.rg;",pixel_eqs_str:"a.du=2*a.x-1-a.q1;a.dv=2*a.y-1-a.q2;a.dist=sqrt(a.du*a.du+a.dv*a.dv);a.ang2=Math.atan2(a.du,a.dv);a.mult=div(.008,a.dist+.4);a.dx=a.mult*Math.sin(a.ang2-1.5);a.dy=a.mult*Math.cos(a.ang2-1.5);a.du=2*a.x-1-a.q3;a.dv=2*a.y-1-a.q4;a.dist=sqrt(a.du*a.du+a.dv*a.dv);a.ang2=Math.atan2(a.du,a.dv);a.mult=div(.008,a.dist+.4);a.dx+=a.mult*Math.sin(a.ang2+1.5);a.dy+=a.mult*Math.cos(a.ang2+1.5);",warp:" shader_body { \n vec3 ret_1;\n vec2 tmpvar_2;\n tmpvar_2 = mix (uv_orig, uv, vec2(q9));\n ret_1 = texture (sampler_main, tmpvar_2).xyz;\n ret_1 = (ret_1 + ((ret_1 - \n ((texture (sampler_blur1, tmpvar_2).xyz * scale1) + bias1)\n ) * 0.3));\n ret_1 = (ret_1 * 0.9);\n ret_1 = (ret_1 + ((\n ((texture (sampler_noise_lq, ((\n (uv_orig * texsize.xy)\n * \n (texsize_noise_lq.zw * 0.4)\n ) + rand_frame.xy)).xyz - 0.5) / 256.0)\n * 122.0) * clamp (\n (treb_att - 1.0)\n , 0.0, 1.0)));\n ret_1 = mix (ret_1, vec3(dot (ret_1, vec3(0.32, 0.49, 0.29))), vec3(0.2, 0.2, 0.2));\n vec4 tmpvar_3;\n tmpvar_3.w = 1.0;\n tmpvar_3.xyz = ret_1;\n ret = tmpvar_3.xyz;\n }",comp:" shader_body { \n vec4 tmpvar_1;\n tmpvar_1.w = 1.0;\n tmpvar_1.xyz = (0.3 + (0.4 * vec3(dot (texture (sampler_main, uv).xyz, vec3(0.32, 0.49, 0.29)))));\n ret = tmpvar_1.xyz;\n }"}},function(a,e){a.exports={baseVals:{rating:5,gammaadj:1.9,echo_zoom:1.169,echo_orient:1,wave_mode:5,additivewave:1,wave_a:0,wave_scale:.9,wave_smoothing:.63,wave_mystery:1,modwavealphastart:2,modwavealphaend:2,warpscale:1.331,zoom:1.004,warp:.19788,sx:.99967,sy:.9999,wave_r:.65,wave_g:.65,wave_b:.65,mv_x:0,mv_y:48,mv_dx:-.941,mv_dy:.426,mv_l:5,mv_r:.316,mv_g:.078,mv_b:.942,mv_a:0},shapes:[{baseVals:{enabled:0}},{baseVals:{enabled:0}},{baseVals:{enabled:0}},{baseVals:{enabled:0}}],waves:[{baseVals:{enabled:0}},{baseVals:{enabled:0}},{baseVals:{enabled:0}},{baseVals:{enabled:0}}],init_eqs_str:"a.du=0;a.q1=0;a.dv=0;a.q2=0;a.dist=0;a.ang2=0;a.mult=0;",frame_eqs_str:"a.wave_r+=.2*(.6*Math.sin(.98*a.time)+.4*Math.sin(1.047*a.time));a.wave_g+=.2*(.6*Math.sin(.835*a.time)+.4*Math.sin(1.081*a.time));a.wave_b+=.2*(.6*Math.sin(.814*a.time)+.4*Math.sin(1.011*a.time));a.q1=2*a.cx-1+.6*(.6*Math.sin(.374*a.time)+.4*Math.sin(.294*a.time));a.q2=2*a.cy-1+.6*(.6*Math.sin(.393*a.time)+.4*Math.sin(.223*a.time));",pixel_eqs_str:"a.du=2*a.x-1-a.q1;a.dv=2*a.y-1-a.q2;a.dist=sqrt(a.du*a.du+a.dv*a.dv);a.ang2=Math.atan2(a.du,a.dv)+.15*a.time;a.mult=.65*Math.sin(.05*a.dist);a.dx=a.mult*Math.sin(2*a.ang2-1.5);a.dy=a.mult*Math.cos(2*a.ang2-1.5);",warp:" shader_body { \n vec3 ret_1;\n vec4 tmpvar_2;\n tmpvar_2 = texture (sampler_main, uv);\n ret_1 = (tmpvar_2.xyz + ((tmpvar_2.xyz - \n ((texture (sampler_blur2, uv).xyz * scale2) + bias2)\n ) * 0.3));\n ret_1 = (ret_1 * 0.9);\n ret_1 = (ret_1 + ((\n ((texture (sampler_noise_lq, ((\n (uv_orig * texsize.xy)\n * \n (texsize_noise_lq.zw * 0.4)\n ) + rand_frame.xy)).xyz - 0.5) / 256.0)\n * 122.0) * clamp (\n (treb_att - 1.0)\n , 0.0, 1.0)));\n vec3 tmpvar_3;\n tmpvar_3 = mix (ret_1, vec3(dot (ret_1, vec3(0.32, 0.49, 0.29))), vec3(0.2, 0.2, 0.2));\n ret_1 = tmpvar_3;\n vec4 tmpvar_4;\n tmpvar_4.w = 1.0;\n tmpvar_4.xyz = tmpvar_3;\n ret = tmpvar_4.xyz;\n }",comp:" shader_body { \n vec2 uv2_1;\n vec3 ret_2;\n uv2_1 = (uv + (vec2(1.0, 0.0) * texsize.zw));\n float tmpvar_3;\n tmpvar_3 = (((texture (sampler_main, uv2_1).xyz + \n (((texture (sampler_blur1, uv2_1).xyz * scale1) + bias1) * 0.4)\n ) + (\n ((texture (sampler_blur2, uv2_1).xyz * scale2) + bias2)\n * 0.15)) + ((\n (texture (sampler_blur3, uv2_1).xyz * scale3)\n + bias3) * 0.1)).x;\n uv2_1 = (uv + (vec2(-1.0, 0.0) * texsize.zw));\n float tmpvar_4;\n tmpvar_4 = (((texture (sampler_main, uv2_1).xyz + \n (((texture (sampler_blur1, uv2_1).xyz * scale1) + bias1) * 0.4)\n ) + (\n ((texture (sampler_blur2, uv2_1).xyz * scale2) + bias2)\n * 0.15)) + ((\n (texture (sampler_blur3, uv2_1).xyz * scale3)\n + bias3) * 0.1)).x;\n uv2_1 = (uv + (vec2(0.0, 1.0) * texsize.zw));\n float tmpvar_5;\n tmpvar_5 = (((texture (sampler_main, uv2_1).xyz + \n (((texture (sampler_blur1, uv2_1).xyz * scale1) + bias1) * 0.4)\n ) + (\n ((texture (sampler_blur2, uv2_1).xyz * scale2) + bias2)\n * 0.15)) + ((\n (texture (sampler_blur3, uv2_1).xyz * scale3)\n + bias3) * 0.1)).x;\n uv2_1 = (uv + (vec2(0.0, -1.0) * texsize.zw));\n vec3 tmpvar_6;\n tmpvar_6.z = 0.14;\n tmpvar_6.x = (tmpvar_3 - tmpvar_4);\n tmpvar_6.y = (tmpvar_5 - ((\n (texture (sampler_main, uv2_1).xyz + (((texture (sampler_blur1, uv2_1).xyz * scale1) + bias1) * 0.4))\n + \n (((texture (sampler_blur2, uv2_1).xyz * scale2) + bias2) * 0.15)\n ) + (\n ((texture (sampler_blur3, uv2_1).xyz * scale3) + bias3)\n * 0.1)).x);\n ret_2 = (0.5 + (0.5 * normalize(tmpvar_6)));\n vec2 x_7;\n x_7 = (ret_2.xy - 0.5);\n ret_2 = (ret_2 * clamp ((\n sqrt(dot (x_7, x_7))\n * 5.0), 0.0, 1.0));\n ret_2 = ret_2.xxy;\n ret_2 = (ret_2 + 1.15);\n ret_2 = (ret_2 * mix (ret_2, (ret_2 * \n (((texture (sampler_blur3, uv).xyz * scale3) + bias3) - ((texture (sampler_blur1, uv).xyz * scale1) + bias1))\n ), pow (hue_shader.yxz, ret_2)));\n ret_2 = (ret_2 * ret_2);\n vec4 tmpvar_8;\n tmpvar_8.w = 1.0;\n tmpvar_8.xyz = ret_2;\n ret = tmpvar_8.xyz;\n }"}},function(a,e){a.exports={baseVals:{rating:5,gammaadj:2.4,decay:1,echo_zoom:.997,echo_alpha:.5,echo_orient:3,wave_mode:2,wave_brighten:0,wrap:0,darken:1,wave_a:.001,wave_scale:.28,wave_smoothing:.9,modwavealphastart:.71,modwavealphaend:1.3,warpanimspeed:2.599,warpscale:.01,zoomexp:.99817,zoom:.86978,warp:.01,sy:1.0017,wave_r:0,wave_g:0,wave_b:0,ob_size:0,ob_r:1,ob_g:1,ob_b:1,ib_size:.04,ib_r:0,ib_g:0,ib_b:0,ib_a:1,mv_x:64,mv_y:48,mv_l:.5,mv_r:.35,mv_g:.35,mv_b:.35,mv_a:.2},shapes:[{baseVals:{enabled:1,sides:20,additive:1,textured:1,rad:1.99867,tex_zoom:.49486,g:1,b:1,a:.75,r2:1,b2:1,a2:1,border_a:0},init_eqs_str:"",frame_eqs_str:""},{baseVals:{enabled:0}},{baseVals:{enabled:0}},{baseVals:{enabled:0}}],waves:[{baseVals:{enabled:0}},{baseVals:{enabled:0}},{baseVals:{enabled:0}},{baseVals:{enabled:0}}],init_eqs_str:"a.ang2=0;a.redsine=0;a.greensine=0;a.bluesine=0;a.redif=0;a.greenif=0;a.blueif=0;a.decay=.1;",frame_eqs_str:"a.sx=1;a.sy=1;a.redsine=.5+.15*a.bass*Math.sin(3*a.time);a.greensine=.5+.15*a.mid*Math.sin(2*a.time);a.bluesine=.5+.15*a.treb*Math.sin(a.time);a.redif=.9*(.00001 (q21 * q13)) && (tmpvar_2.x <= (q24 * q11)))) {\n ret_1.y = (tmpvar_2.y - (tmpvar_3.x * 0.5));\n };\n if (((ret_1.y > (q22 * q11)) && (ret_1.y <= (q25 * q12)))) {\n ret_1.z = (tmpvar_2.z - (tmpvar_3.y * 0.5));\n };\n if (((ret_1.z > (q23 * q12)) && (ret_1.z <= (q26 * q13)))) {\n ret_1.x = (tmpvar_2.x - (tmpvar_3.z * 0.5));\n };\n ret_1 = (ret_1 + ((ret_1 - \n ((texture (sampler_blur1, uv).xyz * scale1) + bias1)\n ) * 0.2));\n ret_1 = (ret_1 - 0.02);\n vec4 tmpvar_4;\n tmpvar_4.w = 1.0;\n tmpvar_4.xyz = ret_1;\n ret = tmpvar_4.xyz;\n }",comp:" shader_body { \n vec2 uv_1;\n vec3 ret_2;\n uv_1 = (0.05 + (0.9 * uv));\n vec4 tmpvar_3;\n tmpvar_3 = texture (sampler_main, uv_1);\n ret_2 = tmpvar_3.xyz;\n vec4 tmpvar_4;\n tmpvar_4 = texture (sampler_noisevol_hq, (((vec3(0.05, 0.05, 0.0) * uv_1.xyy) * (\n (q28 * texsize.xyy)\n * texsize_noisevol_hq.zww)) + ((time * vec3(0.0, 0.0, 1.0)) * q31)));\n if (((tmpvar_3.x > (q26 * q13)) && (tmpvar_3.x <= (q23 * q11)))) {\n ret_2.z = (tmpvar_3.z - (tmpvar_4.x * 0.5));\n };\n if (((tmpvar_3.y > (q25 * q11)) && (tmpvar_3.y <= (q22 * q12)))) {\n ret_2.x = (tmpvar_3.x - (tmpvar_4.y * 0.5));\n };\n if (((ret_2.z > (q24 * q12)) && (ret_2.z <= (q21 * q13)))) {\n ret_2.y = (tmpvar_3.y - (tmpvar_4.z * 0.5));\n };\n ret_2 = (abs((\n ((texture (sampler_blur1, uv_1).xyz * scale1) + bias1)\n - ret_2)) * 6.0);\n ret_2 = (ret_2 * 1.333);\n vec4 tmpvar_5;\n tmpvar_5.w = 1.0;\n tmpvar_5.xyz = ret_2;\n ret = tmpvar_5.xyz;\n }"}},function(a,e){a.exports={baseVals:{rating:4,gammaadj:1.98,decay:.5,echo_zoom:1,echo_alpha:.5,echo_orient:3,wave_mode:7,additivewave:1,modwavealphabyvolume:1,wave_brighten:0,darken:1,wave_a:.001,wave_scale:1.157,wave_smoothing:.63,modwavealphastart:.71,modwavealphaend:1.3,warpscale:1.331,zoom:.9995,warp:.009,wave_r:.65,wave_g:.65,wave_b:.65,ob_size:.5,ob_r:.01,ib_size:.26,mv_x:64,mv_y:48,mv_l:0,mv_a:0,b1x:.7,b1ed:0},shapes:[{baseVals:{enabled:1,sides:14,num_inst:512,rad:.1026,tex_ang:.62832,r2:1,g2:0,a2:1,border_a:0},init_eqs_str:"a.ma=0;a.mx=0;a.my=0;a.r_border=0;a.g_border=0;a.b_border=0;",frame_eqs_str:"a.ma+=3.1415*above(a.bass,1)*.01*a.bass;a.ma-=3.1415*above(a.treb,1)*.01*a.treb;a.mx+=.0002*Math.cos(a.ma);a.my+=.0002*Math.sin(a.ma);a.mx=.00001 1.0)\n ) * (\n (tmpvar_9 * -2.0)\n + 1.570796)));\n tmpvar_7 = (tmpvar_9 * sign((uv5_4.y / uv5_4.x)));\n if ((abs(uv5_4.x) > (1e-08 * abs(uv5_4.y)))) {\n if ((uv5_4.x < 0.0)) {\n if ((uv5_4.y >= 0.0)) {\n tmpvar_7 += 3.141593;\n } else {\n tmpvar_7 = (tmpvar_7 - 3.141593);\n };\n };\n } else {\n tmpvar_7 = (sign(uv5_4.y) * 1.570796);\n };\n xlat_mutablers0.x = (((tmpvar_7 / 3.1416) * 6.0) * q28);\n float tmpvar_10;\n tmpvar_10 = (1.5 / sqrt(dot (uv_1, uv_1)));\n xlat_mutablers0.y = tmpvar_10;\n vec2 tmpvar_11;\n tmpvar_11.x = (xlat_mutablers0.x + (q9 * 4.0));\n tmpvar_11.y = (tmpvar_10 + ((q9 * q28) * 4.0));\n xlat_mutablerss = (tmpvar_11 / 12.0);\n vec2 tmpvar_12;\n tmpvar_12.x = q5;\n tmpvar_12.y = q6;\n ofs_2 = (0.1 * tmpvar_12.yx);\n float tmpvar_13;\n float tmpvar_14;\n tmpvar_14 = -(q9);\n tmpvar_13 = fract(tmpvar_14);\n mat2 tmpvar_15;\n tmpvar_15[uint(0)].x = 1.0;\n tmpvar_15[uint(0)].y = -0.0;\n tmpvar_15[1u].x = 0.0;\n tmpvar_15[1u].y = 1.0;\n xlat_mutableuv2 = ((uv_1 * (\n (q13 * tmpvar_13)\n * tmpvar_15)) * aspect.yx);\n xlat_mutableneu = (3.0 * texture (sampler_main, fract((\n (xlat_mutableuv2 + 0.5)\n + ofs_2)))).xyz;\n ret1_3 = max (vec3(0.0, 0.0, 0.0), (xlat_mutableneu * (1.0 - \n (tmpvar_13 * tmpvar_13)\n )));\n float tmpvar_16;\n tmpvar_16 = fract((tmpvar_14 + 0.3333333));\n mat2 tmpvar_17;\n tmpvar_17[uint(0)].x = -0.4990803;\n tmpvar_17[uint(0)].y = -0.8665558;\n tmpvar_17[1u].x = 0.8665558;\n tmpvar_17[1u].y = -0.4990803;\n xlat_mutableuv2 = ((uv_1 * (\n (q13 * tmpvar_16)\n * tmpvar_17)) * aspect.yx);\n xlat_mutableneu = (3.0 * texture (sampler_main, fract((\n (xlat_mutableuv2 + 0.5)\n + ofs_2)))).xyz;\n ret1_3 = max (ret1_3, (xlat_mutableneu * (1.0 - \n (tmpvar_16 * tmpvar_16)\n )));\n float tmpvar_18;\n tmpvar_18 = fract((tmpvar_14 + 0.6666667));\n mat2 tmpvar_19;\n tmpvar_19[uint(0)].x = -0.5018377;\n tmpvar_19[uint(0)].y = 0.8649619;\n tmpvar_19[1u].x = -0.8649619;\n tmpvar_19[1u].y = -0.5018377;\n xlat_mutableuv2 = ((uv_1 * (\n (q13 * tmpvar_18)\n * tmpvar_19)) * aspect.yx);\n xlat_mutableneu = (3.0 * texture (sampler_main, fract((\n (xlat_mutableuv2 + 0.5)\n + ofs_2)))).xyz;\n ret1_3 = max (ret1_3, (xlat_mutableneu * (1.0 - \n (tmpvar_18 * tmpvar_18)\n )));\n float tmpvar_20;\n tmpvar_20 = fract((tmpvar_14 + 1.0));\n mat2 tmpvar_21;\n tmpvar_21[uint(0)].x = 0.9999949;\n tmpvar_21[uint(0)].y = 0.003185092;\n tmpvar_21[1u].x = -0.003185092;\n tmpvar_21[1u].y = 0.9999949;\n xlat_mutableuv2 = ((uv_1 * (\n (q13 * tmpvar_20)\n * tmpvar_21)) * aspect.yx);\n xlat_mutableneu = (3.0 * texture (sampler_main, fract((\n (xlat_mutableuv2 + 0.5)\n + ofs_2)))).xyz;\n ret1_3 = max (ret1_3, (xlat_mutableneu * (1.0 - \n (tmpvar_20 * tmpvar_20)\n )));\n vec2 tmpvar_22;\n tmpvar_22.x = (ret1_3.x + ret1_3.z);\n tmpvar_22.y = (ret1_3.x - ret1_3.y);\n xlat_mutableret2 = (((\n (texture (sampler_blur1, fract((xlat_mutablerss + (tmpvar_22 / 2.0)))).xyz * scale1)\n + bias1) / tmpvar_10) * 12.0);\n vec4 tmpvar_23;\n tmpvar_23.w = 1.0;\n tmpvar_23.xyz = ((ret1_3 + (\n ((bass_att * 0.004) / sqrt(dot (uv_1, uv_1)))\n * roam_sin).xyz) + (sqrt(xlat_mutableret2.zxy) * clamp (\n (1.0 - (ret1_3 * 4.0))\n , 0.0, 1.0)));\n ret = tmpvar_23.xyz;\n }"}},function(a,e){a.exports={baseVals:{rating:2,gammaadj:1.98,decay:.5,echo_zoom:1,echo_alpha:.5,echo_orient:3,wave_mode:7,additivewave:1,wave_thick:1,modwavealphabyvolume:1,wave_brighten:0,wrap:0,darken:1,wave_a:.001,wave_scale:.958,wave_smoothing:.45,modwavealphastart:0,modwavealphaend:1.32,warpanimspeed:1.459,warpscale:2.007,zoom:.9999,warp:.01,sx:.9999,wave_r:0,wave_g:0,wave_b:0,ob_size:0,ob_g:.1,ob_b:1,ob_a:1,ib_size:0,ib_r:0,ib_g:0,ib_b:0,mv_x:25.6,mv_y:9.6,mv_l:0,mv_r:.5,mv_g:.5,mv_b:.5,mv_a:0,b1ed:0},shapes:[{baseVals:{enabled:0}},{baseVals:{enabled:0}},{baseVals:{enabled:0}},{baseVals:{enabled:0}}],waves:[{baseVals:{enabled:0}},{baseVals:{enabled:0}},{baseVals:{enabled:0}},{baseVals:{enabled:0}}],init_eqs_str:"a.n=0;a.reg26=0;a.uvx0=0;a.reg34=0;a.reg28=0;a.reg23=0;a.q25=0;a.angchg=0;a.reg20=0;a.reg15=0;a.reg10=0;a.q12=0;a.v3=0;a.q22=0;a.q21=0;a.diry=0;a.q13=0;a.q6=0;a.posx=0;a.fps_=0;a.reg25=0;a.uvx=0;a.q1=0;a.travel=0;a.posz=0;a.q5=0;a.movz=0;a.dirz=0;a.dec_s=0;a.reg16=0;a.slow=0;a.reg36=0;a.reg22=0;a.uvy=0;a.rotz=0;a.dist_=0;a.q23=0;a.q24=0;a.reg24=0;a.cran0=0;a.vx=0;a.ran2=0;a.q11=0;a.q10=0;a.reg14=0;a.posy=0;a.vy=0;a.vz=0;a.reg31=0;a.dirx=0;a.dec_m=0;a.q4=0;a.start=0;a.reg12=\n0;a.reg13=0;a.c2=0;a.reg37=0;a.s3=0;a.yslope=0;a.q16=0;a.xslope=0;a.q26=0;a.reg38=0;a.reg35=0;a.reg11=0;a.tx=0;a.avg=0;a.uvz=0;a.c3=0;a.uvy0=0;a.reg27=0;a.q19=0;a.beat=0;a.q17=0;a.vol=0;a.reg32=0;a.reg21=0;a.uvz0=0;a.len=0;a.reg18=0;a.reg30=0;a.q27=0;a.slen=0;a.q14=0;a.dist=0;a.reg17=0;a.v1=0;a.speed=0;a.s1=0;a.t0=0;a.s2=0;a.ran1=0;a.reg33=0;a.q7=0;a.ds=0;a.q28=0;a.ty=0;a.c1=0;a.v2=0;a.q20=0;a.q8=0;a.avg=.01;a.q7=.2;a.q8=div(randint(200),100)-1;a.q16=1.2;a.q18=randint(.8)+.1;a.q17=2.6;a.start=1;a.travel=\n0;a.rotz=0;a.look=0;a.slow=0;a.t0=a.time+3;a.lampx=.5;a.lampy=.5;a.cran0=randint(1);for(var b=a.n=0;1E4>b;b++)a.gmegabuf[Math.floor(a.n)]=0,a.n+=1;for(b=a.n=0;1E4>b;b++)a.megabuf[Math.floor(a.n)]=0,a.n+=1;a.trelx=0;a.trely=0;a.trelz=0;a.reg20=1;a.reg21=0;a.reg22=0;a.reg23=0;a.reg24=1;a.reg25=0;a.reg26=0;a.reg27=0;a.reg28=1;b=0;do{b+=1;var c;a.ran1=div(randint(800),100);a.ran2=div(randint(800),100);a.ran3=div(randint(800),100);a.posx=randint(10)-5;a.posy=randint(10)-5;a.posz=randint(10)-5;a.c1=Math.cos(a.ran1);\na.c2=Math.cos(a.ran2);a.c3=Math.cos(a.ran3);a.s1=Math.sin(a.ran1);a.s2=Math.sin(a.ran2);a.s3=Math.sin(a.ran3);a.reg20=a.c2*a.c1;a.reg21=a.c2*a.s1;a.reg22=-a.s2;a.reg23=a.s3*a.s2*a.c1-a.c3*a.s1;a.reg24=a.s3*a.s2*a.s1+a.c3*a.c1;a.reg25=a.s3*a.c2;a.reg26=a.c3*a.s2*a.c1+a.s3*a.s1;a.reg27=a.c3*a.s2*a.s1-a.s3*a.c1;a.reg28=a.c3*a.c2;a.dist=.001;var d=0;do{d+=1;a.uvx=div(a.reg26*a.dist,a.q7);a.uvy=div(a.reg27*a.dist,a.q7);a.uvz=div(a.reg28*a.dist,a.q7);a.uvx+=a.posx;a.uvy+=a.posy;a.uvz+=a.posz;a.uvx=8*(div(a.uvx,\n8)+30.5-Math.floor(div(a.uvx,8)+30.5)-.5);a.uvy=8*(div(a.uvy,8)+30.5-Math.floor(div(a.uvy,8)+30.5)-.5);a.uvz=8*(div(a.uvz,8)+30.5-Math.floor(div(a.uvz,8)+30.5)-.5);a.uvx0=a.uvx+a.q8;a.uvy0=a.uvy+a.q8;a.uvz0=a.uvz+a.q8;for(c=0;8>c;c++)a.uvx=.00001a.uvx?1:0)?-2-a.uvx:a.uvx,a.uvy=.00001a.uvy?1:0)?-2-a.uvy:a.uvy,a.uvz=.00001a.uvz?1:0)?-2-a.uvz:a.uvz,a.slen=\na.uvx*a.uvx+a.uvy*a.uvy+a.uvz*a.uvz,a.uvx=(.00001a.slen?1:0)?4*a.uvx:.00001a.slen?1:0)?div(a.uvx,a.slen):a.uvx)*a.q17+a.uvx0,a.uvy=(.00001a.slen?1:0)?4*a.uvy:.00001a.slen?1:0)?div(a.uvy,a.slen):a.uvy)*a.q17+a.uvy0,a.uvz=(.00001a.slen?1:0)?4*a.uvz:.00001a.slen?1:0)?div(a.uvz,a.slen):a.uvz)*a.q17+a.uvz0;a.len=sqrt(a.uvx*a.uvx+a.uvy*a.uvy+a.uvz*a.uvz);a.dist*=1.05;c=(.6>a.dist?1:0)*(30d);d=.06>a.dist?1:0}while(.00001b);",frame_eqs_str:"a.fps_=0*a.fps_+1*(.00001=a.fps?1:0)?a.fps:25+.5*(a.fps-25));a.dec_s=1-div(.03*30,a.fps_);a.dec_m=1-div(3,a.fps_);a.beat=a.time>a.t0+3?1:0;a.t0=.00001a.dist_?1:0);a.travel=.00001b;b++){a.n+=1;a.ran1=div(randint(100),100);a.ran2=div(randint(100),200)-.25;a.tx=Math.cos(1.57*a.n+a.ran2)*(4>=a.n?1:0)*a.ran1;a.ty=Math.sin(1.57*a.n+a.ran2)*(4>=a.n?1:0)*a.ran1;a.c1=Math.cos(a.v1);a.c2=Math.cos(a.v2+a.ty);a.c3=Math.cos(a.v3+a.tx);a.s1=Math.sin(a.v1);a.s2=Math.sin(a.v2+a.ty);a.s3=Math.sin(a.v3+a.tx);a.reg10=a.c2*a.c1;a.reg11=a.c2*a.s1;a.reg12=-a.s2;a.reg13=a.s3*a.s2*a.c1-a.c3*a.s1;a.reg14=a.s3*a.s2*a.s1+a.c3*\na.c1;a.reg15=a.s3*a.c2;a.reg16=a.c3*a.s2*a.c1+a.s3*a.s1;a.reg17=a.c3*a.s2*a.s1-a.s3*a.c1;a.reg18=a.c3*a.c2;a.reg20=a.reg30;a.reg21=a.reg31;a.reg22=a.reg32;a.reg23=a.reg33;a.reg24=a.reg34;a.reg25=a.reg35;a.reg26=a.reg36;a.reg27=a.reg37;a.reg28=a.reg38;a.q20=a.reg10*a.reg20+a.reg11*a.reg23+a.reg12*a.reg26;a.q21=a.reg10*a.reg21+a.reg11*a.reg24+a.reg12*a.reg27;a.q22=a.reg10*a.reg22+a.reg11*a.reg25+a.reg12*a.reg28;a.q23=a.reg13*a.reg20+a.reg14*a.reg23+a.reg15*a.reg26;a.q24=a.reg13*a.reg21+a.reg14*a.reg24+\na.reg15*a.reg27;a.q25=a.reg13*a.reg22+a.reg14*a.reg25+a.reg15*a.reg28;a.q26=a.reg16*a.reg20+a.reg17*a.reg23+a.reg18*a.reg26;a.q27=a.reg16*a.reg21+a.reg17*a.reg24+a.reg18*a.reg27;a.q28=a.reg16*a.reg22+a.reg17*a.reg25+a.reg18*a.reg28;a.reg20=a.q20;a.reg21=a.q21;a.reg22=a.q22;a.reg23=a.q23;a.reg24=a.q24;a.reg25=a.q25;a.reg26=a.q26;a.reg27=a.q27;a.reg28=a.q28;a.dist=.002;var c,d=0;do{d+=1;a.uvx=div(a.reg26*a.dist,a.q7);a.uvy=div(a.reg27*a.dist,a.q7);a.uvz=div(a.reg28*a.dist,a.q7);a.uvx+=a.posx;a.uvy+=\na.posy;a.uvz+=a.posz;a.uvx=8*(div(a.uvx,8)+30.5-Math.floor(div(a.uvx,8)+30.5)-.5);a.uvy=8*(div(a.uvy,8)+30.5-Math.floor(div(a.uvy,8)+30.5)-.5);a.uvz=8*(div(a.uvz,8)+30.5-Math.floor(div(a.uvz,8)+30.5)-.5);a.uvx0=a.uvx+a.q8;a.uvy0=a.uvy+a.q8;a.uvz0=a.uvz+a.q8;for(c=0;8>c;c++)a.uvx=.00001a.uvx?1:0)?-2-a.uvx:a.uvx,a.uvy=.00001a.uvy?1:0)?-2-a.uvy:a.uvy,a.uvz=.00001\na.uvz?1:0)?-2-a.uvz:a.uvz,a.slen=a.uvx*a.uvx+a.uvy*a.uvy+a.uvz*a.uvz,a.uvx=(.00001a.slen?1:0)?4*a.uvx:.00001a.slen?1:0)?div(a.uvx,a.slen):a.uvx)*a.q17+a.uvx0,a.uvy=(.00001a.slen?1:0)?4*a.uvy:.00001a.slen?1:0)?div(a.uvy,a.slen):a.uvy)*a.q17+a.uvy0,a.uvz=(.00001a.slen?1:0)?4*a.uvz:.00001a.slen?1:0)?div(a.uvz,a.slen):a.uvz)*a.q17+a.uvz0;a.len=sqrt(a.uvx*a.uvx+a.uvy*a.uvy+a.uvz*a.uvz);a.dist*=1.1;c=(.6>a.dist?1:0)*(30d);a.megabuf[Math.floor(a.n)]=a.megabuf[Math.floor(a.n)]*a.dec_s+(1-a.dec_s)*a.dist;a.avg+=Math.abs(div(a.megabuf[Math.floor(a.n)],5))}a.n=0;for(b=a.avg=0;5>b;b++)a.n+=1,a.avg+=Math.abs(div(a.megabuf[Math.floor(a.n)],5));a.xslope=Math.min(Math.max(div(2,a.avg)*(a.megabuf[1]-a.megabuf[3]),-3),3);a.yslope=Math.min(Math.max(div(2,a.avg)*(a.megabuf[4]-a.megabuf[2]),-3),3);a.monitor=a.avg;a.dist_=a.dist_*a.dec_s+(1-a.dec_s)*a.dist;a.q10=a.ds*a.q7;a.q14=Math.abs(a.ds)+\n2*(Math.abs(a.v1)+Math.abs(a.v2)+Math.abs(a.v3))+.05*a.start;a.q19=.6+.4*Math.sin(.02*a.time+6*a.cran0);a.start*=.9;a.warp=0;a.zoom=1;a.dx=div(-a.v2,a.q16);a.dy=div(a.v3,a.q16);a.rot=a.v1;a.vx-=div(a.v2,a.q16);a.vy+=div(a.v3,a.q16);a.vz+=a.v1;a.q11=a.vx;a.q12=a.vy;a.movz+=a.ds;a.q13=a.movz;a.monitor=a.q13;",pixel_eqs_str:"",pixel_eqs:"",warp:"float sustain;\nfloat ffac;\nfloat xlat_mutabledist;\nfloat xlat_mutablelimit;\nfloat xlat_mutablestruc;\nvec2 xlat_mutableuv1;\nvec3 xlat_mutableuv2;\n shader_body { \n mat3 tmpvar_1;\n tmpvar_1[uint(0)].x = q20;\n tmpvar_1[uint(0)].y = q23;\n tmpvar_1[uint(0)].z = q26;\n tmpvar_1[1u].x = q21;\n tmpvar_1[1u].y = q24;\n tmpvar_1[1u].z = q27;\n tmpvar_1[2u].x = q22;\n tmpvar_1[2u].y = q25;\n tmpvar_1[2u].z = q28;\n vec3 tmpvar_2;\n tmpvar_2.x = q4;\n tmpvar_2.y = q5;\n tmpvar_2.z = q6;\n sustain = (0.98 - q14);\n ffac = q17;\n vec2 uv_3;\n uv_3 = (((uv - 0.5) * (1.0 - \n (q10 / (1.0 - ((texture (sampler_blur1, uv).xyz * scale1) + bias1).z))\n )) + 0.5);\n xlat_mutableuv1 = (((uv_orig - 0.5) * aspect.xy) * q16);\n vec4 tmpvar_4;\n tmpvar_4 = fract((8.0 * texture (sampler_noise_lq, (uv_3 + rand_frame.yz))));\n vec3 tmpvar_5;\n tmpvar_5 = tmpvar_4.xyz;\n if ((tmpvar_4.y > (0.4 * rad))) {\n vec3 tmpvar_6;\n tmpvar_6 = (tmpvar_4.xyz - vec3(0.5, 0.5, 0.5));\n vec4 nb2_7;\n vec4 nb_8;\n vec2 tmpvar_9;\n tmpvar_9 = (0.0078125 * floor((\n (128.0 * texture (sampler_pc_main, (uv_3 - texsize.zw)).yz)\n + vec2(0.5, 0.5))));\n nb_8.x = (1.0 - (tmpvar_9.y + (0.015625 * \n (tmpvar_9.x - 0.5)\n )));\n vec2 tmpvar_10;\n tmpvar_10 = (0.0078125 * floor((\n (128.0 * texture (sampler_pc_main, (uv_3 + (texsize.zw * vec2(1.0, -1.0)))).yz)\n + vec2(0.5, 0.5))));\n nb_8.y = (1.0 - (tmpvar_10.y + (0.015625 * \n (tmpvar_10.x - 0.5)\n )));\n vec2 tmpvar_11;\n tmpvar_11 = (0.0078125 * floor((\n (128.0 * texture (sampler_pc_main, (uv_3 + texsize.zw)).yz)\n + vec2(0.5, 0.5))));\n nb_8.z = (1.0 - (tmpvar_11.y + (0.015625 * \n (tmpvar_11.x - 0.5)\n )));\n vec2 tmpvar_12;\n tmpvar_12 = (0.0078125 * floor((\n (128.0 * texture (sampler_pc_main, (uv_3 + (texsize.zw * vec2(-1.0, 1.0)))).yz)\n + vec2(0.5, 0.5))));\n nb_8.w = (1.0 - (tmpvar_12.y + (0.015625 * \n (tmpvar_12.x - 0.5)\n )));\n vec2 tmpvar_13;\n tmpvar_13 = (0.0078125 * floor((\n (128.0 * texture (sampler_pc_main, (uv_3 + (texsize.zw * vec2(0.0, -1.0)))).yz)\n + vec2(0.5, 0.5))));\n nb2_7.x = (1.0 - (tmpvar_13.y + (0.015625 * \n (tmpvar_13.x - 0.5)\n )));\n vec2 tmpvar_14;\n tmpvar_14 = (0.0078125 * floor((\n (128.0 * texture (sampler_pc_main, (uv_3 + (texsize.zw * vec2(1.0, 0.0)))).yz)\n + vec2(0.5, 0.5))));\n nb2_7.y = (1.0 - (tmpvar_14.y + (0.015625 * \n (tmpvar_14.x - 0.5)\n )));\n vec2 tmpvar_15;\n tmpvar_15 = (0.0078125 * floor((\n (128.0 * texture (sampler_pc_main, (uv_3 + (texsize.zw * vec2(0.0, 1.0)))).yz)\n + vec2(0.5, 0.5))));\n nb2_7.z = (1.0 - (tmpvar_15.y + (0.015625 * \n (tmpvar_15.x - 0.5)\n )));\n vec2 tmpvar_16;\n tmpvar_16 = (0.0078125 * floor((\n (128.0 * texture (sampler_pc_main, (uv_3 + (texsize.zw * vec2(-1.0, 0.0)))).yz)\n + vec2(0.5, 0.5))));\n nb2_7.w = (1.0 - (tmpvar_16.y + (0.015625 * \n (tmpvar_16.x - 0.5)\n )));\n vec4 tmpvar_17;\n tmpvar_17 = min (nb_8, nb2_7);\n nb_8.zw = tmpvar_17.zw;\n nb_8.xy = min (tmpvar_17.xy, tmpvar_17.zw);\n xlat_mutabledist = (min (nb_8.x, nb_8.y) + ((0.006 * tmpvar_6.x) * abs(tmpvar_6.y)));\n } else {\n xlat_mutabledist = tmpvar_5.x;\n };\n vec2 tmpvar_18;\n tmpvar_18 = (0.0078125 * floor((\n (128.0 * texture (sampler_pc_main, uv_3).yz)\n + vec2(0.5, 0.5))));\n xlat_mutabledist = (min (xlat_mutabledist, (1.0 - \n (tmpvar_18.y + (0.015625 * (tmpvar_18.x - 0.5)))\n )) - (q10 * 0.8));\n xlat_mutablelimit = (15.0 + (10.0 * xlat_mutabledist));\n vec3 tmpvar_19;\n tmpvar_19.xy = (xlat_mutableuv1 * xlat_mutabledist);\n tmpvar_19.z = xlat_mutabledist;\n xlat_mutableuv2 = (((tmpvar_19 / q7) * tmpvar_1) + tmpvar_2);\n xlat_mutableuv2 = ((fract(\n ((xlat_mutableuv2 / 8.0) + 0.5)\n ) - 0.5) * 8.0);\n int iterations_21;\n vec3 zz0_22;\n vec3 zz_23;\n zz_23 = xlat_mutableuv2;\n zz0_22 = (xlat_mutableuv2 + q8);\n iterations_21 = int((8.0 - float(\n (xlat_mutabledist > 0.8)\n )));\n for (int n_20 = 0; n_20 <= iterations_21; n_20++) {\n zz_23 = ((2.0 * clamp (zz_23, vec3(-1.0, -1.0, -1.0), vec3(1.0, 1.0, 1.0))) - zz_23);\n zz_23 = (zz_23 * (clamp (\n max ((0.25 / dot (zz_23, zz_23)), 0.25)\n , 0.0, 1.0) * 4.0));\n zz_23 = ((ffac * zz_23) + zz0_22);\n };\n xlat_mutablestruc = (sqrt(dot (zz_23.xz, zz_23.xz)) / xlat_mutablelimit);\n vec2 tmpvar_24;\n tmpvar_24 = (0.0078125 * floor((\n (128.0 * vec2((1.0 - xlat_mutabledist)))\n + vec2(0.5, 0.5))));\n vec2 tmpvar_25;\n tmpvar_25.x = ((64.0 * (\n (1.0 - xlat_mutabledist)\n - tmpvar_24.x)) + 0.5);\n tmpvar_25.y = tmpvar_24.x;\n vec3 tmpvar_26;\n float tmpvar_27;\n tmpvar_27 = (q14 * 2.0);\n tmpvar_26.x = (((1.0 - sustain) * xlat_mutablestruc) + (sustain * mix (texture (sampler_main, uv_3).xyz, \n ((texture (sampler_blur1, uv_3).xyz * scale1) + bias1)\n , vec3(tmpvar_27)).x));\n tmpvar_26.yz = tmpvar_25;\n vec3 tmpvar_28;\n tmpvar_28.y = 1.0;\n tmpvar_28.x = sustain;\n tmpvar_28.z = 1.0;\n vec3 tmpvar_29;\n tmpvar_29.y = 0.0;\n tmpvar_29.x = 0.003921569;\n tmpvar_29.z = (0.01568628 * (0.2 + rad));\n vec4 tmpvar_30;\n tmpvar_30.w = 1.0;\n tmpvar_30.xyz = mix (tmpvar_26, ((\n mix (texture (sampler_main, uv_3).xyz, ((texture (sampler_blur1, uv_3).xyz * scale1) + bias1), vec3(tmpvar_27))\n * tmpvar_28) - tmpvar_29), vec3(clamp ((\n sqrt(dot (zz_23, zz_23))\n - xlat_mutablelimit), 0.0, 1.0)));\n ret = tmpvar_30.xyz;\n }",comp:"float xlat_mutableinten;\nfloat xlat_mutabletmp;\nvec2 xlat_mutableuv1;\nvec2 xlat_mutableuv2;\nfloat xlat_mutablez;\n shader_body { \n float t_rel_2;\n vec3 ret1_3;\n float struc_4;\n xlat_mutableuv1 = ((uv * aspect.xy) - vec2(0.5, 0.5));\n vec4 tmpvar_5;\n tmpvar_5 = texture (sampler_main, uv);\n float tmpvar_6;\n vec4 tmpvar_7;\n tmpvar_7 = texture (sampler_blur1, uv);\n tmpvar_6 = mix (min ((1.0 - tmpvar_5.z), (1.0 - \n ((texture (sampler_blur2, uv).xyz * scale2) + bias2)\n .z)), (1.0 - (\n (tmpvar_7.xyz * scale1)\n + bias1).z), 0.5);\n struc_4 = ((mix (tmpvar_5.xyz, \n ((tmpvar_7.xyz * scale1) + bias1)\n , vec3(tmpvar_6)).x * (1.0 - tmpvar_6)) * tmpvar_6);\n vec2 tmpvar_8;\n tmpvar_8.x = q11;\n tmpvar_8.y = q12;\n xlat_mutableuv2 = ((xlat_mutableuv1 * 0.2) - (tmpvar_8 * 0.2));\n vec2 uvi_9;\n uvi_9 = (xlat_mutableuv2 * 4.0);\n float zv_10;\n zv_10 = (0.008 * time);\n xlat_mutabletmp = clamp (dot ((texture (sampler_noise_hq, \n (xlat_mutableuv2 + (0.03 * ((\n (dot (texture (sampler_noise_hq, uvi_9), vec4(0.32, 0.49, 0.29, 0.0)) + (dot (texture (sampler_noise_hq, (\n (uvi_9 * 2.0)\n + zv_10)), vec4(0.32, 0.49, 0.29, 0.0)) / 2.0))\n + \n (dot (texture (sampler_noise_hq, ((uvi_9 * 4.0) + (2.0 * zv_10))), vec4(0.32, 0.49, 0.29, 0.0)) / 4.0)\n ) + (\n dot (texture (sampler_noise_hq, ((uvi_9 * 8.0) + (4.0 * zv_10))), vec4(0.32, 0.49, 0.29, 0.0))\n / 8.0))))\n ) - 0.4), vec4(0.32, 0.49, 0.29, 0.0)), 0.0, 1.0);\n float tmpvar_11;\n tmpvar_11 = clamp ((pow (xlat_mutabletmp, 1.2) * sign(xlat_mutabletmp)), 0.0, 1.0);\n xlat_mutabletmp = tmpvar_11;\n vec3 tmpvar_12;\n tmpvar_12.xy = vec2(0.1, 0.1);\n tmpvar_12.z = (1.2 - uv.y);\n ret1_3 = ((tmpvar_12 + 0.07) + ((\n clamp (((tmpvar_6 * 2.0) - 1.5), 0.0, 1.0)\n * tmpvar_11) * 4.0));\n t_rel_2 = (q13 * 6.0);\n for (int n_1 = 1; n_1 <= 3; n_1++) {\n xlat_mutablez = (1.0 - fract((\n (float(n_1) / 3.0)\n - \n (fract(-(t_rel_2)) / 3.0)\n )));\n xlat_mutableinten = (((1.0 - xlat_mutablez) * xlat_mutablez) * 2.0);\n vec2 tmpvar_13;\n tmpvar_13.x = q11;\n tmpvar_13.y = q12;\n xlat_mutableuv2 = (((xlat_mutablez * xlat_mutableuv1) / 4.0) - (tmpvar_13 / 6.0));\n vec2 uvi_14;\n uvi_14 = (xlat_mutableuv2 * 4.0);\n float zv_15;\n zv_15 = (0.008 * time);\n xlat_mutabletmp = clamp (dot ((\n (texture (sampler_noise_hq, (xlat_mutableuv2 + (0.03 * (\n ((dot (texture (sampler_noise_hq, uvi_14), vec4(0.32, 0.49, 0.29, 0.0)) + (dot (texture (sampler_noise_hq, \n ((uvi_14 * 2.0) + zv_15)\n ), vec4(0.32, 0.49, 0.29, 0.0)) / 2.0)) + (dot (texture (sampler_noise_hq, (\n (uvi_14 * 4.0)\n + \n (2.0 * zv_15)\n )), vec4(0.32, 0.49, 0.29, 0.0)) / 4.0))\n + \n (dot (texture (sampler_noise_hq, ((uvi_14 * 8.0) + (4.0 * zv_15))), vec4(0.32, 0.49, 0.29, 0.0)) / 8.0)\n )))) - (xlat_mutablez * 0.5))\n - 0.3), vec4(0.32, 0.49, 0.29, 0.0)), 0.0, 1.0);\n xlat_mutabletmp = (((\n clamp ((xlat_mutabletmp * sign(xlat_mutabletmp)), 0.0, 1.0)\n * xlat_mutableinten) * q1) * 2.0);\n ret1_3 = (((ret1_3 + \n ((vec3(4.0, 3.0, 0.8) * q1) * struc_4)\n ) * clamp (\n (1.0 - xlat_mutabletmp)\n , 0.0, 1.0)) + xlat_mutabletmp);\n };\n vec4 tmpvar_16;\n tmpvar_16.w = 1.0;\n tmpvar_16.xyz = (1.0 - exp((-1.6 * ret1_3)));\n ret = tmpvar_16.xyz;\n }"}},function(a,e){a.exports={baseVals:{rating:4,gammaadj:1.98,decay:.5,echo_zoom:1,echo_alpha:.5,echo_orient:3,additivewave:1,wave_dots:1,wave_thick:1,wave_brighten:0,darken:1,wave_a:.001,wave_scale:.527,wave_smoothing:0,wave_mystery:.6,modwavealphastart:0,modwavealphaend:1.32,warpanimspeed:1.459,warpscale:2.007,zoom:.9999,warp:.01,sx:.9999,wave_g:.49,ob_a:1,ib_size:.26,mv_x:64,mv_y:48,mv_l:1.85,mv_r:.5,mv_g:.5,mv_b:.5,mv_a:0,b2x:.6,b3x:.4,b1ed:0},shapes:[{baseVals:{enabled:0}},{baseVals:{enabled:1,sides:5,additive:1,num_inst:256,x:.26,y:.2,rad:.39317,tex_zoom:.9355,g2:0,a2:.2,border_g:0,border_b:0,border_a:0},init_eqs_str:"a.n=0;a.w=0;a.high=0;a.q12=0;a.arg=0;a.q13=0;a.y0=0;a.flen=0;a.x0=0;a.k1=0;a.q11=0;a.z0=0;a.exc=0;a.p2=0;a.p1=0;a.a0=0;a.q2=0;a.slen=0;a.q14=0;a.q3=0;a.q32=0;a.rad0=0;",frame_eqs_str:"a.n=a.instance;a.flen=a.reg00;a.slen=div(a.reg00,2);a.z0=10;a.y0=div(a.gmegabuf[Math.floor(2E3+a.n+a.flen)],a.z0);a.x0=div(a.gmegabuf[Math.floor(2E3+a.n)],a.z0);a.a0=a.gmegabuf[Math.floor(a.n+1E4)];a.k1=div(a.instance,a.num_inst)-.5;a.x=.5+a.x0+Math.sin(8*a.k1*Math.sin(.07*a.q12))*Math.sin(.13*a.q11)*a.q3*.7;a.y=.5+a.q32*(a.y0+Math.sin(8*a.k1*Math.sin(.1*a.q14))*Math.sin(.2*a.q13)*a.q3*.7);a.arg=div(a.q2,8);a.high=Math.exp(-500*pow(a.arg+.5-div(a.instance,a.num_inst),2));a.high+=\nMath.exp(-500*pow(-a.arg+.5-div(a.instance,a.num_inst),2));a.exc=sqrt(pow(a.x-.5,2)+pow(a.y-.5,2));a.rad0=above(a.z0,0)*Math.min(.1,div(a.a0,60))+.005;a.rad0=a.rad0*(1+2*a.exc)*(1+a.high);a.p1=.5+div(Math.sin(a.q12),2);a.p2=.5+div(Math.sin(1.4*a.q13),2);a.exc=pow(a.x-a.p1,2)+pow(a.y-a.p2,2);a.rad=Math.min(a.rad0*(1+div(.004*a.q3,Math.abs(a.exc))),1);a.a=Math.min(8*a.a0+.4,1);a.k1=5*div(a.instance,a.num_inst)+a.high;a.w=1-Math.exp(div(-a.treb_att,2)-.5);a.g=a.w+(1-a.w)*Math.sin(a.k1);a.r=a.w+(1-a.w)*\nMath.sin(a.k1-div(6.28,3));a.b=a.w+(1-a.w)*Math.sin(a.k1-div(12.56,3));a.a2=div(a.a,4);a.g2=0*a.g;a.b2=0*a.b;a.r2=0*a.r;"},{baseVals:{enabled:0}},{baseVals:{enabled:0}}],waves:[{baseVals:{enabled:1,sep:120,spectrum:1,additive:1,scaling:7.52386,smoothing:0,r:0,a:.7},init_eqs_str:"a.flen=0;a.n=0;a.vol=0;a.chg=0;a.dec=0;a.q1=0;a.q2=0;",frame_eqs_str:"",point_eqs_str:"a.flen=a.reg00;a.n=Math.floor(a.sample*a.flen);a.vol=(a.value1+a.value2)*(1+div(.1,a.sample+.03));a.chg=Math.min(Math.max(a.vol-a.gmegabuf[Math.floor(a.n)],-1),1);a.dec=.00001a.q2?1:0))]*(1-a.dec);a.gmegabuf[Math.floor(a.n+1E4)]=.2*a.gmegabuf[Math.floor(a.n+1E4)]+div(.8*a.vol,3);a.a=0;a.x=a.sample;a.y=.2+.23*a.gmegabuf[Math.floor(a.n+0)];"},{baseVals:{enabled:0}},{baseVals:{enabled:0}},{baseVals:{enabled:0}}],init_eqs_str:"a.n=0;a.sw2=0;a.in1=0;a.q12=0;a.q13=0;a.sw3=0;a.dif=0;a.q1=0;a.lev3=0;a.flen=0;a.lev4=0;a.in2=0;a.lev1=0;a.k1=0;a.q11=0;a.ofs=0;a.dec_m=0;a.i=0;a.k=0;a.m2=0;a.vol=0;a.q2=0;a.slen=0;a.m1=0;a.q14=0;a.sw1=0;a.lev2=0;a.q3=0;a.reg00=0;a.vol_=0;a.dec=0;a.q32=0;a.sw4=0;for(var b=a.n=0;5E4>b;b++)a.gmegabuf[Math.floor(a.n)]=0,a.megabuf[Math.floor(a.n)]=0,a.n+=1;a.sw1=randint(8);a.sw2=randint(8);a.sw3=randint(8);a.sw4=randint(8);",frame_eqs_str:"a.flen=512;a.reg00=a.flen;a.slen=div(a.flen,2);a.dec_m=pow(.94,div(30,a.fps));a.n=0;for(var b=a.vol=0;ba.in1*a.in1+a.in2*a.in2?1:0)?.8:.94,a.dec=pow(a.dec,div(30,a.fps)),a.gmegabuf[Math.floor(2E3+a.n)]=a.gmegabuf[Math.floor(2E3+a.n)]*a.dec+(1-a.dec)*a.in1,a.gmegabuf[Math.floor(2E3+a.flen+a.n)]=a.gmegabuf[Math.floor(2E3+a.flen+a.n)]*a.dec+(1-a.dec)*a.in2,a.n+=1;a.q1=div(a.bass+a.treb+a.mid,3);a.q2=div(a.bass_att+a.treb_att+a.mid_att,3);a.q3=a.vol_;a.q11=a.sw1;a.q12=a.sw2;a.q13=\na.sw3;a.q14=a.sw4;a.rot=0;a.zoom=.98;a.warp=.3;a.rot=0;a.q32=a.aspecty;a.monitor=a.dif;",pixel_eqs_str:"",pixel_eqs:"",warp:" shader_body { \n vec4 tmpvar_1;\n tmpvar_1.w = 1.0;\n tmpvar_1.xyz = ((texture (sampler_main, uv).xyz * clamp (\n (q1 - 0.8)\n , 0.0, 1.0)) * 0.92);\n ret = tmpvar_1.xyz;\n }",comp:" shader_body { \n vec3 ret_1;\n ret_1 = (texture (sampler_main, uv).xyz + ((texture (sampler_blur2, uv).xyz * scale2) + bias2));\n ret_1 = (ret_1 + ((0.8 * \n (hue_shader - 0.8)\n ) * (1.0 - uv.y)));\n vec4 tmpvar_2;\n tmpvar_2.w = 1.0;\n tmpvar_2.xyz = ret_1;\n ret = tmpvar_2.xyz;\n }"}},function(a,e){a.exports={baseVals:{rating:0,gammaadj:1.980001,decay:.5,echo_zoom:.999998,echo_alpha:.5,echo_orient:3,wave_mode:4,additivewave:1,wave_dots:1,modwavealphabyvolume:1,darken:1,wave_a:.33064,wave_scale:.897961,wave_smoothing:.108,wave_mystery:.1,modwavealphastart:.72,modwavealphaend:1.28,warpanimspeed:1.4595,warpscale:2.0067,zoom:.9999,warp:.01,sx:.9999,wave_r:0,wave_g:.5,wave_b:.5,wave_y:.54,ob_r:1,ob_g:1,ob_b:1,ib_size:.26,mv_x:24.799994,mv_dy:.16,mv_l:1.5,mv_a:0,b1ed:0},shapes:[{baseVals:{enabled:0}},{baseVals:{enabled:0}},{baseVals:{enabled:0}},{baseVals:{enabled:0}}],waves:[{baseVals:{enabled:1,samples:352,usedots:1,additive:1,scaling:.038558,smoothing:.2,g:0},init_eqs_str:"a.t02=0;a.q1=0;a.ratio=0;a.ampl=0;a.x1=0;a.y1=0;",frame_eqs_str:"a.q1=a.bass_att;",point_eqs_str:"a.r=Math.abs(Math.sin(div(a.frame,38)));a.g=.5*Math.abs(Math.cos(div(a.frame,45)));a.b=.5*Math.abs(Math.sin(div(a.frame,133)));a.a=.3;a.t02+=div(a.q1,10);a.ratio=Math.sin(div(a.frame,49));a.ampl=.01+.4*sqr(Math.sin(div(a.frame,18))*Math.cos(div(a.frame,123)));a.x1=div(a.r-.5,15)+.5+a.ampl*Math.sin(6.28*a.sample);a.y1=div(a.b-.5,15)+.5+a.ampl*Math.cos(6.28*a.sample);a.x=a.x1+.2*(a.ampl+a.ratio)*Math.sin(6.28*a.sample*a.ratio*7.3);a.y=a.y1+.2*(a.ampl+a.ratio)*Math.cos(37.68*a.sample);\n"},{baseVals:{enabled:0}},{baseVals:{enabled:0}},{baseVals:{enabled:0}}],init_eqs_str:"a.index2=0;a.q25=0;a.index=0;a.q18=0;a.q22=0;a.q21=0;a.q29=0;a.q6=0;a.movex=0;a.q1=0;a.dec_med=0;a.q5=0;a.index3=0;a.rott=0;a.is_beat=0;a.q23=0;a.k1=0;a.q24=0;a.dec_slow=0;a.q4=0;a.q26=0;a.p2=0;a.mov=0;a.avg=0;a.beat=0;a.p1=0;a.dx1=0;a.peak=0;a.q2=0;a.q27=0;a.clear=0;a.p3=0;a.q3=0;a.t0=0;a.rot1=0;a.q32=0;a.q28=0;a.q20=0;a.clear=.5;",frame_eqs_str:"a.dec_med=pow(.96,div(30,a.fps));a.dec_slow=pow(.99,div(30,a.fps));a.beat=Math.max(Math.max(a.bass,a.mid),a.treb);a.avg=a.avg*a.dec_slow+a.beat*(1-a.dec_slow);a.is_beat=above(a.beat,.2+a.avg+a.peak)*above(a.time,a.t0+.2);a.t0=a.is_beat*a.time+(1-a.is_beat)*a.t0;a.peak=a.is_beat*a.beat+(1-a.is_beat)*a.peak*a.dec_med;a.index=mod(a.index+a.is_beat,4);a.index2=mod(a.index2+a.is_beat*bnot(a.index),8);a.index3=mod(a.index3+a.is_beat*bnot(a.index)*bnot(a.index2),3);a.q20=a.avg;a.q21=\na.beat;a.q22=a.peak;a.q23=a.index;a.q24=a.is_beat;a.q26=a.bass+a.mid+a.treb;a.k1=a.is_beat*equal(a.index,0);a.p1=a.k1*(a.p1+1)+(1-a.k1)*a.p1;a.p2=a.dec_med*a.p2+(1-a.dec_med)*a.p1;a.p3=a.dec_med*a.p3+(1-a.dec_med)*a.p2;a.rott=div(3.1416*a.p3,4);a.rot1+=a.q26;a.q25=.01*a.rot1;a.q27=8-a.index;a.q28=a.index3;a.dx1=a.dec_med*a.dx1+(1-a.dec_med)*bnot(a.index2);a.q29=a.dx1;a.monitor=a.q29;a.q1=Math.cos(a.rott);a.q2=Math.sin(a.rott);a.q3=-a.q2;a.q4=a.q1;a.q5=1.5*Math.cos(div(a.time,9));a.q6=.5*Math.sin(div(a.time,\n7));a.clear=a.clear*a.dec_med+1-a.dec_med;a.q32=a.clear;a.mov=bnot(a.q24)*a.movex+(div(randint(100),100)-50)*a.q2;a.movex+=div(.2,a.fps)*a.q2;a.q18=a.movex;",pixel_eqs_str:"",pixel_eqs:"",warp:" shader_body { \n vec2 uv6_1;\n vec2 tmpvar_2;\n tmpvar_2 = ((uv - 0.5) * aspect.xy);\n float tmpvar_3;\n tmpvar_3 = (rand_frame * 64.0).x;\n uv6_1 = (0.5 * cos((\n ((tmpvar_2 * mat2(0.7, -0.7, 0.7, 0.7)) * 17.0)\n + \n (rand_frame * 6.0)\n .xy)));\n float x_4;\n x_4 = (uv6_1.x + uv6_1.y);\n vec4 tmpvar_5;\n tmpvar_5.w = 1.0;\n tmpvar_5.xyz = ((0.147 * (\n (texture (sampler_main, (uv + clamp ((\n (sin(tmpvar_3) / cos(tmpvar_3))\n * \n normalize(tmpvar_2)\n ), vec2(-8.0, -8.0), vec2(8.0, 8.0)))).xyz + (0.15 * (vec3((0.01 / \n sqrt((x_4 * x_4))\n )) * roam_cos.xyz)))\n - 0.02)) + ((0.85 * \n (texture (sampler_main, uv_orig).xyz - 0.002)\n ) * q32));\n ret = tmpvar_5.xyz;\n }",comp:"vec2 xlat_mutabledz;\nvec3 xlat_mutableret1;\nvec2 xlat_mutableuv3;\nvec2 xlat_mutableuv4;\n shader_body { \n vec2 uv_1;\n float inten_2;\n float dist_3;\n vec3 ret_4;\n uv_1 = (uv - 0.5);\n float tmpvar_5;\n tmpvar_5 = (time / 2.0);\n dist_3 = (1.0 - fract(tmpvar_5));\n inten_2 = ((sqrt(dist_3) * (1.0 - dist_3)) * 2.0);\n vec2 tmpvar_6;\n tmpvar_6.y = 0.4;\n tmpvar_6.x = q18;\n xlat_mutableuv3 = (((2.0 * uv_1) * dist_3) + tmpvar_6);\n vec2 tmpvar_7;\n tmpvar_7.y = 0.0;\n tmpvar_7.x = texsize.z;\n vec2 tmpvar_8;\n tmpvar_8.y = 0.0;\n tmpvar_8.x = texsize.z;\n xlat_mutabledz.x = (inten_2 * dot ((texture (sampler_main, \n (xlat_mutableuv3 + tmpvar_7)\n ).xyz - texture (sampler_main, \n (xlat_mutableuv3 - tmpvar_8)\n ).xyz), vec3(0.32, 0.49, 0.29)));\n vec2 tmpvar_9;\n tmpvar_9.x = 0.0;\n tmpvar_9.y = texsize.w;\n vec2 tmpvar_10;\n tmpvar_10.x = 0.0;\n tmpvar_10.y = texsize.w;\n xlat_mutabledz.y = (inten_2 * dot ((texture (sampler_main, \n (xlat_mutableuv3 + tmpvar_9)\n ).xyz - texture (sampler_main, \n (xlat_mutableuv3 - tmpvar_10)\n ).xyz), vec3(0.32, 0.49, 0.29)));\n xlat_mutableret1 = max (vec3(0.0, 0.0, 0.0), (texture (sampler_main, xlat_mutableuv3).xyz * inten_2));\n dist_3 = (1.0 - fract((0.3333333 + tmpvar_5)));\n inten_2 = ((sqrt(dist_3) * (1.0 - dist_3)) * 2.0);\n vec2 tmpvar_11;\n tmpvar_11.y = 0.4;\n tmpvar_11.x = q18;\n xlat_mutableuv3 = (((2.0 * uv_1) * dist_3) + tmpvar_11);\n vec2 tmpvar_12;\n tmpvar_12.y = 0.0;\n tmpvar_12.x = texsize.z;\n vec2 tmpvar_13;\n tmpvar_13.y = 0.0;\n tmpvar_13.x = texsize.z;\n xlat_mutabledz.x = (xlat_mutabledz.x + (inten_2 * dot (\n (texture (sampler_main, (xlat_mutableuv3 + tmpvar_12)).xyz - texture (sampler_main, (xlat_mutableuv3 - tmpvar_13)).xyz)\n , vec3(0.32, 0.49, 0.29))));\n vec2 tmpvar_14;\n tmpvar_14.x = 0.0;\n tmpvar_14.y = texsize.w;\n vec2 tmpvar_15;\n tmpvar_15.x = 0.0;\n tmpvar_15.y = texsize.w;\n xlat_mutabledz.y = (xlat_mutabledz.y + (inten_2 * dot (\n (texture (sampler_main, (xlat_mutableuv3 + tmpvar_14)).xyz - texture (sampler_main, (xlat_mutableuv3 - tmpvar_15)).xyz)\n , vec3(0.32, 0.49, 0.29))));\n xlat_mutableret1 = max (xlat_mutableret1, (texture (sampler_main, xlat_mutableuv3).xyz * inten_2));\n dist_3 = (1.0 - fract((0.6666667 + tmpvar_5)));\n inten_2 = ((sqrt(dist_3) * (1.0 - dist_3)) * 2.0);\n vec2 tmpvar_16;\n tmpvar_16.y = 0.4;\n tmpvar_16.x = q18;\n xlat_mutableuv3 = (((2.0 * uv_1) * dist_3) + tmpvar_16);\n vec2 tmpvar_17;\n tmpvar_17.y = 0.0;\n tmpvar_17.x = texsize.z;\n vec2 tmpvar_18;\n tmpvar_18.y = 0.0;\n tmpvar_18.x = texsize.z;\n xlat_mutabledz.x = (xlat_mutabledz.x + (inten_2 * dot (\n (texture (sampler_main, (xlat_mutableuv3 + tmpvar_17)).xyz - texture (sampler_main, (xlat_mutableuv3 - tmpvar_18)).xyz)\n , vec3(0.32, 0.49, 0.29))));\n vec2 tmpvar_19;\n tmpvar_19.x = 0.0;\n tmpvar_19.y = texsize.w;\n vec2 tmpvar_20;\n tmpvar_20.x = 0.0;\n tmpvar_20.y = texsize.w;\n xlat_mutabledz.y = (xlat_mutabledz.y + (inten_2 * dot (\n (texture (sampler_main, (xlat_mutableuv3 + tmpvar_19)).xyz - texture (sampler_main, (xlat_mutableuv3 - tmpvar_20)).xyz)\n , vec3(0.32, 0.49, 0.29))));\n xlat_mutableret1 = max (xlat_mutableret1, (texture (sampler_main, xlat_mutableuv3).xyz * inten_2));\n dist_3 = (1.0 - fract((1.0 + tmpvar_5)));\n inten_2 = ((sqrt(dist_3) * (1.0 - dist_3)) * 2.0);\n vec2 tmpvar_21;\n tmpvar_21.y = 0.4;\n tmpvar_21.x = q18;\n xlat_mutableuv3 = (((2.0 * uv_1) * dist_3) + tmpvar_21);\n vec2 tmpvar_22;\n tmpvar_22.y = 0.0;\n tmpvar_22.x = texsize.z;\n vec2 tmpvar_23;\n tmpvar_23.y = 0.0;\n tmpvar_23.x = texsize.z;\n xlat_mutabledz.x = (xlat_mutabledz.x + (inten_2 * dot (\n (texture (sampler_main, (xlat_mutableuv3 + tmpvar_22)).xyz - texture (sampler_main, (xlat_mutableuv3 - tmpvar_23)).xyz)\n , vec3(0.32, 0.49, 0.29))));\n vec2 tmpvar_24;\n tmpvar_24.x = 0.0;\n tmpvar_24.y = texsize.w;\n vec2 tmpvar_25;\n tmpvar_25.x = 0.0;\n tmpvar_25.y = texsize.w;\n xlat_mutabledz.y = (xlat_mutabledz.y + (inten_2 * dot (\n (texture (sampler_main, (xlat_mutableuv3 + tmpvar_24)).xyz - texture (sampler_main, (xlat_mutableuv3 - tmpvar_25)).xyz)\n , vec3(0.32, 0.49, 0.29))));\n xlat_mutableret1 = max (xlat_mutableret1, (texture (sampler_main, xlat_mutableuv3).xyz * inten_2));\n uv_1 = (uv_1 + xlat_mutabledz);\n vec2 tmpvar_26;\n tmpvar_26.x = q5;\n tmpvar_26.y = q6;\n xlat_mutableuv4 = (uv_1 - (0.4 * tmpvar_26));\n float tmpvar_27;\n tmpvar_27 = (0.1 * clamp ((1.0/(\n (abs(uv_1.y) + 0.1)\n )), 0.0, 12.0));\n vec2 tmpvar_28;\n tmpvar_28.x = (uv_1.x * tmpvar_27);\n tmpvar_28.y = tmpvar_27;\n vec3 tmpvar_29;\n tmpvar_29.xy = vec2(0.0, 0.0);\n tmpvar_29.z = clamp ((1.0 - (3.0 * uv_1.y)), 0.0, 1.0);\n ret_4 = (vec3(0.0, 0.1, 0.1) + (0.1 * tmpvar_29));\n ret_4 = (ret_4 + (vec3(dot (texture (sampler_noise_hq, \n (tmpvar_28 + (0.1 * time))\n ), vec4(0.32, 0.49, 0.29, 0.0))) * (\n (clamp ((1.0 - (12.0 * uv_1.y)), 0.0, 1.0) * 0.1)\n / \n (0.05 + sqrt(dot (xlat_mutableuv4, xlat_mutableuv4)))\n )));\n ret_4 = (ret_4 + ((0.4 * xlat_mutableret1) + (xlat_mutableret1 * q22)));\n vec4 tmpvar_30;\n tmpvar_30.w = 1.0;\n tmpvar_30.xyz = ret_4;\n ret = tmpvar_30.xyz;\n }"}},function(a,e){a.exports={baseVals:{rating:0,gammaadj:1.980001,decay:.5,echo_zoom:.999998,echo_alpha:.5,echo_orient:3,wave_mode:2,modwavealphabyvolume:1,wave_brighten:0,wrap:0,darken:1,wave_a:.001,wave_scale:20.944651,wave_smoothing:0,wave_mystery:.08,modwavealphastart:0,modwavealphaend:1.32,warpanimspeed:1.4595,warpscale:2.0067,zoom:.9999,warp:.01,sx:.9999,wave_r:0,wave_g:.99,ob_size:.015,ob_b:1,ib_size:.26,mv_x:64,mv_y:48,mv_l:1.85,mv_r:.4999,mv_g:.4999,mv_b:.4999,mv_a:0,b1ed:0},shapes:[{baseVals:{enabled:1,textured:1,y:.47,rad:.24057,tex_zoom:.14057,g:.3,b:.5,g2:0,b2:.3,a2:1,border_b:0,border_a:0},init_eqs_str:"a.q24=0;",frame_eqs_str:"a.x=.5;a.y=.5;a.a=a.q24;a.a2=a.q24;"},{baseVals:{enabled:1,sides:100,additive:1,thickoutline:1,rad:.05134,tex_zoom:.12288,r:.3,g:.2,b:.2,a:.7,g2:0,border_r:0,border_g:.5,border_b:.5,border_a:0},init_eqs_str:"",frame_eqs_str:"a.r=.5+.3*Math.sin(a.time);a.g=.5+.3*Math.sin(div(a.time,1.5));a.b=.5+.3*Math.sin(div(a.time,3.7));a.r2=0;a.b2=0;a.g2=0;a.a=.05;a.a2=0;"},{baseVals:{enabled:0}},{baseVals:{enabled:0}}],waves:[{baseVals:{enabled:1,samples:100,thick:1,scaling:.045052,smoothing:.1,a:.6},init_eqs_str:"a.k1=0;a.dy=0;a.dx=0;a.t2=0;a.q24=0;a.q27=0;",frame_eqs_str:"a.t2=.45+div(randint(10),100);",point_eqs_str:"a.k1=bnot(mod(100*a.sample+a.time,2));a.dy=.015*above(Math.sin(11*a.sample),0)+.008*above(Math.sin(a.time+74*a.sample),0)+.008*above(Math.sin(a.time+128*a.sample),0);a.dx=.01*above(Math.sin(27*a.sample),0)+.01*above(Math.sin(a.time+134*a.sample),0);a.x=.2*(a.sample-.5)+.5+a.dx;a.y=a.t2+a.dy;a.a=a.q24*a.k1*bnot(mod(a.q27,4));a.r=.6;a.g=0;a.b=.6;"},{baseVals:{enabled:0}},{baseVals:{enabled:0}},{baseVals:{enabled:0}}],init_eqs_str:"a.dec_xlow=0;a.index2=0;a.speed_=0;a.index=0;a.q12=0;a.q22=0;a.q21=0;a.q6=0;a.q1=0;a.dec_med=0;a.q5=0;a.q9=0;a.movx=0;a.rott=0;a.is_beat=0;a.q23=0;a.k1=0;a.q24=0;a.dec_slow=0;a.q10=0;a.tilt_=0;a.q4=0;a.peakx=0;a.dir__=0;a.dir=0;a.dir_=0;a.movy=0;a.q26=0;a.maxp=0;a.p2=0;a.avg=0;a.trig=0;a.beat=0;a.p1=0;a.peak=0;a.tilt=0;a.q2=0;a.q27=0;a.speed=0;a.q3=0;a.t0=0;a.q7=0;a.q20=0;a.q8=0;a.xk=0;",frame_eqs_str:"a.dec_med=pow(.8,div(30,a.fps));a.dec_slow=pow(.95,div(30,a.fps));a.dec_xlow=pow(.995,div(30,a.fps));a.beat=Math.max(Math.max(a.bass,a.mid),a.treb);a.avg=a.avg*a.dec_slow+a.beat*(1-a.dec_slow);a.is_beat=above(a.beat,.2+a.avg+a.peak)*above(a.time,a.t0+.2);a.t0=a.is_beat*a.time+(1-a.is_beat)*a.t0;a.peak=a.is_beat*a.beat+(1-a.is_beat)*a.peak*a.dec_med;a.index=mod(a.index+a.is_beat,16);a.index2=mod(a.index2+a.is_beat*bnot(a.index),2);a.q20=a.avg;a.q21=a.beat;a.q22=a.peak;a.q23=\na.index;a.q24=a.is_beat;a.q26=a.bass_att+a.mid_att+a.treb_att;a.q27=a.index+1;a.peakx=above(a.q22,1.5*a.maxp);a.maxp=Math.max(a.maxp,a.q22);a.maxp*=a.dec_xlow;a.k1=a.is_beat*equal(mod(a.index,3),0);a.p1=a.k1*(a.p1+1)+(1-a.k1)*a.p1;a.p2=a.dec_med*a.p2+(1-a.dec_med)*a.p1;a.rott=div(3.1416*a.p2,4);a.q1=Math.cos(a.rott);a.q2=Math.sin(a.rott);a.q3=-a.q2;a.q4=a.q1;a.trig=a.q24*bnot(mod(a.index,4));a.dir=a.dir+div(a.trig*a.q26*(div(randint(100),100)-.5),2)+a.peakx;a.dir_=a.dir_*a.dec_slow+a.dir*(1-a.dec_slow);\na.dir__=a.dir__*a.dec_slow+a.dir_*(1-a.dec_slow);a.q5=Math.cos(a.dir__);a.q6=Math.sin(a.dir__);a.speed=a.speed*bnot(a.trig)+div(a.trig*a.q26*5,a.fps);a.speed_=a.speed_*a.dec_slow+a.speed*(1-a.dec_slow);a.movx+=a.speed_*a.q6;a.movy+=a.speed_*a.q5;a.q7=a.movx;a.q8=a.movy;a.tilt=a.dir-a.dir__;a.tilt_=a.dec_slow*a.tilt_+(1-a.dec_slow)*a.tilt;a.monitor=a.maxp;a.q9=Math.cos(a.tilt_*a.speed_);a.q10=Math.sin(a.tilt_*a.speed_);a.q12=a.time;",pixel_eqs_str:"a.zoom=1.3;",warp:" shader_body { \n vec2 uv_1;\n vec2 uv6_2;\n vec2 tmpvar_3;\n tmpvar_3 = ((uv - 0.5) * aspect.xy);\n float tmpvar_4;\n tmpvar_4 = ((2.0 * sqrt(\n dot (tmpvar_3, tmpvar_3)\n )) + (rand_frame * 64.0)).x;\n uv_1 = (uv + (clamp (\n ((sin(tmpvar_4) / cos(tmpvar_4)) * normalize(tmpvar_3))\n , vec2(-2.0, -2.0), vec2(2.0, 2.0)) / 20.0));\n uv6_2 = (0.4 * sin((tmpvar_3 * 22.0)));\n vec4 tmpvar_5;\n tmpvar_5.w = 1.0;\n tmpvar_5.xyz = (((q24 * \n (((texture (sampler_main, uv_1).xyz - (\n ((texture (sampler_blur1, fract(uv_1)).xyz * scale1) + bias1)\n * 0.04)) + (0.15 * (vec3(\n (0.1 / sqrt(dot (uv6_2, uv6_2)))\n ) * roam_cos.xyz))) - 0.02)\n ) * 0.98) + ((1.0 - q24) * texture (sampler_main, uv_orig).xyz));\n ret = tmpvar_5.xyz;\n }",comp:"vec3 xlat_mutableret1;\nvec2 xlat_mutablers;\n shader_body { \n vec2 uv1_1;\n mat2 tmpvar_2;\n tmpvar_2[uint(0)].x = q9;\n tmpvar_2[uint(0)].y = -(q10);\n tmpvar_2[1u].x = q10;\n tmpvar_2[1u].y = q9;\n uv1_1 = (((uv_orig - 0.5) * aspect.xy) * tmpvar_2);\n uv1_1 = (uv1_1 * aspect.yx);\n float tmpvar_3;\n tmpvar_3 = (3.0 / abs(uv1_1.y));\n xlat_mutablers.x = (uv1_1.x * tmpvar_3);\n xlat_mutablers.y = (tmpvar_3 / 2.0);\n mat2 tmpvar_4;\n tmpvar_4[uint(0)].x = q5;\n tmpvar_4[uint(0)].y = -(q6);\n tmpvar_4[1u].x = q6;\n tmpvar_4[1u].y = q5;\n xlat_mutablers = (tmpvar_4 * xlat_mutablers);\n vec2 tmpvar_5;\n tmpvar_5.x = q7;\n tmpvar_5.y = q8;\n xlat_mutablers = (xlat_mutablers + tmpvar_5);\n xlat_mutableret1 = ((texture (sampler_blur1, fract(\n (xlat_mutablers / 12.0)\n )).xyz * scale1) + bias1);\n vec2 tmpvar_6;\n tmpvar_6.y = 0.0;\n tmpvar_6.x = q5;\n vec4 tmpvar_7;\n tmpvar_7.w = 1.0;\n tmpvar_7.xyz = (((xlat_mutableret1 * 32.0) / tmpvar_3) + ((\n ((q22 * sqrt(tmpvar_3)) / 4.0)\n * \n sin(((uv1_1 - q12) * q27))\n .x) * texture (sampler_main, (\n (((4.0 * xlat_mutableret1) / tmpvar_3).xy + ((uv1_1 / 2.0) / (0.5 + abs(uv1_1.y))))\n + tmpvar_6)).xyz));\n ret = tmpvar_7.xyz;\n }"}},function(a,e){a.exports={baseVals:{rating:5,gammaadj:1.98,decay:.5,echo_zoom:.952,echo_alpha:.5,echo_orient:3,wave_mode:5,additivewave:1,wave_thick:1,modwavealphabyvolume:1,wave_brighten:0,darken:1,wave_a:.001,wave_scale:.474,wave_smoothing:.45,modwavealphastart:0,modwavealphaend:1.32,warpanimspeed:1.459,warpscale:2.007,zoom:.9999,warp:.01,sx:.9999,wave_r:.8,wave_g:.49,ob_size:0,ob_a:.3,ib_size:.26,mv_x:64,mv_y:48,mv_l:1.85,mv_r:.5,mv_g:.5,mv_b:.5,mv_a:0,b1x:.8,b1ed:0},shapes:[{baseVals:{enabled:1,sides:7,additive:1,num_inst:1024,rad:.04896,tex_ang:1.00531,tex_zoom:1.53117,r:0,g:1,b:1,a:0,g2:0,border_b:0,border_a:0},init_eqs_str:"a.max_age=0;a.n=0;a.x0=0;a.y0=0;a.z0=0;a.rad0=0;",frame_eqs_str:"a.max_age=a.reg00;a.n=12*a.instance;a.x0=a.gmegabuf[Math.floor(a.n)];a.y0=a.gmegabuf[Math.floor(a.n+1)];a.z0=a.gmegabuf[Math.floor(a.n+2)];.00001a.z0?1:0)?(a.rad=0,a.gmegabuf[Math.floor(a.n+8)]=a.max_age):(a.rad0=div(pow(1-div(a.gmegabuf[Math.floor(a.n+8)],a.max_age),.2),a.z0)*a.gmegabuf[Math.floor(a.n+7)]+.01,a.rad=.032*Math.abs(a.rad0),a.x=.5+div(a.x0,a.z0),a.y=.5+div(a.y0,a.z0));a.a=1;a.a2=.2;a.g=.8;a.g2=0;a.b=.2*(3a.z0?1:0);a.b2=0;\n"},{baseVals:{enabled:0}},{baseVals:{enabled:0}},{baseVals:{enabled:0}}],waves:[{baseVals:{enabled:1,samples:160,sep:20,usedots:1,additive:1,scaling:7.858,smoothing:.1,r:.2,g:.3,a:.6},init_eqs_str:"a.q32=0;",frame_eqs_str:"",point_eqs_str:"a.x=div(randint(100),100);a.y=.5-div(1-.7,a.q32)-.15*(div(randint(100),100)-.5);a.a=.15;a.r=0;a.b=1;a.g=0;"},{baseVals:{enabled:0}},{baseVals:{enabled:0}},{baseVals:{enabled:0}}],init_eqs_str:'a.max_age=0;a.ind2=0;a.n=0;a.free=0;a.high=0;a.index=0;a.tht=0;a.v00=0;a.yobf=0;a.dt=0;a.y0=0;a.q1=0;a.dec_med=0;a.q5=0;a.ran1_=0;a["new"]=0;a.push=0;a.new1=0;a.is_beat=0;a.q24=0;a.vol_alt=0;a.dec_slow=0;a.ran2=0;a.ind1=0;a.q10=0;a.v0=0;a.med=0;a.beat=0;a.vol=0;a.peak=0;a.dec_fast=0;a.q27=0;a.bass1=0;a.q3=0;a.t0=0;a.vol_=0;a.dec=0;a.m=0;a.ran1=0;a.q32=0;a.phi=0;a.air=0;a.max_age=4.5;a.reg00=a.max_age;for(var b=a.n=0;12288>b;b++)a.gmegabuf[Math.floor(a.n)]=a.max_age-1+randint(2),\na.n+=1;a.q9=2*(randint(25)-10);a.q3=div(randint(100),100);a.q4=div(randint(100),100);',frame_eqs_str:'a.dec_fast=1-div(8.8,a.fps);a.dec_med=1-div(6,a.fps);a.dec_slow=1-div(1.6,a.fps);a.vol=div(a.bass+a.med+a.treb,3);a.vol_=a.vol_*a.dec_slow+(1-a.dec_slow)*a.vol;a.beat=a.vol;a.is_beat=above(a.beat,1+4*a.peak)*above(a.time,a.t0+.2);a.t0=.00001b;b++)a.megabuf[Math.floor(a.n)]=a.gmegabuf[Math.floor(a.n)],a.megabuf[Math.floor(a.n+1)]=a.gmegabuf[Math.floor(a.n+\n1)],a.megabuf[Math.floor(a.n+2)]=a.gmegabuf[Math.floor(a.n+2)],.00001a.max_age?1:0)?.00001=10*a.dt*bnot(a.high)?1:0,200>a["new"]?1:0))?(--a.free,a["new"]+=1,a.tht=div(randint(100),500)*(1+1.5*a.high),a.phi=randint(12),a.v0=a.v00*(1+div(randint(10),40)),a.gmegabuf[Math.floor(a.n)]=1.7*a.ran1,a.gmegabuf[Math.floor(a.n+1)]=a.y0+2.2*a.high,a.gmegabuf[Math.floor(a.n+2)]=2,a.gmegabuf[Math.floor(a.n+3)]=a.v0*Math.sin(a.tht)*Math.cos(a.phi),a.gmegabuf[Math.floor(a.n+\n4)]=a.v0*Math.cos(4*a.tht)*(1-1.5*a.high),a.gmegabuf[Math.floor(a.n+5)]=a.v0*Math.sin(a.tht)*Math.sin(a.phi)*1.5*a.vol,a.gmegabuf[Math.floor(a.n+7)]=0,a.gmegabuf[Math.floor(a.n+8)]=div(randint(100),100)):0:(--a.free,a.gmegabuf[Math.floor(a.n)]+=a.gmegabuf[Math.floor(a.n+3)]*a.dt,a.gmegabuf[Math.floor(a.n+1)]+=a.gmegabuf[Math.floor(a.n+4)]*a.dt,a.gmegabuf[Math.floor(a.n+2)]+=a.gmegabuf[Math.floor(a.n+5)]*a.dt,a.gmegabuf[Math.floor(a.n+3)]*=1-a.air,a.gmegabuf[Math.floor(a.n+4)]*=1-a.air,a.gmegabuf[Math.floor(a.n+\n4)]-=.8*a.dt,a.gmegabuf[Math.floor(a.n+5)]*=1-a.air,a.gmegabuf[Math.floor(a.n+7)]=(a.gmegabuf[Math.floor(a.n+1)]>a.yobf?1:0)*Math.min(1,18*(a.gmegabuf[Math.floor(a.n+1)]-a.yobf)),.00001a.gmegabuf[Math.floor(a.n+4)]?1:0))?a.gmegabuf[8]=a.max_age:0,a.gmegabuf[Math.floor(a.n+8)]+=a.dt),a.n+=12;for(b=a.n=0;512>b;b++)a.m=6132+a.n,a.gmegabuf[Math.floor(a.m)]=div(a.gmegabuf[Math.floor(a.n)]+a.megabuf[Math.floor(a.n)],2),a.gmegabuf[Math.floor(a.m+\n1)]=div(a.gmegabuf[Math.floor(a.n+1)]+a.megabuf[Math.floor(a.n+1)],2),a.gmegabuf[Math.floor(a.m+2)]=div(a.gmegabuf[Math.floor(a.n+2)]+a.megabuf[Math.floor(a.n+2)],2),a.gmegabuf[Math.floor(a.m+7)]=a.gmegabuf[Math.floor(a.n+7)],a.gmegabuf[Math.floor(a.m+8)]=a.gmegabuf[Math.floor(a.n+8)],a.n+=12;a.dec=.00001a.bass1?1:0)?.5:.9;a.bass1=a.bass1*a.dec+a.bass*(1-a.dec);a.q1=Math.min(1,Math.max(0,a.bass1-1.5)*Math.abs(a.q3-.5)*3);a.q10=Math.max(a.vol_-.1,.1);a.vol_alt=a.vol;a.q32=a.aspecty;\na.new1=.00001 0.5))))\n , 0.0, 1.0)));\n vec3 tmpvar_21;\n tmpvar_21.z = 0.0;\n tmpvar_21.xy = (((texture (sampler_blur3, uv_2).xyz * scale3) + bias3).xy * vec2(3.0, 5.0));\n ret_3 = (ret_3 + ((xlat_mutablesmoke * \n dot (tmpvar_21, vec3(0.32, 0.49, 0.29))\n ) * vec3(1.0, 0.84, 0.62)));\n float tmpvar_22;\n tmpvar_22 = clamp (((1.2 * \n clamp (texture (sampler_main, uv_2).y, 0.0, 1.0)\n ) - 0.2), 0.0, 1.0);\n vec3 tmpvar_23;\n tmpvar_23.x = tmpvar_22;\n tmpvar_23.y = (tmpvar_22 * tmpvar_22);\n tmpvar_23.z = pow (tmpvar_22, 8.0);\n vec3 tmpvar_24;\n tmpvar_24 = clamp ((tmpvar_23 / vec3(0.8, 0.8, 0.8)), 0.0, 1.0);\n ret_3 = (ret_3 + (tmpvar_24 * (tmpvar_24 * \n (3.0 - (2.0 * tmpvar_24))\n )));\n float tmpvar_25;\n tmpvar_25 = clamp (0.52, 0.0, 1.0);\n vec3 tmpvar_26;\n tmpvar_26.x = tmpvar_25;\n tmpvar_26.y = (tmpvar_25 * tmpvar_25);\n tmpvar_26.z = pow (tmpvar_25, 8.0);\n vec3 tmpvar_27;\n tmpvar_27 = clamp ((tmpvar_26 / vec3(0.8, 0.8, 0.8)), 0.0, 1.0);\n vec3 tmpvar_28;\n tmpvar_28 = mix (clamp (ret_3, 0.0, 1.0), (tmpvar_27 * (tmpvar_27 * \n (3.0 - (2.0 * tmpvar_27))\n )), vec3((pow (\n ((1.0 - uv_2.y) - ((uv_2.x - 0.5) * (q3 - 0.5)))\n , 4.0) * q1)));\n ret_3 = tmpvar_28;\n vec4 tmpvar_29;\n tmpvar_29.w = 1.0;\n tmpvar_29.xyz = tmpvar_28;\n ret = tmpvar_29.xyz;\n }"}},function(a,e){a.exports={baseVals:{rating:5,gammaadj:1.98,decay:.5,echo_zoom:1,echo_alpha:.5,echo_orient:3,wave_mode:7,additivewave:1,wave_thick:1,modwavealphabyvolume:1,wave_brighten:0,wrap:0,darken:1,wave_a:.001,wave_scale:.698,wave_smoothing:.45,modwavealphastart:0,modwavealphaend:1.32,warpanimspeed:1.459,warpscale:2.007,zoom:.9999,warp:.01,sx:.9999,wave_r:0,wave_g:0,wave_b:0,ob_size:0,ob_g:.1,ob_b:1,ob_a:1,ib_size:0,ib_r:0,ib_g:0,ib_b:0,mv_x:25.6,mv_y:9.6,mv_l:5,mv_g:.5,mv_a:0,b1ed:0},shapes:[{baseVals:{enabled:0}},{baseVals:{enabled:0}},{baseVals:{enabled:0}},{baseVals:{enabled:0}}],waves:[{baseVals:{enabled:0}},{baseVals:{enabled:0}},{baseVals:{enabled:0}},{baseVals:{enabled:0}}],init_eqs_str:"a.n=0;a.maxbpm=0;a.reg26=0;a.maxind1=0;a.uvx0=0;a.cosb_=0;a.reg34=0;a.num_res=0;a.reg28=0;a.reg23=0;a.q25=0;a.angchg=0;a.reg20=0;a.reg15=0;a.reg10=0;a.sinb_=0;a.index=0;a.quali=0;a.v3=0;a.q18=0;a.q22=0;a.q21=0;a.diry=0;a.q6=0;a.posx=0;a.dt=0;a.reg25=0;a.mean=0;a.uvx=0;a.q1=0;a.posz=0;a.q5=0;a.reg52=0;a.dt1=0;a.dec_f=0;a.dirz=0;a.dec_s=0;a.reg16=0;a.bt2=0;a.reg36=0;a.minbpm=0;a.reg22=0;a.uvy=0;a.rotz=0;a.cosb=0;a.omega=0;a.dist_=0;a.ec_steps=0;a.bpm=0;a.q23=0;a.q24=0;a.reg24=\n0;a.ran2=0;a.pi=0;a.q10=0;a.reg14=0;a.sinb=0;a.reg53=0;a.posy=0;a.reg31=0;a.dirx=0;a.dec_m=0;a.q4=0;a.start=0;a.reg12=0;a.reg13=0;a.v2p=0;a.c2=0;a.reg37=0;a.ex=0;a.s3=0;a.yslope=0;a.q16=0;a.xslope=0;a.q26=0;a.reg38=0;a.v3p=0;a.reg35=0;a.p2=0;a.mov=0;a.reg11=0;a.tx=0;a.avg=0;a.bt1=0;a.beatsin=0;a.uvz=0;a.c3=0;a.uvy0=0;a.reg27=0;a.q19=0;a.q17=0;a.vol=0;a.reg32=0;a.reg21=0;a.uvz0=0;a.len=0;a.reg18=0;a.beatcos=0;a.reg30=0;a.q2=0;a.b1y=0;a.q27=0;a.bri=0;a.slen=0;a.q14=0;a.dist=0;a.trel1=0;a.p3=0;a.reg17=\n0;a.v1=0;a.speed=0;a.vol2=0;a.q3=0;a.s1=0;a.vol_=0;a.dec=0;a.s2=0;a.quad=0;a.b2y=0;a.ran1=0;a.q32=0;a.reg33=0;a.q7=0;a.ds=0;a.q28=0;a.ty=0;a.excite=0;a.c1=0;a.v2=0;a.q20=0;a.p4=0;a.q8=0;a.maxvol=0;for(var b=a.n=0;2E4>b;b++)a.megabuf[Math.floor(a.n)]=0,a.gmegabuf[Math.floor(a.n)]=0,a.n+=1;a.minbpm=30;a.maxbpm=230;a.num_res=100;for(b=a.index=0;bc;c++)a.uvx=.00001a.uvx?1:0)?-2-a.uvx:a.uvx,a.uvy=.00001a.uvy?1:0)?-2-a.uvy:a.uvy,a.uvz=.00001a.uvz?1:0)?-2-a.uvz:a.uvz,a.slen=a.uvx*a.uvx+a.uvy*a.uvy+a.uvz*a.uvz,a.uvx=2.6*(.00001a.slen?1:0)?4*a.uvx:\n.00001a.slen?1:0)?div(a.uvx,a.slen):a.uvx)+a.uvx0,a.uvy=2.6*(.00001a.slen?1:0)?4*a.uvy:.00001a.slen?1:0)?div(a.uvy,a.slen):a.uvy)+a.uvy0,a.uvz=2.6*(.00001a.slen?1:0)?4*a.uvz:.00001a.slen?1:0)?div(a.uvz,a.slen):a.uvz)+a.uvz0;a.len=sqrt(a.uvx*a.uvx+a.uvy*a.uvy+a.uvz*a.uvz);a.dist*=1.05;c=(.6>a.dist?1:0)*(30d);d=.06>a.dist?1:0}while(.00001b);",frame_eqs_str:"a.dt=Math.min(div(1,a.fps),.1);a.dec_f=.8;a.dec_m=1-4*a.dt;a.dec_s=1-a.dt;a.vol=div(a.bass+div(a.mid,2)+a.treb,3);a.vol_=a.vol_*a.dec_m+(1-a.dec_m)*a.vol;a.dec=a.dec_s;a.vol2=a.vol2*a.dec+(1-a.dec)*Math.min(3,a.vol);a.maxvol=.00001a.maxvol?1:0)?a.vol:a.maxvol*a.dec_s;a.excite=a.vol-a.vol_;a.index=0;a.maxind1=0;for(var b=a.mean=0;ba.gmegabuf[Math.floor(12*a.maxind1+3)]?1:0)?a.maxind1=a.index:0;a.index+=1}a.quali=div(a.gmegabuf[Math.floor(12*a.maxind1+3)],a.mean);a.bpm=a.minbpm*pow(div(a.maxbpm,a.minbpm),div(a.maxind1,a.num_res));.00001b;b++){a.n+=1;a.ran1=div(randint(100),100);a.ran2=div(randint(100),200)-.25;a.tx=Math.cos(1.57*a.n+a.ran2)*(4>=a.n?1:0)*a.ran1;a.ty=Math.sin(1.57*a.n+a.ran2)*(4>=a.n?1:0)*a.ran1;a.c1=Math.cos(a.v1);a.c2=Math.cos(a.v2+a.ty);a.c3=Math.cos(a.v3+a.tx);a.s1=Math.sin(a.v1);a.s2=Math.sin(a.v2+a.ty);a.s3=Math.sin(a.v3+a.tx);a.reg10=a.c2*a.c1;a.reg11=a.c2*a.s1;a.reg12=\n-a.s2;a.reg13=a.s3*a.s2*a.c1-a.c3*a.s1;a.reg14=a.s3*a.s2*a.s1+a.c3*a.c1;a.reg15=a.s3*a.c2;a.reg16=a.c3*a.s2*a.c1+a.s3*a.s1;a.reg17=a.c3*a.s2*a.s1-a.s3*a.c1;a.reg18=a.c3*a.c2;a.reg20=a.reg30;a.reg21=a.reg31;a.reg22=a.reg32;a.reg23=a.reg33;a.reg24=a.reg34;a.reg25=a.reg35;a.reg26=a.reg36;a.reg27=a.reg37;a.reg28=a.reg38;a.q20=a.reg10*a.reg20+a.reg11*a.reg23+a.reg12*a.reg26;a.q21=a.reg10*a.reg21+a.reg11*a.reg24+a.reg12*a.reg27;a.q22=a.reg10*a.reg22+a.reg11*a.reg25+a.reg12*a.reg28;a.q23=a.reg13*a.reg20+\na.reg14*a.reg23+a.reg15*a.reg26;a.q24=a.reg13*a.reg21+a.reg14*a.reg24+a.reg15*a.reg27;a.q25=a.reg13*a.reg22+a.reg14*a.reg25+a.reg15*a.reg28;a.q26=a.reg16*a.reg20+a.reg17*a.reg23+a.reg18*a.reg26;a.q27=a.reg16*a.reg21+a.reg17*a.reg24+a.reg18*a.reg27;a.q28=a.reg16*a.reg22+a.reg17*a.reg25+a.reg18*a.reg28;a.reg20=a.q20;a.reg21=a.q21;a.reg22=a.q22;a.reg23=a.q23;a.reg24=a.q24;a.reg25=a.q25;a.reg26=a.q26;a.reg27=a.q27;a.reg28=a.q28;a.dist=.002;var d;c=0;do{c+=1;a.uvx=div(a.reg26*a.dist,a.q7);a.uvy=div(a.reg27*\na.dist,a.q7);a.uvz=div(a.reg28*a.dist,a.q7);a.uvx+=a.posx;a.uvy+=a.posy;a.uvz+=a.posz;a.uvx=1+2*(div(a.uvx,4)+30.5-Math.floor(div(a.uvx,4)+30.5)-.5);a.uvy=2+2*(div(a.uvy,4)+30.5-Math.floor(div(a.uvy,4)+30.5)-.5);a.uvz=3+2*(div(a.uvz,4)+30.5-Math.floor(div(a.uvz,4)+30.5)-.5);a.uvx0=a.uvx+a.q8;a.uvy0=a.uvy+a.q8;a.uvz0=a.uvz+a.q8;for(d=0;8>d;d++)a.uvx=.00001a.uvx?1:0)?-2-a.uvx:a.uvx,a.uvy=.00001a.uvy?\n1:0)?-2-a.uvy:a.uvy,a.uvz=.00001a.uvz?1:0)?-2-a.uvz:a.uvz,a.slen=a.uvx*a.uvx+a.uvy*a.uvy+a.uvz*a.uvz,a.uvx=2.6*(.00001a.slen?1:0)?4*a.uvx:.00001a.slen?1:0)?div(a.uvx,a.slen):a.uvx)+a.uvx0,a.uvy=2.6*(.00001a.slen?1:0)?4*a.uvy:.00001a.slen?1:0)?div(a.uvy,a.slen):a.uvy)+a.uvy0,a.uvz=2.6*(.00001a.slen?1:0)?4*a.uvz:.00001a.slen?1:0)?div(a.uvz,a.slen):a.uvz)+a.uvz0;a.len=sqrt(a.uvx*\na.uvx+a.uvy*a.uvy+a.uvz*a.uvz);a.dist*=1.1;d=(.6>a.dist?1:0)*(30c);a.megabuf[Math.floor(a.n)]=a.megabuf[Math.floor(a.n)]*a.dec_s+(1-a.dec_s)*a.dist;a.avg+=Math.abs(div(a.megabuf[Math.floor(a.n)],5))}a.n=0;for(b=a.avg=0;5>b;b++)a.n+=1,a.avg+=Math.abs(div(a.megabuf[Math.floor(a.n)],5));a.xslope=Math.min(Math.max(8*(a.megabuf[1]-a.megabuf[3]),-2),2);a.yslope=Math.min(Math.max(8*(a.megabuf[4]-a.megabuf[2]),-2),2);a.dist_=a.dist_*a.dec_m+(1-a.dec_m)*a.dist;\na.q10=a.ds*a.q7;a.q14=Math.abs(a.ds)+2*(Math.abs(a.v1)+Math.abs(a.v2)+Math.abs(a.v3))+div(1,255)+0*a.start;a.start*=.9;a.reg52=-a.v2;a.reg53=a.v3;a.warp=0;a.zoom=1;a.rot=a.v1;a.sinb=Math.sin(a.trel1);a.cosb=Math.cos(a.trel1);a.bt1=0>a.sinb*a.sinb_?1:0;a.bt2=0>a.cosb*a.cosb_?1:0;.00001 0.16)) {\n vec4 nb2_8;\n vec4 nb_9;\n vec4 tmpvar_10;\n tmpvar_10 = texture (sampler_pw_main, (uv_3 + vec2(-(pix))));\n nb_9.x = (1.0 - (tmpvar_10.z + (0.003921569 * tmpvar_10.y)));\n vec4 tmpvar_11;\n tmpvar_11 = texture (sampler_pw_main, (uv_3 + (pix * vec2(1.0, -1.0))));\n nb_9.y = (1.0 - (tmpvar_11.z + (0.003921569 * tmpvar_11.y)));\n vec4 tmpvar_12;\n tmpvar_12 = texture (sampler_pw_main, (uv_3 + vec2(pix)));\n nb_9.z = (1.0 - (tmpvar_12.z + (0.003921569 * tmpvar_12.y)));\n vec4 tmpvar_13;\n tmpvar_13 = texture (sampler_pw_main, (uv_3 + (pix * vec2(-1.0, 1.0))));\n nb_9.w = (1.0 - (tmpvar_13.z + (0.003921569 * tmpvar_13.y)));\n vec4 tmpvar_14;\n tmpvar_14 = texture (sampler_pw_main, (uv_3 + (pix * vec2(0.0, -1.0))));\n nb2_8.x = (1.0 - (tmpvar_14.z + (0.003921569 * tmpvar_14.y)));\n vec4 tmpvar_15;\n tmpvar_15 = texture (sampler_pw_main, (uv_3 + (pix * vec2(1.0, 0.0))));\n nb2_8.y = (1.0 - (tmpvar_15.z + (0.003921569 * tmpvar_15.y)));\n vec4 tmpvar_16;\n tmpvar_16 = texture (sampler_pw_main, (uv_3 + (pix * vec2(0.0, 1.0))));\n nb2_8.z = (1.0 - (tmpvar_16.z + (0.003921569 * tmpvar_16.y)));\n vec4 tmpvar_17;\n tmpvar_17 = texture (sampler_pw_main, (uv_3 + (pix * vec2(-1.0, 0.0))));\n nb2_8.w = (1.0 - (tmpvar_17.z + (0.003921569 * tmpvar_17.y)));\n vec4 tmpvar_18;\n tmpvar_18 = min (nb_9, nb2_8);\n nb_9.zw = tmpvar_18.zw;\n nb_9.xy = min (tmpvar_18.xy, tmpvar_18.zw);\n xlat_mutabledist = ((min (nb_9.x, nb_9.y) + (\n (0.006 * (tmpvar_7.xyz - 0.5).x)\n * tmpvar_7.y)) - (q10 * 0.5));\n };\n float theta_19;\n theta_19 = (xlat_mutabledist * 1.35);\n float theta_20;\n theta_20 = (xlat_mutabledist * 1.35);\n vec3 tmpvar_21;\n tmpvar_21.xy = (xlat_mutableuv1 * ((\n sin(theta_19)\n / \n cos(theta_19)\n ) / 1.35));\n tmpvar_21.z = ((sin(theta_20) / cos(theta_20)) / 1.35);\n xlat_mutableuv2 = (((tmpvar_21 / q7) * tmpvar_1) + tmpvar_2);\n xlat_mutableuv2 = (((\n fract(((xlat_mutableuv2 / 4.0) + 0.5))\n - 0.5) * 2.0) + vec3(1.0, 2.0, 3.0));\n vec3 zz0_23;\n vec3 zz_24;\n zz_24 = xlat_mutableuv2;\n zz0_23 = (xlat_mutableuv2 + q8);\n for (int n_22 = 0; n_22 <= 8; n_22++) {\n zz_24 = ((2.0 * clamp (zz_24, vec3(-1.0, -1.0, -1.0), vec3(1.0, 1.0, 1.0))) - zz_24);\n zz_24 = (zz_24 * max ((1.0/(\n dot (zz_24, zz_24)\n )), 1.0));\n zz_24 = ((2.6 * zz_24) + zz0_23);\n };\n vec4 tmpvar_25;\n tmpvar_25.w = 0.0;\n tmpvar_25.xyz = zz_24;\n float tmpvar_26;\n tmpvar_26 = sqrt(dot (zz_24, zz_24));\n vec4 tmpvar_27;\n tmpvar_27.w = 0.0;\n tmpvar_27.xyz = mod1;\n xlat_mutablestruc = ((1.0 - (\n (1.4 * dot (tmpvar_25, tmpvar_27))\n / 40.0)) - (xlat_mutabledist * 0.3));\n if (((tmpvar_26 < 40.0) && (xlat_mutabledist > (0.04 * \n (1.0 - rad)\n )))) {\n ret_4.x = mix (xlat_mutablestruc, texture (sampler_main, uv_3).x, sustain);\n float x_28;\n x_28 = ((1.0 - xlat_mutabledist) * 255.0);\n float ip_29;\n ip_29 = float(int(x_28));\n vec2 tmpvar_30;\n tmpvar_30.x = (x_28 - ip_29);\n tmpvar_30.y = (ip_29 / 255.0);\n ret_4.yz = tmpvar_30;\n } else {\n ret_4 = ((texture (sampler_fc_main, uv_3) * sustain) - 0.003921569).xyz;\n };\n vec4 tmpvar_31;\n tmpvar_31.w = 1.0;\n tmpvar_31.xyz = ret_4;\n ret = tmpvar_31.xyz;\n }",comp:"float xlat_mutablecross1;\nvec2 xlat_mutabledz1;\nfloat xlat_mutablerdist;\nvec2 xlat_mutableuv0;\nvec2 xlat_mutableuv4;\nvec2 xlat_mutableuva;\nvec2 xlat_mutableuvb;\n shader_body { \n vec2 tmpvar_1;\n tmpvar_1.x = q1;\n tmpvar_1.y = q2;\n vec2 tmpvar_2;\n tmpvar_2.x = q17;\n tmpvar_2.y = q18;\n vec2 uv_3;\n vec3 ret_4;\n uv_3 = (uv + (texsize.zw / 2.0));\n xlat_mutableuv0 = uv_3;\n vec2 uvi_5;\n uvi_5 = (uv_3 + vec2(0.002, 0.0));\n float tmpvar_6;\n tmpvar_6 = mix (((texture (sampler_blur1, uvi_5).xyz * scale1) + bias1).x, texture (sampler_main, uvi_5).x, 0.1);\n vec2 uvi_7;\n uvi_7 = (uv_3 - vec2(0.002, 0.0));\n float tmpvar_8;\n tmpvar_8 = mix (((texture (sampler_blur1, uvi_7).xyz * scale1) + bias1).x, texture (sampler_main, uvi_7).x, 0.1);\n xlat_mutabledz1.x = ((tmpvar_6 * float(\n (tmpvar_6 > 0.02)\n )) - (tmpvar_8 * float(\n (tmpvar_8 > 0.02)\n )));\n vec2 uvi_9;\n uvi_9 = (uv_3 + vec2(0.0, 0.002));\n float tmpvar_10;\n tmpvar_10 = mix (((texture (sampler_blur1, uvi_9).xyz * scale1) + bias1).x, texture (sampler_main, uvi_9).x, 0.1);\n vec2 uvi_11;\n uvi_11 = (uv_3 - vec2(0.0, 0.002));\n float tmpvar_12;\n tmpvar_12 = mix (((texture (sampler_blur1, uvi_11).xyz * scale1) + bias1).x, texture (sampler_main, uvi_11).x, 0.1);\n xlat_mutabledz1.y = ((tmpvar_10 * float(\n (tmpvar_10 > 0.02)\n )) - (tmpvar_12 * float(\n (tmpvar_12 > 0.02)\n )));\n uv_3 = (uv_3 + (xlat_mutabledz1 * 0.26));\n xlat_mutablerdist = ((texture (sampler_blur1, uv_3).xyz * scale1) + bias1).z;\n vec4 tmpvar_13;\n tmpvar_13.w = 0.0;\n tmpvar_13.xyz = texture (sampler_noise_hq, vec2(((xlat_mutablerdist * 0.05) + (time * 0.02)))).xyz;\n vec4 tmpvar_14;\n tmpvar_14 = mix (tmpvar_13, roam_sin, vec4(0.5, 0.5, 0.5, 0.5));\n xlat_mutableuv4 = (uv_3 - 0.4);\n xlat_mutableuva = ((xlat_mutableuv4 - tmpvar_1) - ((\n ((texture (sampler_blur1, uv_3).xyz * scale1) + bias1)\n .x * 0.4) * float(\n (xlat_mutablerdist > 0.2)\n )));\n xlat_mutableuvb = ((xlat_mutableuv4 - tmpvar_2) - ((\n (texture (sampler_blur1, uv_3).xyz * scale1)\n + bias1).x * 0.4));\n float angle_15;\n float tmpvar_16;\n tmpvar_16 = abs(xlat_mutableuva.x);\n if ((xlat_mutableuva.y >= 0.0)) {\n angle_15 = (1.0 - ((xlat_mutableuva.y - tmpvar_16) / (xlat_mutableuva.y + tmpvar_16)));\n } else {\n angle_15 = (3.0 - ((xlat_mutableuva.y + tmpvar_16) / (tmpvar_16 - xlat_mutableuva.y)));\n };\n float tmpvar_17;\n if ((xlat_mutableuva.x < 0.0)) {\n tmpvar_17 = -(angle_15);\n } else {\n tmpvar_17 = angle_15;\n };\n xlat_mutablecross1 = ((pow (\n sin((3.141593 * (tmpvar_17 + (5.1 * q20))))\n , 1.5) + (xlat_mutablerdist / 2.0)) + 0.1);\n vec3 tmpvar_18;\n tmpvar_18 = vec3((((1.0/(\n dot (xlat_mutableuva, xlat_mutableuva)\n )) / xlat_mutablecross1) * q3));\n vec3 tmpvar_19;\n tmpvar_19 = vec3(((1.0/(dot (xlat_mutableuvb, xlat_mutableuvb))) * q19));\n ret_4 = (clamp ((1.0 - \n (2.0 * xlat_mutablerdist)\n ), 0.0, 1.0) * ((\n (tmpvar_18 * tmpvar_14.xyz)\n + \n (tmpvar_19 * (1.0 - tmpvar_14.xyz))\n ) + 0.1));\n ret_4 = (ret_4 + (clamp (\n (2.0 * xlat_mutablerdist)\n , 0.0, 1.0) * (\n (tmpvar_18 * tmpvar_14.zyx)\n + \n (tmpvar_19 * (1.0 - tmpvar_14.zyx))\n )));\n ret_4 = (ret_4 + ((\n (texture (sampler_blur1, xlat_mutableuv0).xyz * scale1)\n + bias1).x * 0.05));\n ret_4 = (1.0 - exp(-(ret_4)));\n vec4 tmpvar_20;\n tmpvar_20.w = 1.0;\n tmpvar_20.xyz = ret_4;\n ret = tmpvar_20.xyz;\n }"}},function(a,e){a.exports={baseVals:{rating:2,gammaadj:1.980001,decay:.5,echo_zoom:.999998,echo_alpha:.5,echo_orient:3,wave_mode:5,wave_dots:1,modwavealphabyvolume:1,wave_brighten:0,darken:1,wave_a:.001,wave_scale:1.169162,wave_smoothing:0,wave_mystery:.08,modwavealphastart:0,modwavealphaend:1.32,warpanimspeed:1.4595,warpscale:2.0067,zoom:.9999,warp:.01,sx:.9999,wave_r:0,wave_g:.99,ob_size:.015,ob_b:1,ib_size:.26,mv_x:64,mv_y:48,mv_l:0,mv_r:.4999,mv_g:.4999,mv_b:.4999,mv_a:0,b2x:.3,b1ed:0},shapes:[{baseVals:{enabled:1,sides:53,rad:.05408,tex_ang:1.00531,tex_zoom:1.531168,r:.2,g:.7,g2:0,a2:1,border_b:0,border_a:0},init_eqs_str:"a.q24=0;a.rad0=0;",frame_eqs_str:"a.x=.5;a.y=.5;a.a=a.q24;a.a2=0;a.a=0;a.rad0=bnot(a.q24)*a.rad0+div(randint(10),50)*a.q24;a.rad=a.rad0;a.b=0;a.r=div(randint(10),10);a.g=1;a.a2=a.q24;"},{baseVals:{enabled:1,sides:44,textured:1,x:.7,rad:.2173,tex_zoom:3.277448,g:1,b:.5,r2:1,b2:1,a2:1,border_r:.5,border_g:.5,border_b:0},init_eqs_str:"a.rot0=0;a.q1=0;a.posx=0;a.q24=0;a.posy=0;a.rad0=0;",frame_eqs_str:"a.textured=1;a.rot0+=div(1,a.fps)*a.q1;a.posx=(1-a.q24)*a.posx+a.q24*(.3+div(randint(100),200));a.posy=(1-a.q24)*a.posy+a.q24*(.3+div(randint(100),200));a.rad0=(1-a.q24)*a.rad0+a.q24*(.05+div(randint(100),300));a.rad=a.rad0;a.x=a.posx;a.y=a.posy;a.ang=a.rot0;"},{baseVals:{enabled:1,sides:63,x:.503,rad:.038857,tex_zoom:2.2233,g:.1,r2:1,b2:1,a2:.7,border_a:0},init_eqs_str:"a.rot0=0;a.q2=0;a.posx=0;a.q24=0;a.posy=0;a.rad0=0;a.q26=0;",frame_eqs_str:"a.textured=1;a.rot0+=div(.1,a.fps)*a.q2;a.posx=(1-a.q24)*a.posx+a.q24*(.3+div(randint(100),200));a.posy=(1-a.q24)*a.posy+a.q24*(.3+div(randint(100),200));a.rad0=(1-a.q24)*a.rad0+a.q24*(.05+div(randint(100),400));a.rad=a.rad0;a.x=a.posx+a.q26;a.y=a.posy;a.ang=a.rot0;"},{baseVals:{enabled:0}}],waves:[{baseVals:{enabled:0}},{baseVals:{enabled:0}},{baseVals:{enabled:0}},{baseVals:{enabled:0}}],init_eqs_str:"a.index2=0;a.q25=0;a.index=0;a.q22=0;a.q21=0;a.q29=0;a.q6=0;a.q1=0;a.dec_med=0;a.q5=0;a.rott=0;a.is_beat=0;a.q31=0;a.q23=0;a.k1=0;a.q24=0;a.t_rel=0;a.dec_slow=0;a.q10=0;a.q4=0;a.q26=0;a.p2=0;a.avg=0;a.beat=0;a.p1=0;a.peak=0;a.q2=0;a.q27=0;a.p3=0;a.q3=0;a.t0=0;a.q7=0;a.ds=0;a.q28=0;a.q30=0;a.q20=0;a.p4=0;a.q8=0;",frame_eqs_str:"a.dec_med=pow(.7,div(30,a.fps));a.dec_slow=pow(.99,div(30,a.fps));a.beat=Math.max(Math.max(a.bass,a.mid),a.treb);a.beat=a.bass+a.mid+a.treb;a.beat*=a.beat;a.avg=a.avg*a.dec_slow+a.beat*(1-a.dec_slow);a.is_beat=above(a.beat,.2*a.avg+a.peak)*above(a.time,a.t0+.2);a.t0=a.is_beat*a.time+(1-a.is_beat)*a.t0;a.peak=a.is_beat*a.beat+(1-a.is_beat)*a.peak*a.dec_med;a.index=mod(a.index+a.is_beat,8);a.index2=mod(a.index2+a.is_beat*bnot(a.index),2);a.q20=a.avg;a.q21=a.beat;a.q22=a.peak;\na.q23=a.index;a.q24=a.is_beat;a.q26=a.bass_att+a.mid_att+a.treb_att+3;a.q27=a.index+1;a.q28=a.index2;a.q29=a.index2;a.monitor=a.q29;a.k1=a.is_beat*equal(mod(a.index,2),0);a.p1=a.k1*(a.p1+1)+(1-a.k1)*a.p1;a.p2=a.dec_med*a.p2+(1-a.dec_med)*a.p1;a.rott=div(3.1416*a.p2,4);a.q1=Math.cos(a.rott);a.q2=Math.sin(a.rott);a.q3=-a.q2;a.q4=a.q1;a.q5=Math.cos(div(a.time,17));a.q6=Math.sin(div(a.time,17));a.q7=-a.q2;a.q8=a.q1;a.ds=a.ds*a.dec_med+a.q24*(1-a.dec_med);a.q25=a.q24;a.q10=a.q22+3;a.t_rel=8*a.time;a.p3=\na.p3*a.dec_med+(1-a.dec_med)*(100*a.index+0*a.q26);a.q30=a.p3;a.p4=a.dec_med*a.p4+(1-a.dec_med)*a.q27;a.q31=a.p4;a.zoom=1.1+.2*a.q1;a.warp=0;a.ob_size=.01;a.ob_r=.9;a.ob_g=1;a.ob_b=0;a.ob_a=.2*Math.sin(div(a.time,7));",pixel_eqs_str:"",pixel_eqs:"",warp:" shader_body { \n vec2 uv_1;\n vec2 tmpvar_2;\n tmpvar_2 = ((uv - 0.5) * aspect.xy);\n float tmpvar_3;\n tmpvar_3 = (sqrt(dot (tmpvar_2, tmpvar_2)) + (rand_frame * 13.0)).x;\n uv_1 = (uv + (clamp (\n ((sin(tmpvar_3) / cos(tmpvar_3)) / normalize(tmpvar_2))\n , vec2(-2.0, -2.0), vec2(2.0, 2.0)) / 16.0));\n vec4 tmpvar_4;\n tmpvar_4.w = 1.0;\n tmpvar_4.xyz = ((q24 * (\n ((texture (sampler_main, uv_1).xyz + ((0.1 * vec3(\n ((q1 * cos((uv_1.x * 44.0))) - cos((uv_1.y * 82.0)))\n )) * (1.0 + texture (sampler_noise_lq, \n ((uv_1 / 16.0) + (time / 100.0))\n )).xyz)) * 0.98)\n - 0.025)) + ((1.0 - q24) * texture (sampler_main, uv_orig).xyz));\n ret = tmpvar_4.xyz;\n }",comp:"vec2 xlat_mutabledz;\nvec3 xlat_mutableneu;\nvec3 xlat_mutableret1;\nvec2 xlat_mutableuv3;\n shader_body { \n vec2 uv2_1;\n vec2 tmpvar_2;\n tmpvar_2.y = 0.0;\n tmpvar_2.x = texsize.z;\n vec2 tmpvar_3;\n tmpvar_3.x = 0.0;\n tmpvar_3.y = texsize.w;\n xlat_mutabledz.x = dot ((texture (sampler_main, (uv + tmpvar_2)).xyz - texture (sampler_main, (uv - tmpvar_2)).xyz), vec3(0.32, 0.49, 0.29));\n xlat_mutabledz.y = dot ((texture (sampler_main, (uv + tmpvar_3)).xyz - texture (sampler_main, (uv - tmpvar_3)).xyz), vec3(0.32, 0.49, 0.29));\n uv2_1 = (uv - 0.5);\n vec2 tmpvar_4;\n tmpvar_4.y = 0.0;\n float tmpvar_5;\n tmpvar_5 = (time / 8.0);\n tmpvar_4.x = tmpvar_5;\n float tmpvar_6;\n tmpvar_6 = (q27 * 2.0);\n xlat_mutableuv3 = (((tmpvar_6 * uv2_1) * 0.1) + tmpvar_4);\n xlat_mutableuv3 = (fract(xlat_mutableuv3) * aspect.yx);\n xlat_mutableuv3 = ((0.1 * cos(\n (22.0 * xlat_mutableuv3)\n )) + (18.0 * xlat_mutabledz));\n float tmpvar_7;\n tmpvar_7 = clamp ((0.02 / sqrt(\n dot (xlat_mutableuv3, xlat_mutableuv3)\n )), 0.0, 1.0);\n vec4 tmpvar_8;\n tmpvar_8 = (1.0 + roam_cos);\n xlat_mutableneu = ((0.1 * vec3(tmpvar_7)) + ((0.45 * \n dot (vec3(tmpvar_7), vec3(0.32, 0.49, 0.29))\n ) * tmpvar_8).xyz);\n xlat_mutableret1 = max (vec3(0.0, 0.0, 0.0), (xlat_mutableneu * 1.252262));\n vec2 tmpvar_9;\n tmpvar_9.y = 0.0;\n tmpvar_9.x = tmpvar_5;\n xlat_mutableuv3 = (((tmpvar_6 * uv2_1) * 0.1) + tmpvar_9);\n xlat_mutableuv3 = (fract(xlat_mutableuv3) * aspect.yx);\n xlat_mutableuv3 = ((0.1 * cos(\n (22.0 * xlat_mutableuv3)\n )) + (18.0 * xlat_mutabledz));\n float tmpvar_10;\n tmpvar_10 = clamp ((0.02 / sqrt(\n dot (xlat_mutableuv3, xlat_mutableuv3)\n )), 0.0, 1.0);\n xlat_mutableneu = ((0.1 * vec3(tmpvar_10)) + ((0.45 * \n dot (vec3(tmpvar_10), vec3(0.32, 0.49, 0.29))\n ) * tmpvar_8).xyz);\n xlat_mutableret1 = max (xlat_mutableret1, (xlat_mutableneu * 1.252262));\n vec4 tmpvar_11;\n tmpvar_11.w = 1.0;\n tmpvar_11.xyz = (xlat_mutableret1 + clamp ((\n (texture (sampler_main, uv).xyz * 4.0)\n * \n (0.2 + xlat_mutableret1)\n ), 0.0, 1.0));\n ret = tmpvar_11.xyz;\n }"}},function(a,e){a.exports={baseVals:{rating:0,gammaadj:1.980001,decay:.5,echo_zoom:.999998,echo_alpha:.5,echo_orient:3,wave_mode:6,wave_thick:1,modwavealphabyvolume:1,darken:1,wave_a:.001,wave_scale:.159809,wave_smoothing:.45,wave_mystery:.08,modwavealphastart:0,modwavealphaend:1.32,warpanimspeed:1.4595,warpscale:2.0067,zoom:.9999,warp:.01,sx:.9999,wave_r:0,wave_g:0,wave_b:0,wave_y:.9,ob_r:1,ob_g:1,ob_b:1,ib_size:.26,mv_x:64,mv_y:48,mv_l:1.85,mv_r:.4999,mv_g:.4999,mv_b:.4999,mv_a:0,b1x:.6999,b1ed:0},shapes:[{baseVals:{enabled:0}},{baseVals:{enabled:0}},{baseVals:{enabled:0}},{baseVals:{enabled:0}}],waves:[{baseVals:{enabled:0}},{baseVals:{enabled:0}},{baseVals:{enabled:0}},{baseVals:{enabled:0}}],init_eqs_str:"a.index=0;a.q22=0;a.q21=0;a.q6=0;a.q1=0;a.dec_med=0;a.q5=0;a.rott=0;a.is_beat=0;a.q23=0;a.k1=0;a.q24=0;a.dec_slow=0;a.q4=0;a.q26=0;a.p2=0;a.avg=0;a.movez=0;a.beat=0;a.p1=0;a.peak=0;a.q2=0;a.q27=0;a.q3=0;a.t0=0;a.q32=0;a.q7=0;a.q20=0;",frame_eqs_str:"a.dec_med=pow(.9,div(30,a.fps));a.dec_slow=pow(.99,div(30,a.fps));a.beat=Math.max(Math.max(a.bass,a.mid),a.treb);a.avg=a.avg*a.dec_slow+a.beat*(1-a.dec_slow);a.is_beat=above(a.beat,.2+a.avg+a.peak)*above(a.time,a.t0+.2);a.t0=a.is_beat*a.time+(1-a.is_beat)*a.t0;a.peak=a.is_beat*a.beat+(1-a.is_beat)*a.peak*a.dec_med;a.index=mod(a.index+a.is_beat,8);a.q20=a.avg;a.q21=a.beat;a.q22=a.peak;a.q23=a.index;a.q24=a.is_beat;a.q26=a.bass+a.mid+a.treb;a.k1=a.is_beat*equal(a.index,0);a.p1=\na.k1*(a.p1+1)+(1-a.k1)*a.p1;a.p2=a.dec_slow*a.p2+(1-a.dec_slow)*a.p1;a.rott=div(3.1416*a.p2,4);a.q27=a.index+1;a.q1=Math.cos(a.rott);a.q2=Math.sin(a.rott);a.q3=-a.q2;a.q4=a.q1;a.zoom=1;a.rot=-0*a.index;a.q32=pow(.996,div(30,a.fps));a.q5=div(Math.sin(div(a.time,11))+1,5);a.movez+=div(.06,a.fps)*(1.1+a.q1);a.q6=a.movez;a.q7=.005*Math.sin(div(a.time,15));",pixel_eqs_str:"",pixel_eqs:"",warp:"vec3 xlat_mutablenoise;\nvec2 xlat_mutablers;\nvec2 xlat_mutablers0;\nvec2 xlat_mutableuv1;\n shader_body { \n vec3 copy_1;\n vec2 tmpvar_2;\n tmpvar_2.x = 0.5;\n tmpvar_2.y = q5;\n xlat_mutableuv1 = (uv - tmpvar_2);\n float tmpvar_3;\n tmpvar_3 = (1.0/(xlat_mutableuv1.y));\n xlat_mutablers0.x = (xlat_mutableuv1.x * tmpvar_3);\n xlat_mutablers0.y = tmpvar_3;\n xlat_mutablers.x = xlat_mutablers0.x;\n xlat_mutablers.y = (tmpvar_3 + (time * 0.3));\n xlat_mutablenoise = (vec3(dot (texture (sampler_pw_noise_lq, (xlat_mutablers / 63.0)), vec4(0.32, 0.49, 0.29, 0.0))) * (dot (texture (sampler_pw_noise_lq, \n (xlat_mutablers / 12.0)\n ), vec4(0.32, 0.49, 0.29, 0.0)) + 0.5));\n xlat_mutablenoise = (xlat_mutablenoise * (clamp (\n ((10.0 * xlat_mutablenoise) - 8.0)\n , 0.0, 1.0) * clamp (\n (2.0 / tmpvar_3)\n , 0.0, 1.0)));\n vec2 tmpvar_4;\n tmpvar_4.x = uv.x;\n tmpvar_4.y = (uv.y - (0.024 / tmpvar_3));\n vec2 tmpvar_5;\n tmpvar_5.x = uv.x;\n tmpvar_5.y = (uv.y - (0.012 / tmpvar_3));\n copy_1 = (texture (sampler_main, tmpvar_4).xyz + texture (sampler_main, tmpvar_5).xyz);\n vec2 tmpvar_6;\n tmpvar_6.x = uv.x;\n tmpvar_6.y = (uv.y - (0.006 / tmpvar_3));\n copy_1 = (copy_1 + texture (sampler_main, tmpvar_6).xyz);\n vec4 tmpvar_7;\n tmpvar_7.w = 1.0;\n tmpvar_7.xyz = ((xlat_mutablenoise + (\n (((copy_1 / 3.0) * (1.0 + slow_roam_cos).xyz) / 2.0)\n * 0.99)) - 0.005);\n ret = tmpvar_7.xyz;\n }",comp:"vec3 xlat_mutablecont;\nvec3 xlat_mutableneu;\nvec3 xlat_mutableret1;\nvec2 xlat_mutablers2;\n shader_body { \n vec2 uv_1;\n float inten_2;\n float dist_3;\n uv_1 = (uv - 0.5);\n uv_1 = (uv_1 * aspect.xy);\n dist_3 = (1.0 - fract(q6));\n inten_2 = ((dist_3 * (1.0 - dist_3)) * 6.0);\n vec2 tmpvar_4;\n tmpvar_4 = fract(((uv_1 * dist_3) + 0.55));\n xlat_mutableneu = texture (sampler_main, tmpvar_4).xyz;\n xlat_mutableret1 = max (vec3(0.0, 0.0, 0.0), (xlat_mutableneu * inten_2));\n xlat_mutablecont = max (vec3(0.0, 0.0, 0.0), ((\n -(texture (sampler_main, tmpvar_4).xyz)\n + \n ((texture (sampler_blur1, (tmpvar_4 + q7)).xyz * scale1) + bias1)\n ) * inten_2));\n dist_3 = (1.0 - fract((0.5 + q6)));\n inten_2 = ((dist_3 * (1.0 - dist_3)) * 6.0);\n vec2 tmpvar_5;\n tmpvar_5 = fract(((uv_1 * dist_3) + 0.55));\n xlat_mutableneu = texture (sampler_main, tmpvar_5).xyz;\n xlat_mutableret1 = max (xlat_mutableret1, (xlat_mutableneu * inten_2));\n xlat_mutablecont = max (xlat_mutablecont, ((\n -(texture (sampler_main, tmpvar_5).xyz)\n + \n ((texture (sampler_blur1, (tmpvar_5 + q7)).xyz * scale1) + bias1)\n ) * inten_2));\n dist_3 = (1.0 - fract((1.0 + q6)));\n inten_2 = ((dist_3 * (1.0 - dist_3)) * 6.0);\n vec2 tmpvar_6;\n tmpvar_6 = fract(((uv_1 * dist_3) + 0.55));\n xlat_mutableneu = texture (sampler_main, tmpvar_6).xyz;\n xlat_mutableret1 = max (xlat_mutableret1, (xlat_mutableneu * inten_2));\n xlat_mutablecont = max (xlat_mutablecont, ((\n -(texture (sampler_main, tmpvar_6).xyz)\n + \n ((texture (sampler_blur1, (tmpvar_6 + q7)).xyz * scale1) + bias1)\n ) * inten_2));\n vec2 tmpvar_7;\n tmpvar_7.y = 5.0;\n tmpvar_7.x = (time / 12.0);\n xlat_mutablers2 = ((0.1 * cos(\n ((uv_1 * 3.0) + tmpvar_7)\n )) + (0.1 * xlat_mutableret1).xy);\n vec4 tmpvar_8;\n tmpvar_8.w = 1.0;\n tmpvar_8.xyz = (((-0.1 * xlat_mutableret1) + (\n (clamp ((0.005 / sqrt(\n dot (xlat_mutablers2, xlat_mutablers2)\n )), 0.0, 1.0) * vec3(18.0, 16.2, 10.8))\n * \n (0.2 + (0.3 * xlat_mutableret1))\n )) + ((\n dot (xlat_mutablecont, vec3(0.32, 0.49, 0.29))\n * \n (1.0 + slow_roam_cos)\n ) / 2.0).xyz);\n ret = tmpvar_8.xyz;\n }"}},function(a,e){a.exports={baseVals:{rating:5,gammaadj:1.980001,decay:.5,echo_zoom:.999998,echo_alpha:.5,echo_orient:3,wave_mode:6,wave_dots:1,modwavealphabyvolume:1,darken:1,wave_a:.001,wave_scale:1.740724,wave_smoothing:.45,wave_mystery:.08,modwavealphastart:0,modwavealphaend:1.32,warpanimspeed:1.4595,warpscale:2.0067,zoom:.9999,warp:.01,sx:.9999,wave_g:.99,ob_size:0,ob_r:1,ob_b:1,ob_a:1,ib_size:.26,mv_x:64,mv_y:48,mv_l:1.85,mv_r:.4999,mv_g:.4999,mv_b:.4999,mv_a:0,b1ed:0},shapes:[{baseVals:{enabled:0}},{baseVals:{enabled:0}},{baseVals:{enabled:0}},{baseVals:{enabled:0}}],waves:[{baseVals:{enabled:0}},{baseVals:{enabled:0}},{baseVals:{enabled:0}},{baseVals:{enabled:0}}],init_eqs_str:"a.index2=0;a.index4=0;a.index=0;a.q22=0;a.q21=0;a.q6=0;a.q1=0;a.dec_med=0;a.q5=0;a.index3=0;a.rott=0;a.is_beat=0;a.q23=0;a.k1=0;a.q24=0;a.t_rel=0;a.dec_slow=0;a.q4=0;a.q26=0;a.p2=0;a.avg=0;a.movez=0;a.beat=0;a.p1=0;a.peak=0;a.q2=0;a.q27=0;a.p3=0;a.q3=0;a.t0=0;a.q7=0;a.q28=0;a.q30=0;a.q20=0;a.q8=0;a.p3=0;a.t_rel=0;",frame_eqs_str:"a.dec_med=pow(.9,div(30,a.fps));a.dec_slow=pow(.99,div(30,a.fps));a.beat=Math.max(Math.max(a.bass,a.mid),a.treb);a.avg=a.avg*a.dec_slow+a.beat*(1-a.dec_slow);a.is_beat=above(a.beat,.5+a.avg+a.peak)*above(a.time,a.t0+.2);a.t0=a.is_beat*a.time+(1-a.is_beat)*a.t0;a.peak=a.is_beat*a.beat+(1-a.is_beat)*a.peak*a.dec_med;a.index=mod(a.index+a.is_beat,8);a.index2=mod(a.index2+a.is_beat*bnot(a.index),4);a.index3=mod(a.index3+a.is_beat*bnot(a.index)*bnot(a.index2),3);a.monitor=a.index4;\na.q20=a.avg;a.q21=a.beat;a.q22=a.peak;a.q23=a.index;a.q24=a.is_beat;a.q26=a.bass_att+a.mid_att+a.treb_att+1;a.k1=a.is_beat*equal(a.index,0);a.p1=a.k1*(a.p1+1)+(1-a.k1)*a.p1;a.p2=a.dec_med*a.p2+(1-a.dec_med)*a.p1;a.p3=a.dec_med*a.p3+(1-a.dec_med)*a.p2;a.rott=div(3.14159265359*a.p3,2);a.q27=8-a.index;a.q28=a.index2+1;a.q1=Math.cos(a.rott);a.q2=Math.sin(a.rott);a.q3=-a.q2;a.q4=a.q1;a.t_rel+=div(.6,a.fps)*a.q1*a.q1;a.q5=Math.cos(a.t_rel);a.q6=Math.sin(a.t_rel);a.q7=-a.q6;a.q8=a.q5;a.movez+=div(div(.6,\na.fps),(1.4-a.q5)*(1.4-a.q5));a.q30=a.movez;a.zoom=1;",pixel_eqs_str:"a.dx=div(0*a.q28,8);",warp:"vec3 xlat_mutablenoise;\nvec3 xlat_mutableret1;\nvec2 xlat_mutablers;\n shader_body { \n float z_1;\n mat2 tmpvar_2;\n tmpvar_2[uint(0)] = _qb.xy;\n tmpvar_2[1u] = _qb.zw;\n vec2 tmpvar_3;\n tmpvar_3 = ((uv * tmpvar_2) - 0.5);\n float tmpvar_4;\n tmpvar_4 = (0.4 / abs(tmpvar_3.y));\n xlat_mutablers.x = (tmpvar_3.x * tmpvar_4);\n xlat_mutablers.y = (tmpvar_4 + q30);\n vec4 tmpvar_5;\n tmpvar_5 = texture (sampler_pw_noise_lq, (xlat_mutablers / 32.0));\n xlat_mutablenoise = (tmpvar_5.xyz * vec3(greaterThanEqual (tmpvar_5.xyz, vec3(0.9, 0.9, 0.9))));\n xlat_mutablenoise = (xlat_mutablenoise * (1.0 + (0.5 * \n (dot (texture (sampler_noise_hq, (16.0 * xlat_mutablers)), vec4(0.32, 0.49, 0.29, 0.0)) - 0.5)\n )));\n xlat_mutableret1 = xlat_mutablenoise;\n z_1 = (1.2 / abs(tmpvar_3.y));\n xlat_mutablers.x = (tmpvar_3.x * z_1);\n xlat_mutablers.y = (z_1 + q30);\n vec4 tmpvar_6;\n tmpvar_6 = texture (sampler_pw_noise_lq, (xlat_mutablers / 32.0));\n xlat_mutablenoise = (tmpvar_6.xyz * vec3(greaterThanEqual (tmpvar_6.xyz, vec3(0.9, 0.9, 0.9))));\n xlat_mutableret1 = (xlat_mutableret1 + xlat_mutablenoise);\n z_1 = (0.4 / abs(tmpvar_3.x));\n xlat_mutablers.y = (tmpvar_3.y * z_1);\n xlat_mutablers.x = (z_1 + q30);\n vec4 tmpvar_7;\n tmpvar_7 = texture (sampler_pw_noise_lq, (xlat_mutablers / 32.0));\n xlat_mutablenoise = (tmpvar_7.xyz * vec3(greaterThanEqual (tmpvar_7.xyz, vec3(0.9, 0.9, 0.9))));\n xlat_mutablenoise = (xlat_mutablenoise * (1.0 + (0.5 * \n (dot (texture (sampler_noise_hq, (16.0 * xlat_mutablers)), vec4(0.32, 0.49, 0.29, 0.0)) - 0.5)\n )));\n xlat_mutableret1 = (xlat_mutableret1 + xlat_mutablenoise);\n z_1 = (1.2 / abs(tmpvar_3.x));\n xlat_mutablers.y = (tmpvar_3.y * z_1);\n xlat_mutablers.x = (z_1 + q30);\n vec4 tmpvar_8;\n tmpvar_8 = texture (sampler_pw_noise_lq, (xlat_mutablers / 32.0));\n xlat_mutablenoise = (tmpvar_8.xyz * vec3(greaterThanEqual (tmpvar_8.xyz, vec3(0.9, 0.9, 0.9))));\n xlat_mutableret1 = (xlat_mutableret1 + xlat_mutablenoise);\n vec4 tmpvar_9;\n tmpvar_9.w = 1.0;\n tmpvar_9.xyz = xlat_mutableret1;\n ret = tmpvar_9.xyz;\n }",comp:"uniform sampler2D sampler_clouds2;\n shader_body { \n vec2 uv_1;\n vec2 uv1_2;\n uv_1 = (uv * aspect.xy);\n vec2 tmpvar_3;\n tmpvar_3.y = 0.0;\n tmpvar_3.x = texsize.z;\n vec2 tmpvar_4;\n tmpvar_4.x = 0.0;\n tmpvar_4.y = texsize.w;\n vec2 tmpvar_5;\n tmpvar_5.x = (dot (texture (sampler_main, (uv_1 - tmpvar_3)).xyz, vec3(0.32, 0.49, 0.29)) - dot (texture (sampler_main, (uv_1 + tmpvar_3)).xyz, vec3(0.32, 0.49, 0.29)));\n tmpvar_5.y = (dot (texture (sampler_main, (uv_1 - tmpvar_4)).xyz, vec3(0.32, 0.49, 0.29)) - dot (texture (sampler_main, (uv_1 + tmpvar_4)).xyz, vec3(0.32, 0.49, 0.29)));\n uv1_2 = ((0.3 * sin(\n ((uv_1 + (0.02 * time)) * 6.0)\n )) + (0.2 * tmpvar_5));\n vec4 tmpvar_6;\n tmpvar_6 = texture (sampler_clouds2, (uv_orig + tmpvar_5));\n vec4 tmpvar_7;\n tmpvar_7.w = 1.0;\n tmpvar_7.xyz = (((-2.0 * \n (0.1 * texture (sampler_main, uv_1))\n .xyz) + (\n clamp (((0.004 * q26) / sqrt(dot (uv1_2, uv1_2))), 0.0, 1.0)\n * vec3(1.0, 0.8, 0.4))) + (0.4 * (tmpvar_6.xyz + \n (dot (tmpvar_6.xyz, vec3(0.32, 0.49, 0.29)) - 0.7)\n )));\n ret = tmpvar_7.xyz;\n }"}},function(a,e){a.exports={baseVals:{rating:5,gammaadj:1.780001,decay:.5,echo_zoom:.999998,echo_alpha:.5,echo_orient:3,wave_mode:5,wave_dots:1,wave_thick:1,wave_brighten:0,darken:1,wave_a:.001,wave_scale:5.552,wave_smoothing:.504,wave_mystery:-1,modwavealphastart:.71,modwavealphaend:1.3,warpanimspeed:1.4595,warpscale:2.0067,zoom:.9999,warp:.01,sx:.9999,wave_g:0,wave_b:0,ob_size:.06,ob_r:1,ob_g:1,ob_b:1,ib_size:.26,mv_x:64,mv_y:48,mv_l:.85,mv_r:.5,mv_g:.5,mv_b:.5,mv_a:0,b1x:.6999,b1ed:0},shapes:[{baseVals:{enabled:0}},{baseVals:{enabled:0}},{baseVals:{enabled:1,sides:5,additive:1,x:.9,rad:.22613,g:.6,g2:0,border_a:0},init_eqs_str:"",frame_eqs_str:"a.x=.05+div(randint(900),1E3);a.y=.05+div(randint(900),1E3);a.ang=div(randint(320),100);"},{baseVals:{enabled:0}}],waves:[{baseVals:{enabled:0}},{baseVals:{enabled:0}},{baseVals:{enabled:0}},{baseVals:{enabled:0}}],init_eqs_str:"a.ready=0;a.index2=0;a.index4=0;a.index=0;a.q22=0;a.q21=0;a.sp0=0;a.q29=0;a.q1=0;a.dec_med=0;a.q5=0;a.index3=0;a.rott=0;a.is_beat=0;a.q31=0;a.q23=0;a.k1=0;a.q24=0;a.dec_slow=0;a.go=0;a.q4=0;a.is_beat2=0;a.q26=0;a.p2=0;a.avg=0;a.movez=0;a.q19=0;a.beat=0;a.p1=0;a.peak=0;a.q2=0;a.q27=0;a.q3=0;a.t0=0;a.q32=0;a.q28=0;a.q30=0;a.q20=0;a.index4=randint(2);a.index3=randint(4);",frame_eqs_str:"a.dec_med=pow(.9,div(30,a.fps));a.dec_slow=pow(.99,div(30,a.fps));a.beat=Math.max(Math.max(a.bass,a.mid),a.treb);a.avg=a.avg*a.dec_slow+a.beat*(1-a.dec_slow);a.is_beat=above(a.beat,.5+a.avg+a.peak)*above(a.time,a.t0+.2);a.t0=a.is_beat*a.time+(1-a.is_beat)*a.t0;a.peak=a.is_beat*a.beat+(1-a.is_beat)*a.peak*a.dec_med;a.index=mod(a.index+a.is_beat,4);a.index2=mod(a.index2+a.is_beat*bnot(a.index),4);a.index3=mod(a.index3+a.is_beat*bnot(a.index)*bnot(a.index2),4);a.index4=mod(a.index4+\na.is_beat*bnot(a.index)*bnot(a.index2)*bnot(a.index3),2);a.q20=a.avg;a.q21=a.beat;a.q22=a.peak;a.q23=a.index;a.q24=a.is_beat;a.q26=a.bass+a.mid+a.treb;a.ready=a.is_beat*bnot(a.ready)+bnot(a.is_beat2)*a.ready;a.is_beat2=a.ready*above(a.time,a.t0+.2);a.q19=a.is_beat2;a.k1=a.is_beat*equal(a.index,0);a.p1=a.k1*(a.p1+1)+(1-a.k1)*a.p1;a.p2=a.dec_med*a.p2+(1-a.dec_med)*a.p1;a.rott=div(3.14159265359*a.p2,2);a.q27=a.index+1;a.q28=a.index2+1;a.q29=4*a.index3+1;a.q30=a.index4;a.q1=Math.cos(a.rott);a.q2=Math.sin(a.rott);\na.q3=-a.q2;a.q4=a.q1;a.sp0=a.dec_slow*a.sp0+(a.q24+.05)*(1-a.dec_slow);a.go=a.go*a.dec_med+(1-a.dec_med)*(1-bnot(a.index2+a.index3));a.movez+=div(.015*30,a.fps)*a.go;a.q31=a.movez;a.q32=.5+.02*Math.sin(div(a.time,5));a.q5=mod(a.index4,2);a.zoom=1;a.rot=0;a.dx=.05*Math.max(Math.sin(div(a.time,9.7))-.95,0);a.dy=.002*(1-a.go);a.rot=50*(a.dx-a.dy);",pixel_eqs_str:"",pixel_eqs:"",warp:" shader_body { \n vec2 tmpvar_1;\n tmpvar_1 = ((uv * texsize.xy) * 0.03);\n vec2 tmpvar_2;\n tmpvar_2.x = (cos((tmpvar_1.y * q1)) * sin(-(tmpvar_1.y)));\n tmpvar_2.y = (sin(tmpvar_1.x) * cos((tmpvar_1.y * q2)));\n vec4 tmpvar_3;\n tmpvar_3.w = 1.0;\n tmpvar_3.xyz = ((texture (sampler_main, (uv - \n ((tmpvar_2 * texsize.zw) * 18.0)\n )).xyz * 0.99) - 0.01);\n ret = tmpvar_3.xyz;\n }",comp:" shader_body { \n vec2 uv1_1;\n vec3 tmpvar_2;\n tmpvar_2 = texture (sampler_main, uv).xyz;\n vec2 tmpvar_3;\n tmpvar_3.x = (texture (sampler_main, (uv - vec2(0.001, 0.0))).xyz - texture (sampler_main, (uv + vec2(0.001, 0.0))).xyz).x;\n tmpvar_3.y = (texture (sampler_main, (uv - vec2(0.0, 0.001))).xyz - texture (sampler_main, (uv + vec2(0.0, 0.001))).xyz).x;\n uv1_1 = ((0.3 * cos(\n ((uv - 0.5) + 1.8)\n )) - (2.0 * tmpvar_3));\n vec4 tmpvar_4;\n tmpvar_4.w = 1.0;\n tmpvar_4.xyz = (0.8 * ((0.3 * \n dot (tmpvar_2, vec3(0.32, 0.49, 0.29))\n ) + (\n (22.0 * clamp ((0.01 / sqrt(\n dot (uv1_1, uv1_1)\n )), 0.0, 1.0))\n * \n (tmpvar_2 + 0.1)\n )));\n ret = tmpvar_4.xyz;\n }"}},function(a,e){a.exports={baseVals:{rating:3,gammaadj:1.98,decay:.5,echo_zoom:1,echo_alpha:.5,echo_orient:3,wave_mode:7,additivewave:1,wave_thick:1,modwavealphabyvolume:1,wave_brighten:0,wrap:0,darken:1,wave_a:.001,wave_scale:.958,wave_smoothing:.45,modwavealphastart:0,modwavealphaend:1.32,warpanimspeed:1.459,warpscale:2.007,zoom:.9999,warp:.01,sx:.9999,wave_r:0,wave_g:0,wave_b:0,ob_size:.05,ob_g:.1,ob_b:1,ob_a:1,ib_size:0,ib_r:0,ib_g:0,ib_b:0,mv_x:25.6,mv_y:9.6,mv_l:0,mv_r:.5,mv_g:.5,mv_b:.5,mv_a:0,b1ed:0},shapes:[{baseVals:{enabled:0}},{baseVals:{enabled:0}},{baseVals:{enabled:0}},{baseVals:{enabled:0}}],waves:[{baseVals:{enabled:0}},{baseVals:{enabled:0}},{baseVals:{enabled:0}},{baseVals:{enabled:0}}],init_eqs_str:"a.look=0;a.n=0;a.reg26=0;a.uvx0=0;a.reg34=0;a.reg28=0;a.reg23=0;a.q25=0;a.angchg=0;a.reg20=0;a.reg15=0;a.reg10=0;a.q12=0;a.v3=0;a.q22=0;a.q21=0;a.diry=0;a.q13=0;a.q6=0;a.posx=0;a.fps_=0;a.reg25=0;a.uvx=0;a.q1=0;a.travel=0;a.posz=0;a.q5=0;a.dirz=0;a.dec_s=0;a.reg16=0;a.slow=0;a.reg36=0;a.reg22=0;a.uvy=0;a.rotz=0;a.ly=0;a.dist_=0;a.q23=0;a.q24=0;a.reg24=0;a.cran0=0;a.ran2=0;a.q11=0;a.q10=0;a.reg14=0;a.posy=0;a.reg31=0;a.dirx=0;a.q4=0;a.start=0;a.reg12=0;a.reg13=0;a.c2=0;a.reg37=\n0;a.s3=0;a.yslope=0;a.lampy=0;a.q16=0;a.xslope=0;a.q26=0;a.reg38=0;a.reg35=0;a.reg11=0;a.tx=0;a.avg=0;a.uvz=0;a.c3=0;a.uvy0=0;a.reg27=0;a.q19=0;a.beat=0;a.reg32=0;a.lx=0;a.reg21=0;a.uvz0=0;a.len=0;a.reg18=0;a.reg30=0;a.q2=0;a.q27=0;a.slen=0;a.q14=0;a.dist=0;a.reg17=0;a.v1=0;a.speed=0;a.s1=0;a.t0=0;a.s2=0;a.ran1=0;a.reg33=0;a.q7=0;a.ds=0;a.q28=0;a.lampx=0;a.ty=0;a.c1=0;a.v2=0;a.q20=0;a.q8=0;a.avg=.01;a.q7=.25;a.q8=randint(2)-1;a.q16=1+randint(2);a.q18=randint(.8)+.1;a.q30=1;a.q31=128;a.start=1;a.travel=\n0;a.rotz=0;a.look=0;a.slow=0;a.t0=a.time+3;a.lampx=.5;a.lampy=.5;a.cran0=randint(1);for(var b=a.n=0;1E4>b;b++)a.gmegabuf[Math.floor(a.n)]=0,a.n+=1;for(b=a.n=0;1E4>b;b++)a.megabuf[Math.floor(a.n)]=0,a.n+=1;a.trelx=0;a.trely=0;a.trelz=0;a.reg20=1;a.reg21=0;a.reg22=0;a.reg23=0;a.reg24=1;a.reg25=0;a.reg26=0;a.reg27=0;a.reg28=1;b=0;do{b+=1;var c;a.ran1=div(randint(800),100);a.ran2=div(randint(800),100);a.ran3=div(randint(800),100);a.posx=randint(5)-2;a.posy=randint(5)-2;a.posz=randint(5)-2;a.c1=Math.cos(a.ran1);\na.c2=Math.cos(a.ran2);a.c3=Math.cos(a.ran3);a.s1=Math.sin(a.ran1);a.s2=Math.sin(a.ran2);a.s3=Math.sin(a.ran3);a.reg20=a.c2*a.c1;a.reg21=a.c2*a.s1;a.reg22=-a.s2;a.reg23=a.s3*a.s2*a.c1-a.c3*a.s1;a.reg24=a.s3*a.s2*a.s1+a.c3*a.c1;a.reg25=a.s3*a.c2;a.reg26=a.c3*a.s2*a.c1+a.s3*a.s1;a.reg27=a.c3*a.s2*a.s1-a.s3*a.c1;a.reg28=a.c3*a.c2;a.dist=.001;var d=0;do{d+=1;a.uvx=div(a.reg26*a.dist,a.q7);a.uvy=div(a.reg27*a.dist,a.q7);a.uvz=div(a.reg28*a.dist,a.q7);a.uvx+=a.posx;a.uvy+=a.posy;a.uvz+=a.posz;a.uvx=8*(div(a.uvx,\n8)+30.5-Math.floor(div(a.uvx,8)+30.5)-.5);a.uvy=8*(div(a.uvy,8)+30.5-Math.floor(div(a.uvy,8)+30.5)-.5);a.uvz=8*(div(a.uvz,8)+30.5-Math.floor(div(a.uvz,8)+30.5)-.5);a.uvx0=a.uvx+a.q8;a.uvy0=a.uvy+a.q8;a.uvz0=a.uvz+a.q8;for(c=0;8>c;c++)a.uvx=.00001a.uvx?1:0)?-2-a.uvx:a.uvx,a.uvy=.00001a.uvy?1:0)?-2-a.uvy:a.uvy,a.uvz=.00001a.uvz?1:0)?-2-a.uvz:a.uvz,a.slen=\na.uvx*a.uvx+a.uvy*a.uvy+a.uvz*a.uvz,a.uvx=2.6*(.00001a.slen?1:0)?4*a.uvx:.00001a.slen?1:0)?div(a.uvx,a.slen):a.uvx)+a.uvx0,a.uvy=2.6*(.00001a.slen?1:0)?4*a.uvy:.00001a.slen?1:0)?div(a.uvy,a.slen):a.uvy)+a.uvy0,a.uvz=2.6*(.00001a.slen?1:0)?4*a.uvz:.00001a.slen?1:0)?div(a.uvz,a.slen):a.uvz)+a.uvz0;a.len=sqrt(a.uvx*a.uvx+a.uvy*a.uvy+a.uvz*a.uvz);a.dist*=1.05;c=(.6>a.dist?1:0)*(30\nd);d=.06>a.dist?1:0}while(.00001b);",frame_eqs_str:"a.fps_=0*a.fps_+1*(.00001=a.fps?1:0)?a.fps:25+.5*(a.fps-25));a.dec_s=1-div(.06*30,a.fps_);a.beat=a.time>a.t0+3?1:0;a.t0=.00001Math.abs(a.rotz-0)?1:0)?a.beat*(randint(100)<20*a.travel?1:0)*(div(randint(10),10)-.3):bnot(a.beat*(30>randint(100)?1:0))*a.rotz;a.slow=.00001randint(1E3*a.avg)?1:0):bnot(a.beat*(50>randint(100)?1:0));a.look=.00001randint(1E3*a.speed)?1:0):bnot(a.beat*(50>randint(100)?1:0));a.lx=.00001a.dist_?1:0)*2;a.travel=.00001b;b++){a.n+=1;a.ran1=div(randint(100),100);a.ran2=div(randint(100),200)-.25;a.tx=Math.cos(1.57*a.n+a.ran2)*(4>=a.n?1:0)*a.ran1;a.ty=Math.sin(1.57*a.n+a.ran2)*(4>=a.n?1:0)*a.ran1;a.c1=Math.cos(a.v1);a.c2=Math.cos(a.v2+a.ty);a.c3=Math.cos(a.v3+a.tx);a.s1=Math.sin(a.v1);a.s2=Math.sin(a.v2+a.ty);a.s3=Math.sin(a.v3+a.tx);a.reg10=a.c2*a.c1;a.reg11=a.c2*a.s1;a.reg12=-a.s2;a.reg13=a.s3*a.s2*a.c1-a.c3*a.s1;a.reg14=a.s3*a.s2*a.s1+a.c3*a.c1;a.reg15=a.s3*\na.c2;a.reg16=a.c3*a.s2*a.c1+a.s3*a.s1;a.reg17=a.c3*a.s2*a.s1-a.s3*a.c1;a.reg18=a.c3*a.c2;a.reg20=a.reg30;a.reg21=a.reg31;a.reg22=a.reg32;a.reg23=a.reg33;a.reg24=a.reg34;a.reg25=a.reg35;a.reg26=a.reg36;a.reg27=a.reg37;a.reg28=a.reg38;a.q20=a.reg10*a.reg20+a.reg11*a.reg23+a.reg12*a.reg26;a.q21=a.reg10*a.reg21+a.reg11*a.reg24+a.reg12*a.reg27;a.q22=a.reg10*a.reg22+a.reg11*a.reg25+a.reg12*a.reg28;a.q23=a.reg13*a.reg20+a.reg14*a.reg23+a.reg15*a.reg26;a.q24=a.reg13*a.reg21+a.reg14*a.reg24+a.reg15*a.reg27;\na.q25=a.reg13*a.reg22+a.reg14*a.reg25+a.reg15*a.reg28;a.q26=a.reg16*a.reg20+a.reg17*a.reg23+a.reg18*a.reg26;a.q27=a.reg16*a.reg21+a.reg17*a.reg24+a.reg18*a.reg27;a.q28=a.reg16*a.reg22+a.reg17*a.reg25+a.reg18*a.reg28;a.reg20=a.q20;a.reg21=a.q21;a.reg22=a.q22;a.reg23=a.q23;a.reg24=a.q24;a.reg25=a.q25;a.reg26=a.q26;a.reg27=a.q27;a.reg28=a.q28;a.dist=.002;var c,d=0;do{d+=1;a.uvx=div(a.reg26*a.dist,a.q7);a.uvy=div(a.reg27*a.dist,a.q7);a.uvz=div(a.reg28*a.dist,a.q7);a.uvx+=a.posx;a.uvy+=a.posy;a.uvz+=a.posz;\na.uvx=8*(div(a.uvx,8)+30.5-Math.floor(div(a.uvx,8)+30.5)-.5);a.uvy=8*(div(a.uvy,8)+30.5-Math.floor(div(a.uvy,8)+30.5)-.5);a.uvz=8*(div(a.uvz,8)+30.5-Math.floor(div(a.uvz,8)+30.5)-.5);a.uvx0=a.uvx+a.q8;a.uvy0=a.uvy+a.q8;a.uvz0=a.uvz+a.q8;for(c=0;8>c;c++)a.uvx=.00001a.uvx?1:0)?-2-a.uvx:a.uvx,a.uvy=.00001a.uvy?1:0)?-2-a.uvy:a.uvy,a.uvz=.00001a.uvz?1:0)?\n-2-a.uvz:a.uvz,a.slen=a.uvx*a.uvx+a.uvy*a.uvy+a.uvz*a.uvz,a.uvx=2.6*(.00001a.slen?1:0)?4*a.uvx:.00001a.slen?1:0)?div(a.uvx,a.slen):a.uvx)+a.uvx0,a.uvy=2.6*(.00001a.slen?1:0)?4*a.uvy:.00001a.slen?1:0)?div(a.uvy,a.slen):a.uvy)+a.uvy0,a.uvz=2.6*(.00001a.slen?1:0)?4*a.uvz:.00001a.slen?1:0)?div(a.uvz,a.slen):a.uvz)+a.uvz0;a.len=sqrt(a.uvx*a.uvx+a.uvy*a.uvy+a.uvz*a.uvz);a.dist*=1.1;c=(.6>a.dist?1:0)*(30d);a.megabuf[Math.floor(a.n)]=a.megabuf[Math.floor(a.n)]*a.dec_s+(1-a.dec_s)*a.dist;a.avg+=Math.abs(div(a.megabuf[Math.floor(a.n)],5))}a.n=0;for(b=a.avg=0;5>b;b++)a.n+=1,a.avg+=Math.abs(div(a.megabuf[Math.floor(a.n)],5));a.xslope=Math.min(Math.max(div(2,a.avg)*(a.megabuf[1]-a.megabuf[3]),-3),3);a.yslope=Math.min(Math.max(div(2,a.avg)*(a.megabuf[4]-a.megabuf[2]),-3),3);a.monitor=a.avg;a.dist_=a.dist_*a.dec_s+(1-a.dec_s)*a.dist;a.q10=a.ds*a.q7;a.q14=Math.abs(a.ds)+2*(Math.abs(a.v1)+\nMath.abs(a.v2)+Math.abs(a.v3))+div(1,255)+.05*a.start;a.q19=.6+.4*Math.sin(.02*a.time+6*a.cran0);a.start*=.9;a.q11=a.v1;a.q12=a.v2;a.q13=a.v3;a.monitor=a.q16;",pixel_eqs_str:"a.warp=0;a.zoom=1;a.dx=div(-a.q12,a.q16)*(1+0*pow(a.x-.5,2));a.dy=div(a.q13,a.q16)*(1+0*pow(a.y-.5,2));a.rot=a.q11;",warp:"float sustain;\nfloat xlat_mutabledist;\nfloat xlat_mutablestruc;\nvec2 xlat_mutableuv1;\nvec3 xlat_mutableuv2;\n shader_body { \n mat3 tmpvar_1;\n tmpvar_1[uint(0)].x = q20;\n tmpvar_1[uint(0)].y = q23;\n tmpvar_1[uint(0)].z = q26;\n tmpvar_1[1u].x = q21;\n tmpvar_1[1u].y = q24;\n tmpvar_1[1u].z = q27;\n tmpvar_1[2u].x = q22;\n tmpvar_1[2u].y = q25;\n tmpvar_1[2u].z = q28;\n vec3 tmpvar_2;\n tmpvar_2.x = q4;\n tmpvar_2.y = q5;\n tmpvar_2.z = q6;\n sustain = (0.98 - q14);\n vec2 uv_3;\n vec3 ret_4;\n vec2 tmpvar_5;\n tmpvar_5 = (uv - 0.5);\n xlat_mutableuv1 = ((tmpvar_5 * aspect.xy) * q16);\n vec4 tmpvar_6;\n tmpvar_6 = texture (sampler_pc_main, uv);\n uv_3 = ((tmpvar_5 * (1.0 - \n (q10 / (1.0 - ((tmpvar_6.z + \n (0.003921569 * tmpvar_6.y)\n ) + (q10 * 0.7))))\n )) + 0.5);\n vec4 tmpvar_7;\n tmpvar_7 = fract((8.0 * texture (sampler_noise_lq, (uv_3 + rand_frame.yz))));\n xlat_mutabledist = tmpvar_7.x;\n if ((tmpvar_7.y > 0.2)) {\n vec3 tmpvar_8;\n tmpvar_8 = (tmpvar_7.xyz - vec3(0.4, 0.5, 0.5));\n vec2 uvi_9;\n uvi_9 = ((tmpvar_8.zy * 0.003) + uv_3);\n vec2 pix_10;\n vec4 nb2_11;\n vec4 nb_12;\n vec2 x_13;\n x_13 = (uvi_9 - 0.5);\n pix_10 = (texsize.zw * (1.0 + (\n sqrt(dot (x_13, x_13))\n * 8.0)));\n float tmpvar_14;\n tmpvar_14 = (q10 * 0.7);\n vec4 tmpvar_15;\n tmpvar_15 = texture (sampler_pc_main, (uvi_9 - pix_10));\n nb_12.x = (1.0 - ((tmpvar_15.z + \n (0.003921569 * tmpvar_15.y)\n ) + tmpvar_14));\n vec4 tmpvar_16;\n tmpvar_16 = texture (sampler_pc_main, (uvi_9 + (pix_10 * vec2(1.0, -1.0))));\n nb_12.y = (1.0 - ((tmpvar_16.z + \n (0.003921569 * tmpvar_16.y)\n ) + tmpvar_14));\n vec4 tmpvar_17;\n tmpvar_17 = texture (sampler_pc_main, (uvi_9 + pix_10));\n nb_12.z = (1.0 - ((tmpvar_17.z + \n (0.003921569 * tmpvar_17.y)\n ) + tmpvar_14));\n vec4 tmpvar_18;\n tmpvar_18 = texture (sampler_pc_main, (uvi_9 + (pix_10 * vec2(-1.0, 1.0))));\n nb_12.w = (1.0 - ((tmpvar_18.z + \n (0.003921569 * tmpvar_18.y)\n ) + tmpvar_14));\n vec4 tmpvar_19;\n tmpvar_19 = texture (sampler_pc_main, (uvi_9 + (pix_10 * vec2(0.0, -1.0))));\n nb2_11.x = (1.0 - ((tmpvar_19.z + \n (0.003921569 * tmpvar_19.y)\n ) + tmpvar_14));\n vec4 tmpvar_20;\n tmpvar_20 = texture (sampler_pc_main, (uvi_9 + (pix_10 * vec2(1.0, 0.0))));\n nb2_11.y = (1.0 - ((tmpvar_20.z + \n (0.003921569 * tmpvar_20.y)\n ) + tmpvar_14));\n vec4 tmpvar_21;\n tmpvar_21 = texture (sampler_pc_main, (uvi_9 + (pix_10 * vec2(0.0, 1.0))));\n nb2_11.z = (1.0 - ((tmpvar_21.z + \n (0.003921569 * tmpvar_21.y)\n ) + tmpvar_14));\n vec4 tmpvar_22;\n tmpvar_22 = texture (sampler_pc_main, (uvi_9 + (pix_10 * vec2(-1.0, 0.0))));\n nb2_11.w = (1.0 - ((tmpvar_22.z + \n (0.003921569 * tmpvar_22.y)\n ) + tmpvar_14));\n vec4 tmpvar_23;\n tmpvar_23 = min (nb_12, nb2_11);\n nb_12.zw = tmpvar_23.zw;\n nb_12.xy = min (tmpvar_23.xy, tmpvar_23.zw);\n xlat_mutabledist = (min (nb_12.x, nb_12.y) + ((0.008 * tmpvar_8.x) * abs(tmpvar_8.y)));\n };\n vec4 tmpvar_24;\n tmpvar_24 = texture (sampler_pc_main, uv_3);\n float tmpvar_25;\n tmpvar_25 = min (xlat_mutabledist, (1.0 - (\n (tmpvar_24.z + (0.003921569 * tmpvar_24.y))\n + \n (q10 * 0.7)\n )));\n xlat_mutabledist = tmpvar_25;\n float tmpvar_26;\n tmpvar_26 = (tmpvar_25 + pow (tmpvar_25, 3.0));\n vec3 tmpvar_27;\n tmpvar_27.xy = (xlat_mutableuv1 * tmpvar_26);\n tmpvar_27.z = tmpvar_26;\n xlat_mutableuv2 = (((tmpvar_27 / q7) * tmpvar_1) + tmpvar_2);\n xlat_mutableuv2 = ((fract(\n ((xlat_mutableuv2 / 8.0) + 0.5)\n ) - 0.5) * 8.0);\n float li_28;\n vec3 zz0_29;\n vec3 zz_30;\n zz0_29 = (xlat_mutableuv2 + q8);\n li_28 = 0.0;\n zz_30 = ((2.0 * clamp (xlat_mutableuv2, vec3(-1.0, -1.0, -1.0), vec3(1.0, 1.0, 1.0))) - xlat_mutableuv2);\n float tmpvar_31;\n tmpvar_31 = dot (zz_30, zz_30);\n if ((tmpvar_31 <= 0.25)) {\n zz_30 = (zz_30 * 4.0);\n li_28 = 24.0;\n } else {\n if ((tmpvar_31 <= 1.0)) {\n zz_30 = (zz_30 / tmpvar_31);\n };\n };\n zz_30 = ((2.6 * zz_30) + zz0_29);\n zz_30 = ((2.0 * clamp (zz_30, vec3(-1.0, -1.0, -1.0), vec3(1.0, 1.0, 1.0))) - zz_30);\n float tmpvar_32;\n tmpvar_32 = dot (zz_30, zz_30);\n if ((tmpvar_32 <= 0.25)) {\n zz_30 = (zz_30 * 4.0);\n li_28 = 24.0;\n } else {\n if ((tmpvar_32 <= 1.0)) {\n zz_30 = (zz_30 / tmpvar_32);\n };\n };\n zz_30 = ((2.6 * zz_30) + zz0_29);\n zz_30 = ((2.0 * clamp (zz_30, vec3(-1.0, -1.0, -1.0), vec3(1.0, 1.0, 1.0))) - zz_30);\n float tmpvar_33;\n tmpvar_33 = dot (zz_30, zz_30);\n if ((tmpvar_33 <= 0.25)) {\n zz_30 = (zz_30 * 4.0);\n li_28 = 24.0;\n } else {\n if ((tmpvar_33 <= 1.0)) {\n zz_30 = (zz_30 / tmpvar_33);\n };\n };\n zz_30 = ((2.6 * zz_30) + zz0_29);\n zz_30 = ((2.0 * clamp (zz_30, vec3(-1.0, -1.0, -1.0), vec3(1.0, 1.0, 1.0))) - zz_30);\n float tmpvar_34;\n tmpvar_34 = dot (zz_30, zz_30);\n if ((tmpvar_34 <= 0.25)) {\n zz_30 = (zz_30 * 4.0);\n li_28 = 24.0;\n } else {\n if ((tmpvar_34 <= 1.0)) {\n zz_30 = (zz_30 / tmpvar_34);\n };\n };\n zz_30 = ((2.6 * zz_30) + zz0_29);\n zz_30 = ((2.0 * clamp (zz_30, vec3(-1.0, -1.0, -1.0), vec3(1.0, 1.0, 1.0))) - zz_30);\n float tmpvar_35;\n tmpvar_35 = dot (zz_30, zz_30);\n if ((tmpvar_35 <= 0.25)) {\n zz_30 = (zz_30 * 4.0);\n li_28 = 24.0;\n } else {\n if ((tmpvar_35 <= 1.0)) {\n zz_30 = (zz_30 / tmpvar_35);\n };\n };\n zz_30 = ((2.6 * zz_30) + zz0_29);\n zz_30 = ((2.0 * clamp (zz_30, vec3(-1.0, -1.0, -1.0), vec3(1.0, 1.0, 1.0))) - zz_30);\n float tmpvar_36;\n tmpvar_36 = dot (zz_30, zz_30);\n if ((tmpvar_36 <= 0.25)) {\n zz_30 = (zz_30 * 4.0);\n li_28 = 24.0;\n } else {\n if ((tmpvar_36 <= 1.0)) {\n zz_30 = (zz_30 / tmpvar_36);\n };\n };\n zz_30 = ((2.6 * zz_30) + zz0_29);\n zz_30 = ((2.0 * clamp (zz_30, vec3(-1.0, -1.0, -1.0), vec3(1.0, 1.0, 1.0))) - zz_30);\n float tmpvar_37;\n tmpvar_37 = dot (zz_30, zz_30);\n if ((tmpvar_37 <= 0.25)) {\n zz_30 = (zz_30 * 4.0);\n li_28 = 24.0;\n } else {\n if ((tmpvar_37 <= 1.0)) {\n zz_30 = (zz_30 / tmpvar_37);\n };\n };\n zz_30 = ((2.6 * zz_30) + zz0_29);\n zz_30 = ((2.0 * clamp (zz_30, vec3(-1.0, -1.0, -1.0), vec3(1.0, 1.0, 1.0))) - zz_30);\n float tmpvar_38;\n tmpvar_38 = dot (zz_30, zz_30);\n if ((tmpvar_38 <= 0.25)) {\n zz_30 = (zz_30 * 4.0);\n li_28 = 24.0;\n } else {\n if ((tmpvar_38 <= 1.0)) {\n zz_30 = (zz_30 / tmpvar_38);\n };\n };\n zz_30 = ((2.6 * zz_30) + zz0_29);\n vec4 tmpvar_39;\n tmpvar_39.xyz = zz_30;\n tmpvar_39.w = li_28;\n float tmpvar_40;\n tmpvar_40 = sqrt(dot (zz_30, zz_30));\n xlat_mutablestruc = (sqrt(dot (tmpvar_39.xyw, tmpvar_39.xyw)) / 24.0);\n vec4 tmpvar_41;\n tmpvar_41 = texture (sampler_pc_main, uv_3);\n float tmpvar_42;\n float tmpvar_43;\n tmpvar_43 = (q10 * 0.7);\n tmpvar_42 = ((log(\n (1.0 + (tmpvar_40 / 24.0))\n ) * 0.02) * (1.0 - (1.0 - \n ((tmpvar_41.z + (0.003921569 * tmpvar_41.y)) + tmpvar_43)\n )));\n float tmpvar_44;\n vec4 tmpvar_45;\n tmpvar_45 = texture (sampler_pc_main, uv_3);\n tmpvar_44 = (1.0 - ((tmpvar_45.z + \n (0.003921569 * tmpvar_45.y)\n ) + tmpvar_43));\n if ((((tmpvar_25 <= tmpvar_44) && (tmpvar_40 < 24.0)) && (tmpvar_25 > 0.005))) {\n ret_4.x = (((1.0 - sustain) * xlat_mutablestruc) + (sustain * mix (texture (sampler_main, uv_3).xyz, \n ((texture (sampler_blur1, uv_3).xyz * scale1) + bias1)\n , vec3(\n (q14 * 4.0)\n )).x));\n float x_46;\n x_46 = ((1.0 - tmpvar_25) * 255.0);\n float ip_47;\n ip_47 = float(int(x_46));\n vec2 tmpvar_48;\n tmpvar_48.x = (x_46 - ip_47);\n tmpvar_48.y = (ip_47 / 255.0);\n ret_4.yz = tmpvar_48;\n } else {\n vec3 tmpvar_49;\n tmpvar_49.y = 0.0;\n tmpvar_49.x = sustain;\n tmpvar_49.z = (1.0 - tmpvar_42);\n vec3 tmpvar_50;\n tmpvar_50.xy = vec2(0.003921569, 0.0);\n tmpvar_50.z = (q14 / 6.0);\n ret_4 = ((texture (sampler_fc_main, uv_3).xyz * tmpvar_49) - tmpvar_50);\n };\n vec4 tmpvar_51;\n tmpvar_51.w = 1.0;\n tmpvar_51.xyz = ret_4;\n ret = tmpvar_51.xyz;\n }",comp:" shader_body { \n vec3 tmpvar_1;\n tmpvar_1.x = q4;\n tmpvar_1.y = q5;\n tmpvar_1.z = q6;\n mat3 tmpvar_2;\n tmpvar_2[uint(0)].x = q20;\n tmpvar_2[uint(0)].y = q23;\n tmpvar_2[uint(0)].z = q26;\n tmpvar_2[1u].x = q21;\n tmpvar_2[1u].y = q24;\n tmpvar_2[1u].z = q27;\n tmpvar_2[2u].x = q22;\n tmpvar_2[2u].y = q25;\n tmpvar_2[2u].z = q28;\n vec2 tmpvar_3;\n tmpvar_3.x = q1;\n tmpvar_3.y = q2;\n vec2 uv_4;\n vec3 ret_5;\n uv_4 = (((uv - 0.5) * 0.9) + 0.5);\n vec3 tmpvar_6;\n tmpvar_6.xy = ((uv_4 - 0.5) * min ((1.0 - texture (sampler_main, uv_4).z), (1.0 - \n ((texture (sampler_blur2, uv_4).xyz * scale2) + bias2)\n .z)));\n tmpvar_6.z = min ((1.0 - texture (sampler_main, uv_4).z), (1.0 - (\n (texture (sampler_blur2, uv_4).xyz * scale2)\n + bias2).z));\n float tmpvar_7;\n tmpvar_7 = clamp ((abs(\n ((1.0 - ((texture (sampler_blur2, uv_4).xyz * scale2) + bias2).z) - clamp ((1.0 - (\n (texture (sampler_blur2, tmpvar_3).xyz * scale2)\n + bias2).z), 0.1, 0.4))\n ) + 0.2), 0.0, 1.0);\n float tmpvar_8;\n tmpvar_8 = clamp (((1.0 - \n exp(-(((texture (sampler_blur1, uv_4).xyz * scale1) + bias1).x))\n ) - 0.2), 0.0, 1.0);\n ret_5 = ((mix (texture (sampler_main, uv_4).xyz, \n ((texture (sampler_blur1, uv_4).xyz * scale1) + bias1)\n , vec3(tmpvar_7)).x * (0.2 + \n ((1.0 - tmpvar_7) * (1.0 - min ((1.0 - texture (sampler_main, uv_4).z), (1.0 - \n ((texture (sampler_blur2, uv_4).xyz * scale2) + bias2)\n .z))))\n )) * (1.0 + (0.5 * \n sin((((tmpvar_6 / q7) * tmpvar_2) + tmpvar_1))\n )));\n vec3 tmpvar_9;\n tmpvar_9.xy = vec2(0.0, 1.0);\n tmpvar_9.z = (tmpvar_8 * 3.0);\n ret_5 = (mix (ret_5, tmpvar_9, vec3(tmpvar_8)) + ((\n pow ((1.0 - mix (texture (sampler_main, uv_4).xyz, (\n (texture (sampler_blur1, uv_4).xyz * scale1)\n + bias1), vec3(0.8, 0.8, 0.8)).z), 3.0)\n * \n (0.5 + (0.5 * slow_roam_cos))\n ) * q19).xyz);\n ret_5 = (1.0 - exp((-2.0 * ret_5)));\n vec4 tmpvar_10;\n tmpvar_10.w = 1.0;\n tmpvar_10.xyz = ret_5;\n ret = tmpvar_10.xyz;\n }"}},function(a,e){a.exports={baseVals:{rating:0,gammaadj:1.980001,decay:.5,echo_zoom:.999998,echo_alpha:.5,echo_orient:3,wave_mode:7,additivewave:1,wave_thick:1,modwavealphabyvolume:1,wave_brighten:0,darken:1,wave_a:.001,wave_scale:.958178,wave_smoothing:.45,modwavealphastart:0,modwavealphaend:1.32,warpanimspeed:1.4595,warpscale:2.0067,zoom:.9999,warp:.01,sx:.9999,ob_r:.3999,ob_a:.2,ib_size:0,mv_x:64,mv_y:48,mv_l:1.85,mv_r:.4999,mv_g:.4999,mv_b:.4999,mv_a:0,b1ed:0},shapes:[{baseVals:{enabled:0}},{baseVals:{enabled:1,sides:100,additive:1,x:.26,y:.2,rad:.393173,tex_zoom:.9355,r:0,g:.55,b:.5,g2:.4,b2:.4,a2:.07,border_r:.3,border_g:.7,border_b:.8,border_a:0},init_eqs_str:"a.g0=0;a.y0=0;a.q1=0;a.x0=0;a.q24=0;a.q26=0;a.r0=0;a.trig=0;a.q2=0;a.b0=0;a.rad0=0;",frame_eqs_str:"a.trig=a.q24;a.x0=a.x0*bnot(a.trig)+a.trig*(.5+div(randint(100),200));a.y0=a.y0*bnot(a.trig)+a.trig*(.5+div(randint(100),200));a.x0+=div(.1*a.q1*(3+a.q26),a.fps);a.y0+=div(.1*a.q2*(3+a.q26),a.fps);a.x0-=Math.floor(a.x0);a.y0-=Math.floor(a.y0);a.tex_ang=a.time;a.tex_zoom=a.q1;a.ang=div(a.time,100)*a.q2;a.x=a.x0;a.y=a.y0;a.rad0=a.rad0*bnot(a.trig)+a.trig*(.04+div(randint(100),1E3));a.rad=a.rad0;a.r0=bnot(a.trig)*a.r0+div(a.trig*randint(10),10);a.g0=bnot(a.trig)*a.g0+div(a.trig*\nrandint(10),10);a.b0=bnot(a.trig)*a.b0+div(a.trig*randint(10),10);a.r=a.r0;a.b=a.b0;a.g=a.g0;a.r2=0;a.b2=0;a.g2=0;a.a=1;a.a2=.3;"},{baseVals:{enabled:0}},{baseVals:{enabled:0}}],waves:[{baseVals:{enabled:0}},{baseVals:{enabled:0}},{baseVals:{enabled:0}},{baseVals:{enabled:0}}],init_eqs_str:"a.index2=0;a.index4=0;a.index=0;a.q22=0;a.q21=0;a.q1=0;a.dec_med=0;a.index3=0;a.rott=0;a.is_beat=0;a.q23=0;a.k1=0;a.q24=0;a.dec_slow=0;a.q11=0;a.q4=0;a.q26=0;a.p2=0;a.avg=0;a.beat=0;a.p1=0;a.peak=0;a.q2=0;a.q27=0;a.q3=0;a.t0=0;a.q28=0;a.q20=0;",frame_eqs_str:"a.dec_med=pow(.9,div(30,a.fps));a.dec_slow=pow(.99,div(30,a.fps));a.beat=Math.max(Math.max(a.bass,a.mid),a.treb);a.avg=a.avg*a.dec_slow+a.beat*(1-a.dec_slow);a.is_beat=above(a.beat,.2+a.avg+a.peak)*above(a.time,a.t0+.2);a.t0=a.is_beat*a.time+(1-a.is_beat)*a.t0;a.peak=a.is_beat*a.beat+(1-a.is_beat)*a.peak*a.dec_med;a.index=mod(a.index+a.is_beat,8);a.index2=mod(a.index2+a.is_beat*bnot(a.index),2);a.index3=mod(a.index3+a.is_beat*bnot(a.index)*bnot(a.index2),3);a.monitor=a.index4;\na.q20=a.avg;a.q21=a.beat;a.q22=a.peak;a.q23=a.index;a.q24=a.is_beat;a.q26=a.bass+a.mid+a.treb;a.q11=Math.min(a.q22,3);a.k1=a.is_beat*equal(a.index,0);a.p1=a.k1*(a.p1+1)+(1-a.k1)*a.p1;a.p2=a.dec_med*a.p2+(1-a.dec_med)*a.p1;a.rott=div(3.14159265359*a.p2,2);a.q27=8-a.index;a.q28=a.index2;a.q1=Math.cos(a.rott);a.q2=Math.sin(a.rott);a.q3=-a.q2;a.q4=a.q1;a.zoom=1+.02*a.q1;a.rot=.01*a.q2;a.dx=0*a.index;a.wave_a=0;",pixel_eqs_str:"",pixel_eqs:"",warp:"float xlat_mutabledx;\nfloat xlat_mutabledy;\nvec2 xlat_mutableuv2;\nvec2 xlat_mutablezz;\n shader_body { \n vec2 uv_1;\n vec3 crisp_2;\n vec2 tmpvar_3;\n tmpvar_3.y = 0.0;\n tmpvar_3.x = texsize.w;\n vec2 tmpvar_4;\n tmpvar_4.x = 0.0;\n tmpvar_4.y = texsize.z;\n xlat_mutablezz = ((uv * texsize.xy) * 0.01);\n vec2 tmpvar_5;\n tmpvar_5.x = (cos((xlat_mutablezz.y * q1)) * sin(-(xlat_mutablezz.y)));\n tmpvar_5.y = (sin(xlat_mutablezz.x) * cos((xlat_mutablezz.y * q2)));\n uv_1 = (uv - ((tmpvar_5 * texsize.zw) * (8.0 + \n (6.0 * q11)\n )));\n xlat_mutableuv2 = (((uv_1 / 2.0) * q27) / 4.0);\n xlat_mutabledx = dot ((texture (sampler_main, (xlat_mutableuv2 + tmpvar_3)).xyz - texture (sampler_main, (xlat_mutableuv2 - tmpvar_3)).xyz), vec3(0.32, 0.49, 0.29));\n xlat_mutabledy = dot ((texture (sampler_main, (xlat_mutableuv2 + tmpvar_4)).xyz - texture (sampler_main, (xlat_mutableuv2 - tmpvar_4)).xyz), vec3(0.32, 0.49, 0.29));\n float tmpvar_6;\n tmpvar_6 = (0.15 + (0.1 * q28));\n vec2 tmpvar_7;\n tmpvar_7 = (xlat_mutableuv2 + (time / 100.0));\n xlat_mutabledx = (xlat_mutabledx + (tmpvar_6 * (texture (sampler_noise_hq, tmpvar_7).x - 0.5)));\n xlat_mutabledy = (xlat_mutabledy + (tmpvar_6 * (texture (sampler_noise_hq, tmpvar_7).y - 0.5)));\n vec2 tmpvar_8;\n tmpvar_8.x = xlat_mutabledx;\n tmpvar_8.y = xlat_mutabledy;\n xlat_mutablezz = tmpvar_8;\n crisp_2 = (texture (sampler_main, (uv_1 + (tmpvar_8 * 0.04))).xyz + texture (sampler_main, uv_1).xyz);\n crisp_2 = (crisp_2 * 0.5);\n crisp_2 = (crisp_2 + ((0.05 * \n (0.9 + (0.1 * roam_cos))\n .xyz) - (\n sqrt(dot (tmpvar_8, tmpvar_8))\n * 0.3)));\n vec4 tmpvar_9;\n tmpvar_9.w = 1.0;\n tmpvar_9.xyz = ((crisp_2 * 0.97) - 0.015);\n ret = tmpvar_9.xyz;\n }",comp:"vec2 xlat_mutabledz;\nvec3 xlat_mutableneu;\nvec3 xlat_mutableret1;\nvec2 xlat_mutableuv3;\n shader_body { \n vec2 uv2_1;\n vec2 tmpvar_2;\n tmpvar_2.y = 0.0;\n tmpvar_2.x = texsize.z;\n vec2 tmpvar_3;\n tmpvar_3.x = 0.0;\n tmpvar_3.y = texsize.w;\n xlat_mutabledz.x = dot ((texture (sampler_main, (uv + tmpvar_2)).xyz - texture (sampler_main, (uv - tmpvar_2)).xyz), vec3(0.32, 0.49, 0.29));\n xlat_mutabledz.y = dot ((texture (sampler_main, (uv + tmpvar_3)).xyz - texture (sampler_main, (uv - tmpvar_3)).xyz), vec3(0.32, 0.49, 0.29));\n uv2_1 = (uv - 0.5);\n xlat_mutableuv3 = ((0.2 * uv2_1) + 0.5);\n float tmpvar_4;\n tmpvar_4 = (time / 2.0);\n xlat_mutableuv3 = ((0.2 * cos(\n ((42.0 * fract(xlat_mutableuv3)) + tmpvar_4)\n )) + (99.0 * xlat_mutabledz));\n float tmpvar_5;\n tmpvar_5 = clamp ((0.01 / sqrt(\n dot (xlat_mutableuv3, xlat_mutableuv3)\n )), 0.0, 1.0);\n xlat_mutableneu = ((0.1 * vec3(tmpvar_5)) + (0.9 * dot (vec3(tmpvar_5), vec3(0.32, 0.49, 0.29))));\n xlat_mutableret1 = max (vec3(0.0, 0.0, 0.0), (xlat_mutableneu * 1.252262));\n xlat_mutableuv3 = ((0.2 * uv2_1) + 0.5);\n xlat_mutableuv3 = ((0.2 * cos(\n ((42.0 * fract(xlat_mutableuv3)) + tmpvar_4)\n )) + (99.0 * xlat_mutabledz));\n float tmpvar_6;\n tmpvar_6 = clamp ((0.01 / sqrt(\n dot (xlat_mutableuv3, xlat_mutableuv3)\n )), 0.0, 1.0);\n xlat_mutableneu = ((0.1 * vec3(tmpvar_6)) + (0.9 * dot (vec3(tmpvar_6), vec3(0.32, 0.49, 0.29))));\n xlat_mutableret1 = max (xlat_mutableret1, (xlat_mutableneu * 1.252262));\n vec4 tmpvar_7;\n tmpvar_7.w = 1.0;\n tmpvar_7.xyz = (xlat_mutableret1 + clamp ((\n (16.0 * ((0.5 * texture (sampler_main, (uv + \n (0.1 * xlat_mutabledz)\n )).xyz) + 0.01))\n * \n (0.1 + xlat_mutableret1)\n ), 0.0, 1.0));\n ret = tmpvar_7.xyz;\n }"}},function(a,e){a.exports={baseVals:{rating:4,gammaadj:1.98,decay:.5,echo_zoom:1,echo_alpha:.5,echo_orient:3,wave_mode:4,additivewave:1,wave_thick:1,modwavealphabyvolume:1,wave_brighten:0,darken:1,wave_a:.001,wave_scale:.527,wave_smoothing:.45,modwavealphastart:0,modwavealphaend:1.32,warpanimspeed:1.459,warpscale:2.007,zoom:.9999,warp:.01,sx:.9999,wave_r:.8,wave_g:.49,ob_size:.015,ob_a:1,ib_size:.26,mv_x:64,mv_y:48,mv_l:1.85,mv_r:.5,mv_g:.5,mv_b:.5,mv_a:0,b2x:.3,b1ed:0},shapes:[{baseVals:{enabled:0}},{baseVals:{enabled:0}},{baseVals:{enabled:0}},{baseVals:{enabled:0}}],waves:[{baseVals:{enabled:1,samples:506,sep:116,spectrum:1,thick:1,scaling:1.07408,smoothing:0,a:.7},init_eqs_str:"a.n=0;a.m=0;",frame_eqs_str:"",point_eqs_str:"a.n=Math.floor((a.reg00+.5)*a.sample);a.m=30001+div(a.n,div(a.reg00,a.reg01));a.gmegabuf[Math.floor(a.m)]=a.value1+a.value2;a.x=a.gmegabuf[Math.floor(1E4+a.n)];a.y=a.gmegabuf[Math.floor(15E3+a.n)];a.a=a.gmegabuf[Math.floor(2E4+a.n)];a.b=Math.min(Math.max(a.gmegabuf[Math.floor(25E3+a.n)],0),1);a.r=1-a.b;a.g=.5;"},{baseVals:{enabled:1,samples:506,thick:1,scaling:.89152,smoothing:.82},init_eqs_str:"a.n=0;",frame_eqs_str:"",point_eqs_str:"a.n=Math.floor((a.reg00+.5)*a.sample)+a.reg00;a.x=a.gmegabuf[Math.floor(1E4+a.n)];a.y=a.gmegabuf[Math.floor(15E3+a.n)];a.a=a.gmegabuf[Math.floor(2E4+a.n)];a.b=Math.min(Math.max(a.gmegabuf[Math.floor(25E3+a.n)],0),1);a.r=1-a.b;a.g=.5;"},{baseVals:{enabled:1,samples:506,thick:1,scaling:.89152,smoothing:.82},init_eqs_str:"a.n=0;",frame_eqs_str:"",point_eqs_str:"a.n=Math.floor((a.reg00+.5)*a.sample)+2*a.reg00;a.x=a.gmegabuf[Math.floor(1E4+a.n)];a.y=a.gmegabuf[Math.floor(15E3+a.n)];a.a=a.gmegabuf[Math.floor(2E4+a.n)];a.b=Math.min(Math.max(a.gmegabuf[Math.floor(25E3+a.n)],0),1);a.r=1-a.b;a.g=.5;"},{baseVals:{enabled:1,samples:506,spectrum:1,thick:1},init_eqs_str:"a.n=0;",frame_eqs_str:"",point_eqs_str:"a.n=Math.floor((a.reg00-.5)*a.sample)+3*a.reg00;a.x=a.gmegabuf[Math.floor(1E4+a.n)];a.y=a.gmegabuf[Math.floor(15E3+a.n)];a.a=a.gmegabuf[Math.floor(2E4+a.n)];a.b=Math.min(Math.max(a.gmegabuf[Math.floor(25E3+a.n)],0),1);a.r=1-a.b;a.g=.5;"}],init_eqs_str:"a.xang=0;a.fov=0;a.hell=0;a.cbeat=0;a.index2=0;a.bindex=0;a.ran4=0;a.index=0;a.dec_v=0;a.yang=0;a.q29=0;a.q6=0;a.amp_=0;a.xlen=0;a.smooth=0;a.q1=0;a.dec_med=0;a.sum=0;a.q5=0;a.dec_f=0;a.trely=0;a.flen=0;a.reg01=0;a.my=0;a.oz=0;a.imag=0;a.is_beat=0;a.yind=0;a.oy0a=0;a.dec_slow=0;a.ran2=0;a.ind=0;a.z0=0;a.ylen=0;a.real=0;a.ran4_=0;a.ran3=0;a.q4=0;a.mz=0;a.oy0=0;a.amp=0;a.tc0=0;a.oy=0;a.avg=0;a.mx=0;a.vol=0;a.ran2_=0;a.peak=0;a.decc=0;a.q2=0;a.bd_bt=0;a.zang=0;a.q3=0;a.reg00=0;\na.trelz=0;a.q32=0;a.ran3_=0;a.q28=0;a.trelx=0;a.q30=0;a.ox=0;a.xind=0;for(var b=a.index=0;7E4>b;b++)a.megabuf[Math.floor(a.index)]=0,a.gmegabuf[Math.floor(a.index)]=0,a.index+=1;a.zang=1;a.yang=0;a.zang=2;",frame_eqs_str:"a.xlen=45;a.ylen=45;a.flen=30;a.reg00=div(a.xlen*a.ylen,4);a.reg01=div(a.reg00,4);a.dec_med=1-div(.06*30,a.fps);a.dec_slow=1-div(.6,a.fps);a.dec_f=pow(.8,div(30,a.fps));a.q30=a.dec_slow;a.smooth=Math.max(1,pow(6,div(a.fps,30))-2);a.cbeat=a.bass+a.mid+a.treb;a.decc=.00001a.cbeat?1:0)?.8:a.dec_med;a.vol=a.vol*a.decc+(1-a.decc)*a.cbeat;a.avg=a.avg*a.dec_slow+a.cbeat*(1-a.dec_slow);a.is_beat=above(a.cbeat,1.5*a.avg)*above(a.time,a.tc0+.2);a.tc0=.00001c;c++){a.xind=-1;for(var d=0;3>d;d++)a.ox=mod((a.cx+.5)*a.xlen+a.xind,a.xlen),a.oy=mod((a.cy+.5)*a.ylen+a.yind,a.ylen),a.amp=3*(a.cx*a.cx+a.cy*a.cy),a.megabuf[Math.floor(a.oy*a.ylen+a.ox)]-=div(div(60,a.fps)*sqrt(a.amp)*above(a.amp,.02),1+a.xind*a.xind+a.yind*a.yind),a.xind+=1;a.yind+=1}a.ind+=1}for(b=a.yind=0;bMath.abs(mod(a.bindex,4)-0)?1:0)?a.ran2=div(randint(100)-30,60):0;.00001\nMath.abs(mod(a.bindex,4)-2)?1:0)?a.ran3=div(randint(100)-30,60):0;.00001Math.abs(mod(a.bindex,6)-2)?1:0)?a.ran4=div(randint(100)-30,60):0;a.dec_v=Math.min(Math.max(0,1-div(8*a.vol,a.fps)),a.dec_slow);a.ran2_=a.ran2_*a.dec_v+(1-a.dec_v)*a.ran2;a.ran3_=a.ran3_*a.dec_v+(1-a.dec_v)*a.ran3;a.ran4_=a.ran4_*a.dec_v+(1-a.dec_v)*a.ran4;a.trelx+=div(div(a.ran2_,a.fps),7);a.trely+=div(div(a.ran3_,a.fps),2);a.trelz+=div(div(a.ran4_,a.fps),6);a.zang=6*Math.sin(a.trelz);a.xang=6*Math.sin(div(a.zang,\n5)+a.trelx);a.yang=6*Math.sin(0*div(a.zang,3)+a.trely);a.q1=Math.cos(a.xang);a.q2=Math.sin(a.xang);a.q3=Math.cos(a.yang);a.q4=Math.sin(a.yang);a.q5=Math.cos(a.zang);a.q6=Math.sin(a.zang);a.fov=1;for(b=a.yind=0;b 0.0))\n ) * 0.2) * min (1.0, (1.0/(tmpvar_8))));\n ret_4 = (ret_4 + tmpvar_14);\n ret_4 = (ret_4 + ((\n (sin((12.0 * q2)) * tmpvar_7)\n * tmpvar_14) * dot (\n (12.0 * ((texture (sampler_blur1, (tmpvar_10 - vec2(-0.5, 0.3))).xyz * scale1) + bias1))\n , vec3(0.32, 0.49, 0.29))));\n ret_4 = (ret_4 + ((\n ((0.5 / abs(tmpvar_8)) * normalize(xlat_mutablecol))\n * \n float((tmpvar_8 < 0.0))\n ) * tmpvar_7));\n vec4 tmpvar_15;\n tmpvar_15.w = 1.0;\n tmpvar_15.xyz = ret_4;\n ret = tmpvar_15.xyz;\n }"}},function(a,e){a.exports={baseVals:{rating:5,gammaadj:1.980001,decay:.5,echo_zoom:.999998,echo_alpha:.5,echo_orient:3,modwavealphabyvolume:1,darken:1,wave_a:.001,wave_scale:10.437056,wave_smoothing:.45,wave_mystery:.08,modwavealphastart:0,modwavealphaend:1.32,warpanimspeed:1.4595,warpscale:2.0067,zoom:.9999,warp:.01,sx:.9999,wave_r:0,wave_g:.99,ob_size:0,ob_r:1,ob_g:1,ob_b:1,ib_size:0,mv_x:64,mv_y:48,mv_l:1.85,mv_r:.4999,mv_g:.4999,mv_b:.4999,mv_a:0,b1ed:0},shapes:[{baseVals:{enabled:1,rad:.048958,tex_ang:1.00531,tex_zoom:1.531168,r:.5,g:1,b:.9,r2:.83,g2:.93,b2:.8,a2:1,border_b:0,border_a:0},init_eqs_str:"a.trel=0;a.q20=0;a.q28=0;a.q26=0;",frame_eqs_str:"a.trel=div(a.time,2)+a.q20;a.x=.5+Math.sin(2*a.trel);a.y=.5+Math.cos(1.3*a.trel+div(a.q28,3));a.a=div(a.q26,4)+.2;"},{baseVals:{enabled:0}},{baseVals:{enabled:1,x:.503,rad:.038857,tex_zoom:.609857,g:.1,a:.9,r2:1,b2:1,border_r:.5,border_g:.5,border_b:.5,border_a:0},init_eqs_str:"a.is_beat=0;a.t0=0;a.q21=0;",frame_eqs_str:"a.x=div(randint(10),10);a.y=div(randint(10),10);a.r=div(randint(4),3);a.g=div(randint(4),3);a.b=div(randint(4),3);a.is_beat=above(a.time,a.t0+.03);a.t0=a.is_beat*a.time+(1-a.is_beat)*a.t0;a.a=Math.min(div(a.q21,2),.9)*a.is_beat;a.rad=div(a.a*a.a,3);"},{baseVals:{enabled:0}}],waves:[{baseVals:{enabled:1,sep:120,additive:1,scaling:.891519,smoothing:.82,a:.6},init_eqs_str:"a.k1=0;a.k2=0;a.xi=0;a.yi=0;a.dx=0;a.dy=0;a.q22=0;a.t2=0;a.t1=1+.3*(.01*randint(101)-.01*randint(101));a.t2=1+.3*(.01*randint(101)-.01*randint(101));a.t3=1+.3*(.01*randint(101)-.01*randint(101));a.t4=1+.3*(.01*randint(101)-.01*randint(101));a.t5=1+.3*(.01*randint(101)-.01*randint(101));a.t6=1+.3*(.01*randint(101)-.01*randint(101));a.t7=1+.3*(.01*randint(101)-.01*randint(101));a.t8=1+.3*(.01*randint(101)-.01*randint(101));",frame_eqs_str:"a.t2+=a.bass_att;",point_eqs_str:"a.k1=mod(100*a.sample,8);a.k2=bnot(a.k1);a.xi=a.value1*a.k2+a.xi*(1-a.k2);a.yi=a.value2*(1-a.k2)+a.yi*a.k2;a.dx=.99*a.dx+a.xi;a.dy=.99*a.dy+a.yi;a.x=.5+div(a.xi,2);a.y=.5+div(a.yi,2);a.a=div(a.q22,8);a.a=Math.min(a.a,.2);"},{baseVals:{enabled:0}},{baseVals:{enabled:0}},{baseVals:{enabled:0}}],init_eqs_str:"a.index=0;a.q22=0;a.q21=0;a.fade=0;a.q1=0;a.dec_med=0;a.rott=0;a.is_beat=0;a.q23=0;a.k1=0;a.q24=0;a.dec_slow=0;a.q4=0;a.q26=0;a.p2=0;a.avg=0;a.beat=0;a.p1=0;a.peak=0;a.q2=0;a.q27=0;a.q3=0;a.t0=0;a.q32=0;a.q20=0;a.fade=.5;",frame_eqs_str:"a.dec_med=pow(.9,div(30,a.fps));a.dec_slow=pow(.99,div(30,a.fps));a.beat=Math.max(Math.max(a.bass,a.mid),a.treb);a.avg=a.avg*a.dec_slow+a.beat*(1-a.dec_slow);a.is_beat=above(a.beat,.5+a.avg+a.peak)*above(a.time,a.t0+.2);a.t0=a.is_beat*a.time+(1-a.is_beat)*a.t0;a.peak=a.is_beat*a.beat+(1-a.is_beat)*a.peak*a.dec_med;a.index=mod(a.index+a.is_beat,8);a.q20=a.avg;a.q21=a.beat;a.q22=a.peak;a.q23=a.index;a.q24=a.is_beat;a.q26=a.bass+a.mid+a.treb;a.k1=a.is_beat*equal(a.index,0);a.p1=\na.k1*(a.p1+1)+(1-a.k1)*a.p1;a.p2=a.dec_med*a.p2+(1-a.dec_med)*a.p1;a.rott=div(3.14159265358*a.p2,2);a.q27=a.index+1;a.q1=Math.cos(a.rott);a.q2=Math.sin(a.rott);a.q3=-a.q2;a.q4=a.q1;a.zoom=1;a.rot=-0*a.index;a.fade=a.fade*a.dec_med+pow(.996,div(30,a.fps))*(1-a.dec_med);a.q32=a.fade;",pixel_eqs_str:"",pixel_eqs:"",warp:" shader_body { \n vec2 zz_1;\n mat2 tmpvar_2;\n tmpvar_2[uint(0)] = _qa.xy;\n tmpvar_2[1u] = _qa.zw;\n zz_1 = (((\n (uv - vec2(0.5, 0.5))\n * texsize.xy) * (0.015 * q27)) * tmpvar_2);\n vec4 tmpvar_3;\n tmpvar_3.w = 1.0;\n tmpvar_3.xyz = (((q32 * texture (sampler_main, \n (uv + ((clamp (\n (sin(zz_1) / cos(zz_1))\n , vec2(-20.0, -20.0), vec2(20.0, 20.0)) * texsize.zw) * 8.0))\n ).xyz) + (\n (0.03 * texture (sampler_noise_lq, ((uv * 0.3) + (0.01 * rand_frame).xy)))\n .xyz * \n (1.0 - ((texture (sampler_blur1, uv).xyz * scale1) + bias1))\n )) - 0.02);\n ret = tmpvar_3.xyz;\n }",comp:" shader_body { \n vec4 tmpvar_1;\n tmpvar_1 = texture (sampler_main, uv);\n vec4 tmpvar_2;\n tmpvar_2.w = 1.0;\n tmpvar_2.xyz = ((tmpvar_1.xyz + clamp (\n (3.0 * (((texture (sampler_blur1, \n (uv - (0.01 * tmpvar_1.xyz).xy)\n ).xyz * scale1) + bias1) - vec3(0.1, 0.1, 0.2)))\n , 0.0, 1.0)) * 1.3);\n ret = tmpvar_2.xyz;\n }"}},function(a,e){a.exports={baseVals:{rating:4,gammaadj:1.98,decay:.5,echo_zoom:.952,echo_alpha:.5,echo_orient:3,wave_mode:6,additivewave:1,wave_thick:1,modwavealphabyvolume:1,wave_brighten:0,darken:1,wave_a:.001,wave_scale:2.103,wave_smoothing:.54,wave_mystery:.38,modwavealphastart:.81,modwavealphaend:1.4,warpanimspeed:1.459,warpscale:2.007,zoom:.9999,warp:.01,sx:.9999,wave_r:0,wave_g:0,wave_b:0,ob_size:.015,ob_b:1,ib_size:.26,mv_x:64,mv_y:48,mv_l:1.85,mv_r:.5,mv_g:.5,mv_b:.5,mv_a:0,b1ed:0},shapes:[{baseVals:{enabled:1,sides:16,thickoutline:1,textured:1,num_inst:3,x:.73,rad:.29466,tex_zoom:1.87511,r:.7,g:.7,b:1,g2:0,border_b:0,border_a:0},init_eqs_str:"a.trig=0;a.q25=0;a.x0=0;a.y0=0;",frame_eqs_str:"a.trig=a.q25;a.a=.8*a.trig;a.a2=0;a.x0=a.x0*bnot(a.trig)+div(a.trig*randint(100),100);a.y0=a.y0*bnot(a.trig)+div(a.trig*randint(100),100);a.tex_ang=randint(20);a.rad=.1+div(randint(10),8);a.x=a.x0;a.y=a.y0;a.r=.7+.3*Math.sin(div(a.time,12));a.b=.7+.3*Math.sin(div(a.time,15));a.g=.7+.3*Math.sin(div(a.time,8));a.r2=a.r;a.b2=a.b;a.g2=a.g;"},{baseVals:{enabled:1,sides:36,thickoutline:1,textured:1,num_inst:4,x:.3,rad:.05429,ang:1.25664,tex_ang:.37699,tex_zoom:1.02841,g:.7,b:.5,r2:1,g2:0,border_g:.59,border_b:0,border_a:0},init_eqs_str:"a.q31=0;a.q32=0;a.q30=0;",frame_eqs_str:"a.x=a.q31;a.y=a.q32;a.rad=.06;a.tex_ang=a.time;a.a=a.q30;"},{baseVals:{enabled:0}},{baseVals:{enabled:0}}],waves:[{baseVals:{enabled:0}},{baseVals:{enabled:0}},{baseVals:{enabled:0}},{baseVals:{enabled:0}}],init_eqs_str:"a.q25=0;a.index=0;a.q12=0;a.q22=0;a.q21=0;a.q15=0;a.q29=0;a.q6=0;a.dec_med=0;a.q5=0;a.mindev=0;a.trel=0;a.t0a=0;a.mov1=0;a.vis=0;a.is_beat=0;a.q31=0;a.q23=0;a.q24=0;a.dec_slow=0;a.q11=0;a.hpeak=0;a.q10=0;a.sdev=0;a.med=0;a.spb=0;a.dir=0;a.spb_=0;a.q16=0;a.rota=0;a.q19=0;a.vol=0;a.peak=0;a.trig1=0;a.wamp=0;a.speed=0;a.t0=0;a.vol_=0;a.q32=0;a.q7=0;a.wamp_=0;a.q30=0;a.q20=0;a.q8=0;a.t0a=a.time;a.t0=a.time+.5;a.spb_=.5;a.volb=.5;a.volx=.5;a.vol_=1;a.index=0;a.mov1=0;a.sdev=.1;a.wamp_=\n.1;",frame_eqs_str:"a.dec_med=pow(.8,div(30,a.fps));a.dec_slow=pow(.95,div(30,a.fps));a.vol=div(a.bass+a.med+a.treb,1.5);a.vol_=a.vol_*a.dec_slow+(1-a.dec_slow)*a.vol;a.is_beat=above(a.vol,a.vol_+2*a.peak)*above(a.time,a.t0+.45*a.spb_);a.t0a=.00001= 0.0)\n )) + (q30 * 0.02));\n xlat_mutablered = (xlat_mutablered + ((\n (xlat_mutablewave / abs(sin((\n (1.0/(tmpvar_12))\n + \n (q1 / 8.0)\n ))))\n * xlat_mutablegreen) * ky_3));\n xlat_mutablezv = ((time * 0.08) + (0.2 * sin(\n ((uv * 16.0) + time)\n ))).x;\n vec2 tmpvar_13;\n tmpvar_13.x = q28;\n tmpvar_13.y = (0.2 * time);\n xlat_mutableuv2 = ((tmpvar_4 * 2.0) + tmpvar_13);\n vec3 tmpvar_14;\n tmpvar_14.xy = xlat_mutableuv2;\n tmpvar_14.z = xlat_mutablezv;\n vec3 tmpvar_15;\n tmpvar_15.xy = (xlat_mutableuv2 * vec2(2.0, 2.0));\n tmpvar_15.z = (xlat_mutablezv * 2.0);\n vec3 tmpvar_16;\n tmpvar_16.xy = (xlat_mutableuv2 * vec2(4.0, 4.0));\n tmpvar_16.z = (xlat_mutablezv * 3.0);\n vec3 tmpvar_17;\n tmpvar_17.xy = (xlat_mutableuv2 * vec2(8.0, 8.0));\n tmpvar_17.z = (xlat_mutablezv * 7.0);\n xlat_mutableuv2 = (tmpvar_4 + ((\n ((texture (sampler_noisevol_hq, tmpvar_14).z + (texture (sampler_noisevol_hq, tmpvar_15).z / 2.0)) + (texture (sampler_noisevol_hq, tmpvar_16).z / 4.0))\n + \n (texture (sampler_noisevol_hq, tmpvar_17).z / 8.0)\n ) * 0.1));\n vec3 tmpvar_18;\n tmpvar_18.x = xlat_mutablered;\n tmpvar_18.y = xlat_mutablegreen;\n tmpvar_18.z = ((1.0 + xlat_mutableuv2.y) * sqrt(dot (xlat_mutableuv2, xlat_mutableuv2)));\n vec4 tmpvar_19;\n tmpvar_19.w = 1.0;\n tmpvar_19.xyz = ((tmpvar_18 * 0.8) - 0.005);\n ret = tmpvar_19.xyz;\n }",comp:"float trel;\nfloat vshift;\nvec3 xlat_mutableret1;\nvec2 xlat_mutablers;\nvec2 xlat_mutablers0;\nfloat xlat_mutablesmoke;\nfloat xlat_mutablesmoke2;\nvec2 xlat_mutableuv_l;\nfloat xlat_mutablew;\nfloat xlat_mutablez;\n shader_body { \n trel = (time / 2.0);\n vshift = (0.5 - q29);\n vec2 tmpvar_1;\n tmpvar_1.x = rand_preset.x;\n tmpvar_1.y = ((-0.1 - (rand_preset.y / 2.0)) + vshift);\n vec2 tmpvar_2;\n tmpvar_2.y = 0.0;\n tmpvar_2.x = (0.005 * time);\n xlat_mutableuv_l = (uv + tmpvar_2);\n vec2 tmpvar_3;\n vec2 tmpvar_4;\n tmpvar_4 = (uv - 0.5);\n tmpvar_3 = sin(((\n (tmpvar_4 * aspect.xy)\n + 0.5) - (tmpvar_1 * aspect.xy)));\n vec2 tmpvar_5;\n tmpvar_5 = fract(uv);\n vec3 tmpvar_6;\n tmpvar_6 = vec3((texture (sampler_fc_main, tmpvar_5).x + (2.0 * (\n (texture (sampler_blur2, tmpvar_5).xyz * scale2)\n + bias2).x)));\n vec3 tmpvar_7;\n tmpvar_7.x = tmpvar_6.x;\n tmpvar_7.y = pow (tmpvar_6.x, 2.1);\n tmpvar_7.z = pow (tmpvar_6.x, 4.0);\n xlat_mutablew = ((uv.y - (\n ((dot (texture (sampler_noise_hq, vec2((xlat_mutableuv_l.x / 6.0))), vec4(0.32, 0.49, 0.29, 0.0)) + uv.x) - 0.5)\n / 4.0)) - vshift);\n xlat_mutablez = (0.2 / xlat_mutablew);\n float tmpvar_8;\n tmpvar_8 = clamp ((-0.2 - (\n (8.0 * q5)\n * xlat_mutablew)), 0.0, 1.0);\n xlat_mutablers0.x = (((uv.x - 0.5) * (1.0 + \n abs(xlat_mutablez)\n )) * 2.0);\n xlat_mutablers0.y = xlat_mutablez;\n xlat_mutablers0 = (xlat_mutablers0 * ((4.0 * \n float((xlat_mutablez <= 0.0))\n ) + 1.0));\n vec2 tmpvar_9;\n tmpvar_9.x = 0.0;\n tmpvar_9.y = (1.5 + xlat_mutablew);\n vec2 x_10;\n x_10 = (xlat_mutablers0 - tmpvar_9);\n float tmpvar_11;\n tmpvar_11 = ((q26 / sqrt(\n dot (x_10, x_10)\n )) / 4.0);\n xlat_mutablers0 = (xlat_mutablers0 * (1.0 + (tmpvar_11 * tmpvar_11)));\n xlat_mutablers.x = (xlat_mutablers0 * 2.0).x;\n xlat_mutablers.y = (xlat_mutablers0.y + (trel * 0.5));\n float tmpvar_12;\n tmpvar_12 = (texture (sampler_main, uv).y * tmpvar_8);\n vec3 tmpvar_13;\n tmpvar_13.xy = (xlat_mutablers * 2.0);\n tmpvar_13.z = (0.2 * trel);\n float tmpvar_14;\n tmpvar_14 = dot (((\n ((2.0 * clamp ((texture (sampler_noisevol_hq, \n (tmpvar_13 * float((xlat_mutablez >= 0.0)))\n ) - 0.2), 0.0, 1.0)).xyz * clamp ((texture (sampler_noise_hq, (xlat_mutablers / 16.0)) - 0.5), 0.0, 1.0).x)\n / \n sqrt(abs(xlat_mutablez))\n ) + tmpvar_11), vec3(0.32, 0.49, 0.29));\n vec3 tmpvar_15;\n tmpvar_15.x = tmpvar_14;\n tmpvar_15.y = pow (tmpvar_14, 2.1);\n tmpvar_15.z = pow (tmpvar_14, 6.0);\n xlat_mutableret1 = (((\n ((uv.y * (1.0 + q23)) + (0.1 / sqrt(dot (tmpvar_3, tmpvar_3))))\n * vec3(0.1, 0.1, 0.2)) * tmpvar_8) + (clamp (tmpvar_15, 0.0, 1.0) * (1.0 - \n clamp ((-1.0 - (12.0 * xlat_mutablew)), 0.0, 1.0)\n )));\n xlat_mutableret1 = (xlat_mutableret1 * clamp ((1.0 - \n (tmpvar_12 * 8.0)\n ), 0.0, 1.0));\n vec4 tmpvar_16;\n tmpvar_16 = texture (sampler_main, fract((tmpvar_4 + 0.5)));\n xlat_mutablesmoke = (tmpvar_16.z * (tmpvar_16.z * q5));\n xlat_mutableret1 = (xlat_mutableret1 * clamp ((1.0 - \n ((xlat_mutablesmoke * q32) * tmpvar_8)\n ), 0.0, 1.0));\n xlat_mutablesmoke2 = (texture (sampler_main, ((tmpvar_4 / 2.0) + 0.5)).z * q30);\n xlat_mutableret1 = (xlat_mutableret1 + (clamp (tmpvar_7, 0.0, 1.0) - (\n ((xlat_mutablez * clamp ((xlat_mutablesmoke + \n (xlat_mutablew * q31)\n ), 0.0, 1.0)) * vec3(1.0, 0.4, 0.1))\n * tmpvar_8)));\n float tmpvar_17;\n tmpvar_17 = clamp (((\n (tmpvar_12 * xlat_mutablez)\n * xlat_mutablez) / 16.0), 0.0, 1.0);\n vec3 tmpvar_18;\n tmpvar_18.x = tmpvar_17;\n tmpvar_18.y = pow (tmpvar_17, 2.1);\n tmpvar_18.z = pow (tmpvar_17, 6.0);\n xlat_mutableret1 = (xlat_mutableret1 + clamp (tmpvar_18, 0.0, 1.0));\n xlat_mutableret1 = (xlat_mutableret1 * clamp ((\n (((dot (texture (sampler_noise_mq, vec2(\n ((xlat_mutableuv_l.x / 4.0) + (0.02 * trel))\n )), vec4(0.32, 0.49, 0.29, 0.0)) / 6.0) - uv.y) + 0.8)\n * 32.0), 0.0, 1.0));\n vec3 tmpvar_19;\n tmpvar_19.x = xlat_mutablesmoke2;\n tmpvar_19.y = pow (xlat_mutablesmoke2, 2.1);\n tmpvar_19.z = pow (xlat_mutablesmoke2, 6.0);\n xlat_mutableret1 = ((xlat_mutableret1 * clamp (\n (1.0 - xlat_mutablesmoke2)\n , 0.0, 1.0)) + clamp (tmpvar_19, 0.0, 1.0));\n vec4 tmpvar_20;\n tmpvar_20.w = 1.0;\n tmpvar_20.xyz = (1.0 - exp((\n -(xlat_mutableret1)\n * 2.0)));\n ret = tmpvar_20.xyz;\n }"}},function(a,e){a.exports={baseVals:{rating:3,gammaadj:1.98,decay:.5,echo_zoom:1,echo_alpha:.5,echo_orient:3,wave_mode:6,wave_thick:1,modwavealphabyvolume:1,wave_brighten:0,darken:1,wave_a:.001,wave_scale:.527,wave_smoothing:.09,modwavealphastart:0,modwavealphaend:1.32,warpanimspeed:1.459,warpscale:2.007,zoom:.9999,warp:.01,sx:.9999,wave_r:.8,wave_g:.49,ob_a:1,ib_size:.26,mv_x:64,mv_y:48,mv_l:1.85,mv_r:.5,mv_g:.5,mv_b:.5,mv_a:0,b2x:.7,b1ed:0},shapes:[{baseVals:{enabled:1,sides:12,num_inst:1024,rad:.03632,tex_ang:1.00531,tex_zoom:1.53117,b:1,a:0,g2:0,border_b:0,border_a:0},init_eqs_str:"a.fov=0;a.n=0;a.x0=0;a.y0=0;a.z0=0;a.q32=0;a.t1=0;",frame_eqs_str:"a.fov=a.reg03;a.n=a.instance*a.reg00;a.x0=a.gmegabuf[Math.floor(a.n)];a.y0=a.gmegabuf[Math.floor(a.n+1)];a.z0=a.gmegabuf[Math.floor(a.n+2)]+a.reg02;a.x=div(a.x0,a.z0)*a.fov+.5;a.y=div(a.y0,a.z0)*a.q32*a.fov+.5;a.r=a.gmegabuf[Math.floor(a.n+3)];a.g=a.gmegabuf[Math.floor(a.n+4)];a.b=a.gmegabuf[Math.floor(a.n+5)];a.r2=div(a.r,2);a.g2=div(a.g,2);a.b2=div(a.b2,2);a.a=div(a.instance,1024);a.a2=.5*a.a;a.rad=Math.min(div(.02,a.z0),.5)*(0b;b++)a.megabuf[Math.floor(a.index)]=0,a.gmegabuf[Math.floor(a.index)]=0,a.index+=1;a.recsize=8;a.reg00=a.recsize;a.points=1024;a.reg01=0;a.zofs=1+2*div(randint(100),100);a.reg02=a.zofs;a.fov=.3;a.reg03=a.fov;',frame_eqs_str:'a.dec_f=pow(.3,div(30,a.fps));a.dec_m=pow(.85,div(30,a.fps));a.dec_s=pow(.95,div(30,a.fps));a.beat=a.bass+a.mid+a.treb-(a.bass_att+a.mid_att+a.treb_att)+(a.bass+a.mid+a.treb);a.beat/=3;a.peak=a.peak*a.dec_m+(1-a.dec_m)*pow(a.beat-1,1)*(1Math.abs(a.index-2)?1:0))?(a.ran4=div(randint(100),50)-1,a.ran5=div(randint(100),50)-1,a.ran6=div(randint(100),50)-1):0;a.ran4_=a.dec_m*a.ran4_+(1-a.dec_m)*a.ran4;a.ran5_=a.dec_m*a.ran5_+(1-a.dec_m)*a.ran5;a.ran6_=\na.dec_m*a.ran6_+(1-a.dec_m)*a.ran6;.00001Math.abs(a.index-6)?1:0))?(a.ran7=div(randint(100),50)-1,a.ran8=div(randint(100),50)-1,a.ran9=div(randint(100),50)-1):0;a.ran7_=a.dec_m*a.ran7_+(1-a.dec_m)*a.ran7;a.ran8_=a.dec_m*a.ran8_+(1-a.dec_m)*a.ran8;a.ran9_=a.dec_m*a.ran9_+(1-a.dec_m)*a.ran9;a.pk=sqrt(a.peak+.1);a["new"]=Math.floor(12*(a.ran4-a.ran5)*a.pk-div(12*(a.ran3-a.ran1),a.pk));a["new"]=Math.max(Math.min(a["new"],20),2);a.reg01+=a["new"];a.dec=a.dec_m;a.n=a.recsize*\na.points;a.m=0;a.dt1=div(div(.00001 0.5)))\n * 2.0)));\n flash_1 = tmpvar_14;\n float angle_15;\n float tmpvar_16;\n tmpvar_16 = abs(xlat_mutableuv2.x);\n if ((xlat_mutableuv2.y >= 0.0)) {\n angle_15 = (1.0 - ((xlat_mutableuv2.y - tmpvar_16) / (xlat_mutableuv2.y + tmpvar_16)));\n } else {\n angle_15 = (3.0 - ((xlat_mutableuv2.y + tmpvar_16) / (tmpvar_16 - xlat_mutableuv2.y)));\n };\n angle_15 = (angle_15 * 0.25);\n float tmpvar_17;\n if ((xlat_mutableuv2.x < 0.0)) {\n tmpvar_17 = -(angle_15);\n } else {\n tmpvar_17 = angle_15;\n };\n flash_1 = (tmpvar_14 * (tmpvar_14 / (\n abs((fract((\n (3.0 * tmpvar_17)\n + \n (time * 2.0)\n )) - 0.5))\n + 0.18)));\n vec3 tmpvar_18;\n tmpvar_18 = max ((texture (sampler_main, uv).xyz * 2.0), ((\n (texture (sampler_blur2, uv).xyz * scale2)\n + bias2) * 2.0));\n vec2 tmpvar_19;\n tmpvar_19 = sin(xlat_mutableuv3);\n ret_4 = (clamp ((0.025 / \n sqrt(dot (tmpvar_19, tmpvar_19))\n ), 0.0, 1.0) * vec3(0.4, 0.1, 1.0));\n ret_4 = (ret_4 + clamp ((stars_3 * stars_3), 0.0, 1.0));\n ret_4 = (ret_4 * clamp ((1.0 - \n (2.0 * dot (tmpvar_18, vec3(0.32, 0.49, 0.29)))\n ), 0.0, 1.0));\n ret_4 = (ret_4 + tmpvar_18);\n vec3 tmpvar_20;\n tmpvar_20.x = q10;\n tmpvar_20.y = q11;\n tmpvar_20.z = q12;\n ret_4 = (ret_4 + ((2.0 * \n clamp (flash_1, 0.0, 1.0)\n ) * tmpvar_20));\n vec4 tmpvar_21;\n tmpvar_21.w = 1.0;\n tmpvar_21.xyz = ret_4;\n ret = tmpvar_21.xyz;\n }"}},function(a,e){a.exports={baseVals:{rating:4,gammaadj:1.98,decay:.5,echo_zoom:1,echo_alpha:.5,echo_orient:3,additivewave:1,wave_thick:1,modwavealphabyvolume:1,darken:1,wave_a:.001,wave_scale:.133,wave_smoothing:0,wave_mystery:-1,modwavealphastart:1,modwavealphaend:1.3,warpanimspeed:1.459,warpscale:2.007,zoom:.9999,warp:.01,sx:.9999,wave_r:.5,wave_g:.5,wave_b:.5,ob_size:.015,ob_b:1,ib_size:.26,mv_a:0,b2x:.3,b1ed:0},shapes:[{baseVals:{enabled:1,sides:40,thickoutline:1,rad:.06623,tex_zoom:1.79845,r:0,a:.1,g2:0,border_b:0,border_a:0},init_eqs_str:"a.vol=0;a.bob=0;a.border_1=0;a.ro=0;a.sp=0;a.red=0;a.spi=0;a.tm=0;a.bob=1.5;a.ro=0;a.red=randint(20);",frame_eqs_str:"a.vol=1+.2*div(a.bass_att+a.treb_att+a.mid_att,3);a.bob=a.bob*above(a.bob,.01)-.01+(1-above(a.bob,.01));a.bob=.4+.4*Math.sin(.8*a.time);a.bob*=a.vol;a.border_1=.4;a.sides=30;a.ro+=.02;a.ang=a.ro;a.sp=.025*a.red;a.spi=.5-a.sp;a.tm=.1*a.time;a.border_r=.5+a.sp*Math.sin(.6*a.tm)+a.spi*Math.cos(1.46*a.tm);a.border_g=.5+a.sp*Math.sin(1.294*a.tm)+a.spi*Math.cos(.87*a.tm);a.border_b=.5+a.sp*Math.sin(1.418*a.tm)+a.spi*Math.cos(.76*a.tm);"},{baseVals:{enabled:1,sides:40,additive:1,num_inst:4,g:1,b:1,g2:0,border_a:0},init_eqs_str:"",frame_eqs_str:"a.x=.5+.225*Math.sin(.7*div(a.time,a.instance));a.y=.5+.3*Math.cos(.7*div(a.time,a.instance));a.x-=.4*a.x*Math.sin(a.time);a.y-=.4*a.y*Math.cos(a.time);a.rad*=a.mid_att;a.r=.5+.5*Math.sin(.5*a.frame);a.b=.5+.5*Math.sin(.5*a.frame+2.094);a.g=.5+.5*Math.sin(.5*a.frame+4.188);"},{baseVals:{enabled:1,sides:40,additive:1,g:1,b:1,g2:0,border_a:0},init_eqs_str:"",frame_eqs_str:"a.x=.5+.5*(.3*Math.sin(1.1*a.time)+.7*Math.sin(.5*a.time));a.x=.5+.225*Math.sin(a.time+2.09);a.y=.5+.3*Math.cos(a.time+2.09);a.rad*=a.bass_att;a.r=.5+.5*Math.sin(.5*a.frame);a.b=.5+.5*Math.sin(.5*a.frame+2.094);a.g=.5+.5*Math.sin(.5*a.frame+4.188);"},{baseVals:{enabled:1,sides:40,additive:1,num_inst:5,rad:.07419,g:1,b:1,g2:0,border_a:0},init_eqs_str:"",frame_eqs_str:"a.x=.5+.225*Math.sin(div(a.time,a.instance));a.y=.5+.3*Math.cos(div(a.time,a.instance));a.x+=.4*a.x*Math.sin(a.time);a.y+=.4*a.y*Math.cos(a.time);a.rad*=a.treb_att;a.r=.5+.5*Math.sin(.5*a.frame);a.b=.5+.5*Math.sin(.5*a.frame+2.094);a.g=.5+.5*Math.sin(.5*a.frame+4.188);"}],waves:[{baseVals:{enabled:0}},{baseVals:{enabled:0}},{baseVals:{enabled:0}},{baseVals:{enabled:0}}],init_eqs_str:"a.index2=0;a.index=0;a.q22=0;a.q21=0;a.q1=0;a.dec_med=0;a.ps=0;a.rott=0;a.is_beat=0;a.q23=0;a.k1=0;a.q24=0;a.dec_slow=0;a.q4=0;a.q26=0;a.p2=0;a.avg=0;a.beat=0;a.p1=0;a.peak=0;a.q2=0;a.q27=0;a.q3=0;a.t0=0;a.q28=0;a.q20=0;",frame_eqs_str:"a.dec_med=pow(.7,div(30,a.fps));a.dec_slow=pow(.99,div(30,a.fps));a.beat=Math.max(Math.max(a.bass,a.mid),a.treb);a.avg=a.avg*a.dec_slow+a.beat*(1-a.dec_slow);a.is_beat=above(a.beat,.2+a.avg+a.peak)*above(a.time,a.t0+.2);a.t0=a.is_beat*a.time+(1-a.is_beat)*a.t0;a.peak=a.is_beat*a.beat+(1-a.is_beat)*a.peak*a.dec_med;a.index=mod(a.index+a.is_beat,8);a.index2=mod(a.index2+a.is_beat*bnot(a.index),2);a.q20=a.avg;a.q21=a.beat;a.q22=a.peak;a.ps=.9*a.ps+.1*a.q22;a.q23=a.ps;a.q24=a.is_beat;\na.q26=a.bass_att+a.mid_att+a.treb_att;a.q27=a.index+1;a.q28=a.index2;a.k1=a.is_beat*equal(mod(a.index,2),0);a.p1=a.k1*(a.p1+1)+(1-a.k1)*a.p1;a.p2=a.dec_med*a.p2+(1-a.dec_med)*a.p1;a.rott=div(3.1416*a.p2,4);a.q1=Math.cos(a.rott);a.q2=Math.sin(a.rott);a.q3=-a.q2;a.q4=a.q1;",pixel_eqs_str:"a.zoom=1.05;",warp:" shader_body { \n vec2 uv_1;\n vec2 tmpvar_2;\n tmpvar_2 = (uv - vec2(0.5, 0.5));\n vec4 tmpvar_3;\n tmpvar_3.w = 0.0;\n vec4 tmpvar_4;\n tmpvar_4 = texture (sampler_blur1, uv);\n tmpvar_3.xyz = ((tmpvar_4.xyz * scale1) + bias1);\n float tmpvar_5;\n tmpvar_5 = (dot (tmpvar_3, roam_sin) * 16.0);\n mat2 tmpvar_6;\n tmpvar_6[uint(0)].x = cos(tmpvar_5);\n tmpvar_6[uint(0)].y = -(sin(tmpvar_5));\n tmpvar_6[1u].x = sin(tmpvar_5);\n tmpvar_6[1u].y = cos(tmpvar_5);\n uv_1 = ((tmpvar_2 + (\n (0.2 * dot (((tmpvar_4.xyz * scale1) + bias1), vec3(0.32, 0.49, 0.29)))\n * \n (tmpvar_2 * tmpvar_6)\n )) - 0.5);\n vec2 tmpvar_7;\n tmpvar_7 = ((uv_1 * texsize.xy) * 0.02);\n vec2 tmpvar_8;\n tmpvar_8.x = (cos((tmpvar_7.y * q1)) * sin(-(tmpvar_7.y)));\n tmpvar_8.y = (sin(tmpvar_7.x) * cos((tmpvar_7.y * q2)));\n uv_1 = (uv_1 - ((tmpvar_8 * texsize.zw) * 12.0));\n vec4 tmpvar_9;\n tmpvar_9.w = 1.0;\n tmpvar_9.xyz = ((texture (sampler_main, uv_1).xyz * 0.98) - 0.02);\n ret = tmpvar_9.xyz;\n }",comp:"vec3 xlat_mutableret1;\nvec2 xlat_mutablers;\nvec2 xlat_mutableuv1;\nfloat xlat_mutablez;\n shader_body { \n xlat_mutableuv1 = (uv - 0.5);\n xlat_mutablez = (0.2 / abs(xlat_mutableuv1.y));\n xlat_mutablers.x = (xlat_mutableuv1.x * xlat_mutablez);\n xlat_mutablers.y = ((xlat_mutablez / 2.0) + (time * 4.0));\n vec4 tmpvar_1;\n tmpvar_1 = texture (sampler_noise_hq, xlat_mutablers);\n xlat_mutableret1 = ((tmpvar_1.xyz * vec3(\n greaterThanEqual (tmpvar_1.xyz, vec3(0.0, 0.0, 0.0))\n )) - 0.6);\n float tmpvar_2;\n tmpvar_2 = clamp ((128.0 * xlat_mutableuv1.y), 0.0, 1.0);\n vec2 tmpvar_3;\n tmpvar_3 = fract(((\n (xlat_mutableuv1 * (1.0 - abs(xlat_mutableuv1.x)))\n - 0.5) - (\n (xlat_mutableret1 * 0.05)\n * tmpvar_2).xy));\n float x_4;\n x_4 = (tmpvar_3.y - 0.52);\n vec3 tmpvar_5;\n tmpvar_5 = (texture (sampler_main, tmpvar_3) + ((0.02 / \n (0.02 + sqrt((x_4 * x_4)))\n ) * slow_roam_sin)).xyz;\n xlat_mutableret1 = tmpvar_5;\n vec2 tmpvar_6;\n tmpvar_6 = (32.0 * ((\n (uv * mat2(0.6, -0.8, 0.8, 0.6))\n + \n (tmpvar_5 * 0.1)\n .xy) + (time / 64.0)));\n vec2 tmpvar_7;\n tmpvar_7 = abs((fract(tmpvar_6) - 0.5));\n vec3 tmpvar_8;\n tmpvar_8 = clamp (((0.25 / \n sqrt(dot (tmpvar_7, tmpvar_7))\n ) * vec3((texture (sampler_pw_noise_lq, \n (tmpvar_6 / 256.0)\n ).y - 0.9))), 0.0, 1.0);\n vec4 tmpvar_9;\n tmpvar_9.w = 1.0;\n tmpvar_9.xyz = (tmpvar_5 + ((\n (tmpvar_8.x * tmpvar_8.x)\n + \n ((rand_preset * (0.5 - uv.y)).xyz * vec3(0.0, 0.0, 1.0))\n ) * (1.0 - tmpvar_2)));\n ret = tmpvar_9.xyz;\n }"}},function(a,e){a.exports={baseVals:{rating:2,gammaadj:1.98,decay:.5,echo_zoom:1,echo_alpha:.5,echo_orient:3,wave_mode:3,wave_thick:1,wrap:0,darken:1,wave_a:100,wave_scale:.282,wave_smoothing:.9,wave_mystery:1,warpanimspeed:1.459,warpscale:2.007,zoom:.9999,warp:.01,sx:.9999,wave_r:.5,wave_g:.5,wave_b:.5,ob_size:.05,ob_g:.1,ob_b:1,ob_a:1,ib_size:0,ib_r:0,ib_g:0,ib_b:0,mv_a:0,b1ed:0},shapes:[{baseVals:{enabled:0}},{baseVals:{enabled:0}},{baseVals:{enabled:0}},{baseVals:{enabled:0}}],waves:[{baseVals:{enabled:1,spectrum:1,thick:1,scaling:.2248},init_eqs_str:"a.ma=0;a.mx=0;a.my=0;",frame_eqs_str:"a.r=a.bass;a.g=a.treb;a.b=.5;",point_eqs_str:"a.ma+=3.1415*above(a.bass,1)*.01*a.bass;a.ma-=3.1415*above(a.treb,1)*.01*a.treb;a.mx+=.0002*Math.cos(a.ma);a.my+=.0002*Math.sin(a.ma);a.mx=.00001b;b++)a.gmegabuf[Math.floor(a.n)]=0,a.n+=1;for(b=a.n=0;1E4>b;b++)a.megabuf[Math.floor(a.n)]=0,a.n+=1;a.trelx=0;a.trely=0;a.trelz=0;a.reg20=1;a.reg21=0;a.reg22=0;a.reg23=0;a.reg24=1;a.reg25=0;a.reg26=0;a.reg27=0;a.reg28=1;b=0;do{b+=1;var c;a.ran1=div(randint(800),100);a.ran2=div(randint(800),100);a.ran3=div(randint(800),100);a.posx=randint(5)-2;a.posy=randint(5)-2;a.posz=randint(5)-2;a.c1=Math.cos(a.ran1);\na.c2=Math.cos(a.ran2);a.c3=Math.cos(a.ran3);a.s1=Math.sin(a.ran1);a.s2=Math.sin(a.ran2);a.s3=Math.sin(a.ran3);a.reg20=a.c2*a.c1;a.reg21=a.c2*a.s1;a.reg22=-a.s2;a.reg23=a.s3*a.s2*a.c1-a.c3*a.s1;a.reg24=a.s3*a.s2*a.s1+a.c3*a.c1;a.reg25=a.s3*a.c2;a.reg26=a.c3*a.s2*a.c1+a.s3*a.s1;a.reg27=a.c3*a.s2*a.s1-a.s3*a.c1;a.reg28=a.c3*a.c2;a.dist=.001;var d=0;do{d+=1;a.uvx=div(a.reg26*a.dist,a.q7);a.uvy=div(a.reg27*a.dist,a.q7);a.uvz=div(a.reg28*a.dist,a.q7);a.uvx+=a.posx;a.uvy+=a.posy;a.uvz+=a.posz;a.uvx=8*(div(a.uvx,\n8)+30.5-Math.floor(div(a.uvx,8)+30.5)-.5);a.uvy=8*(div(a.uvy,8)+30.5-Math.floor(div(a.uvy,8)+30.5)-.5);a.uvz=8*(div(a.uvz,8)+30.5-Math.floor(div(a.uvz,8)+30.5)-.5);a.uvx0=a.uvx+a.q8;a.uvy0=a.uvy+a.q8;a.uvz0=a.uvz+a.q8;for(c=0;8>c;c++)a.uvx=.00001a.uvx?1:0)?-2-a.uvx:a.uvx,a.uvy=.00001a.uvy?1:0)?-2-a.uvy:a.uvy,a.uvz=.00001a.uvz?1:0)?-2-a.uvz:a.uvz,a.slen=\na.uvx*a.uvx+a.uvy*a.uvy+a.uvz*a.uvz,a.uvx=2.6*(.00001a.slen?1:0)?4*a.uvx:.00001a.slen?1:0)?div(a.uvx,a.slen):a.uvx)+a.uvx0,a.uvy=2.6*(.00001a.slen?1:0)?4*a.uvy:.00001a.slen?1:0)?div(a.uvy,a.slen):a.uvy)+a.uvy0,a.uvz=2.6*(.00001a.slen?1:0)?4*a.uvz:.00001a.slen?1:0)?div(a.uvz,a.slen):a.uvz)+a.uvz0;a.len=sqrt(a.uvx*a.uvx+a.uvy*a.uvy+a.uvz*a.uvz);a.dist*=1.05;c=(.6>a.dist?1:0)*(30\nd);d=.06>a.dist?1:0}while(.00001b);",frame_eqs_str:"a.wave_a=0;a.fps_=0*a.fps_+1*(.00001=a.fps?1:0)?a.fps:25+.5*(a.fps-25));a.dec_s=1-div(.06*30,a.fps_);a.beat=a.time>a.t0+3?1:0;a.t0=.00001Math.abs(a.rotz-0)?1:0)?a.beat*(randint(100)<20*a.travel?1:0)*(div(randint(10),10)-.3):bnot(a.beat*(30>randint(100)?1:0))*a.rotz;a.slow=.00001<\nMath.abs(bnot(a.slow))?a.beat*(6>randint(1E3*a.avg)?1:0):bnot(a.beat*(50>randint(100)?1:0));a.look=.00001randint(1E3*a.speed)?1:0):bnot(a.beat*(50>randint(100)?1:0));a.lx=.00001a.dist_?1:0)*2;a.travel=.00001b;b++){a.n+=1;a.ran1=div(randint(100),100);a.ran2=div(randint(100),200)-.25;a.tx=Math.cos(1.57*a.n+a.ran2)*(4>=a.n?1:0)*a.ran1;a.ty=Math.sin(1.57*a.n+a.ran2)*(4>=a.n?1:0)*a.ran1;a.c1=Math.cos(a.v1);a.c2=Math.cos(a.v2+a.ty);a.c3=Math.cos(a.v3+a.tx);a.s1=Math.sin(a.v1);a.s2=Math.sin(a.v2+a.ty);a.s3=Math.sin(a.v3+a.tx);a.reg10=a.c2*a.c1;a.reg11=a.c2*a.s1;a.reg12=-a.s2;a.reg13=a.s3*a.s2*a.c1-a.c3*a.s1;a.reg14=a.s3*a.s2*\na.s1+a.c3*a.c1;a.reg15=a.s3*a.c2;a.reg16=a.c3*a.s2*a.c1+a.s3*a.s1;a.reg17=a.c3*a.s2*a.s1-a.s3*a.c1;a.reg18=a.c3*a.c2;a.reg20=a.reg30;a.reg21=a.reg31;a.reg22=a.reg32;a.reg23=a.reg33;a.reg24=a.reg34;a.reg25=a.reg35;a.reg26=a.reg36;a.reg27=a.reg37;a.reg28=a.reg38;a.q20=a.reg10*a.reg20+a.reg11*a.reg23+a.reg12*a.reg26;a.q21=a.reg10*a.reg21+a.reg11*a.reg24+a.reg12*a.reg27;a.q22=a.reg10*a.reg22+a.reg11*a.reg25+a.reg12*a.reg28;a.q23=a.reg13*a.reg20+a.reg14*a.reg23+a.reg15*a.reg26;a.q24=a.reg13*a.reg21+a.reg14*\na.reg24+a.reg15*a.reg27;a.q25=a.reg13*a.reg22+a.reg14*a.reg25+a.reg15*a.reg28;a.q26=a.reg16*a.reg20+a.reg17*a.reg23+a.reg18*a.reg26;a.q27=a.reg16*a.reg21+a.reg17*a.reg24+a.reg18*a.reg27;a.q28=a.reg16*a.reg22+a.reg17*a.reg25+a.reg18*a.reg28;a.reg20=a.q20;a.reg21=a.q21;a.reg22=a.q22;a.reg23=a.q23;a.reg24=a.q24;a.reg25=a.q25;a.reg26=a.q26;a.reg27=a.q27;a.reg28=a.q28;a.dist=.002;var c,d=0;do{d+=1;a.uvx=div(a.reg26*a.dist,a.q7);a.uvy=div(a.reg27*a.dist,a.q7);a.uvz=div(a.reg28*a.dist,a.q7);a.uvx+=a.posx;\na.uvy+=a.posy;a.uvz+=a.posz;a.uvx=8*(div(a.uvx,8)+30.5-Math.floor(div(a.uvx,8)+30.5)-.5);a.uvy=8*(div(a.uvy,8)+30.5-Math.floor(div(a.uvy,8)+30.5)-.5);a.uvz=8*(div(a.uvz,8)+30.5-Math.floor(div(a.uvz,8)+30.5)-.5);a.uvx0=a.uvx+a.q8;a.uvy0=a.uvy+a.q8;a.uvz0=a.uvz+a.q8;for(c=0;8>c;c++)a.uvx=.00001a.uvx?1:0)?-2-a.uvx:a.uvx,a.uvy=.00001a.uvy?1:0)?-2-a.uvy:a.uvy,a.uvz=.00001a.uvz?1:0)?-2-a.uvz:a.uvz,a.slen=a.uvx*a.uvx+a.uvy*a.uvy+a.uvz*a.uvz,a.uvx=2.6*(.00001a.slen?1:0)?4*a.uvx:.00001a.slen?1:0)?div(a.uvx,a.slen):a.uvx)+a.uvx0,a.uvy=2.6*(.00001a.slen?1:0)?4*a.uvy:.00001a.slen?1:0)?div(a.uvy,a.slen):a.uvy)+a.uvy0,a.uvz=2.6*(.00001a.slen?1:0)?4*a.uvz:.00001a.slen?1:0)?div(a.uvz,a.slen):a.uvz)+a.uvz0;a.len=sqrt(a.uvx*a.uvx+a.uvy*a.uvy+a.uvz*a.uvz);a.dist*=1.1;c=(.6>a.dist?1:0)*(30<\na.len?1:0)}while(.00001d);a.megabuf[Math.floor(a.n)]=a.megabuf[Math.floor(a.n)]*a.dec_s+(1-a.dec_s)*a.dist;a.avg+=Math.abs(div(a.megabuf[Math.floor(a.n)],5))}a.n=0;for(b=a.avg=0;5>b;b++)a.n+=1,a.avg+=Math.abs(div(a.megabuf[Math.floor(a.n)],5));a.xslope=Math.min(Math.max(div(2,a.avg)*(a.megabuf[1]-a.megabuf[3]),-3),3);a.yslope=Math.min(Math.max(div(2,a.avg)*(a.megabuf[4]-a.megabuf[2]),-3),3);a.monitor=a.avg;a.dist_=a.dist_*a.dec_s+(1-a.dec_s)*a.dist;a.q10=a.ds*a.q7;a.q14=Math.abs(a.ds)+\n2*(Math.abs(a.v1)+Math.abs(a.v2)+Math.abs(a.v3))+div(1,255)+.05*a.start;a.q19=.6+.4*Math.sin(.02*a.time+6*a.cran0);a.start*=.9;a.q11=a.v1;a.q12=a.v2;a.q13=a.v3;a.monitor=a.q16;",pixel_eqs_str:"a.warp=0;a.zoom=1;a.dx=div(-a.q12,a.q16)*(1+0*pow(a.x-.5,2));a.dy=div(a.q13,a.q16)*(1+0*pow(a.y-.5,2));a.rot=a.q11;",warp:" shader_body { \n float dy_1;\n float dx_2;\n vec3 ret_3;\n vec2 tmpvar_4;\n tmpvar_4 = ((uv * texsize.xy) * texsize_noise_lq.zw);\n vec2 tmpvar_5;\n tmpvar_5 = (texsize.zw * 4.0);\n vec2 tmpvar_6;\n tmpvar_6.x = (((2.0 * \n ((texture (sampler_blur1, (uv + (vec2(1.0, 0.0) * tmpvar_5))).xyz * scale1) + bias1)\n ) - (2.0 * \n ((texture (sampler_blur1, (uv - (vec2(1.0, 0.0) * tmpvar_5))).xyz * scale1) + bias1)\n )).y * 0.5);\n tmpvar_6.y = (((2.0 * \n ((texture (sampler_blur1, (uv + (vec2(0.0, 1.0) * tmpvar_5))).xyz * scale1) + bias1)\n ) - (2.0 * \n ((texture (sampler_blur1, (uv - (vec2(0.0, 1.0) * tmpvar_5))).xyz * scale1) + bias1)\n )).y * 0.5);\n ret_3.y = texture (sampler_fw_main, clamp ((uv + (\n (tmpvar_6 * texsize.zw)\n * 4.0)), 0.0, 1.0)).y;\n ret_3.y = (ret_3.y + ((\n (ret_3 - ((texture (sampler_blur1, uv).xyz * scale1) + bias1))\n .y * 0.025) + -0.01));\n ret_3.y = (ret_3.y + ((texture (sampler_noise_lq, tmpvar_4).y - 0.5) * 0.02));\n dx_2 = (((2.0 * \n ((texture (sampler_blur1, (uv + (vec2(1.0, 0.0) * tmpvar_5))).xyz * scale1) + bias1)\n ) - (2.0 * \n ((texture (sampler_blur1, (uv - (vec2(1.0, 0.0) * tmpvar_5))).xyz * scale1) + bias1)\n )).z * 0.5);\n dy_1 = (((2.0 * \n ((texture (sampler_blur1, (uv + (vec2(0.0, 1.0) * tmpvar_5))).xyz * scale1) + bias1)\n ) - (2.0 * \n ((texture (sampler_blur1, (uv - (vec2(0.0, 1.0) * tmpvar_5))).xyz * scale1) + bias1)\n )).z * 0.5);\n vec2 tmpvar_7;\n tmpvar_7.x = dx_2;\n tmpvar_7.y = dy_1;\n ret_3.z = ((texture (sampler_main, (uv - \n ((tmpvar_7 * texsize.zw) * 4.0)\n )).z - (ret_3.y * 0.01)) + 0.004);\n ret_3.z = (ret_3.z + ((texture (sampler_noise_lq, tmpvar_4).y - 0.5) * 0.01));\n dx_2 = (((2.0 * \n ((texture (sampler_blur1, (uv + (tmpvar_5 * vec2(1.0, 0.0)))).xyz * scale1) + bias1)\n ) - (2.0 * \n ((texture (sampler_blur1, (uv + (tmpvar_5 * vec2(-1.0, 0.0)))).xyz * scale1) + bias1)\n )).x * 0.5);\n dy_1 = (((2.0 * \n ((texture (sampler_blur1, (uv + (tmpvar_5 * vec2(0.0, 1.0)))).xyz * scale1) + bias1)\n ) - (2.0 * \n ((texture (sampler_blur1, (uv + (tmpvar_5 * vec2(0.0, -1.0)))).xyz * scale1) + bias1)\n )).x * 0.5);\n vec2 tmpvar_8;\n tmpvar_8.x = dx_2;\n tmpvar_8.y = dy_1;\n vec2 tmpvar_9;\n tmpvar_9 = (tmpvar_8 * texsize.zw);\n vec2 domain_10;\n domain_10 = (uv - (tmpvar_9 * 2.5));\n vec4 tmpvar_11;\n tmpvar_11.w = 0.0;\n tmpvar_11.xyz = max (vec4(0.0, 0.0, 0.0, 0.0), texture (sampler_fc_main, (domain_10 + (texsize.zw * vec2(-1.0, 0.0))))).xyz;\n vec4 tmpvar_12;\n tmpvar_12.w = 0.0;\n tmpvar_12.xyz = max (tmpvar_11, texture (sampler_fc_main, (domain_10 + (texsize.zw * vec2(0.0, -1.0))))).xyz;\n vec4 tmpvar_13;\n tmpvar_13.w = 0.0;\n tmpvar_13.xyz = max (tmpvar_12, texture (sampler_fc_main, domain_10)).xyz;\n vec4 tmpvar_14;\n tmpvar_14.w = 0.0;\n tmpvar_14.xyz = max (tmpvar_13, texture (sampler_fc_main, (domain_10 + (texsize.zw * vec2(0.0, 1.0))))).xyz;\n ret_3.x = ((max (tmpvar_14, texture (sampler_fc_main, \n (domain_10 + (texsize.zw * vec2(1.0, 0.0)))\n )).x + (\n (texture (sampler_main, (uv + (tmpvar_9 * 4.0))).x - ((texture (sampler_blur1, (uv + \n (tmpvar_9 * 4.0)\n )).xyz * scale1) + bias1).x)\n * 0.206)) - 0.09);\n vec4 tmpvar_15;\n tmpvar_15.w = 1.0;\n tmpvar_15.xyz = ret_3;\n ret = tmpvar_15.xyz;\n }",comp:"float xlat_mutablelamp;\nvec2 xlat_mutablers0;\nvec2 xlat_mutablerss;\nvec2 xlat_mutableuv1;\n shader_body { \n vec3 tmpvar_1;\n tmpvar_1.x = q4;\n tmpvar_1.y = q5;\n tmpvar_1.z = q6;\n mat3 tmpvar_2;\n tmpvar_2[uint(0)].x = q20;\n tmpvar_2[uint(0)].y = q23;\n tmpvar_2[uint(0)].z = q26;\n tmpvar_2[1u].x = q21;\n tmpvar_2[1u].y = q24;\n tmpvar_2[1u].z = q27;\n tmpvar_2[2u].x = q22;\n tmpvar_2[2u].y = q25;\n tmpvar_2[2u].z = q28;\n vec2 tmpvar_3;\n tmpvar_3.x = q1;\n tmpvar_3.y = q2;\n vec2 uv_4;\n vec3 dots_5;\n vec3 ret_6;\n vec2 tmpvar_7;\n vec2 tmpvar_8;\n tmpvar_8 = (uv - 0.5);\n tmpvar_7 = (0.5 + (tmpvar_8 * vec2(1.1, 0.81)));\n vec2 tmpvar_9;\n tmpvar_9 = (uv - vec2(0.5, 0.5));\n uv_4 = (tmpvar_8 * aspect.xy);\n float tmpvar_10;\n float tmpvar_11;\n tmpvar_11 = (min (abs(\n (uv_4.y / uv_4.x)\n ), 1.0) / max (abs(\n (uv_4.y / uv_4.x)\n ), 1.0));\n float tmpvar_12;\n tmpvar_12 = (tmpvar_11 * tmpvar_11);\n tmpvar_12 = (((\n ((((\n ((((-0.01213232 * tmpvar_12) + 0.05368138) * tmpvar_12) - 0.1173503)\n * tmpvar_12) + 0.1938925) * tmpvar_12) - 0.3326756)\n * tmpvar_12) + 0.9999793) * tmpvar_11);\n tmpvar_12 = (tmpvar_12 + (float(\n (abs((uv_4.y / uv_4.x)) > 1.0)\n ) * (\n (tmpvar_12 * -2.0)\n + 1.570796)));\n tmpvar_10 = (tmpvar_12 * sign((uv_4.y / uv_4.x)));\n if ((abs(uv_4.x) > (1e-08 * abs(uv_4.y)))) {\n if ((uv_4.x < 0.0)) {\n if ((uv_4.y >= 0.0)) {\n tmpvar_10 += 3.141593;\n } else {\n tmpvar_10 = (tmpvar_10 - 3.141593);\n };\n };\n } else {\n tmpvar_10 = (sign(uv_4.y) * 1.570796);\n };\n xlat_mutablers0.x = ((tmpvar_10 / 3.1416) * 2.0);\n xlat_mutablers0.y = (0.02 / sqrt(dot (uv_4, uv_4)));\n vec2 tmpvar_13;\n tmpvar_13.x = xlat_mutablers0.x;\n tmpvar_13.y = (xlat_mutablers0.y + time);\n xlat_mutablerss = (tmpvar_13 * mat2(0.7, -0.7, 0.7, 0.7));\n vec4 tmpvar_14;\n tmpvar_14 = vec4(greaterThanEqual ((texture (sampler_pw_noise_lq, \n (xlat_mutablerss / 32.0)\n ) - 0.7), vec4(0.0, 0.0, 0.0, 0.0)));\n vec2 tmpvar_15;\n tmpvar_15 = abs((fract(\n (xlat_mutablerss * 8.0)\n ) - 0.5));\n vec2 tmpvar_16;\n tmpvar_16.x = (xlat_mutablers0.x * 2.0);\n tmpvar_16.y = (xlat_mutablers0.y + (time / 2.0));\n xlat_mutablerss = (tmpvar_16 * mat2(0.7, -0.7, 0.7, 0.7));\n vec4 tmpvar_17;\n tmpvar_17 = vec4(greaterThanEqual ((texture (sampler_pw_noise_lq, \n (xlat_mutablerss / 32.0)\n ) - 0.7), vec4(0.0, 0.0, 0.0, 0.0)));\n vec2 tmpvar_18;\n tmpvar_18 = abs((fract(\n (xlat_mutablerss * 8.0)\n ) - 0.5));\n xlat_mutablerss = tmpvar_18;\n dots_5 = (vec3((clamp (\n (0.04 / sqrt(dot (tmpvar_15, tmpvar_15)))\n , 0.0, 1.0) * tmpvar_14.x)) + (clamp (\n (0.04 / sqrt(dot (tmpvar_18, tmpvar_18)))\n , 0.0, 1.0) * tmpvar_17.x));\n dots_5 = (dots_5 * clamp ((0.04 / \n abs((0.01 / xlat_mutablers0.y))\n ), 0.0, 1.0));\n dots_5 = (dots_5 * (dots_5 * 2.0));\n vec2 tmpvar_19;\n tmpvar_19.x = -((tmpvar_9.y * -1024.0));\n tmpvar_19.y = (tmpvar_9.x * -1024.0);\n vec2 tmpvar_20;\n tmpvar_20.x = tmpvar_19.x;\n tmpvar_20.y = -(tmpvar_19.y);\n uv_4 = (vec2(-100.0, 100.0) * (tmpvar_20 / (\n (tmpvar_19.x * tmpvar_19.x)\n + \n (tmpvar_19.y * tmpvar_19.y)\n )).yx);\n uv_4 = (0.5 + ((\n (1.0 - abs(((\n fract((mix ((0.5 + \n ((tmpvar_7 - 0.5) * 2.0)\n ), (uv_4 + 0.5), vec2(0.5, 0.5)) * 0.5))\n * 2.0) - 1.0)))\n - 0.5) * 0.98));\n uv_4 = (((uv_4 - 0.5) * 0.9) + 0.5);\n xlat_mutableuv1 = ((uv_4 - tmpvar_3) * aspect.xy);\n float tmpvar_21;\n tmpvar_21 = min ((1.0 - texture (sampler_main, uv_4).z), (1.0 - (\n (texture (sampler_blur2, uv_4).xyz * scale2)\n + bias2).z));\n vec3 tmpvar_22;\n tmpvar_22.xy = ((uv_4 - 0.5) * min ((1.0 - texture (sampler_main, uv_4).z), (1.0 - \n ((texture (sampler_blur2, uv_4).xyz * scale2) + bias2)\n .z)));\n tmpvar_22.z = min ((1.0 - texture (sampler_main, uv_4).z), (1.0 - (\n (texture (sampler_blur2, uv_4).xyz * scale2)\n + bias2).z));\n float tmpvar_23;\n tmpvar_23 = clamp ((abs(\n ((1.0 - ((texture (sampler_blur2, uv_4).xyz * scale2) + bias2).z) - clamp ((1.0 - (\n (texture (sampler_blur2, tmpvar_3).xyz * scale2)\n + bias2).z), 0.1, 0.4))\n ) + 0.2), 0.0, 1.0);\n vec3 tmpvar_24;\n tmpvar_24 = mix (texture (sampler_main, uv_4).xyz, ((texture (sampler_blur1, uv_4).xyz * scale1) + bias1), vec3(tmpvar_23));\n float tmpvar_25;\n tmpvar_25 = clamp (((1.0 - \n exp(-(((texture (sampler_blur1, uv_4).xyz * scale1) + bias1).x))\n ) - 0.2), 0.0, 1.0);\n ret_6 = ((tmpvar_24.x * (0.2 + \n ((1.0 - tmpvar_23) * (1.0 - tmpvar_21))\n )) * (1.0 + (0.5 * \n sin((((tmpvar_22 / q7) * tmpvar_2) + tmpvar_1))\n )));\n vec3 tmpvar_26;\n tmpvar_26.xy = vec2(0.0, 1.0);\n tmpvar_26.z = (tmpvar_25 * 3.0);\n vec3 tmpvar_27;\n tmpvar_27 = mix (ret_6, tmpvar_26, vec3(tmpvar_25));\n xlat_mutablelamp = (((\n clamp ((1.0 - (4.0 * sqrt(\n dot (xlat_mutableuv1, xlat_mutableuv1)\n ))), 0.0, 1.0)\n * tmpvar_24.x) * clamp (\n (1.0 - (2.0 * mix (tmpvar_21, (1.0 - \n ((texture (sampler_blur1, uv_4).xyz * scale1) + bias1)\n .z), 0.2)))\n , 0.0, 1.0)) * 1.8);\n ret_6 = (tmpvar_27 + ((1.0 - \n dot (tmpvar_27, vec3(0.32, 0.49, 0.29))\n ) * xlat_mutablelamp));\n ret_6 = (1.0 - exp((-2.0 * ret_6)));\n ret_6 = (ret_6 + (dots_5 * (1.0 + ret_6)));\n vec4 tmpvar_28;\n tmpvar_28.w = 1.0;\n tmpvar_28.xyz = ret_6;\n ret = tmpvar_28.xyz;\n }"}},function(a,e){a.exports={baseVals:{rating:3,wave_mode:4,additivewave:1,wave_dots:1,modwavealphabyvolume:1,wave_a:.331,wave_scale:.898,wave_smoothing:.108,wave_mystery:.1,modwavealphastart:.72,modwavealphaend:1.28,zoom:1.3345,wave_r:0,wave_g:.5,wave_b:.5,wave_y:.54,mv_x:24.8,mv_dy:.16,mv_l:1.5,mv_a:0},shapes:[{baseVals:{enabled:0}},{baseVals:{enabled:0}},{baseVals:{enabled:0}},{baseVals:{enabled:0}}],waves:[{baseVals:{enabled:1,samples:352,usedots:1,additive:1,scaling:.03856,smoothing:.2,g:0},init_eqs_str:"a.t02=0;a.q1=0;a.ratio=0;a.ampl=0;a.x1=0;a.y1=0;",frame_eqs_str:"a.q1=a.bass_att;",point_eqs_str:"a.r=Math.abs(Math.sin(div(a.frame,38)));a.g=.5*Math.abs(Math.cos(div(a.frame,45)));a.b=.5*Math.abs(Math.sin(div(a.frame,133)));a.a=.3;a.t02+=div(a.q1,10);a.ratio=Math.sin(div(a.frame,49));a.ampl=.01+.4*sqr(Math.sin(div(a.frame,18))*Math.cos(div(a.frame,123)));a.x1=div(a.r-.5,15)+.5+a.ampl*Math.sin(6.28*a.sample);a.y1=div(a.b-.5,15)+.5+a.ampl*Math.cos(6.28*a.sample);a.x=a.x1+.2*(a.ampl+a.ratio)*Math.sin(6.28*a.sample*a.ratio*7.3);a.y=a.y1+.2*(a.ampl+a.ratio)*Math.cos(37.68*a.sample);\n"},{baseVals:{enabled:0}},{baseVals:{enabled:0}},{baseVals:{enabled:0}}],init_eqs_str:"a.oldshift=0;a.shift=0;a.normalframez=0;a.crash=0;a.nex=0;a.rshift=0;a.q1=0;a.zoom1=0;",frame_eqs_str:"a.dx=0;a.oldshift=a.shift;a.normalframez+=1;a.shift=above(a.bass_att,1)*above(a.treb_att,.9);a.crash=Math.abs(a.oldshift-a.shift);a.nex=1*equal(a.rshift,0)+2*equal(a.rshift,1);a.rshift=.00001 0.9)) {\n ret_2.x = 0.0;\n };\n if ((ret_2.y > 0.9)) {\n ret_2.y = 0.0;\n };\n if ((ret_2.z > 0.9)) {\n ret_2.z = 0.0;\n };\n vec4 tmpvar_7;\n tmpvar_7.w = 1.0;\n tmpvar_7.xyz = ret_2;\n ret = tmpvar_7.xyz;\n }",comp:" shader_body { \n vec3 ret1_1;\n vec2 uv1_2;\n vec3 ret_3;\n vec4 tmpvar_4;\n tmpvar_4 = texture (sampler_main, uv);\n ret_3 = (tmpvar_4.xyz * (0.6 + (0.3 * \n sin(((uv.x * 10.0) + time))\n )));\n vec2 tmpvar_5;\n tmpvar_5.x = (texture (sampler_main, (uv - vec2(0.001, 0.0))).xyz - texture (sampler_main, (uv + vec2(0.001, 0.0))).xyz).x;\n tmpvar_5.y = (texture (sampler_main, (uv - vec2(0.0, 0.001))).xyz - texture (sampler_main, (uv + vec2(0.0, 0.001))).xyz).x;\n uv1_2 = ((0.5 * cos(\n (((uv - 0.5) * 1.5) + 1.6)\n )) - (3.0 * tmpvar_5));\n ret1_1 = ((0.3 * dot (tmpvar_4.xyz, vec3(0.32, 0.49, 0.29))) + ((\n clamp ((0.01 / sqrt(dot (uv1_2, uv1_2))), 0.0, 1.0)\n * \n mix (vec3(dot (((texture (sampler_blur2, uv).xyz * scale2) + bias2), vec3(0.32, 0.49, 0.29))), ret_3, pow (ret_3, vec3((0.05 + (mid_att * 0.03)))))\n ) * (\n (4.0 + bass)\n + \n (mid + treb_att)\n )));\n ret_3 = ret1_1;\n vec4 tmpvar_6;\n tmpvar_6.w = 1.0;\n tmpvar_6.xyz = ret1_1;\n ret = tmpvar_6.xyz;\n }"}},function(a,e){a.exports={baseVals:{rating:3,gammaadj:1,decay:.997,echo_zoom:.997,echo_orient:1,wave_thick:1,wave_brighten:0,darken:1,wave_a:.001,wave_scale:.01,wave_smoothing:.27,wave_mystery:-.38,modwavealphastart:.71,modwavealphaend:1.3,warpscale:1.331,zoom:.99951,warp:.01,ob_size:.5,ob_r:.01,ib_size:.26,ib_r:1,ib_g:1,ib_b:1,mv_x:64,mv_y:48,mv_l:.85,mv_r:.5,mv_g:.5,mv_b:.5,mv_a:0},shapes:[{baseVals:{enabled:0}},{baseVals:{enabled:0}},{baseVals:{enabled:0}},{baseVals:{enabled:0}}],waves:[{baseVals:{enabled:1},init_eqs_str:"",frame_eqs_str:"",point_eqs_str:"a.x=.5+.2*a.bass*Math.sin(20*a.sample*a.time*a.treb);a.y=.5+.2*a.bass*Math.cos(20*a.sample*a.time*a.treb);a.r=1+.5*Math.sin(.1*a.sample+10*a.time*a.bass);a.g=1+.5*Math.sin(2*a.sample+50*a.time*a.treb);a.b=1+.5*Math.sin(5*a.sample+20*a.time*a.mid);a.a=a.r;"},{baseVals:{enabled:0}},{baseVals:{enabled:0}},{baseVals:{enabled:0}}],init_eqs_str:"",frame_eqs_str:"a.warp=0;a.decay=.92;",pixel_eqs_str:"a.zoom+=.03*a.bass_att*a.bass_att*a.rad;a.rot+=a.rad*bitand(-2.5,5*Math.cos(a.time))*.01;",warp:" shader_body { \n vec4 tmpvar_1;\n tmpvar_1.w = 1.0;\n tmpvar_1.xyz = texture (sampler_main, uv).xyz;\n ret = tmpvar_1.xyz;\n }",comp:" shader_body { \n vec2 uv1_1;\n vec3 ret_2;\n vec3 tmpvar_3;\n tmpvar_3 = texture (sampler_main, uv).xyz;\n vec2 tmpvar_4;\n tmpvar_4.x = (texture (sampler_main, (uv - vec2(0.001, 0.0))).xyz - texture (sampler_main, (uv + vec2(0.001, 0.0))).xyz).x;\n tmpvar_4.y = (texture (sampler_main, (uv - vec2(0.0, 0.001))).xyz - texture (sampler_main, (uv + vec2(0.0, 0.001))).xyz).x;\n uv1_1 = ((0.5 * cos(\n (((uv - 0.5) * 1.5) + 1.6)\n )) - (3.0 * tmpvar_4));\n ret_2 = (0.8 * ((0.3 * \n dot (tmpvar_3, vec3(0.32, 0.49, 0.29))\n ) + (\n (clamp ((0.01 / sqrt(\n dot (uv1_1, uv1_1)\n )), 0.0, 1.0) * tmpvar_3)\n * \n ((4.0 + bass) + (mid + treb_att))\n )));\n ret_2 = (ret_2 * 0.77);\n vec4 tmpvar_5;\n tmpvar_5.w = 1.0;\n tmpvar_5.xyz = ret_2;\n ret = tmpvar_5.xyz;\n }"}},,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,function(a,e,t){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var r=_(t(6)),n=_(t(7));function _(a){return a&&a.__esModule?a:{default:a}}var s={};s["$$$ Royal - Mashup (197)"]=t(35),s["$$$ Royal - Mashup (220)"]=t(36),s["$$$ Royal - Mashup (431)"]=t(37),s["_Aderrasi - Wanderer in Curved Space - mash0000 - faclempt kibitzing meshuggana schmaltz (Geiss color mix)"]=t(38),s["_Geiss - Artifact 01"]=t(39),s["_Geiss - Desert Rose 2"]=t(40),s["_Geiss - untitled"]=t(41),s._Mig_049=t(42),s._Mig_085=t(43),s["_Rovastar + Geiss - Hurricane Nightmare (Posterize Mix)"]=t(44),s["Aderrasi + Geiss - Airhandler (Kali Mix) - Canvas Mix"]=t(45),s["Aderrasi - Potion of Spirits"]=t(20),s["Aderrasi - Songflower (Moss Posy)"]=t(21),s["Aderrasi - Storm of the Eye (Thunder) - mash0000 - quasi pseudo meta concentrics"]=t(46),s["An AdamFX n Martin Infusion 2 flexi - Why The Sky Looks Diffrent Today - AdamFx n Martin Infusion - Tack Tile Disfunction B"]=t(47),s["cope + martin - mother-of-pearl"]=t(48),s["Cope - The Neverending Explosion of Red Liquid Fire"]=t(49),s["Eo.S. + Phat - cubetrace - v2"]=t(22),s["Eo.S. + Zylot - skylight (Stained Glass Majesty mix)"]=t(23),s["Eo.S. - glowsticks v2 05 and proton lights (+Krash′s beat code) _Phat_remix02b"]=t(24),s["fiShbRaiN + Flexi - witchcraft 2.0"]=t(50),s["flexi + amandio c - organic [random mashup]"]=t(51),s["flexi + amandio c - organic12-3d-2.milk"]=t(52),s["Flexi + amandio c - piercing 05 - Kopie (2) - Kopie"]=t(53),s["flexi + fishbrain - neon mindblob grafitti"]=t(54),s["flexi + geiss - pogo cubes vs. tokamak vs. game of life [stahls jelly 4.5 finish]"]=t(55),s["Flexi + Martin - astral projection"]=t(56),s["Flexi + Martin - cascading decay swing"]=t(57),s["Flexi + stahlregen - jelly showoff parade"]=t(58),s["Flexi - alien fish pond"]=t(59),s["Flexi - area 51"]=t(60),s["flexi - bouncing balls [double mindblob neon mix]"]=t(61),s["Flexi - infused with the spiral"]=t(62),s["Flexi - mindblob [shiny mix]"]=t(63),s["Flexi - mindblob mix"]=t(64),s["flexi - mom, why the sky looks different today"]=t(65),s["flexi - patternton, district of media, capitol of the united abstractions of fractopia"]=t(66),s["Flexi - predator-prey-spirals"]=t(67),s["Flexi - smashing fractals [acid etching mix]"]=t(68),s["flexi - swing out on the spiral"]=t(69),s["Flexi - truly soft piece of software - this is generic texturing (Jelly) "]=t(70),s["flexi - what is the matrix"]=t(71),s["Flexi, fishbrain, Geiss + Martin - tokamak witchery"]=t(72),s["Flexi, martin + geiss - dedicated to the sherwin maxawow"]=t(73),s["Fumbling_Foo & Flexi, Martin, Orb, Unchained - Star Nova v7b"]=t(74),s["Geiss + Flexi + Martin - disconnected"]=t(75),s["Geiss - Cauldron - painterly 2 (saturation remix)"]=t(76),s["Geiss - Reaction Diffusion 2"]=t(77),s["Geiss - Spiral Artifact"]=t(78),s["Geiss - Thumb Drum"]=t(79),s["Geiss, Flexi + Stahlregen - Thumbdrum Tokamak [crossfiring aftermath jelly mashup]"]=t(80),s["Goody - The Wild Vort"]=t(81),s["high-altitude basket unraveling - singh grooves nitrogen argon nz+"]=t(82),s["Idiot - Star Of Annon"]=t(25),s["Krash + Illusion - Spiral Movement"]=t(26),s["martin + flexi - diamond cutter [prismaticvortex.com] - camille - i wish i wish i wish i was constrained"]=t(83),s["Martin - acid wiring"]=t(84),s["martin - angel flight"]=t(85),s["martin - another kind of groove"]=t(86),s["martin - bombyx mori"]=t(87),s["martin - castle in the air"]=t(88),s["martin - chain breaker"]=t(89),s["Martin - charisma"]=t(90),s["martin - disco mix 4"]=t(91),s["martin - extreme heat"]=t(92),s["martin - frosty caves 2"]=t(93),s["martin - fruit machine"]=t(94),s["martin - ghost city"]=t(95),s["martin - glass corridor"]=t(96),s["martin - infinity (2010 update)"]=t(27),s["Martin - liquid arrows"]=t(97),s["martin - mandelbox explorer - high speed demo version"]=t(98),s["martin - mucus cervix"]=t(99),s["Martin - QBikal - Surface Turbulence IIb"]=t(100),s["martin - reflections on black tiles"]=t(101),s["martin - stormy sea (2010 update)"]=t(102),s["martin - The Bridge of Khazad-Dum"]=t(103),s["martin - witchcraft reloaded"]=t(104),s["martin [shadow harlequins shape code] - fata morgana"]=t(105),s["martin, flexi, fishbrain + sto - enterstate [random mashup]"]=t(106),s["Milk Artist At our Best - FED - SlowFast Ft AdamFX n Martin - HD CosmoFX"]=t(107),s["ORB - Waaa"]=t(108),s["Phat+fiShbRaiN+Eo.S_Mandala_Chasers_remix"]=t(28),s["Rovastar + Loadus + Geiss - FractalDrop (Triple Mix)"]=t(109),s["Rovastar - Oozing Resistance"]=t(29),s["sawtooth grin roam"]=t(110),s["shifter - dark tides bdrv mix 2"]=t(111),s["suksma - heretical crosscut playpen"]=t(112),s["suksma - Rovastar - Sunflower Passion (Enlightment Mix)_Phat_edit + flexi und martin shaders - circumflex in character classes in regular expression"]=t(113),s["suksma - uninitialized variabowl (hydroponic chronic)"]=t(114),s["suksma - vector exp 1 - couldn′t not"]=t(115),s["TonyMilkdrop - Leonardo Da Vinci's Balloon [Flexi - merry-go-round + techstyle]"]=t(116),s["TonyMilkdrop - Magellan's Nebula [Flexi - you enter first + multiverse]"]=t(117),s["Unchained & Rovastar - Wormhole Pillars (Hall of Shadows mix)"]=t(30),s["Unchained - Rewop"]=t(31),s["Unchained - Unified Drag 2"]=t(32),s["yin - 191 - Temporal singularities"]=t(33),s["Zylot - Paint Spill (Music Reactive Paint Mix)"]=t(118),s["Zylot - Star Ornament"]=t(34),s["Zylot - True Visionary (Final Mix)"]=t(119);var v=function(){function a(){(0,r.default)(this,a)}return(0,n.default)(a,null,[{key:"getPresets",value:function(){return s}}]),a}();e.default=v,a.exports=v}])});
\ No newline at end of file
+!(function (a, e) {
+ 'object' == typeof exports && 'object' == typeof module
+ ? (module.exports = e())
+ : 'function' == typeof define && define.amd
+ ? define('butterchurnPresets', [], e)
+ : 'object' == typeof exports
+ ? (exports.butterchurnPresets = e())
+ : (a.butterchurnPresets = e());
+})('undefined' != typeof self ? self : this, function () {
+ return (function (a) {
+ var e = {};
+ function t(r) {
+ if (e[r]) return e[r].exports;
+ var n = (e[r] = { i: r, l: !1, exports: {} });
+ return (a[r].call(n.exports, n, n.exports, t), (n.l = !0), n.exports);
+ }
+ return (
+ (t.m = a),
+ (t.c = e),
+ (t.d = function (a, e, r) {
+ t.o(a, e) || Object.defineProperty(a, e, { configurable: !1, enumerable: !0, get: r });
+ }),
+ (t.n = function (a) {
+ var e =
+ a && a.__esModule
+ ? function () {
+ return a.default;
+ }
+ : function () {
+ return a;
+ };
+ return (t.d(e, 'a', e), e);
+ }),
+ (t.o = function (a, e) {
+ return Object.prototype.hasOwnProperty.call(a, e);
+ }),
+ (t.p = ''),
+ t((t.s = 165))
+ );
+ })([
+ function (a, e, t) {
+ a.exports = !t(5)(function () {
+ return (
+ 7 !=
+ Object.defineProperty({}, 'a', {
+ get: function () {
+ return 7;
+ },
+ }).a
+ );
+ });
+ },
+ function (a, e) {
+ a.exports = function (a) {
+ return 'object' == typeof a ? null !== a : 'function' == typeof a;
+ };
+ },
+ function (a, e) {
+ var t = (a.exports =
+ 'undefined' != typeof window && window.Math == Math
+ ? window
+ : 'undefined' != typeof self && self.Math == Math
+ ? self
+ : Function('return this')());
+ 'number' == typeof __g && (__g = t);
+ },
+ function (a, e) {
+ var t = (a.exports = { version: '2.5.3' });
+ 'number' == typeof __e && (__e = t);
+ },
+ function (a, e, t) {
+ var r = t(15),
+ n = t(16),
+ _ = t(18),
+ s = Object.defineProperty;
+ e.f = t(0)
+ ? Object.defineProperty
+ : function (a, e, t) {
+ if ((r(a), (e = _(e, !0)), r(t), n))
+ try {
+ return s(a, e, t);
+ } catch (a) {}
+ if ('get' in t || 'set' in t) throw TypeError('Accessors not supported!');
+ return ('value' in t && (a[e] = t.value), a);
+ };
+ },
+ function (a, e) {
+ a.exports = function (a) {
+ try {
+ return !!a();
+ } catch (a) {
+ return !0;
+ }
+ };
+ },
+ function (a, e, t) {
+ 'use strict';
+ ((e.__esModule = !0),
+ (e.default = function (a, e) {
+ if (!(a instanceof e)) throw new TypeError('Cannot call a class as a function');
+ }));
+ },
+ function (a, e, t) {
+ 'use strict';
+ e.__esModule = !0;
+ var r,
+ n = t(8),
+ _ = (r = n) && r.__esModule ? r : { default: r };
+ e.default = (function () {
+ function a(a, e) {
+ for (var t = 0; t < e.length; t++) {
+ var r = e[t];
+ ((r.enumerable = r.enumerable || !1),
+ (r.configurable = !0),
+ 'value' in r && (r.writable = !0),
+ (0, _.default)(a, r.key, r));
+ }
+ }
+ return function (e, t, r) {
+ return (t && a(e.prototype, t), r && a(e, r), e);
+ };
+ })();
+ },
+ function (a, e, t) {
+ a.exports = { default: t(9), __esModule: !0 };
+ },
+ function (a, e, t) {
+ t(10);
+ var r = t(3).Object;
+ a.exports = function (a, e, t) {
+ return r.defineProperty(a, e, t);
+ };
+ },
+ function (a, e, t) {
+ var r = t(11);
+ r(r.S + r.F * !t(0), 'Object', { defineProperty: t(4).f });
+ },
+ function (a, e, t) {
+ var r = t(2),
+ n = t(3),
+ _ = t(12),
+ s = t(14),
+ v = function (a, e, t) {
+ var m,
+ i,
+ p,
+ b = a & v.F,
+ l = a & v.G,
+ x = a & v.S,
+ o = a & v.P,
+ d = a & v.B,
+ u = a & v.W,
+ c = l ? n : n[e] || (n[e] = {}),
+ y = c.prototype,
+ h = l ? r : x ? r[e] : (r[e] || {}).prototype;
+ for (m in (l && (t = e), t))
+ ((i = !b && h && void 0 !== h[m]) && m in c) ||
+ ((p = i ? h[m] : t[m]),
+ (c[m] =
+ l && 'function' != typeof h[m]
+ ? t[m]
+ : d && i
+ ? _(p, r)
+ : u && h[m] == p
+ ? (function (a) {
+ var e = function (e, t, r) {
+ if (this instanceof a) {
+ switch (arguments.length) {
+ case 0:
+ return new a();
+ case 1:
+ return new a(e);
+ case 2:
+ return new a(e, t);
+ }
+ return new a(e, t, r);
+ }
+ return a.apply(this, arguments);
+ };
+ return ((e.prototype = a.prototype), e);
+ })(p)
+ : o && 'function' == typeof p
+ ? _(Function.call, p)
+ : p),
+ o && (((c.virtual || (c.virtual = {}))[m] = p), a & v.R && y && !y[m] && s(y, m, p)));
+ };
+ ((v.F = 1),
+ (v.G = 2),
+ (v.S = 4),
+ (v.P = 8),
+ (v.B = 16),
+ (v.W = 32),
+ (v.U = 64),
+ (v.R = 128),
+ (a.exports = v));
+ },
+ function (a, e, t) {
+ var r = t(13);
+ a.exports = function (a, e, t) {
+ if ((r(a), void 0 === e)) return a;
+ switch (t) {
+ case 1:
+ return function (t) {
+ return a.call(e, t);
+ };
+ case 2:
+ return function (t, r) {
+ return a.call(e, t, r);
+ };
+ case 3:
+ return function (t, r, n) {
+ return a.call(e, t, r, n);
+ };
+ }
+ return function () {
+ return a.apply(e, arguments);
+ };
+ };
+ },
+ function (a, e) {
+ a.exports = function (a) {
+ if ('function' != typeof a) throw TypeError(a + ' is not a function!');
+ return a;
+ };
+ },
+ function (a, e, t) {
+ var r = t(4),
+ n = t(19);
+ a.exports = t(0)
+ ? function (a, e, t) {
+ return r.f(a, e, n(1, t));
+ }
+ : function (a, e, t) {
+ return ((a[e] = t), a);
+ };
+ },
+ function (a, e, t) {
+ var r = t(1);
+ a.exports = function (a) {
+ if (!r(a)) throw TypeError(a + ' is not an object!');
+ return a;
+ };
+ },
+ function (a, e, t) {
+ a.exports =
+ !t(0) &&
+ !t(5)(function () {
+ return (
+ 7 !=
+ Object.defineProperty(t(17)('div'), 'a', {
+ get: function () {
+ return 7;
+ },
+ }).a
+ );
+ });
+ },
+ function (a, e, t) {
+ var r = t(1),
+ n = t(2).document,
+ _ = r(n) && r(n.createElement);
+ a.exports = function (a) {
+ return _ ? n.createElement(a) : {};
+ };
+ },
+ function (a, e, t) {
+ var r = t(1);
+ a.exports = function (a, e) {
+ if (!r(a)) return a;
+ var t, n;
+ if (e && 'function' == typeof (t = a.toString) && !r((n = t.call(a)))) return n;
+ if ('function' == typeof (t = a.valueOf) && !r((n = t.call(a)))) return n;
+ if (!e && 'function' == typeof (t = a.toString) && !r((n = t.call(a)))) return n;
+ throw TypeError("Can't convert object to primitive value");
+ };
+ },
+ function (a, e) {
+ a.exports = function (a, e) {
+ return { enumerable: !(1 & a), configurable: !(2 & a), writable: !(4 & a), value: e };
+ };
+ },
+ function (a, e) {
+ a.exports = {
+ baseVals: {
+ rating: 5,
+ gammaadj: 1.42,
+ decay: 1,
+ echo_zoom: 0.999823,
+ echo_alpha: 0.5,
+ echo_orient: 1,
+ wave_mode: 5,
+ wave_thick: 1,
+ wave_brighten: 0,
+ wrap: 0,
+ darken: 1,
+ wave_a: 0.001185,
+ wave_scale: 0.325446,
+ wave_smoothing: 0.9,
+ modwavealphastart: 0.5,
+ modwavealphaend: 1,
+ warpanimspeed: 2.630064,
+ warpscale: 3.209168,
+ zoomexp: 1.000158,
+ dx: 1e-5,
+ dy: 1e-5,
+ warp: 0.01,
+ wave_r: 0.5,
+ wave_g: 0.5,
+ wave_b: 0.5,
+ ob_size: 0.005,
+ ob_a: 0.5,
+ ib_size: 0,
+ ib_r: 0,
+ ib_g: 0,
+ ib_b: 0,
+ ib_a: 0.1,
+ mv_x: 6.4,
+ mv_y: 4.8,
+ mv_l: 5,
+ mv_a: 0,
+ },
+ shapes: [
+ { baseVals: { enabled: 0 } },
+ { baseVals: { enabled: 0 } },
+ { baseVals: { enabled: 0 } },
+ { baseVals: { enabled: 0 } },
+ ],
+ waves: [
+ { baseVals: { enabled: 0 } },
+ { baseVals: { enabled: 0 } },
+ { baseVals: { enabled: 0 } },
+ { baseVals: { enabled: 0 } },
+ ],
+ init_eqs_str: 'a.thresh=0;a.dx_r=0;a.dy_r=0;a.tg1=0;a.tg2=0;a.tg3=0;a.six=0;',
+ frame_eqs_str:
+ 'a.wave_r=.5+.5*Math.sin(1.6*a.time);a.wave_g=.5+.5*Math.sin(4.1*a.time);a.wave_b=-1+(1-a.wave_r+1-a.wave_g);a.warp=2;a.ob_r+=a.wave_b*above(Math.sin(.1*a.time),0);a.ob_b+=a.wave_g*above(Math.sin(.1*a.time),0);a.ob_g+=a.wave_r*above(Math.sin(.1*a.time),0);a.ob_r+=a.wave_g*below(Math.sin(.1*a.time),0);a.ob_b+=a.wave_r*below(Math.sin(.1*a.time),0);a.ob_g+=a.wave_b*below(Math.sin(.1*a.time),0);',
+ pixel_eqs_str:
+ 'a.thresh=2*above(a.bass_att,a.thresh)+(1-above(a.bass_att,a.thresh))*(.96*(a.thresh-1.3)+1.3);a.dx_r=.05*equal(a.thresh,2)*Math.sin(5*a.time)+(1-equal(a.thresh,2))*a.dx_r;a.dy_r=.056*equal(a.thresh,2)*Math.sin(6*a.time)+(1-equal(a.thresh,2))*a.dy_r;a.tg1=Math.abs(Math.sin(a.time));a.tg2=22*above(a.tg1,.75)+12*below(a.tg1,.25)+18*above(a.tg1,.25)*below(a.tg1,.5)+12*above(a.tg1,.5)*below(a.tg1,.75);a.tg3=.00001 1.0)\n ) * (\n (tmpvar_8 * -2.0)\n + 1.570796)));\n tmpvar_6 = (tmpvar_8 * sign((uv_1.y / uv_1.x)));\n if ((abs(uv_1.x) > (1e-08 * abs(uv_1.y)))) {\n if ((uv_1.x < 0.0)) {\n if ((uv_1.y >= 0.0)) {\n tmpvar_6 += 3.141593;\n } else {\n tmpvar_6 = (tmpvar_6 - 3.141593);\n };\n };\n } else {\n tmpvar_6 = (sign(uv_1.y) * 1.570796);\n };\n xlat_mutablers0.x = ((tmpvar_6 / 3.1416) * 2.0);\n xlat_mutablers0.y = (0.03 / sqrt(dot (uv_1, uv_1)));\n ret1_3 = vec3(0.0, 0.0, 0.0);\n for (int n_2 = 0; n_2 <= 10; n_2++) {\n float tmpvar_9;\n tmpvar_9 = fract((-(q9) + (\n float(n_2)\n / 10.0)));\n xlat_mutableang2 = (((q1 * 3.14) * float(n_2)) / 10.0);\n float tmpvar_10;\n tmpvar_10 = cos(xlat_mutableang2);\n float tmpvar_11;\n tmpvar_11 = sin(xlat_mutableang2);\n mat2 tmpvar_12;\n tmpvar_12[uint(0)].x = tmpvar_10;\n tmpvar_12[uint(0)].y = -(tmpvar_11);\n tmpvar_12[1u].x = tmpvar_11;\n tmpvar_12[1u].y = tmpvar_10;\n xlat_mutableuv2 = (uv_1 * ((q13 * tmpvar_9) * tmpvar_12));\n ret1_3 = max (ret1_3, (texture (sampler_main, (xlat_mutableuv2 + 0.5)).xyz * (1.0 - tmpvar_9)));\n };\n vec4 tmpvar_13;\n tmpvar_13.w = 1.0;\n tmpvar_13.xyz = ((ret1_3 * 2.0) + ((\n (bass_att * xlat_mutablers0.y)\n * texture (sampler_main, \n ((uv_1 * q12) + vec2(0.5, 0.0))\n ).yzx) * clamp (\n (1.0 - (ret1_3 * 32.0))\n , 0.0, 1.0)));\n ret = tmpvar_13.xyz;\n }',
+ };
+ },
+ function (a, e) {
+ a.exports = {
+ baseVals: {
+ rating: 5,
+ gammaadj: 1,
+ decay: 0.965,
+ echo_zoom: 1.483827,
+ echo_alpha: 0.5,
+ echo_orient: 3,
+ wave_mode: 7,
+ additivewave: 1,
+ wave_brighten: 0,
+ wrap: 0,
+ darken_center: 1,
+ darken: 1,
+ wave_a: 0.001,
+ wave_scale: 1.285751,
+ wave_smoothing: 0.63,
+ modwavealphastart: 0.71,
+ modwavealphaend: 1.3,
+ warpanimspeed: 0.01,
+ warpscale: 1.470245,
+ zoomexp: 4.778023,
+ zoom: 0.998162,
+ warp: 0.01,
+ sx: 1.001828,
+ wave_r: 0.65,
+ wave_g: 0.65,
+ wave_b: 0.65,
+ ob_size: 0.005,
+ ob_r: 1,
+ ob_g: 0.5,
+ ob_b: 0.5,
+ ob_a: 1,
+ ib_size: 0.5,
+ ib_r: 0,
+ ib_g: 0,
+ ib_b: 0,
+ ib_a: 1,
+ mv_x: 64,
+ mv_y: 4.800001,
+ mv_dx: 0.4,
+ mv_l: 1,
+ mv_r: 0,
+ mv_g: 0.5,
+ mv_a: 0.1,
+ },
+ shapes: [
+ {
+ baseVals: {
+ enabled: 1,
+ sides: 3,
+ additive: 1,
+ thickoutline: 1,
+ textured: 1,
+ x: 1,
+ y: 0.59,
+ rad: 0.559231,
+ ang: 3.39292,
+ tex_zoom: 100,
+ r: 0,
+ g: 1,
+ b: 1,
+ g2: 0,
+ border_r: 0,
+ border_g: 0,
+ border_b: 0,
+ border_a: 1,
+ },
+ init_eqs_str: '',
+ frame_eqs_str: 'a.x=.1*Math.sin(div(a.time,10))+.5+.1*a.treb_att;',
+ },
+ { baseVals: { enabled: 0 } },
+ {
+ baseVals: {
+ enabled: 1,
+ sides: 6,
+ textured: 1,
+ x: 0.3,
+ y: 0.7,
+ rad: 1.089252,
+ ang: 0.816814,
+ tex_ang: 3.141592,
+ tex_zoom: 0.504215,
+ g: 1,
+ b: 1,
+ r2: 1,
+ b2: 1,
+ border_a: 0,
+ },
+ init_eqs_str: '',
+ frame_eqs_str: '',
+ },
+ {
+ baseVals: {
+ enabled: 1,
+ sides: 3,
+ textured: 1,
+ rad: 0.284278,
+ ang: 3.141593,
+ tex_ang: 4.900885,
+ tex_zoom: 2.987755,
+ g: 1,
+ b: 1,
+ r2: 0.95,
+ b2: 1,
+ a2: 1,
+ border_r: 0,
+ border_g: 0,
+ border_b: 0,
+ border_a: 1,
+ },
+ init_eqs_str: 'a["var"]=0;',
+ frame_eqs_str:
+ 'a.ang=div(a.time,10);a.tex_zoom=3.4+.03*a.bass;a["var"]=above(a.bass_att,.7);a.a=a["var"];a.a2=a["var"];a.border_a=a["var"];',
+ },
+ ],
+ waves: [
+ {
+ baseVals: { enabled: 1, usedots: 1, thick: 1, additive: 1, r: 0, a: 0.06 },
+ init_eqs_str:
+ 'a.px=0;a.xoffset2=0;a.py=0;a.xoffset1=0;a.pheight=0;a.pphase=0;a.yspout=0;a.pphase2=0;a.xspout=0;a.lrorient=0;a.yheight=0;',
+ frame_eqs_str: '',
+ point_eqs_str:
+ 'a.xspout=.5;a.yspout=-.01;a.pphase=9999*a.sample*a.sample*.0001;a.pphase2=.1+.01*mod(3349*a.sample*a.sample,100);a.pheight=.002*mod(9893*a.sample,100);a.yheight=.01*mod(1231*a.sample*a.sample,100);a.r=.01*mod(5454*a.sample,100)*Math.abs(Math.sin(.25*a.time));a.g=.01*mod(9954*a.sample,100);a.xoffset1=Math.cos(a.time*a.pphase2+a.pphase)*a.pheight;a.xoffset2=-1*Math.cos(a.time*a.pphase2+a.pphase)*a.pheight;a.lrorient=.00001 1.0)\n ) * (\n (tmpvar_8 * -2.0)\n + 1.570796)));\n tmpvar_6 = (tmpvar_8 * sign((uv_1.y / uv_1.x)));\n if ((abs(uv_1.x) > (1e-08 * abs(uv_1.y)))) {\n if ((uv_1.x < 0.0)) {\n if ((uv_1.y >= 0.0)) {\n tmpvar_6 += 3.141593;\n } else {\n tmpvar_6 = (tmpvar_6 - 3.141593);\n };\n };\n } else {\n tmpvar_6 = (sign(uv_1.y) * 1.570796);\n };\n xlat_mutablers0.x = (((tmpvar_6 / 3.1416) * 6.0) * q28);\n xlat_mutablers0.y = inversesqrt(dot (uv_1, uv_1));\n vec2 tmpvar_9;\n tmpvar_9.x = (xlat_mutablers0.x + (q9 * 8.0));\n tmpvar_9.y = (xlat_mutablers0.y + ((q9 * q28) * 4.0));\n xlat_mutablerss = (tmpvar_9 / 12.0);\n vec2 tmpvar_10;\n tmpvar_10.x = q5;\n tmpvar_10.y = q6;\n ofs_2 = (0.1 * tmpvar_10.yx);\n float tmpvar_11;\n float tmpvar_12;\n tmpvar_12 = -(q9);\n tmpvar_11 = fract(tmpvar_12);\n mat2 tmpvar_13;\n tmpvar_13[uint(0)].x = 1.0;\n tmpvar_13[uint(0)].y = -0.0;\n tmpvar_13[1u].x = 0.0;\n tmpvar_13[1u].y = 1.0;\n xlat_mutableuv2 = ((uv_1 * (\n (q13 * tmpvar_11)\n * tmpvar_13)) * aspect.yx);\n vec2 tmpvar_14;\n tmpvar_14 = fract(((xlat_mutableuv2 + 0.5) + ofs_2));\n xlat_mutableneu = (texture (sampler_main, tmpvar_14).xyz + ((texture (sampler_blur1, tmpvar_14).xyz * scale1) + bias1));\n ret1_3 = max (vec3(0.0, 0.0, 0.0), ((xlat_mutableneu * \n (1.0 - (tmpvar_11 * tmpvar_11))\n ) * 2.0));\n float tmpvar_15;\n tmpvar_15 = fract((tmpvar_12 + 0.3333333));\n mat2 tmpvar_16;\n tmpvar_16[uint(0)].x = -0.4990803;\n tmpvar_16[uint(0)].y = -0.8665558;\n tmpvar_16[1u].x = 0.8665558;\n tmpvar_16[1u].y = -0.4990803;\n xlat_mutableuv2 = ((uv_1 * (\n (q13 * tmpvar_15)\n * tmpvar_16)) * aspect.yx);\n vec2 tmpvar_17;\n tmpvar_17 = fract(((xlat_mutableuv2 + 0.5) + ofs_2));\n xlat_mutableneu = (texture (sampler_main, tmpvar_17).xyz + ((texture (sampler_blur1, tmpvar_17).xyz * scale1) + bias1));\n ret1_3 = max (ret1_3, ((xlat_mutableneu * \n (1.0 - (tmpvar_15 * tmpvar_15))\n ) * 2.0));\n float tmpvar_18;\n tmpvar_18 = fract((tmpvar_12 + 0.6666667));\n mat2 tmpvar_19;\n tmpvar_19[uint(0)].x = -0.5018377;\n tmpvar_19[uint(0)].y = 0.8649619;\n tmpvar_19[1u].x = -0.8649619;\n tmpvar_19[1u].y = -0.5018377;\n xlat_mutableuv2 = ((uv_1 * (\n (q13 * tmpvar_18)\n * tmpvar_19)) * aspect.yx);\n vec2 tmpvar_20;\n tmpvar_20 = fract(((xlat_mutableuv2 + 0.5) + ofs_2));\n xlat_mutableneu = (texture (sampler_main, tmpvar_20).xyz + ((texture (sampler_blur1, tmpvar_20).xyz * scale1) + bias1));\n ret1_3 = max (ret1_3, ((xlat_mutableneu * \n (1.0 - (tmpvar_18 * tmpvar_18))\n ) * 2.0));\n float tmpvar_21;\n tmpvar_21 = fract((tmpvar_12 + 1.0));\n mat2 tmpvar_22;\n tmpvar_22[uint(0)].x = 0.9999949;\n tmpvar_22[uint(0)].y = 0.003185092;\n tmpvar_22[1u].x = -0.003185092;\n tmpvar_22[1u].y = 0.9999949;\n xlat_mutableuv2 = ((uv_1 * (\n (q13 * tmpvar_21)\n * tmpvar_22)) * aspect.yx);\n vec2 tmpvar_23;\n tmpvar_23 = fract(((xlat_mutableuv2 + 0.5) + ofs_2));\n xlat_mutableneu = (texture (sampler_main, tmpvar_23).xyz + ((texture (sampler_blur1, tmpvar_23).xyz * scale1) + bias1));\n ret1_3 = max (ret1_3, ((xlat_mutableneu * \n (1.0 - (tmpvar_21 * tmpvar_21))\n ) * 2.0));\n vec2 tmpvar_24;\n tmpvar_24.x = (ret1_3.x + ret1_3.z);\n tmpvar_24.y = (ret1_3.x - ret1_3.y);\n vec4 tmpvar_25;\n tmpvar_25.w = 1.0;\n tmpvar_25.xyz = ((ret1_3 + (\n ((bass_att * 0.004) / sqrt(dot (uv_1, uv_1)))\n * roam_sin).xyz) + ((2.0 * \n (bass_att * ((texture (sampler_blur1, fract(\n (xlat_mutablerss + (tmpvar_24 / 2.0))\n )).xyz * scale1) + bias1).zxy)\n ) * clamp (\n (1.0 - (ret1_3 * 4.0))\n , 0.0, 1.0)));\n ret = tmpvar_25.xyz;\n }',
+ };
+ },
+ function (a, e) {
+ a.exports = {
+ baseVals: {
+ rating: 0,
+ gammaadj: 1.14,
+ decay: 1,
+ echo_zoom: 1,
+ wave_mode: 6,
+ wave_thick: 1,
+ wave_brighten: 0,
+ wrap: 0,
+ darken: 1,
+ wave_a: 1.17,
+ wave_scale: 0.797,
+ wave_smoothing: 0,
+ modwavealphastart: 0.71,
+ modwavealphaend: 1.3,
+ warpscale: 1.331,
+ zoomexp: 0.9995,
+ zoom: 0.9998,
+ rot: 0.02,
+ dy: -0.008,
+ warp: 0.01,
+ sx: 1.0098,
+ wave_r: 0.5,
+ wave_g: 0.5,
+ wave_b: 0.5,
+ wave_x: 0.9,
+ ob_size: 0.005,
+ ob_a: 0.8,
+ ib_size: 0,
+ ib_r: 0,
+ ib_g: 0,
+ ib_b: 0,
+ ib_a: 1,
+ mv_x: 44.8,
+ mv_y: 38.4,
+ mv_l: 5,
+ mv_g: 0.91,
+ mv_b: 0.71,
+ mv_a: 0,
+ },
+ shapes: [
+ { baseVals: { enabled: 0 } },
+ { baseVals: { enabled: 0 } },
+ { baseVals: { enabled: 0 } },
+ { baseVals: { enabled: 0 } },
+ ],
+ waves: [
+ { baseVals: { enabled: 0 } },
+ { baseVals: { enabled: 0 } },
+ { baseVals: { enabled: 0 } },
+ { baseVals: { enabled: 0 } },
+ ],
+ init_eqs_str:
+ 'a.q12=0;a.q18=0;a.q6=0;a.bass_thresh=0;a.wg=0;a.q11=0;a.q10=0;a.wb=0;a.q17=0;a.vol=0;a.q2=0;a.q3=0;a.wr=0;a.q7=0;a.q8=0;',
+ frame_eqs_str:
+ 'a.wave_r+=.3*Math.sin(50*a.vol);a.wave_b+=.3*Math.sin(20*a.vol);a.wave_g+=.5*Math.sin(35*a.vol);a.q8=a.wave_r;a.q7=a.wave_b;a.q6=a.wave_g;a.wr=.5+.4*(.6*Math.sin(1.1*a.time)+.4*Math.sin(.8*a.time));a.wb=.5+.4*(.6*Math.sin(1.6*a.time)+.4*Math.sin(.5*a.time));a.wg=.5+.4*(.6*Math.sin(1.34*a.time)+.4*Math.sin(.4*a.time));a.monitor=a.wg;a.q10=a.wr;a.q11=a.wb;a.q12=a.wg;a.q18=.007*Math.sin(.1*a.time);a.q17=-.007*Math.sin(.254*a.time);a.q2=a.bass_thresh;a.vol=.25*(a.bass+a.mid+a.treb);\na.vol*=a.vol;a.q3=a.vol;a.warp=0;',
+ pixel_eqs_str: '',
+ pixel_eqs: '',
+ warp: ' shader_body { \n vec3 noise3_1;\n vec3 tmpvar_2;\n tmpvar_2 = (texture (sampler_main, uv).xyz + ((texture (sampler_blur1, uv).xyz * scale1) + bias1));\n vec2 tmpvar_3;\n tmpvar_3 = (0.5 + ((\n (uv - vec2(0.0, 1.0))\n - 0.5) * (1.0 + \n (tmpvar_2.y * 0.03)\n )));\n vec2 tmpvar_4;\n tmpvar_4.x = (tmpvar_3.x + pow (tmpvar_2.x, 0.0));\n tmpvar_4.y = (tmpvar_3.y + pow (tmpvar_2.x, 0.005));\n noise3_1 = (texture (sampler_noise_lq, ((\n (uv_orig * texsize.xy)\n * texsize_noise_lq.zw) + rand_frame.xy)).xyz * fract(q15));\n vec3 tmpvar_5;\n tmpvar_5 = (noise3_1 * (vec3(1.0, 1.0, 1.0) - vec3(fract(\n (q3 * 0.5)\n ))));\n noise3_1 = tmpvar_5;\n vec4 tmpvar_6;\n tmpvar_6 = texture (sampler_main, fract(tmpvar_4));\n vec3 tmpvar_7;\n tmpvar_7.x = q10;\n tmpvar_7.y = q11;\n tmpvar_7.z = q12;\n vec3 tmpvar_8;\n tmpvar_8 = mix (tmpvar_5, tmpvar_7, tmpvar_6.xxx);\n vec4 tmpvar_9;\n tmpvar_9.w = 1.0;\n tmpvar_9.xyz = (tmpvar_6.xyz + clamp ((\n (tmpvar_6.yzx * tmpvar_8.zxy)\n - \n (tmpvar_6.zxy * tmpvar_8.yzx)\n ), 0.0, 1.0));\n ret = tmpvar_9.xyz;\n }',
+ comp: ' shader_body { \n vec2 uv1_1;\n vec2 tmpvar_2;\n tmpvar_2.y = 0.0;\n tmpvar_2.x = texsize.z;\n vec2 tmpvar_3;\n tmpvar_3.x = 0.0;\n tmpvar_3.y = texsize.w;\n vec2 tmpvar_4;\n tmpvar_4.x = (texture (sampler_main, (uv - tmpvar_2)).xyz - texture (sampler_main, (uv + tmpvar_2)).xyz).x;\n tmpvar_4.y = (texture (sampler_main, (uv - tmpvar_3)).xyz - texture (sampler_main, (uv + tmpvar_3)).xyz).x;\n uv1_1 = ((0.3 * cos(\n ((uv - 0.5) * 2.0)\n )) - tmpvar_4);\n float tmpvar_5;\n tmpvar_5 = clamp ((0.04 / sqrt(\n dot (uv1_1, uv1_1)\n )), 0.0, 1.0);\n uv1_1 = ((0.3 * cos(\n (uv1_1 * 12.0)\n )) - (9.0 * tmpvar_4));\n vec4 tmpvar_6;\n tmpvar_6.w = 1.0;\n tmpvar_6.xyz = (tmpvar_5 + ((texture (sampler_main, uv).xyz * 12.0) * vec3(clamp (\n (0.04 / sqrt(dot (uv1_1, uv1_1)))\n , 0.0, 1.0))));\n ret = tmpvar_6.xyz;\n }',
+ };
+ },
+ function (a, e) {
+ a.exports = {
+ baseVals: {
+ rating: 0,
+ gammaadj: 1.14,
+ decay: 1,
+ echo_zoom: 1,
+ wave_mode: 1,
+ wave_thick: 1,
+ modwavealphabyvolume: 1,
+ wave_brighten: 0,
+ wrap: 0,
+ darken: 1,
+ wave_a: 0.001,
+ wave_scale: 0.179,
+ wave_smoothing: 0,
+ wave_mystery: 0.3,
+ modwavealphastart: 0.71,
+ modwavealphaend: 1.3,
+ warpscale: 1.331,
+ zoomexp: 0.8195,
+ zoom: 1.0697,
+ dy: 0.006,
+ warp: 0.01,
+ sx: 0.9996,
+ wave_g: 0,
+ wave_b: 0,
+ ob_a: 0.8,
+ ib_size: 0,
+ ib_r: 0,
+ ib_g: 0,
+ ib_b: 0,
+ ib_a: 1,
+ mv_x: 0,
+ mv_y: 0,
+ mv_l: 1,
+ mv_g: 0.91,
+ mv_b: 0.71,
+ mv_a: 0,
+ },
+ shapes: [
+ { baseVals: { enabled: 0 } },
+ { baseVals: { enabled: 0 } },
+ { baseVals: { enabled: 0 } },
+ { baseVals: { enabled: 0 } },
+ ],
+ waves: [
+ { baseVals: { enabled: 0 } },
+ { baseVals: { enabled: 0 } },
+ { baseVals: { enabled: 0 } },
+ { baseVals: { enabled: 0 } },
+ ],
+ init_eqs_str:
+ 'a.q12=0;a.q18=0;a.q6=0;a.q5=0;a.bass_thresh=0;a.wg=0;a.q11=0;a.q10=0;a.wb=0;a.q17=0;a.vol=0;a.q2=0;a.q3=0;a.wr=0;a.q7=0;a.mtime=0;a.q8=0;',
+ frame_eqs_str:
+ 'a.bass_thresh=2*above(a.bass_att,a.bass_thresh)+(1-above(a.bass_att,a.bass_thresh))*(.91*(a.bass_thresh-1.3)+1.3);a.wave_r=.5+.5*(.6*Math.sin(1.3*a.time)+.4*Math.sin(.98*a.time));a.wave_b=.5+.5*(.6*Math.sin(1.1*a.time)+.4*Math.sin(.78*a.time));a.wave_g=.5+.5*(.6*Math.sin(1.2*a.time)+.4*Math.sin(.6*a.time));a.q8=a.wave_r;a.q7=a.wave_b;a.q6=a.wave_g;a.wr=.5+.4*(.6*Math.sin(.2*a.time)+.4*Math.sin(.8*a.time));a.wb=.5+.4*(.6*Math.sin(.377*a.time)+.4*Math.sin(.5*a.time));a.wg=.5+\n.4*(.6*Math.sin(.7*a.time)+.4*Math.sin(.4*a.time));a.q10=a.wr;a.q11=a.wb;a.q12=a.wg;a.q10=.8;a.q11=.2;a.q12=.1;a.q18=.01*Math.sin(.1*a.mtime);a.q17=-.01*Math.sin(.254*a.mtime);a.q2=a.bass_thresh;a.vol=.25*(a.bass+a.mid+a.treb);a.vol*=a.vol;a.q3=a.vol;a.q5=.5*a.vol;a.mtime+=.01*a.vol;a.q2=.25*a.mtime;',
+ pixel_eqs_str: '',
+ pixel_eqs: '',
+ warp: ' shader_body { \n vec3 noise2_1;\n vec3 ret_2;\n vec3 tmpvar_3;\n tmpvar_3 = (((texture (sampler_blur1, uv).xyz * scale1) + bias1) + texture (sampler_main, uv).xyz);\n vec2 tmpvar_4;\n tmpvar_4 = (0.5 + ((uv - 0.5) * (1.0 + \n (tmpvar_3.y * 0.05)\n )));\n vec2 tmpvar_5;\n tmpvar_5.x = (tmpvar_4.x + pow (tmpvar_3.x, q17));\n tmpvar_5.y = (tmpvar_4.y + pow (tmpvar_3.x, q18));\n vec4 tmpvar_6;\n tmpvar_6 = texture (sampler_fc_main, fract(tmpvar_5));\n vec3 tmpvar_7;\n tmpvar_7.x = q10;\n tmpvar_7.y = q11;\n tmpvar_7.z = q12;\n noise2_1 = (texture (sampler_noise_lq, ((\n (uv_orig * texsize.xy)\n * texsize_noise_lq.zw) + rand_frame.xy)).xyz + ((tmpvar_7 * vec3(rad)) * vol));\n vec3 a_8;\n a_8 = (1.0 - tmpvar_6.xyz);\n ret_2 = (tmpvar_6.xyz + (0.3 * clamp (\n ((a_8.yzx * noise2_1.zxy) - (a_8.zxy * noise2_1.yzx))\n , 0.0, 1.0)));\n ret_2 = (ret_2 * 0.97);\n vec4 tmpvar_9;\n tmpvar_9.w = 1.0;\n tmpvar_9.xyz = ret_2;\n ret = tmpvar_9.xyz;\n }',
+ comp: ' shader_body { \n vec3 ret_1;\n ret_1 = (texture (sampler_main, uv).xyz * vec3(0.9, 0.3, 0.5));\n ret_1 = (ret_1 * 1.34);\n ret_1 = (ret_1 * ret_1);\n vec4 tmpvar_2;\n tmpvar_2.w = 1.0;\n tmpvar_2.xyz = ret_1;\n ret = tmpvar_2.xyz;\n }',
+ };
+ },
+ function (a, e) {
+ a.exports = {
+ baseVals: {
+ rating: 5,
+ gammaadj: 1.28,
+ decay: 0.8,
+ echo_zoom: 1,
+ echo_orient: 3,
+ wave_mode: 7,
+ additivewave: 1,
+ modwavealphabyvolume: 1,
+ wave_brighten: 0,
+ brighten: 1,
+ wave_a: 0.001,
+ wave_scale: 1.286,
+ wave_smoothing: 0.63,
+ modwavealphastart: 0.71,
+ modwavealphaend: 1.3,
+ zoomexp: 3.04777,
+ zoom: 1.0173,
+ warp: 0.01605,
+ wave_g: 0.65,
+ wave_b: 0.65,
+ ob_size: 0,
+ ob_a: 1,
+ mv_x: 64,
+ mv_y: 48,
+ mv_l: 0,
+ mv_a: 0,
+ b1ed: 0,
+ },
+ shapes: [
+ { baseVals: { enabled: 0 } },
+ { baseVals: { enabled: 0 } },
+ { baseVals: { enabled: 0 } },
+ { baseVals: { enabled: 0 } },
+ ],
+ waves: [
+ {
+ baseVals: { enabled: 1, thick: 1, additive: 1, g: 0, b: 0 },
+ init_eqs_str: 'a.ma=0;a.mx=0;a.my=0;',
+ frame_eqs_str: '',
+ point_eqs_str:
+ 'a.ma+=3.1415*above(a.bass,1)*.01*a.bass;a.ma-=3.1415*above(a.treb,1)*.01*a.treb;a.mx+=.0002*Math.cos(a.ma);a.my+=.0002*Math.sin(a.ma);a.mx=.00001b;b++)a.gmegabuf[Math.floor(a.n)]=0,a.n+=1;for(b=a.n=0;1E4>b;b++)a.megabuf[Math.floor(a.n)]=0,a.n+=1;a.trelx=0;a.trely=0;a.trelz=0;a.reg20=1;a.reg21=0;a.reg22=0;a.reg23=0;a.reg24=1;a.reg25=0;a.reg26=0;a.reg27=0;a.reg28=1;b=0;do{b+=1;var c;a.ran1=div(randint(800),100);a.ran2=\ndiv(randint(800),100);a.ran3=div(randint(800),100);a.posx=randint(5)-2;a.posy=randint(5)-2;a.posz=randint(5)-2;a.c1=Math.cos(a.ran1);a.c2=Math.cos(a.ran2);a.c3=Math.cos(a.ran3);a.s1=Math.sin(a.ran1);a.s2=Math.sin(a.ran2);a.s3=Math.sin(a.ran3);a.reg20=a.c2*a.c1;a.reg21=a.c2*a.s1;a.reg22=-a.s2;a.reg23=a.s3*a.s2*a.c1-a.c3*a.s1;a.reg24=a.s3*a.s2*a.s1+a.c3*a.c1;a.reg25=a.s3*a.c2;a.reg26=a.c3*a.s2*a.c1+a.s3*a.s1;a.reg27=a.c3*a.s2*a.s1-a.s3*a.c1;a.reg28=a.c3*a.c2;a.dist=.001;var d=0;do{d+=1;a.uvx=div(a.reg26*\na.dist,a.q7);a.uvy=div(a.reg27*a.dist,a.q7);a.uvz=div(a.reg28*a.dist,a.q7);a.uvx+=a.posx;a.uvy+=a.posy;a.uvz+=a.posz;a.uvx=8*(div(a.uvx,8)+30.5-Math.floor(div(a.uvx,8)+30.5)-.5);a.uvy=8*(div(a.uvy,8)+30.5-Math.floor(div(a.uvy,8)+30.5)-.5);a.uvz=8*(div(a.uvz,8)+30.5-Math.floor(div(a.uvz,8)+30.5)-.5);a.uvx0=a.uvx+a.q8;a.uvy0=a.uvy+a.q8;a.uvz0=a.uvz+a.q8;for(c=0;8>c;c++)a.uvx=.00001a.uvx?1:0)?-2-a.uvx:a.uvx,a.uvy=.00001a.uvy?1:0)?-2-a.uvy:a.uvy,a.uvz=.00001a.uvz?1:0)?-2-a.uvz:a.uvz,a.slen=a.uvx*a.uvx+a.uvy*a.uvy+a.uvz*a.uvz,a.uvx=2.6*(.00001a.slen?1:0)?4*a.uvx:.00001