From b94a832d2e000fd461a5a78b54f0fd63a6ddde49 Mon Sep 17 00:00:00 2001 From: Daniel <790119+DanTheMan827@users.noreply.github.com> Date: Tue, 31 Mar 2026 14:15:52 -0500 Subject: [PATCH 01/51] feat(vitest): add vitest config and tests Add tests for HiFi, ffmpeg, and download api functions. --- .github/workflows/tests.yml | 41 ++++ .gitignore | 2 + bun.lock | 127 +++++++++- js/HiFi.test.ts | 199 ++++++++++++++++ js/HiFi.ts | 80 ++++--- js/api.test.ts | 457 ++++++++++++++++++++++++++++++++++++ js/ffmpeg.js | 12 +- js/ffmpeg.test.ts | 19 ++ js/ffmpeg.worker.js | 5 +- package.json | 12 +- tsconfig.json | 2 +- vite-plugin-blob.ts | 8 +- vite.config.ts | 11 + 13 files changed, 931 insertions(+), 44 deletions(-) create mode 100644 .github/workflows/tests.yml create mode 100644 js/HiFi.test.ts create mode 100644 js/api.test.ts create mode 100644 js/ffmpeg.test.ts diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 0000000..6668a1e --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,41 @@ +name: Run Tests + +on: + push: + branches: ['*'] + pull_request: + branches: ['*'] + workflow_dispatch: + +jobs: + test: + 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: Install playwright dependencies + run: bun run install:playwright + + - name: Run vitest + run: bun run test:headless diff --git a/.gitignore b/.gitignore index 8a825ee..3d5c22d 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,8 @@ dist .env # Neutralino .tmp/ +.vitest-attachments/ +**/__screenshots__/* bin/ *.log .storage/ diff --git a/bun.lock b/bun.lock index 1efe8fd..a66978e 100644 --- a/bun.lock +++ b/bun.lock @@ -17,6 +17,7 @@ "@kawarp/core": "^1.1.1", "@svta/common-media-library": "^0.18.1", "@uimaxbai/am-lyrics": "^1.1.4", + "@vitest/web-worker": "^4.1.2", "appwrite": "^23.0.0", "butterchurn": "^2.6.7", "butterchurn-presets": "^2.4.7", @@ -36,17 +37,21 @@ "svgo": "^4.0.1", "url-toolkit": "^2.2.5", "uuid": "^13.0.0", + "vitest": "^4.1.2", }, "devDependencies": { "@capacitor/assets": "^3.0.5", "@capacitor/cli": "^8.2.0", + "@testing-library/dom": "^10.4.1", "@types/node": "^25.3.5", + "@vitest/browser-playwright": "^4.1.2", "eslint": "^9.39.3", "eslint-config-prettier": "^10.1.8", "formidable": "^3.5.4", "globals": "^17.4.0", "htmlhint": "^1.9.2", "miniflare": "^4.20260301.1", + "playwright": "^1.58.2", "prettier": "^3.8.1", "stylelint": "^16.26.1", "stylelint-config-standard": "^39.0.1", @@ -245,6 +250,8 @@ "@babel/types": ["@babel/types@7.29.0", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A=="], + "@blazediff/core": ["@blazediff/core@1.9.1", "", {}, "sha512-ehg3jIkYKulZh+8om/O25vkvSsXXwC+skXmyA87FFx6A/45eqOkZsBltMw/TVteb0mloiGT8oGRTcjRAz66zaA=="], + "@cacheable/memory": ["@cacheable/memory@2.0.8", "", { "dependencies": { "@cacheable/utils": "^2.4.0", "@keyv/bigmap": "^1.3.1", "hookified": "^1.15.1", "keyv": "^5.6.0" } }, "sha512-FvEb29x5wVwu/Kf93IWwsOOEuhHh6dYCJF3vcKLzXc0KXIW181AOzv6ceT4ZpBHDvAfG60eqb+ekmrnLHIy+jw=="], "@cacheable/utils": ["@cacheable/utils@2.4.1", "", { "dependencies": { "hashery": "^1.5.1", "keyv": "^5.6.0" } }, "sha512-eiFgzCbIneyMlLOmNG4g9xzF7Hv3Mga4LjxjcSC/ues6VYq2+gUbQI8JqNuw/ZM8tJIeIaBGpswAsqV2V7ApgA=="], @@ -481,6 +488,8 @@ "@paralleldrive/cuid2": ["@paralleldrive/cuid2@2.3.1", "", { "dependencies": { "@noble/hashes": "^1.1.5" } }, "sha512-XO7cAxhnTZl0Yggq6jOgjiOHhbgcO4NqFqwSmQpjK3b6TEE6Uj/jfSk6wzYyemh3+I0sHirKSetjQwn5cZktFw=="], + "@polka/url": ["@polka/url@1.0.0-next.29", "", {}, "sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww=="], + "@poppinss/colors": ["@poppinss/colors@4.1.6", "", { "dependencies": { "kleur": "^4.1.5" } }, "sha512-H9xkIdFswbS8n1d6vmRd8+c10t2Qe+rZITbbDHHkQixH5+2x1FDGmi/0K+WgWiqQFKPSlIYB7jlH6Kpfn6Fleg=="], "@poppinss/dumper": ["@poppinss/dumper@0.6.5", "", { "dependencies": { "@poppinss/colors": "^4.1.5", "@sindresorhus/is": "^7.0.2", "supports-color": "^10.0.0" } }, "sha512-NBdYIb90J7LfOI32dOewKI1r7wnkiH6m920puQ3qHUeZkxNkQiFnXVWoE6YtFSv6QOiPPf7ys6i+HWWecDz7sw=="], @@ -553,10 +562,14 @@ "@speed-highlight/core": ["@speed-highlight/core@1.2.15", "", {}, "sha512-BMq1K3DsElxDWawkX6eLg9+CKJrTVGCBAWVuHXVUV2u0s2711qiChLSId6ikYPfxhdYocLNt3wWwSvDiTvFabw=="], + "@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], + "@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=="], "@svta/common-media-library": ["@svta/common-media-library@0.18.1", "", {}, "sha512-VMj1jI8OWphurcozF+dezABUm9Mht6iAsSiKsFUKVT35fddOowvLoGz23Gx6lEHaAHkDy9o/aVi5s9DSp3K15Q=="], + "@testing-library/dom": ["@testing-library/dom@10.4.1", "", { "dependencies": { "@babel/code-frame": "^7.10.4", "@babel/runtime": "^7.12.5", "@types/aria-query": "^5.0.1", "aria-query": "5.3.0", "dom-accessibility-api": "^0.5.9", "lz-string": "^1.5.0", "picocolors": "1.1.1", "pretty-format": "^27.0.2" } }, "sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg=="], + "@trapezedev/gradle-parse": ["@trapezedev/gradle-parse@7.1.3", "", {}, "sha512-WQVF5pEJ5o/mUyvfGTG9nBKx9Te/ilKM3r2IT69GlbaooItT5ao7RyF1MUTBNjHLPk/xpGUY3c6PyVnjDlz0Vw=="], "@trapezedev/project": ["@trapezedev/project@7.1.3", "", { "dependencies": { "@ionic/utils-fs": "^3.1.5", "@ionic/utils-subprocess": "^2.1.8", "@prettier/plugin-xml": "^2.2.0", "@trapezedev/gradle-parse": "7.1.3", "@xmldom/xmldom": "^0.7.5", "conventional-changelog": "^3.1.4", "cross-spawn": "^7.0.3", "diff": "^5.1.0", "env-paths": "^3.0.0", "gradle-to-js": "^2.0.0", "ini": "^2.0.0", "kleur": "^4.1.5", "lodash": "^4.17.21", "mergexml": "^1.2.3", "plist": "^3.0.4", "prettier": "^2.7.1", "prompts": "^2.4.2", "replace": "^1.1.0", "tempy": "^1.0.1", "tmp": "^0.2.1", "ts-node": "^10.2.1", "xcode": "^3.0.1", "xml-js": "^1.6.11", "xpath": "^0.0.32", "yargs": "^17.2.1" } }, "sha512-GANh8Ey73MechZrryfJoILY9hBnWqzS6AdB53zuWBCBbaiImyblXT41fWdN6pB2f5+cNI2FAUxGfVhl+LeEVbQ=="], @@ -569,6 +582,12 @@ "@tsconfig/node16": ["@tsconfig/node16@1.0.4", "", {}, "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA=="], + "@types/aria-query": ["@types/aria-query@5.0.4", "", {}, "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw=="], + + "@types/chai": ["@types/chai@5.2.3", "", { "dependencies": { "@types/deep-eql": "*", "assertion-error": "^2.0.1" } }, "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA=="], + + "@types/deep-eql": ["@types/deep-eql@4.0.2", "", {}, "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw=="], + "@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="], "@types/fs-extra": ["@types/fs-extra@8.1.5", "", { "dependencies": { "@types/node": "*" } }, "sha512-0dzKcwO+S8s2kuF5Z9oUWatQJj5Uq/iqphEtE3GQJVRRYm/tD1LglU2UnXi2A8jLq5umkGouOXOR9y0n613ZwQ=="], @@ -591,6 +610,26 @@ "@uimaxbai/am-lyrics": ["@uimaxbai/am-lyrics@1.1.4", "", { "dependencies": { "@babel/runtime": "^7.27.6", "lit": "^3.1.4" }, "peerDependencies": { "@lit/react": "^1.0.0", "react": ">=17.0.0" }, "optionalPeers": ["@lit/react", "react"] }, "sha512-LEwvbfgz6o71kYTq1vMlfou/powr8q4CJQWuyL2H48Dwo1/vH59SKiB3nz/WOEQ1S69uaSmfqf8Prtx6+ZNIrQ=="], + "@vitest/browser": ["@vitest/browser@4.1.2", "", { "dependencies": { "@blazediff/core": "1.9.1", "@vitest/mocker": "4.1.2", "@vitest/utils": "4.1.2", "magic-string": "^0.30.21", "pngjs": "^7.0.0", "sirv": "^3.0.2", "tinyrainbow": "^3.1.0", "ws": "^8.19.0" }, "peerDependencies": { "vitest": "4.1.2" } }, "sha512-CwdIf90LNf1Zitgqy63ciMAzmyb4oIGs8WZ40VGYrWkssQKeEKr32EzO8MKUrDPPcPVHFI9oQ5ni2Hp24NaNRQ=="], + + "@vitest/browser-playwright": ["@vitest/browser-playwright@4.1.2", "", { "dependencies": { "@vitest/browser": "4.1.2", "@vitest/mocker": "4.1.2", "tinyrainbow": "^3.1.0" }, "peerDependencies": { "playwright": "*", "vitest": "4.1.2" } }, "sha512-N0Z2HzMLvMR6k/tWPTS6Q/DaRscrkax/f2f9DIbNQr+Cd1l4W4wTf/I6S983PAMr0tNqqoTL+xNkLh9M5vbkLg=="], + + "@vitest/expect": ["@vitest/expect@4.1.2", "", { "dependencies": { "@standard-schema/spec": "^1.1.0", "@types/chai": "^5.2.2", "@vitest/spy": "4.1.2", "@vitest/utils": "4.1.2", "chai": "^6.2.2", "tinyrainbow": "^3.1.0" } }, "sha512-gbu+7B0YgUJ2nkdsRJrFFW6X7NTP44WlhiclHniUhxADQJH5Szt9mZ9hWnJPJ8YwOK5zUOSSlSvyzRf0u1DSBQ=="], + + "@vitest/mocker": ["@vitest/mocker@4.1.2", "", { "dependencies": { "@vitest/spy": "4.1.2", "estree-walker": "^3.0.3", "magic-string": "^0.30.21" }, "peerDependencies": { "msw": "^2.4.9", "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" }, "optionalPeers": ["msw", "vite"] }, "sha512-Ize4iQtEALHDttPRCmN+FKqOl2vxTiNUhzobQFFt/BM1lRUTG7zRCLOykG/6Vo4E4hnUdfVLo5/eqKPukcWW7Q=="], + + "@vitest/pretty-format": ["@vitest/pretty-format@4.1.2", "", { "dependencies": { "tinyrainbow": "^3.1.0" } }, "sha512-dwQga8aejqeuB+TvXCMzSQemvV9hNEtDDpgUKDzOmNQayl2OG241PSWeJwKRH3CiC+sESrmoFd49rfnq7T4RnA=="], + + "@vitest/runner": ["@vitest/runner@4.1.2", "", { "dependencies": { "@vitest/utils": "4.1.2", "pathe": "^2.0.3" } }, "sha512-Gr+FQan34CdiYAwpGJmQG8PgkyFVmARK8/xSijia3eTFgVfpcpztWLuP6FttGNfPLJhaZVP/euvujeNYar36OQ=="], + + "@vitest/snapshot": ["@vitest/snapshot@4.1.2", "", { "dependencies": { "@vitest/pretty-format": "4.1.2", "@vitest/utils": "4.1.2", "magic-string": "^0.30.21", "pathe": "^2.0.3" } }, "sha512-g7yfUmxYS4mNxk31qbOYsSt2F4m1E02LFqO53Xpzg3zKMhLAPZAjjfyl9e6z7HrW6LvUdTwAQR3HHfLjpko16A=="], + + "@vitest/spy": ["@vitest/spy@4.1.2", "", {}, "sha512-DU4fBnbVCJGNBwVA6xSToNXrkZNSiw59H8tcuUspVMsBDBST4nfvsPsEHDHGtWRRnqBERBQu7TrTKskmjqTXKA=="], + + "@vitest/utils": ["@vitest/utils@4.1.2", "", { "dependencies": { "@vitest/pretty-format": "4.1.2", "convert-source-map": "^2.0.0", "tinyrainbow": "^3.1.0" } }, "sha512-xw2/TiX82lQHA06cgbqRKFb5lCAy3axQ4H4SoUFhUsg+wztiet+co86IAMDtF6Vm1hc7J6j09oh/rgDn+JdKIQ=="], + + "@vitest/web-worker": ["@vitest/web-worker@4.1.2", "", { "dependencies": { "obug": "^2.1.1" }, "peerDependencies": { "vitest": "4.1.2" } }, "sha512-P5XxkZuiVcN8NGePi53VS3gJUHu3J0luyPPuWBirZB3d6Tjfqy4+AW+3vCQTMr5KnEfZXBmEXWh3o9K58bSoAw=="], + "@xml-tools/parser": ["@xml-tools/parser@1.0.11", "", { "dependencies": { "chevrotain": "7.1.1" } }, "sha512-aKqQ077XnR+oQtHJlrAflaZaL7qZsulWc/i/ZEooar5JiWj1eLt0+Wg28cpa+XLney107wXqneC+oG1IZvxkTA=="], "@xmldom/xmldom": ["@xmldom/xmldom@0.7.13", "", {}, "sha512-lm2GW5PkosIzccsaZIz7tp8cPADSIlIHWDFTR1N0SzfinhhYgeIQjFMz4rYzanCScr3DqQLeomUDArp6MWKm+g=="], @@ -611,7 +650,7 @@ "ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], - "ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], + "ansi-styles": ["ansi-styles@5.2.0", "", {}, "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA=="], "appwrite": ["appwrite@23.0.0", "", { "dependencies": { "json-bigint": "1.0.0" } }, "sha512-K11a597npl3jsnxWKzjw163n4GguH4+/zBCOiU15yc1u+7QF0nP9mxsY4JxKrBU6bmQRtgtMTPv/6YOLSwp/QQ=="], @@ -619,6 +658,8 @@ "argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="], + "aria-query": ["aria-query@5.3.0", "", { "dependencies": { "dequal": "^2.0.3" } }, "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A=="], + "array-buffer-byte-length": ["array-buffer-byte-length@1.0.2", "", { "dependencies": { "call-bound": "^1.0.3", "is-array-buffer": "^3.0.5" } }, "sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw=="], "array-ify": ["array-ify@1.0.0", "", {}, "sha512-c5AMf34bKdvPhQ7tBGhqkgKNUzMr4WUs+WDtC2ZUGOUncbxKMTvqxYctiseW3+L4bA8ec+GcZ6/A/FW4m8ukng=="], @@ -631,6 +672,8 @@ "asap": ["asap@2.0.6", "", {}, "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA=="], + "assertion-error": ["assertion-error@2.0.1", "", {}, "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA=="], + "astral-regex": ["astral-regex@2.0.0", "", {}, "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ=="], "async": ["async@3.2.6", "", {}, "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA=="], @@ -715,6 +758,8 @@ "caniuse-lite": ["caniuse-lite@1.0.30001781", "", {}, "sha512-RdwNCyMsNBftLjW6w01z8bKEvT6e/5tpPVEgtn22TiLGlstHOVecsX2KHFkD5e/vRnIE4EGzpuIODb3mtswtkw=="], + "chai": ["chai@6.2.2", "", {}, "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg=="], + "chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="], "chevrotain": ["chevrotain@7.1.1", "", { "dependencies": { "regexp-to-ast": "0.5.0" } }, "sha512-wy3mC1x4ye+O+QkEinVJkPf5u2vsrDIYW9G7ZuwFl6v/Yu0LwUuT2POsb+NUWApebyxfkQq6+yDfRExbnI5rcw=="], @@ -843,6 +888,8 @@ "depd": ["depd@2.0.0", "", {}, "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw=="], + "dequal": ["dequal@2.0.3", "", {}, "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA=="], + "detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="], "dezalgo": ["dezalgo@1.0.4", "", { "dependencies": { "asap": "^2.0.0", "wrappy": "1" } }, "sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig=="], @@ -851,6 +898,8 @@ "dir-glob": ["dir-glob@3.0.1", "", { "dependencies": { "path-type": "^4.0.0" } }, "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA=="], + "dom-accessibility-api": ["dom-accessibility-api@0.5.16", "", {}, "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg=="], + "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=="], @@ -889,6 +938,8 @@ "es-errors": ["es-errors@1.3.0", "", {}, "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw=="], + "es-module-lexer": ["es-module-lexer@2.0.0", "", {}, "sha512-5POEcUuZybH7IdmGsD8wlf0AI55wMecM9rVBTI/qEAy2c1kTOm3DjFYjrBdI2K3BaJjJYfYFeRtM0t9ssnRuxw=="], + "es-object-atoms": ["es-object-atoms@1.1.1", "", { "dependencies": { "es-errors": "^1.3.0" } }, "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA=="], "es-set-tostringtag": ["es-set-tostringtag@2.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6", "has-tostringtag": "^1.0.2", "hasown": "^2.0.2" } }, "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA=="], @@ -917,7 +968,7 @@ "estraverse": ["estraverse@5.3.0", "", {}, "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA=="], - "estree-walker": ["estree-walker@1.0.1", "", {}, "sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg=="], + "estree-walker": ["estree-walker@3.0.3", "", { "dependencies": { "@types/estree": "^1.0.0" } }, "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g=="], "esutils": ["esutils@2.0.3", "", {}, "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="], @@ -929,6 +980,8 @@ "expand-template": ["expand-template@2.0.3", "", {}, "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg=="], + "expect-type": ["expect-type@1.3.0", "", {}, "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA=="], + "fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="], "fast-fifo": ["fast-fifo@1.3.2", "", {}, "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ=="], @@ -975,7 +1028,7 @@ "fs.realpath": ["fs.realpath@1.0.0", "", {}, "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw=="], - "fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], + "fsevents": ["fsevents@2.3.2", "", { "os": "darwin" }, "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA=="], "function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="], @@ -1241,7 +1294,9 @@ "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=="], + "lz-string": ["lz-string@1.5.0", "", { "bin": { "lz-string": "bin/bin.js" } }, "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ=="], + + "magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="], "make-error": ["make-error@1.3.6", "", {}, "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw=="], @@ -1285,6 +1340,8 @@ "modify-values": ["modify-values@1.0.1", "", {}, "sha512-xV2bxeN6F7oYjZWTe/YPAy6MN2M+sL4u/Rlm2AHCIVGfo2p1yGmBHQ6vHehl4bRTZBdHu3TSkWdYgkwpYzAGSw=="], + "mrmime": ["mrmime@2.0.1", "", {}, "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ=="], + "ms": ["ms@2.1.2", "", {}, "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="], "nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="], @@ -1325,6 +1382,8 @@ "object.assign": ["object.assign@4.1.7", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.3", "define-properties": "^1.2.1", "es-object-atoms": "^1.0.0", "has-symbols": "^1.1.0", "object-keys": "^1.1.1" } }, "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw=="], + "obug": ["obug@2.1.1", "", {}, "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ=="], + "on-headers": ["on-headers@1.1.0", "", {}, "sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A=="], "once": ["once@1.4.0", "", { "dependencies": { "wrappy": "1" } }, "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w=="], @@ -1361,6 +1420,8 @@ "path-type": ["path-type@4.0.0", "", {}, "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw=="], + "pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="], + "pend": ["pend@1.2.0", "", {}, "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg=="], "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="], @@ -1369,8 +1430,14 @@ "pify": ["pify@2.3.0", "", {}, "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog=="], + "playwright": ["playwright@1.58.2", "", { "dependencies": { "playwright-core": "1.58.2" }, "optionalDependencies": { "fsevents": "2.3.2" }, "bin": { "playwright": "cli.js" } }, "sha512-vA30H8Nvkq/cPBnNw4Q8TWz1EJyqgpuinBcHET0YVJVFldr8JDNiU9LaWAE1KqSkRYazuaBhTpB5ZzShOezQ6A=="], + + "playwright-core": ["playwright-core@1.58.2", "", { "bin": { "playwright-core": "cli.js" } }, "sha512-yZkEtftgwS8CsfYo7nm0KE8jsvm6i/PTgVtB8DL726wNf6H2IMsDuxCpJj59KDaxCtSnrWan2AeDqM7JBaultg=="], + "plist": ["plist@3.1.0", "", { "dependencies": { "@xmldom/xmldom": "^0.8.8", "base64-js": "^1.5.1", "xmlbuilder": "^15.1.1" } }, "sha512-uysumyrvkUX0rX/dEVqt8gC3sTBzd4zoWfLeS29nb53imdaXVvLINYXTI2GNqzaMuvacNx4uJQ8+b3zXR0pkgQ=="], + "pngjs": ["pngjs@7.0.0", "", {}, "sha512-LKWqWJRhstyYo9pGvgor/ivk2w94eSjE3RGVuzLGlr3NmD8bf7RcYGze1mNdEHRP6TRP6rMuDHk5t44hnTRyow=="], + "pocketbase": ["pocketbase@0.26.8", "", {}, "sha512-aQ/ewvS7ncvAE8wxoW10iAZu6ElgbeFpBhKPnCfvRovNzm2gW8u/sQNPGN6vNgVEagz44kK//C61oKjfa+7Low=="], "possible-typed-array-names": ["possible-typed-array-names@1.1.0", "", {}, "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg=="], @@ -1397,6 +1464,8 @@ "pretty-bytes": ["pretty-bytes@6.1.1", "", {}, "sha512-mQUvGU6aUFQ+rNvTIAcZuWGRT9a6f6Yrg9bHs4ImKF+HZCEK+plBvnAZYSIQztknZF2qnzNtr6F8s0+IuptdlQ=="], + "pretty-format": ["pretty-format@27.5.1", "", { "dependencies": { "ansi-regex": "^5.0.1", "ansi-styles": "^5.0.0", "react-is": "^17.0.1" } }, "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ=="], + "process-nextick-args": ["process-nextick-args@2.0.1", "", {}, "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag=="], "prompts": ["prompts@2.4.2", "", { "dependencies": { "kleur": "^3.0.3", "sisteransi": "^1.0.5" } }, "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q=="], @@ -1415,6 +1484,8 @@ "rc": ["rc@1.2.8", "", { "dependencies": { "deep-extend": "^0.6.0", "ini": "~1.3.0", "minimist": "^1.2.0", "strip-json-comments": "~2.0.1" }, "bin": { "rc": "./cli.js" } }, "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw=="], + "react-is": ["react-is@17.0.2", "", {}, "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w=="], + "read-pkg": ["read-pkg@3.0.0", "", { "dependencies": { "load-json-file": "^4.0.0", "normalize-package-data": "^2.3.2", "path-type": "^3.0.0" } }, "sha512-BLq/cCO9two+lBgiTYNqD6GdtK8s4NpaWrl6/rCO9w0TUS8oJl7cmToOZfRYllKTISY6nt1U7jQ53brmKqY6BA=="], "read-pkg-up": ["read-pkg-up@3.0.0", "", { "dependencies": { "find-up": "^2.0.0", "read-pkg": "^3.0.0" } }, "sha512-YFzFrVvpC6frF1sz8psoHDBGF7fLPc+llq/8NB43oagqWkx8ar5zYtsTORtOjw9W2RHLpWP+zTWwBvf1bCmcSw=="], @@ -1499,6 +1570,8 @@ "side-channel-weakmap": ["side-channel-weakmap@1.0.2", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.5", "object-inspect": "^1.13.3", "side-channel-map": "^1.0.1" } }, "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A=="], + "siginfo": ["siginfo@2.0.0", "", {}, "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g=="], + "signal-exit": ["signal-exit@3.0.7", "", {}, "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="], "simple-concat": ["simple-concat@1.0.1", "", {}, "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q=="], @@ -1511,6 +1584,8 @@ "simple-swizzle": ["simple-swizzle@0.2.4", "", { "dependencies": { "is-arrayish": "^0.3.1" } }, "sha512-nAu1WFPQSMNr2Zn9PGSZK9AGn4t/y97lEm+MXTtUDwfP0ksAIX4nO+6ruD9Jwut4C49SB1Ws+fbXsm/yScWOHw=="], + "sirv": ["sirv@3.0.2", "", { "dependencies": { "@polka/url": "^1.0.0-next.24", "mrmime": "^2.0.0", "totalist": "^3.0.0" } }, "sha512-2wcC/oGxHis/BoHkkPwldgiPSYcpZK3JU28WoMVv55yHJgcZ8rlXvuG9iZggz+sU1d4bRgIGASwyWqjxu3FM0g=="], + "sisteransi": ["sisteransi@1.0.5", "", {}, "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg=="], "slash": ["slash@3.0.0", "", {}, "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q=="], @@ -1539,6 +1614,10 @@ "split2": ["split2@4.2.0", "", {}, "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg=="], + "stackback": ["stackback@0.0.2", "", {}, "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw=="], + + "std-env": ["std-env@4.0.0", "", {}, "sha512-zUMPtQ/HBY3/50VbpkupYHbRroTRZJPRLvreamgErJVys0ceuzMkD44J/QjqhHjOzK42GQ3QZIeFG1OYfOtKqQ=="], + "stop-iteration-iterator": ["stop-iteration-iterator@1.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "internal-slot": "^1.1.0" } }, "sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ=="], "stream-buffers": ["stream-buffers@2.2.0", "", {}, "sha512-uyQK/mx5QjHun80FLJTfaWE7JtwfRMKBLkMne6udYOmvH0CawotVa7TfgYHzAnpphn4+TweIx1QKMnRIbipmUg=="], @@ -1615,12 +1694,20 @@ "through2": ["through2@4.0.2", "", { "dependencies": { "readable-stream": "3" } }, "sha512-iOqSav00cVxEEICeD7TjLB1sueEL+81Wpzp2bY17uZjZN0pWZPuo4suZ/61VujxmqSGFfgOcNuTZ85QJwNZQpw=="], + "tinybench": ["tinybench@2.9.0", "", {}, "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg=="], + + "tinyexec": ["tinyexec@1.0.4", "", {}, "sha512-u9r3uZC0bdpGOXtlxUIdwf9pkmvhqJdrVCH9fapQtgy/OeTTMZ1nqH7agtvEfmGui6e1XxjcdrlxvxJvc3sMqw=="], + "tinyglobby": ["tinyglobby@0.2.15", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.3" } }, "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ=="], + "tinyrainbow": ["tinyrainbow@3.1.0", "", {}, "sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw=="], + "tmp": ["tmp@0.2.5", "", {}, "sha512-voyz6MApa1rQGUxT3E+BK7/ROe8itEx7vD8/HEvt4xwXucvQ5G5oeEiHkmHZJuBO21RpOf+YYm9MOivj709jow=="], "to-regex-range": ["to-regex-range@5.0.1", "", { "dependencies": { "is-number": "^7.0.0" } }, "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ=="], + "totalist": ["totalist@3.0.1", "", {}, "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ=="], + "tr46": ["tr46@0.0.3", "", {}, "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="], "tree-kill": ["tree-kill@1.2.2", "", { "bin": { "tree-kill": "cli.js" } }, "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A=="], @@ -1693,6 +1780,8 @@ "vite-plugin-pwa": ["vite-plugin-pwa@1.2.0", "", { "dependencies": { "debug": "^4.3.6", "pretty-bytes": "^6.1.1", "tinyglobby": "^0.2.10", "workbox-build": "^7.4.0", "workbox-window": "^7.4.0" }, "peerDependencies": { "@vite-pwa/assets-generator": "^1.0.0", "vite": "^3.1.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" }, "optionalPeers": ["@vite-pwa/assets-generator"] }, "sha512-a2xld+SJshT9Lgcv8Ji4+srFJL4k/1bVbd1x06JIkvecpQkwkvCncD1+gSzcdm3s+owWLpMJerG3aN5jupJEVw=="], + "vitest": ["vitest@4.1.2", "", { "dependencies": { "@vitest/expect": "4.1.2", "@vitest/mocker": "4.1.2", "@vitest/pretty-format": "4.1.2", "@vitest/runner": "4.1.2", "@vitest/snapshot": "4.1.2", "@vitest/spy": "4.1.2", "@vitest/utils": "4.1.2", "es-module-lexer": "^2.0.0", "expect-type": "^1.3.0", "magic-string": "^0.30.21", "obug": "^2.1.1", "pathe": "^2.0.3", "picomatch": "^4.0.3", "std-env": "^4.0.0-rc.1", "tinybench": "^2.9.0", "tinyexec": "^1.0.2", "tinyglobby": "^0.2.15", "tinyrainbow": "^3.1.0", "vite": "^6.0.0 || ^7.0.0 || ^8.0.0", "why-is-node-running": "^2.3.0" }, "peerDependencies": { "@edge-runtime/vm": "*", "@opentelemetry/api": "^1.9.0", "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", "@vitest/browser-playwright": "4.1.2", "@vitest/browser-preview": "4.1.2", "@vitest/browser-webdriverio": "4.1.2", "@vitest/ui": "4.1.2", "happy-dom": "*", "jsdom": "*" }, "optionalPeers": ["@edge-runtime/vm", "@opentelemetry/api", "@types/node", "@vitest/browser-playwright", "@vitest/browser-preview", "@vitest/browser-webdriverio", "@vitest/ui", "happy-dom", "jsdom"], "bin": { "vitest": "vitest.mjs" } }, "sha512-xjR1dMTVHlFLh98JE3i/f/WePqJsah4A0FK9cc8Ehp9Udk0AZk6ccpIZhh1qJ/yxVWRZ+Q54ocnD8TXmkhspGg=="], + "webidl-conversions": ["webidl-conversions@3.0.1", "", {}, "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="], "whatwg-url": ["whatwg-url@5.0.0", "", { "dependencies": { "tr46": "~0.0.3", "webidl-conversions": "^3.0.0" } }, "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw=="], @@ -1709,6 +1798,8 @@ "which-typed-array": ["which-typed-array@1.1.20", "", { "dependencies": { "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.8", "call-bound": "^1.0.4", "for-each": "^0.3.5", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-tostringtag": "^1.0.2" } }, "sha512-LYfpUkmqwl0h9A2HL09Mms427Q1RZWuOHsukfVcKRq9q95iQxdw0ix1JQrqbcDR9PH1QDwf5Qo8OZb5lksZ8Xg=="], + "why-is-node-running": ["why-is-node-running@2.3.0", "", { "dependencies": { "siginfo": "^2.0.0", "stackback": "0.0.2" }, "bin": { "why-is-node-running": "cli.js" } }, "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w=="], + "word-wrap": ["word-wrap@1.2.5", "", {}, "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA=="], "wordwrap": ["wordwrap@1.0.0", "", {}, "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q=="], @@ -1881,26 +1972,36 @@ "@rollup/plugin-node-resolve/@rollup/pluginutils": ["@rollup/pluginutils@5.3.0", "", { "dependencies": { "@types/estree": "^1.0.0", "estree-walker": "^2.0.2", "picomatch": "^4.0.2" }, "peerDependencies": { "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" }, "optionalPeers": ["rollup"] }, "sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q=="], + "@rollup/plugin-replace/magic-string": ["magic-string@0.25.9", "", { "dependencies": { "sourcemap-codec": "^1.4.8" } }, "sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ=="], + "@rollup/plugin-replace/rollup": ["rollup@2.80.0", "", { "optionalDependencies": { "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-cIFJOD1DESzpjOBl763Kp1AH7UE/0fcdHe6rZXUdQ9c50uvgigvW97u3IcSeBwOkgqL/PXPBktBCh0KEu5L8XQ=="], "@rollup/pluginutils/@types/estree": ["@types/estree@0.0.39", "", {}, "sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw=="], + "@rollup/pluginutils/estree-walker": ["estree-walker@1.0.1", "", {}, "sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg=="], + "@rollup/pluginutils/picomatch": ["picomatch@2.3.2", "", {}, "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA=="], "@rollup/pluginutils/rollup": ["rollup@2.80.0", "", { "optionalDependencies": { "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-cIFJOD1DESzpjOBl763Kp1AH7UE/0fcdHe6rZXUdQ9c50uvgigvW97u3IcSeBwOkgqL/PXPBktBCh0KEu5L8XQ=="], + "@surma/rollup-plugin-off-main-thread/magic-string": ["magic-string@0.25.9", "", { "dependencies": { "sourcemap-codec": "^1.4.8" } }, "sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ=="], + "@trapezedev/project/@ionic/utils-subprocess": ["@ionic/utils-subprocess@2.1.14", "", { "dependencies": { "@ionic/utils-array": "2.1.6", "@ionic/utils-fs": "3.1.7", "@ionic/utils-process": "2.1.11", "@ionic/utils-stream": "3.1.6", "@ionic/utils-terminal": "2.3.4", "cross-spawn": "^7.0.3", "debug": "^4.0.0", "tslib": "^2.0.1" } }, "sha512-nGYvyGVjU0kjPUcSRFr4ROTraT3w/7r502f5QJEsMRKTqa4eEzCshtwRk+/mpASm0kgBN5rrjYA5A/OZg8ahqg=="], "@trapezedev/project/env-paths": ["env-paths@3.0.0", "", {}, "sha512-dtJUTepzMW3Lm/NPxRf3wP4642UWhjL2sQxc+ym2YMj1m/H2zDNQOlezafzkHwn6sMstjHTwG6iQQsctDW/b1A=="], "@trapezedev/project/prettier": ["prettier@2.8.8", "", { "bin": { "prettier": "bin-prettier.js" } }, "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q=="], + "@vitest/browser/ws": ["ws@8.20.0", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA=="], + "babel-plugin-polyfill-corejs2/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], "brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], "cacheable/keyv": ["keyv@5.6.0", "", { "dependencies": { "@keyv/serialize": "^1.1.1" } }, "sha512-CYDD3SOtsHtyXeEORYRx2qBtpDJFjRTGXUtmNEMGyzYOKj1TE3tycdlho7kA1Ufx9OYWZzg52QFBGALTirzDSw=="], + "chalk/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], + "conventional-changelog-writer/meow": ["meow@8.1.2", "", { "dependencies": { "@types/minimist": "^1.2.0", "camelcase-keys": "^6.2.2", "decamelize-keys": "^1.1.0", "hard-rejection": "^2.1.0", "minimist-options": "4.1.0", "normalize-package-data": "^3.0.0", "read-pkg-up": "^7.0.1", "redent": "^3.0.0", "trim-newlines": "^3.0.0", "type-fest": "^0.18.0", "yargs-parser": "^20.2.3" } }, "sha512-r85E3NdZ+mpYk1C6RjPFEMSE+s1iZMuHtsHAqY0DT3jZczl0diWUZ8g6oU7h0M9cD2EL+PzaYghhCLzR0ZNn5Q=="], "conventional-changelog-writer/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], @@ -2281,10 +2382,14 @@ "replace/yargs": ["yargs@15.4.1", "", { "dependencies": { "cliui": "^6.0.0", "decamelize": "^1.2.0", "find-up": "^4.1.0", "get-caller-file": "^2.0.1", "require-directory": "^2.1.1", "require-main-filename": "^2.0.0", "set-blocking": "^2.0.0", "string-width": "^4.2.0", "which-module": "^2.0.0", "y18n": "^4.0.0", "yargs-parser": "^18.1.2" } }, "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A=="], + "rollup/fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], + "simple-plist/bplist-parser": ["bplist-parser@0.3.1", "", { "dependencies": { "big-integer": "1.6.x" } }, "sha512-PyJxiNtA5T2PlLIeBot4lbp7rj4OadzjnMZD/G5zuBNt8ei/yCU7+wW0h2bag9vr8c+/WuRWmSxbqAl9hL1rBA=="], "simple-swizzle/is-arrayish": ["is-arrayish@0.3.4", "", {}, "sha512-m6UrgzFVUYawGBh1dUsWR5M2Clqic9RVXC/9f8ceNlv2IcO9j9J/z8UoCLPqtsPBFNzEpfR3xftohbfqDx8EQA=="], + "slice-ansi/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], + "stylelint/debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], "stylelint/file-entry-cache": ["file-entry-cache@11.1.2", "", { "dependencies": { "flat-cache": "^6.1.20" } }, "sha512-N2WFfK12gmrK1c1GXOqiAJ1tc5YE+R53zvQ+t5P8S5XhnmKYVB5eZEiLNZKDSmoG8wqqbF9EXYBBW/nef19log=="], @@ -2299,6 +2404,8 @@ "ts-node/diff": ["diff@4.0.4", "", {}, "sha512-X07nttJQkwkfKfvTPG/KSnE2OMdcUCao6+eXF3wmnIQRn2aPAHH3VxDbDOdegkd6JbPsXqShpvEOHfAT+nCNwQ=="], + "vite/fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], + "vite-plugin-pwa/debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], "workbox-build/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=="], @@ -2313,6 +2420,8 @@ "workbox-build/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=="], + "wrap-ansi/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], + "write-file-atomic/signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="], "xcode/uuid": ["uuid@7.0.3", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-DPSke0pXhTZgoF/d+WSt2QaKMCFSfx7QegxEWT+JOuHF5aWrKEn0G+ztjuJg/gG8/ItK+rbPCD/yNv8yyih6Cg=="], @@ -2365,8 +2474,14 @@ "@ionic/utils-terminal/debug/ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], + "@rollup/plugin-babel/rollup/fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], + "@rollup/plugin-node-resolve/@rollup/pluginutils/estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="], + "@rollup/plugin-replace/rollup/fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], + + "@rollup/pluginutils/rollup/fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], + "@trapezedev/project/@ionic/utils-subprocess/@ionic/utils-process": ["@ionic/utils-process@2.1.11", "", { "dependencies": { "@ionic/utils-object": "2.1.6", "@ionic/utils-terminal": "2.3.4", "debug": "^4.0.0", "signal-exit": "^3.0.3", "tree-kill": "^1.2.2", "tslib": "^2.0.1" } }, "sha512-Uavxn+x8j3rDlZEk1X7YnaN6wCgbCwYQOeIjv/m94i1dzslqWhqIHEqxEyeE8HsT5Negboagg7GtQiABy+BLbA=="], "@trapezedev/project/@ionic/utils-subprocess/@ionic/utils-stream": ["@ionic/utils-stream@3.1.6", "", { "dependencies": { "debug": "^4.0.0", "tslib": "^2.0.1" } }, "sha512-4+Kitey1lTA1yGtnigeYNhV/0tggI3lWBMjC7tBs1K9GXa/q7q4CtOISppdh8QgtOhrhAXS2Igp8rbko/Cj+lA=="], @@ -2471,6 +2586,8 @@ "workbox-build/glob/minimatch": ["minimatch@10.2.4", "", { "dependencies": { "brace-expansion": "^5.0.2" } }, "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg=="], + "workbox-build/rollup/fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], + "@capacitor/assets/@capacitor/cli/@ionic/utils-subprocess/@ionic/utils-process": ["@ionic/utils-process@2.1.11", "", { "dependencies": { "@ionic/utils-object": "2.1.6", "@ionic/utils-terminal": "2.3.4", "debug": "^4.0.0", "signal-exit": "^3.0.3", "tree-kill": "^1.2.2", "tslib": "^2.0.1" } }, "sha512-Uavxn+x8j3rDlZEk1X7YnaN6wCgbCwYQOeIjv/m94i1dzslqWhqIHEqxEyeE8HsT5Negboagg7GtQiABy+BLbA=="], "@capacitor/assets/@capacitor/cli/@ionic/utils-subprocess/@ionic/utils-stream": ["@ionic/utils-stream@3.1.6", "", { "dependencies": { "debug": "^4.0.0", "tslib": "^2.0.1" } }, "sha512-4+Kitey1lTA1yGtnigeYNhV/0tggI3lWBMjC7tBs1K9GXa/q7q4CtOISppdh8QgtOhrhAXS2Igp8rbko/Cj+lA=="], @@ -2585,6 +2702,8 @@ "replace/chalk/ansi-styles/color-convert/color-name": ["color-name@1.1.3", "", {}, "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw=="], + "replace/yargs/cliui/wrap-ansi/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], + "replace/yargs/find-up/locate-path/p-locate": ["p-locate@4.1.0", "", { "dependencies": { "p-limit": "^2.2.0" } }, "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A=="], "workbox-build/glob/minimatch/brace-expansion/balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="], diff --git a/js/HiFi.test.ts b/js/HiFi.test.ts new file mode 100644 index 0000000..7cf99f9 --- /dev/null +++ b/js/HiFi.test.ts @@ -0,0 +1,199 @@ +import { expect, suite, test } from 'vitest'; +import { HiFiClient, TidalResponse } from './HiFi'; + +const ARTIST_ID = 3523908; // deadmau5 +const ALBUM_ID = 433360012; // deadmau5 - 4x4=12 +const ALBUM_ATMOS = 463900719; // Taylor Swift - The Life of a Showgirl +const TRACK_ATMOS = 463900720; // Taylor Swift - The Fate of Ophelia +const TRACK_NO_LOSSLESS = 31097959; // deadmau5 - while(1<2) +const TRACK_VIDEO = 466464180; // Taylow Swift - The Fate of Ophelia +const TRACK_LOSSLESS = 31097949; // deadmau5 - Avaritia +const PLAYLIST_ID = '36ea71a8-445e-41a4-82ab-6628c581535d'; // Pop Hits + +const instance = new HiFiClient(); +await instance.fetchToken(); + +function checkVersion({ version }: { version?: string }) { + expect(version).toBeTypeOf('string'); + expect(version).not.equals(''); + expect(version).equals(HiFiClient.API_VERSION); +} + +async function getJson(res: Response | Promise) { + res = await res; + expect(res).toBeInstanceOf(Response); + expect(res.ok).toBeTruthy(); + return await res.json(); +} + +async function checkRoute( + route: string, + routeResult: () => Promise, + checks: (data: any) => Promise, + mainKey: string | null = 'data' +) { + const routeData = await instance.query(route); + const routeRes = await routeResult(); + expect(routeData).toBeInstanceOf(TidalResponse); + expect(routeData).toEqual(routeRes); + + const json = await routeData.json(); + checkVersion(json); + + if (mainKey != null) { + expect(json).toHaveProperty(mainKey); + expect(json[mainKey]).not.toBeUndefined(); + } + + await checks(json); +} + +test('Get token', async () => { + const instance = new HiFiClient(); + + const token = await instance.fetchToken(); + expect(token).toBeTypeOf('string'); + expect(token).not.toBeUndefined(); + expect(token).not.length(0); + expect(token).equals(instance.token); + + const token2 = await instance.fetchToken(true); + expect(token2).toBeTypeOf('string'); + expect(token2).not.toBeUndefined(); + expect(token2).not.length(0); + expect(token2).equals(instance.token); + expect(token2).not.equals(token); + + expect(instance.appTokenExpiry).toBeGreaterThan(Date.now()); +}); + +test('Fetch atmos track info', async () => { + await checkRoute( + `/info/?id=${TRACK_ATMOS}`, + () => instance.getInfo(TRACK_ATMOS), + async (info) => { + expect(info.data.audioModes).toContain('DOLBY_ATMOS'); + } + ); +}); + +test('Fetch track', async () => { + await checkRoute( + `/track/?id=${TRACK_LOSSLESS}`, + () => instance.getTrack(TRACK_LOSSLESS), + async (track) => { + expect(track.data.trackId).toBe(TRACK_LOSSLESS); + expect(track.data.assetPresentation).toBeTypeOf('string'); + expect(track.data.audioQuality).toBeTypeOf('string'); + expect(track.data.manifestMimeType).toBeTypeOf('string'); + expect(track.data.manifestHash).toBeTypeOf('string'); + expect(track.data.manifest).toBeTypeOf('string'); + expect(track.data.albumReplayGain).toBeTypeOf('number'); + expect(track.data.albumPeakAmplitude).toBeTypeOf('number'); + expect(track.data.trackReplayGain).toBeTypeOf('number'); + expect(track.data.trackPeakAmplitude).toBeTypeOf('number'); + expect(track.data.bitDepth).toBeTypeOf('number'); + expect(track.data.sampleRate).toBeTypeOf('number'); + } + ); +}); + +test.skipIf(!instance.refreshToken)('Fetch recommendations', async () => { + await checkRoute( + `/recommendations/?id=${ARTIST_ID}`, + () => instance.getRecommendations(ARTIST_ID), + async (rec) => {} + ); +}); + +test('Fetch similar artists', async () => { + await checkRoute( + `/artist/similar/?id=${ARTIST_ID}`, + () => instance.getSimilarArtists(ARTIST_ID), + async (rec) => {}, + 'artists' + ); +}); + +test('Fetch similar albums', async () => { + await checkRoute( + `/album/similar/?id=${ALBUM_ID}`, + () => instance.getSimilarAlbums(ALBUM_ID), + async (rec) => {}, + 'albums' + ); +}); + +test('Fetch artist info', async () => { + await checkRoute( + `/artist/?id=${ARTIST_ID}`, + () => instance.getArtist(ARTIST_ID), + async (info) => { + expect(info).toHaveProperty('cover'); + expect(info.cover).not.toBeUndefined(); + }, + 'artist' + ); +}); + +test('Search', async () => { + const query = 'deadmau5'; + await checkRoute( + `/search/?q=${encodeURIComponent(query)}`, + () => + instance.search({ + q: query, + }), + async (res) => {} + ); +}); + +test('Fetch album info', async () => { + await checkRoute( + `/album/?id=${ALBUM_ID}`, + () => instance.getAlbum(ALBUM_ID), + async (info) => { + expect(info.data).toHaveProperty('cover'); + expect(info.data.cover).not.toBeUndefined(); + } + ); +}); + +test('Fetch playlist info', async () => { + await checkRoute( + `/playlist/?id=${PLAYLIST_ID}`, + () => instance.getPlaylist(PLAYLIST_ID), + async (info) => { + expect(info.playlist).toHaveProperty('image'); + expect(info.playlist.image).not.toBeUndefined(); + }, + 'playlist' + ); +}); + +test.skipIf(!instance.refreshToken)('Fetch lyrics ', async () => { + await checkRoute( + `/lyrics/?id=${TRACK_ATMOS}`, + () => instance.getLyrics(TRACK_ATMOS), + async (info) => {}, + 'lyrics' + ); +}); + +test('Fetch video ', async () => { + await checkRoute( + `/video/?id=${TRACK_VIDEO}`, + () => instance.getVideo(TRACK_VIDEO), + async (info) => {}, + 'video' + ); +}); + +test('Fetch track manifests ', async () => { + await checkRoute( + `/trackManifests/?id=${TRACK_LOSSLESS}`, + () => instance.getTrackManifest(TRACK_LOSSLESS), + async (info) => {}, + 'data' + ); +}); diff --git a/js/HiFi.ts b/js/HiFi.ts index 85dced6..d87a7ae 100644 --- a/js/HiFi.ts +++ b/js/HiFi.ts @@ -1,9 +1,5 @@ import { EventEmitter } from 'events'; -const API_VERSION = '2.7'; -const BROWSER_CLIENT_ID = 'txNoH4kkV41MfH25'; -const BROWSER_CLIENT_SECRET = 'dQjy0MinCEvxi1O4UmxvxWnDjt4cgHBPw8ll6nYBk98='; - type Params = Record; class ResponseError extends Error { @@ -37,6 +33,10 @@ export enum HiFiClientEvents { } class HiFiClient { + static readonly API_VERSION = '2.7'; + static readonly BROWSER_CLIENT_ID = 'txNoH4kkV41MfH25'; + static readonly BROWSER_CLIENT_SECRET = 'dQjy0MinCEvxi1O4UmxvxWnDjt4cgHBPw8ll6nYBk98='; + static #instance: HiFiClient | null = null; static get instance() { if (!HiFiClient.#instance) { @@ -158,8 +158,8 @@ class HiFiClient { } async #fetchAppToken({ - clientId = BROWSER_CLIENT_ID, - clientSecret = BROWSER_CLIENT_SECRET, + clientId = HiFiClient.BROWSER_CLIENT_ID, + clientSecret = HiFiClient.BROWSER_CLIENT_SECRET, refreshToken, scope = 'r_usr+w_usr+w_sub', signal = new AbortController().signal, @@ -218,8 +218,8 @@ class HiFiClient { static #getOptions({ countryCode = 'US', baseUrl = null, - clientId = BROWSER_CLIENT_ID, - clientSecret = BROWSER_CLIENT_SECRET, + clientId = HiFiClient.BROWSER_CLIENT_ID, + clientSecret = HiFiClient.BROWSER_CLIENT_SECRET, token, tokenExpiry, refreshToken: tokenRefresh, @@ -228,6 +228,16 @@ class HiFiClient { return { countryCode, baseUrl, clientId, clientSecret, token, tokenExpiry, tokenRefresh, storage }; } + async fetchToken(force: boolean = false, signal: AbortSignal | undefined = undefined) { + return await this.#fetchAppToken({ + clientId: this.#clientId, + clientSecret: this.#clientSecret, + signal, + refreshToken: this.refreshToken || undefined, + force: !!force, + }); + } + async #fetchAuthenticated( url: string, params?: Params | URLSearchParams, @@ -334,7 +344,7 @@ class HiFiClient { 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 HiFiClient.#jsonResponse({ version: API_VERSION, data }); + return HiFiClient.#jsonResponse({ version: HiFiClient.API_VERSION, data }); } async getTrack(id: number, quality = 'HI_RES_LOSSLESS', immersiveAudio: boolean = false, signal?: AbortSignal) { @@ -347,7 +357,7 @@ class HiFiClient { immersiveAudio: String(immersiveAudio), }; const data = await this.#fetchJson(url, params, signal); - return HiFiClient.#jsonResponse({ version: API_VERSION, data }); + return HiFiClient.#jsonResponse({ version: HiFiClient.API_VERSION, data }); } async getTrackManifest( @@ -382,7 +392,7 @@ class HiFiClient { drmData.certificateUrl = url; } - return HiFiClient.#jsonResponse({ version: API_VERSION, data: res }); + return HiFiClient.#jsonResponse({ version: HiFiClient.API_VERSION, data: res }); } async getWidevine() { @@ -392,7 +402,7 @@ class HiFiClient { 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 HiFiClient.#jsonResponse({ version: API_VERSION, data }); + return HiFiClient.#jsonResponse({ version: HiFiClient.API_VERSION, data }); } async getSimilarArtists(id: number, cursor?: string | number | null, signal?: AbortSignal) { @@ -436,7 +446,10 @@ class HiFiClient { }; }; - return HiFiClient.#jsonResponse({ version: API_VERSION, artists: (payload?.data || []).map(resolveArtist) }); + return HiFiClient.#jsonResponse({ + version: HiFiClient.API_VERSION, + artists: (payload?.data || []).map(resolveArtist), + }); } async getSimilarAlbums(id: number, cursor?: string | number | null, signal?: AbortSignal) { @@ -497,7 +510,10 @@ class HiFiClient { }; }; - return HiFiClient.#jsonResponse({ version: API_VERSION, albums: (payload?.data || []).map(resolveAlbum) }); + return HiFiClient.#jsonResponse({ + version: HiFiClient.API_VERSION, + albums: (payload?.data || []).map(resolveAlbum), + }); } async getArtist( @@ -530,7 +546,7 @@ class HiFiClient { }; } - return HiFiClient.#jsonResponse({ version: API_VERSION, artist: artist_data, cover }); + return HiFiClient.#jsonResponse({ version: HiFiClient.API_VERSION, artist: artist_data, cover }); } // f provided -> gather albums and optionally tracks @@ -584,10 +600,11 @@ class HiFiClient { } } - return HiFiClient.#jsonResponse({ version: API_VERSION, albums: page_data, tracks: top_tracks }); + return HiFiClient.#jsonResponse({ version: HiFiClient.API_VERSION, albums: page_data, tracks: top_tracks }); } - if (!album_ids.length) return HiFiClient.#jsonResponse({ version: API_VERSION, albums: page_data, tracks: [] }); + if (!album_ids.length) + return HiFiClient.#jsonResponse({ version: HiFiClient.API_VERSION, albums: page_data, tracks: [] }); const fetchAlbumTracks = async (album_id: number) => { return await this.#withAlbumTrackSlot(async () => { @@ -613,7 +630,7 @@ class HiFiClient { if (Array.isArray(t)) tracks.push(...t); } - return HiFiClient.#jsonResponse({ version: API_VERSION, albums: page_data, tracks }); + return HiFiClient.#jsonResponse({ version: HiFiClient.API_VERSION, albums: page_data, tracks }); } #buildCoverEntry(cover_slug: string, name?: string | null, track_id?: number | null) { @@ -640,7 +657,7 @@ class HiFiClient { 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 HiFiClient.#jsonResponse({ version: API_VERSION, covers: [entry] }); + return HiFiClient.#jsonResponse({ version: HiFiClient.API_VERSION, covers: [entry] }); } const search_data = await this.#fetchJson( @@ -658,7 +675,7 @@ class HiFiClient { covers.push(this.#buildCoverEntry(cover_slug, track.title, track.id)); } if (!covers.length) throw new ResponseError(404, 'Cover not found'); - return HiFiClient.#jsonResponse({ version: API_VERSION, covers }); + return HiFiClient.#jsonResponse({ version: HiFiClient.API_VERSION, covers }); } async search( @@ -690,7 +707,7 @@ class HiFiClient { }, signal ); - return HiFiClient.#jsonResponse({ version: API_VERSION, data: res }); + return HiFiClient.#jsonResponse({ version: HiFiClient.API_VERSION, data: res }); } catch (err: any) { if (err.status && ![400, 404].includes(err.status)) throw err; // fallback to text search @@ -705,7 +722,7 @@ class HiFiClient { }, signal ); - return HiFiClient.#jsonResponse({ version: API_VERSION, data: fallback }); + return HiFiClient.#jsonResponse({ version: HiFiClient.API_VERSION, data: fallback }); } const mapping: Array<[string | undefined, string, Params]> = [ @@ -746,7 +763,7 @@ class HiFiClient { for (const [val, url, params] of mapping) { if (val) { const data = await this.#fetchJson(url, params, signal); - return HiFiClient.#jsonResponse({ version: API_VERSION, data }); + return HiFiClient.#jsonResponse({ version: HiFiClient.API_VERSION, data }); } } @@ -783,7 +800,7 @@ class HiFiClient { if (Array.isArray(pageItems)) allItems.push(...pageItems); } albumData.items = allItems; - return HiFiClient.#jsonResponse({ version: API_VERSION, data: albumData }); + return HiFiClient.#jsonResponse({ version: HiFiClient.API_VERSION, data: albumData }); } async getMix(id: string, signal?: AbortSignal) { @@ -803,7 +820,7 @@ class HiFiClient { } } return HiFiClient.#jsonResponse({ - version: API_VERSION, + version: HiFiClient.API_VERSION, mix: header, items: items.map((it: any) => (it.item ? it.item : it)), }); @@ -817,7 +834,7 @@ class HiFiClient { this.#fetchJson(itemsUrl, { countryCode: this.#countryCode, limit, offset }, signal), ]); const items = (itemsData && itemsData.items) || itemsData; - return HiFiClient.#jsonResponse({ version: API_VERSION, playlist: playlistData, items }); + return HiFiClient.#jsonResponse({ version: HiFiClient.API_VERSION, playlist: playlistData, items }); } // simplified artist/cover/lyrics/video/topvideos/similar methods (same pattern) @@ -833,7 +850,7 @@ class HiFiClient { err.status = 404; throw err; } - return HiFiClient.#jsonResponse({ version: API_VERSION, lyrics: data }); + return HiFiClient.#jsonResponse({ version: HiFiClient.API_VERSION, lyrics: data }); } async getVideo(id: number, quality = 'HIGH', mode = 'STREAM', presentation = 'FULL', signal?: AbortSignal) { @@ -843,7 +860,7 @@ class HiFiClient { { videoquality: quality, playbackmode: mode, assetpresentation: presentation }, signal ); - return HiFiClient.#jsonResponse({ version: API_VERSION, video: data }); + return HiFiClient.#jsonResponse({ version: HiFiClient.API_VERSION, video: data }); } async getTopVideos( @@ -867,7 +884,7 @@ class HiFiClient { } } return HiFiClient.#jsonResponse({ - version: API_VERSION, + version: HiFiClient.API_VERSION, videos: videos.slice(offset, offset + limit), total: videos.length, }); @@ -886,7 +903,10 @@ class HiFiClient { switch (pathname) { case '/': return new TidalResponse( - HiFiClient.#jsonResponse({ version: API_VERSION, Repo: 'https://github.com/binimum/hifi-api' }) + HiFiClient.#jsonResponse({ + version: HiFiClient.API_VERSION, + Repo: 'https://github.com/binimum/hifi-api', + }) ); case '/info': return new TidalResponse(await this.getInfo(Number(qp.id))); diff --git a/js/api.test.ts b/js/api.test.ts new file mode 100644 index 0000000..2320b5f --- /dev/null +++ b/js/api.test.ts @@ -0,0 +1,457 @@ +import { expect, test, suite, vi } from 'vitest'; +import { apiSettings, preferDolbyAtmosSettings, losslessContainerSettings } from './storage.js'; +import { MusicAPI } from './music-api.js'; +import { LyricsManager } from './lyrics.js'; +import type { LosslessAPI } from './api.js'; +import { HiFiClient } from './HiFi.js'; +import { FileRef } from '!/@dantheman827/taglib-ts/src/fileRef.js'; +import { Mp4File } from '!/@dantheman827/taglib-ts/src/mp4/mp4File.js'; +import { MpegFile } from '!/@dantheman827/taglib-ts/src/mpeg/mpegFile.js'; +import { FlacFile } from '!/@dantheman827/taglib-ts/src/flac/flacFile.js'; +import { Mp4Atom, Mp4Atoms } from '!/@dantheman827/taglib-ts/src/mp4/mp4Atoms.js'; +import { ByteVector, StringType } from '!/@dantheman827/taglib-ts/src/byteVector.js'; +import { Mp4Codec } from '!/@dantheman827/taglib-ts/src/mp4/mp4Properties.js'; +import { OggFile } from '!/@dantheman827/taglib-ts/src/ogg/oggFile.js'; +import { ffmpeg } from './ffmpeg.js'; + +vi.mock(import('./storage.js'), async (importOriginal) => { + const mod = await importOriginal(); + + return { + ...mod, + preferDolbyAtmosSettings: { + ...mod.preferDolbyAtmosSettings, + isEnabled: vi.fn(), + setEnabled: vi.fn(), + }, + losslessContainerSettings: { + ...mod.losslessContainerSettings, + getContainer: vi.fn(), + setContainer: vi.fn(), + }, + }; +}); + +vi.mock(import('./ffmpeg.js'), async (importOriginal) => { + const mod = await importOriginal(); + + return { + ...mod, + ffmpeg: vi.fn(mod.ffmpeg), + }; +}); + +vi.mock(import('./doTimed.js'), async (importOriginal) => { + const mod = await importOriginal(); + + return { + ...mod, + doTimed: (label: string, fn: () => any) => { + return fn() as any; + }, + doTimedAsync ? Promise : T>( + message: string, + callback: () => R, + throwError: boolean = false + ): R { + return new Promise(async (resolve, reject) => { + try { + const ret = await callback(); + resolve(ret); + } catch (err) { + if (throwError) { + reject(err); + return; + } + + resolve(undefined); + } + }) as R; + }, + } satisfies typeof import('./doTimed.js'); +}); + +vi.spyOn(console, 'error').mockImplementation(() => {}); +vi.spyOn(console, 'log').mockImplementation(() => {}); +vi.spyOn(console, 'warn').mockImplementation(() => {}); + +enum Detection { + DolbyAtmos, + FlacHD, + FlacLossless, + AlacHD, + AlacLossless, + Mp4Flac, + AacLow, + AacReallyLow, + AacHigh, + AAC_256, + MP3_320, + MP3_256, + MP3_128, + OGG_320, + OGG_256, + OGG_128, +} + +suite('Track Downloads', async () => { + const SILENCE_TRACK = 46022548; + const TRACK_ATMOS = 463900720; // Taylor Swift - The Fate of Ophelia + const TRACK_NO_LOSSLESS = 31097959; // deadmau5 - while(1<2) + + const { LosslessAPI } = await import('./api.js'); + await MusicAPI.initialize(apiSettings); + await LyricsManager.initialize(apiSettings); + await HiFiClient.initialize(); + + const api: LosslessAPI = MusicAPI.instance.tidalAPI; + + async function downloadTrack(trackId: number, quality: string) { + const track = await (await HiFiClient.instance.getInfo(trackId)).json(); + return await api.downloadTrack(trackId.toString(), quality, undefined, { + track: track.data, + triggerDownload: false, + }); + } + + test.beforeEach(() => { + vi.clearAllMocks(); + }); + + test.each([ + { + display_quality: 'Dolby Atmos', + quality: 'HI_RES_LOSSLESS', + container: 'flac', + preferDolbyAtmos: true, + trackId: TRACK_ATMOS, + detection: Detection.DolbyAtmos, + ffmpegCalls: 0, + }, + { + display_quality: 'HD Lossless (FLAC)', + quality: 'HI_RES_LOSSLESS', + container: 'flac', + preferDolbyAtmos: false, + trackId: SILENCE_TRACK, + detection: Detection.FlacHD, + ffmpegCalls: 1, + }, + { + display_quality: 'Lossless (FLAC)', + quality: 'LOSSLESS', + container: 'flac', + preferDolbyAtmos: false, + trackId: SILENCE_TRACK, + detection: Detection.FlacLossless, + ffmpegCalls: 0, + }, + { + display_quality: 'HD Lossless (ALAC)', + quality: 'HI_RES_LOSSLESS', + container: 'alac', + preferDolbyAtmos: false, + trackId: SILENCE_TRACK, + detection: Detection.AlacHD, + ffmpegCalls: 1, + }, + { + display_quality: 'Lossless (ALAC)', + quality: 'LOSSLESS', + container: 'alac', + preferDolbyAtmos: false, + trackId: SILENCE_TRACK, + detection: Detection.AlacLossless, + ffmpegCalls: 1, + }, + { + display_quality: 'HD Lossless (Unchanged)', + quality: 'HI_RES_LOSSLESS', + container: 'nochange', + preferDolbyAtmos: false, + trackId: SILENCE_TRACK, + detection: Detection.Mp4Flac, + ffmpegCalls: 0, + }, + { + display_quality: 'Lossless (Unchanged)', + quality: 'LOSSLESS', + container: 'nochange', + preferDolbyAtmos: false, + trackId: SILENCE_TRACK, + detection: Detection.FlacLossless, + ffmpegCalls: 0, + }, + { + display_quality: 'Lossless, but not really', + quality: 'HI_RES_LOSSLESS', + container: 'flac', + preferDolbyAtmos: false, + trackId: TRACK_NO_LOSSLESS, + detection: Detection.AacReallyLow, + ffmpegCalls: 0, + }, + { + display_quality: 'High', + quality: 'HIGH', + container: 'flac', + preferDolbyAtmos: false, + trackId: SILENCE_TRACK, + detection: Detection.AacHigh, + ffmpegCalls: 0, + }, + { + display_quality: 'Low', + quality: 'LOW', + container: 'flac', + preferDolbyAtmos: false, + trackId: SILENCE_TRACK, + detection: Detection.AacLow, + ffmpegCalls: 0, + }, + + { + display_quality: 'AAC 256', + quality: 'FFMPEG_AAC_256', + container: 'flac', + preferDolbyAtmos: false, + trackId: TRACK_ATMOS, + detection: Detection.AAC_256, + ffmpegCalls: 1, + }, + + { + display_quality: 'MP3 320', + quality: 'FFMPEG_MP3_320', + container: 'flac', + preferDolbyAtmos: false, + trackId: SILENCE_TRACK, + detection: Detection.MP3_320, + ffmpegCalls: 1, + }, + { + display_quality: 'MP3 256', + quality: 'FFMPEG_MP3_256', + container: 'flac', + preferDolbyAtmos: false, + trackId: SILENCE_TRACK, + detection: Detection.MP3_256, + ffmpegCalls: 1, + }, + { + display_quality: 'MP3 128', + quality: 'FFMPEG_MP3_128', + container: 'flac', + preferDolbyAtmos: false, + trackId: SILENCE_TRACK, + detection: Detection.MP3_128, + ffmpegCalls: 1, + }, + + { + display_quality: 'OGG 320', + quality: 'FFMPEG_OGG_320', + container: 'flac', + preferDolbyAtmos: false, + trackId: SILENCE_TRACK, + detection: Detection.OGG_320, + ffmpegCalls: 1, + }, + { + display_quality: 'OGG 256', + quality: 'FFMPEG_OGG_256', + container: 'flac', + preferDolbyAtmos: false, + trackId: SILENCE_TRACK, + detection: Detection.OGG_256, + ffmpegCalls: 1, + }, + { + display_quality: 'OGG 128', + quality: 'FFMPEG_OGG_128', + container: 'flac', + preferDolbyAtmos: false, + trackId: SILENCE_TRACK, + detection: Detection.OGG_128, + ffmpegCalls: 1, + }, + ])('$display_quality', async ({ quality, container, preferDolbyAtmos, trackId, detection, ffmpegCalls }) => { + vi.mocked(preferDolbyAtmosSettings.isEnabled).mockReturnValue(preferDolbyAtmos); + vi.mocked(losslessContainerSettings.getContainer).mockReturnValue(container); + + const blob = await downloadTrack(trackId, quality); + expect(ffmpeg).toHaveBeenCalledTimes(ffmpegCalls); + const file = await FileRef.fromBlob(blob); + const stream = file.file().stream(); + + expect(file.isValid).toBe(true); + + let trak: Mp4Atom | null = null; + let stsd: Mp4Atom | null = null; + let stsdData: ByteVector | null = null; + + const streamPosition = await stream.tell(); + + if (file.file() instanceof Mp4File) { + const atoms = await Mp4Atoms.create(stream); + const moov = atoms.find('moov'); + expect(moov).not.toBeNull(); + + let trak: Mp4Atom | null = null; + let data: ByteVector; + + const trakList = moov.findAll('trak'); + for (const track of trakList) { + const hdlr = track.find('mdia', 'hdlr'); + if (!hdlr) continue; + trak = track; + await stream.seek(hdlr.offset); + data = await stream.readBlock(hdlr.length); + if (data.containsAt(ByteVector.fromString('soun', StringType.Latin1), 16)) { + break; + } + trak = null; + } + expect(trak).toBeInstanceOf(Mp4Atom); + stsd = trak!.find('mdia', 'minf', 'stbl', 'stsd'); + expect(stsd).toBeInstanceOf(Mp4Atom); + await stream.seek(stsd.offset); + stsdData = await stream.readBlock(stsd.length); + } + + stream.seek(streamPosition); + + switch (detection) { + case Detection.DolbyAtmos: { + expect(file.file()).toBeInstanceOf(Mp4File); + const codec = stsdData.toString().substring(20, 24); + expect(codec).toBe('ec-3'); + break; + } + case Detection.FlacHD: { + expect(file.file()).toBeInstanceOf(FlacFile); + const flac = file.file() as FlacFile; + expect(flac.audioProperties().bitsPerSample).toBe(24); + expect(flac.audioProperties().sampleRate).toBe(176400); + break; + } + case Detection.FlacLossless: { + expect(file.file()).toBeInstanceOf(FlacFile); + const flac = file.file() as FlacFile; + expect(flac.audioProperties().bitsPerSample).toBe(16); + expect(flac.audioProperties().sampleRate).toBe(44100); + break; + } + case Detection.Mp4Flac: { + expect(file.file()).toBeInstanceOf(Mp4File); + const codec = stsdData.toString().substring(20, 24); + expect(codec).toBe('fLaC'); + break; + } + case Detection.AlacHD: { + expect(file.file()).toBeInstanceOf(Mp4File); + const mp4 = file.file() as Mp4File; + expect(mp4.audioProperties().codec).toBe(Mp4Codec.ALAC); + expect(mp4.audioProperties().bitsPerSample).toBe(24); + expect(mp4.audioProperties().sampleRate).toBe(176400); + break; + } + case Detection.AlacLossless: { + expect(file.file()).toBeInstanceOf(Mp4File); + const mp4 = file.file() as Mp4File; + expect(mp4.audioProperties().codec).toBe(Mp4Codec.ALAC); + expect(mp4.audioProperties().bitsPerSample).toBe(16); + expect(mp4.audioProperties().sampleRate).toBe(44100); + break; + } + case Detection.AacLow: { + expect(file.file()).toBeInstanceOf(Mp4File); + const mp4 = file.file() as Mp4File; + expect(mp4.audioProperties().codec).toBe(Mp4Codec.AAC); + expect(mp4.audioProperties().bitsPerSample).toBe(16); + expect(mp4.audioProperties().sampleRate).toBe(44100); + expect(mp4.audioProperties().bitrate).toBe(97); + break; + } + case Detection.AacReallyLow: { + expect(file.file()).toBeInstanceOf(Mp4File); + const mp4 = file.file() as Mp4File; + expect(mp4.audioProperties().codec).toBe(Mp4Codec.AAC); + expect(mp4.audioProperties().bitsPerSample).toBe(16); + expect(mp4.audioProperties().sampleRate).toBe(22050); + expect(mp4.audioProperties().bitrate).toBe(97); + break; + } + case Detection.AacHigh: { + expect(file.file()).toBeInstanceOf(Mp4File); + const mp4 = file.file() as Mp4File; + expect(mp4.audioProperties().codec).toBe(Mp4Codec.AAC); + expect(mp4.audioProperties().bitsPerSample).toBe(16); + expect(mp4.audioProperties().sampleRate).toBe(44100); + expect(mp4.audioProperties().bitrate).toBe(322); + break; + } + + case Detection.AAC_256: { + expect(file.file()).toBeInstanceOf(Mp4File); + const mp4 = file.file() as Mp4File; + expect(mp4.audioProperties().codec).toBe(Mp4Codec.AAC); + expect(mp4.audioProperties().bitsPerSample).toBe(16); + expect(mp4.audioProperties().sampleRate).toBe(44100); + expect(mp4.audioProperties().bitrate).toBe(263); + break; + } + + case Detection.MP3_320: { + expect(file.file()).toBeInstanceOf(MpegFile); + const mp3 = file.file() as MpegFile; + expect(mp3.audioProperties().sampleRate).toBe(44100); + expect(mp3.audioProperties().bitrate).toBe(322); + break; + } + + case Detection.MP3_256: { + expect(file.file()).toBeInstanceOf(MpegFile); + const mp3 = file.file() as MpegFile; + expect(mp3.audioProperties().sampleRate).toBe(44100); + expect(mp3.audioProperties().bitrate).toBe(258); + break; + } + + case Detection.MP3_128: { + expect(file.file()).toBeInstanceOf(MpegFile); + const mp3 = file.file() as MpegFile; + expect(mp3.audioProperties().sampleRate).toBe(44100); + expect(mp3.audioProperties().bitrate).toBe(129); + break; + } + + case Detection.OGG_320: { + expect(file.file()).toBeInstanceOf(OggFile); + const ogg = file.file() as OggFile; + expect(ogg.audioProperties().sampleRate).toBe(44100); + //expect(ogg.audioProperties().bitrate).toBe(320); + break; + } + + case Detection.OGG_256: { + expect(file.file()).toBeInstanceOf(OggFile); + const ogg = file.file() as OggFile; + expect(ogg.audioProperties().sampleRate).toBe(44100); + //expect(ogg.audioProperties().bitrate).toBe(256); + break; + } + + case Detection.OGG_128: { + expect(file.file()).toBeInstanceOf(OggFile); + const ogg = file.file() as OggFile; + expect(ogg.audioProperties().sampleRate).toBe(44100); + //expect(ogg.audioProperties().bitrate).toBe(128); + break; + } + + default: + throw new Error('Unknown detection type'); + } + }); +}); diff --git a/js/ffmpeg.js b/js/ffmpeg.js index 80f1012..e01157c 100644 --- a/js/ffmpeg.js +++ b/js/ffmpeg.js @@ -49,7 +49,8 @@ async function ffmpegWorker( onProgress = null, signal = null, extraFiles = [], - logConsole = true + logConsole = true, + rawArgs = false ) { const audioData = audioBlob ? await audioBlob.arrayBuffer() : null; const assets = loadFfmpeg(); @@ -128,7 +129,7 @@ async function ffmpegWorker( { audioData, extraFiles, - args, + ...(rawArgs ? { rawArgs: args } : { args }), output: { name: outputName, mime: outputMime, @@ -153,6 +154,7 @@ async function ffmpegWorker( * @param {AbortSignal|null} [opts.signal=null] - Optional abort signal to cancel encoding * @param {Array} [opts.extraFiles=[]] - Additional files to provide to FFmpeg * @param {Boolean} [opts.logConsole=true] - Whether to log FFmpeg output to the console + * @param {string[]} [opts.rawArgs=[]] - Whether to pass args as raw command line (without default input/output) * @returns {Promise} Encoded audio blob * @throws {FfmpegError} If Web Workers are not available * @throws {Error} If FFmpeg encoding fails @@ -167,6 +169,7 @@ export async function ffmpeg( signal = null, extraFiles = [], logConsole = true, + rawArgs = null, } = {} ) { try { @@ -174,13 +177,14 @@ export async function ffmpeg( if (typeof Worker !== 'undefined') { return await ffmpegWorker( audioBlob, - args, + rawArgs || args, outputName, outputMime, onProgress, signal, extraFiles, - logConsole + logConsole, + !!rawArgs ); } diff --git a/js/ffmpeg.test.ts b/js/ffmpeg.test.ts new file mode 100644 index 0000000..547b513 --- /dev/null +++ b/js/ffmpeg.test.ts @@ -0,0 +1,19 @@ +import { expect, test, suite } from 'vitest'; +import { ffmpeg } from './ffmpeg'; + +test('Run `ffmpeg --help`', async () => { + const lines: string[] = []; + const info = await ffmpeg(null, { + rawArgs: ['--help'], + logConsole: false, + outputName: null, + onProgress: (progress) => { + if (progress.stage == 'stdout') { + lines.push(progress.message); + } + }, + }); + + expect(lines).length.greaterThan(0); + expect(lines[0]).matches(/ffmpeg version/i); +}); diff --git a/js/ffmpeg.worker.js b/js/ffmpeg.worker.js index 8da739d..8276cd0 100644 --- a/js/ffmpeg.worker.js +++ b/js/ffmpeg.worker.js @@ -1,4 +1,4 @@ -import { FFmpeg } from '@ffmpeg/ffmpeg'; +import { FFmpeg } from '!/@ffmpeg/ffmpeg/dist/esm/classes.js'; let ffmpeg = null; let loadingPromise = null; @@ -99,6 +99,7 @@ self.onmessage = async (e) => { const { audioData, extraFiles = [], + rawArgs, args = [], output = { name: 'output', @@ -123,7 +124,7 @@ self.onmessage = async (e) => { await ffmpeg.writeFile(file.name, new Uint8Array(file.data)); } - const ffmpegArgs = ['-i', 'input', ...args, ...(output.name ? [output.name] : [])]; + const ffmpegArgs = rawArgs || ['-i', 'input', ...args, ...(output.name ? [output.name] : [])]; self.postMessage({ type: 'command', command: ffmpegArgs }); const exitCode = await ffmpeg.exec(ffmpegArgs); diff --git a/package.json b/package.json index b6ced34..be101bb 100644 --- a/package.json +++ b/package.json @@ -5,6 +5,11 @@ "description": "[\"Monochrome](https://monochrome.tf)", "scripts": { "dev": "vite", + "test": "vitest run --config=vite.config.ts", + "test:headless": "HEADLESS=true vitest run --config=vite.config.ts", + "test:watch": "vitest --config=vite.config.ts", + "test:watch:headless": "HEADLESS=true vitest --config=vite.config.ts", + "install:playwright": "playwright install chromium", "build": "vite 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", @@ -28,13 +33,16 @@ "devDependencies": { "@capacitor/assets": "^3.0.5", "@capacitor/cli": "^8.2.0", + "@testing-library/dom": "^10.4.1", "@types/node": "^25.3.5", + "@vitest/browser-playwright": "^4.1.2", "eslint": "^9.39.3", "eslint-config-prettier": "^10.1.8", "formidable": "^3.5.4", "globals": "^17.4.0", "htmlhint": "^1.9.2", "miniflare": "^4.20260301.1", + "playwright": "^1.58.2", "prettier": "^3.8.1", "stylelint": "^16.26.1", "stylelint-config-standard": "^39.0.1", @@ -61,6 +69,7 @@ "@kawarp/core": "^1.1.1", "@svta/common-media-library": "^0.18.1", "@uimaxbai/am-lyrics": "^1.1.4", + "@vitest/web-worker": "^4.1.2", "appwrite": "^23.0.0", "butterchurn": "^2.6.7", "butterchurn-presets": "^2.4.7", @@ -79,6 +88,7 @@ "simple-icons": "^16.12.0", "svgo": "^4.0.1", "url-toolkit": "^2.2.5", - "uuid": "^13.0.0" + "uuid": "^13.0.0", + "vitest": "^4.1.2" } } diff --git a/tsconfig.json b/tsconfig.json index ff42e5e..8c26866 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -4,7 +4,7 @@ "module": "ESNext", "moduleResolution": "Bundler", "lib": ["ESNext", "DOM", "DOM.Iterable", "webworker"], - "types": ["vite/client", "node"], + "types": ["vite/client", "node", "./js/global.d.ts"], "baseUrl": ".", "paths": { "!/*": ["node_modules/*"] diff --git a/vite-plugin-blob.ts b/vite-plugin-blob.ts index 069ac5e..47dc25e 100644 --- a/vite-plugin-blob.ts +++ b/vite-plugin-blob.ts @@ -1,7 +1,7 @@ import fs from 'fs/promises'; import path from 'path'; import { gzipSync, constants as zlibConstants } from 'zlib'; -import type { Plugin } from 'vite'; +import type { Plugin, ResolvedConfig } from 'vite'; import mime from 'mime'; import { createHash } from 'crypto'; @@ -26,10 +26,14 @@ function hashString(input: string, algorithm = 'sha256'): string { */ export default function blobAssetPlugin(): Plugin { const devAssets = new Map(); + let resolvedConfig: ResolvedConfig | null = null; return { name: 'vite-blob-asset', + async configResolved(config: ResolvedConfig) { + resolvedConfig = config; + }, async load(id) { if (!id.includes('?blob-url')) return; @@ -45,7 +49,7 @@ export default function blobAssetPlugin(): Plugin { let assetUrl: string; - if (this.meta.watchMode) { + if (resolvedConfig?.command === 'serve') { /** dev server path */ assetUrl = `/@blob/${hashString(absPath)}/${path.basename(filepath)}.gz`; devAssets.set(assetUrl, compressed); diff --git a/vite.config.ts b/vite.config.ts index bd7d0a0..23f4158 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -6,6 +6,7 @@ import uploadPlugin from './vite-plugin-upload.js'; import blobAssetPlugin from './vite-plugin-blob.js'; import svgUse from './vite-plugin-svg-use.js'; import { execSync } from 'child_process'; +import { playwright } from '@vitest/browser-playwright'; function getGitCommitHash() { try { @@ -19,9 +20,19 @@ export default defineConfig(({ mode }) => { const commitHash = getGitCommitHash(); return { + test: { + // https://vitest.dev/guide/browser/ + browser: { + enabled: true, + provider: playwright(), + headless: !!process.env.HEADLESS, + instances: [{ browser: 'chromium' }], + }, + }, base: './', define: { __COMMIT_HASH__: JSON.stringify(commitHash), + __VITEST__: !!process.env.VITEST, }, worker: { format: 'es', From b00cb086f4dab542316556aaeb1fceb55652cc67 Mon Sep 17 00:00:00 2001 From: SamidyFR <168582143+SamidyFR@users.noreply.github.com> Date: Wed, 1 Apr 2026 21:02:31 +0000 Subject: [PATCH 02/51] style: auto-fix linting issues --- index.html | 82 +++++++-- js/listening-party.js | 410 +++++++++++++++++++++++++++++------------- styles.css | 10 +- 3 files changed, 360 insertions(+), 142 deletions(-) diff --git a/index.html b/index.html index 6cfff22..6deece3 100644 --- a/index.html +++ b/index.html @@ -1832,16 +1832,24 @@

Listening Parties

- Listen to music together with your friends in real-time. - Host controls the music, everyone enjoys it. + Listen to music together with your friends in real-time. Host controls the music, everyone + enjoys it.

@@ -1860,37 +1868,75 @@ -
+

Currently Playing

-
-
+

Participants (0)

-
-
+

Song Requests

-
-
+
-
-
+
+
Chat
-
-
-
- - +
+
+ +
diff --git a/js/listening-party.js b/js/listening-party.js index dceb92d..9cddb6c 100644 --- a/js/listening-party.js +++ b/js/listening-party.js @@ -19,20 +19,24 @@ class Modal {

${title}

`; document.body.appendChild(modal); - + const cleanup = (val) => { modal.remove(); resolve(val); }; - modal.querySelectorAll('.modal-action-btn').forEach(btn => { + modal.querySelectorAll('.modal-action-btn').forEach((btn) => { btn.onclick = () => { const action = actions[btn.dataset.index]; if (action.callback) { @@ -52,7 +56,7 @@ class Modal { return this.show({ title, content: message, - actions: [{ label: 'OK', type: 'primary' }] + actions: [{ label: 'OK', type: 'primary' }], }); } @@ -62,8 +66,8 @@ class Modal { content: message, actions: [ { label: confirmLabel, type: type }, - { label: 'Cancel', type: 'secondary', callback: () => false } - ] + { label: 'Cancel', type: 'secondary', callback: () => false }, + ], }); } } @@ -82,7 +86,7 @@ export class ListeningPartyManager { this.isJoining = false; this.isInternalSync = false; this.originalSafePlay = null; - + this.setupEventListeners(); } @@ -119,25 +123,27 @@ export class ListeningPartyManager { is_playing: player.currentTrack ? !player.activeElement.paused : false, playback_time: player.activeElement.currentTime || 0, playback_timestamp: Date.now(), - queue: player.queue?.map(t => syncManager._minifyItem('track', t)) || [] + queue: player.queue?.map((t) => syncManager._minifyItem('track', t)) || [], }; if (currentTrack) partyData.current_track = currentTrack; try { const party = await pb.collection('parties').create(partyData, { f_id: user.$id }); navigate(`/party/${party.id}`); - } catch (e) { console.error('Create error:', e); } + } catch (e) { + console.error('Create error:', e); + } } async joinParty(partyId) { if (this.currentParty?.id === partyId || this.isJoining) return; this.isJoining = true; - + try { const user = authManager.user; const f_id = user ? user.$id : 'guest'; const party = await pb.collection('parties').getOne(partyId, { expand: 'host', f_id }); - + const confirmed = await this.showJoinModal(user); if (!confirmed) { this.isJoining = false; @@ -148,14 +154,20 @@ export class ListeningPartyManager { this.currentParty = party; const pbUser = user ? await syncManager._getUserRecord(user.$id) : null; this.isHost = pbUser && pbUser.id === party.host; - - const profile = confirmed.profile || await this.getMemberProfile(pbUser); - const memberData = { party: partyId, name: profile.name, avatar_url: profile.avatar_url, is_host: !!this.isHost, last_seen: Date.now() }; + + const profile = confirmed.profile || (await this.getMemberProfile(pbUser)); + const memberData = { + party: partyId, + name: profile.name, + avatar_url: profile.avatar_url, + is_host: !!this.isHost, + last_seen: Date.now(), + }; if (pbUser?.id) memberData.user = pbUser.id; const member = await pb.collection('party_members').create(memberData, { f_id }); this.memberId = member.id; - + this.setupSubscriptions(partyId); this.startHeartbeat(); this.renderPartyUI(); @@ -169,12 +181,12 @@ export class ListeningPartyManager { this.syncWithHost(party); } } - } catch (error) { - console.error('Join error:', error); + } catch (error) { + console.error('Join error:', error); Modal.alert('Error', 'Failed to join the party. It may have ended.'); - navigate('/parties'); - } finally { - this.isJoining = false; + navigate('/parties'); + } finally { + this.isJoining = false; } } @@ -198,18 +210,21 @@ export class ListeningPartyManager { `, actions: [ - { - label: 'Join Party', + { + label: 'Join Party', type: 'primary', callback: (modal) => { const name = modal.querySelector('#guest-name-input').value.trim() || 'Guest'; - const profile = { name, avatar_url: `https://api.dicebear.com/9.x/identicon/svg?seed=${name}` }; + const profile = { + name, + avatar_url: `https://api.dicebear.com/9.x/identicon/svg?seed=${name}`, + }; localStorage.setItem('party_guest_profile', JSON.stringify(profile)); return { profile }; - } + }, }, - { label: 'Cancel', type: 'secondary', callback: () => false } - ] + { label: 'Cancel', type: 'secondary', callback: () => false }, + ], }).then(resolve); }); } @@ -227,57 +242,96 @@ export class ListeningPartyManager { async getMemberProfile(pbUser = null) { const user = authManager.user; if (user) { - const name = pbUser?.display_name || pbUser?.username || user.displayName || user.email?.split('@')[0] || 'Member'; - const avatar = pbUser?.avatar_url || user.photoURL || `https://api.dicebear.com/9.x/identicon/svg?seed=${name}`; + const name = + pbUser?.display_name || pbUser?.username || user.displayName || user.email?.split('@')[0] || 'Member'; + const avatar = + pbUser?.avatar_url || user.photoURL || `https://api.dicebear.com/9.x/identicon/svg?seed=${name}`; return { name, avatar_url: avatar }; } const cached = localStorage.getItem('party_guest_profile'); - return cached ? JSON.parse(cached) : { name: 'Guest', avatar_url: 'https://api.dicebear.com/9.x/identicon/svg?seed=Guest' }; + return cached + ? JSON.parse(cached) + : { name: 'Guest', avatar_url: 'https://api.dicebear.com/9.x/identicon/svg?seed=Guest' }; } setupSubscriptions(partyId) { - this.unsubscribeFunctions.forEach(unsub => unsub()); + this.unsubscribeFunctions.forEach((unsub) => unsub()); this.unsubscribeFunctions = []; const f_id = authManager.user ? authManager.user.$id : 'guest'; - pb.collection('parties').subscribe(partyId, (e) => { - if (e.action === 'update') { - this.currentParty = e.record; - if (!this.isHost) this.syncWithHost(e.record); - this.updatePartyHeader(); - } else if (e.action === 'delete') { - Modal.alert('Party Ended', 'The host has ended the listening party.'); - this.leaveParty(false); - } - }, { f_id }).then(unsub => this.unsubscribeFunctions.push(unsub)); + pb.collection('parties') + .subscribe( + partyId, + (e) => { + if (e.action === 'update') { + this.currentParty = e.record; + if (!this.isHost) this.syncWithHost(e.record); + this.updatePartyHeader(); + } else if (e.action === 'delete') { + Modal.alert('Party Ended', 'The host has ended the listening party.'); + this.leaveParty(false); + } + }, + { f_id } + ) + .then((unsub) => this.unsubscribeFunctions.push(unsub)); - pb.collection('party_members').subscribe('*', (e) => { - if (e.record.party === partyId) this.loadMembers(); - }, { f_id }).then(unsub => this.unsubscribeFunctions.push(unsub)); + pb.collection('party_members') + .subscribe( + '*', + (e) => { + if (e.record.party === partyId) this.loadMembers(); + }, + { f_id } + ) + .then((unsub) => this.unsubscribeFunctions.push(unsub)); - pb.collection('party_messages').subscribe('*', (e) => { - if (e.record.party === partyId && e.action === 'create') this.addChatMessage(e.record); - }, { f_id }).then(unsub => this.unsubscribeFunctions.push(unsub)); + pb.collection('party_messages') + .subscribe( + '*', + (e) => { + if (e.record.party === partyId && e.action === 'create') this.addChatMessage(e.record); + }, + { f_id } + ) + .then((unsub) => this.unsubscribeFunctions.push(unsub)); - pb.collection('party_requests').subscribe('*', (e) => { - if (e.record.party === partyId) this.loadRequests(); - }, { f_id }).then(unsub => this.unsubscribeFunctions.push(unsub)); + pb.collection('party_requests') + .subscribe( + '*', + (e) => { + if (e.record.party === partyId) this.loadRequests(); + }, + { f_id } + ) + .then((unsub) => this.unsubscribeFunctions.push(unsub)); } - async loadInitialData(partyId) { this.loadMembers(); this.loadMessages(); this.loadRequests(); } + async loadInitialData(partyId) { + this.loadMembers(); + this.loadMessages(); + this.loadRequests(); + } async loadMembers() { const f_id = authManager.user ? authManager.user.$id : 'guest'; - this.members = await pb.collection('party_members').getFullList({ filter: `party = "${this.currentParty.id}"`, sort: '-is_host,name', f_id }); + this.members = await pb + .collection('party_members') + .getFullList({ filter: `party = "${this.currentParty.id}"`, sort: '-is_host,name', f_id }); this.renderMembers(); } async loadMessages() { const f_id = authManager.user ? authManager.user.$id : 'guest'; - const res = await pb.collection('party_messages').getList(1, 50, { filter: `party = "${this.currentParty.id}"`, sort: '-created', f_id }); + const res = await pb + .collection('party_messages') + .getList(1, 50, { filter: `party = "${this.currentParty.id}"`, sort: '-created', f_id }); this.messages = res.items.reverse(); const container = document.getElementById('party-chat-messages'); - if (container) { container.innerHTML = ''; this.messages.forEach(m => this.addChatMessage(m)); } + if (container) { + container.innerHTML = ''; + this.messages.forEach((m) => this.addChatMessage(m)); + } } async loadRequests() { @@ -286,26 +340,36 @@ export class ListeningPartyManager { this.requests = await pb.collection('party_requests').getFullList({ filter: `party = "${this.currentParty.id}"`, sort: 'created', - f_id: f_id + f_id: f_id, }); this.renderRequests(); - } catch (e) { console.error('Failed to load requests:', e); } + } catch (e) { + console.error('Failed to load requests:', e); + } } renderPartyUI() { - this.updatePartyHeader(); this.renderMembers(); this.renderRequests(); this.showPartyIndicator(); - if (this.isHost) { this.unlockControls(); this.setupHostPlayerSync(); } - else { this.lockControls(); this.setupGuestPlayerInterferenceCheck(); } + this.updatePartyHeader(); + this.renderMembers(); + this.renderRequests(); + this.showPartyIndicator(); + if (this.isHost) { + this.unlockControls(); + this.setupHostPlayerSync(); + } else { + this.lockControls(); + this.setupGuestPlayerInterferenceCheck(); + } } updatePartyHeader() { const titleEl = document.getElementById('party-title'); const countEl = document.getElementById('party-member-count'); const metaEl = document.getElementById('party-meta'); - + if (titleEl) titleEl.textContent = this.currentParty.name; if (countEl) countEl.textContent = this.members.length; - + if (metaEl) { const host = this.currentParty.expand?.host; const hostName = host?.display_name || host?.username || 'Unknown'; @@ -325,11 +389,15 @@ export class ListeningPartyManager {
${track.title}
${getTrackArtists(track)}
- ${!this.currentParty.is_playing ? ` + ${ + !this.currentParty.is_playing + ? `
${SVG_PAUSE(24)} Paused
- ` : ''} + ` + : '' + }
`; } else { @@ -341,7 +409,12 @@ export class ListeningPartyManager { renderMembers() { const list = document.getElementById('party-members-list'); if (!list) return; - list.innerHTML = this.members.map(m => `
${m.name}
${m.is_host ? '
Host
' : '
Listening
'}
`).join(''); + list.innerHTML = this.members + .map( + (m) => + `
${m.name}
${m.is_host ? '
Host
' : '
Listening
'}
` + ) + .join(''); } renderRequests() { @@ -351,13 +424,14 @@ export class ListeningPartyManager { list.innerHTML = `
No requests yet. Right-click a song to request!
`; return; } - - list.innerHTML = this.requests.map(r => { - try { - const api = Player.instance.api; - const artists = getTrackArtists(r.track); - const coverUrl = api.getCoverUrl(r.track.artwork || r.track.cover || r.track.album?.cover); - return `
+ + list.innerHTML = this.requests + .map((r) => { + try { + const api = Player.instance.api; + const artists = getTrackArtists(r.track); + const coverUrl = api.getCoverUrl(r.track.artwork || r.track.cover || r.track.album?.cover); + return `
${r.track.title || 'Unknown Title'}
@@ -365,20 +439,25 @@ export class ListeningPartyManager {
${this.isHost ? `` : ''}
`; - } catch (e) { return ''; } - }).join(''); + } catch (e) { + return ''; + } + }) + .join(''); if (this.isHost) { const f_id = authManager.user ? authManager.user.$id : 'guest'; - list.querySelectorAll('.add-request-btn').forEach(btn => btn.addEventListener('click', async (e) => { - const reqId = e.currentTarget.dataset.reqId; - const req = this.requests.find(r => r.id === reqId); - if (req) { - Player.instance.addToQueue(req.track); - showNotification(`Added "${req.track.title}" to queue`); - await pb.collection('party_requests').delete(req.id, { f_id }); - } - })); + list.querySelectorAll('.add-request-btn').forEach((btn) => + btn.addEventListener('click', async (e) => { + const reqId = e.currentTarget.dataset.reqId; + const req = this.requests.find((r) => r.id === reqId); + if (req) { + Player.instance.addToQueue(req.track); + showNotification(`Added "${req.track.title}" to queue`); + await pb.collection('party_requests').delete(req.id, { f_id }); + } + }) + ); } } @@ -387,15 +466,17 @@ export class ListeningPartyManager { if (!container) return; const div = document.createElement('div'); div.className = 'chat-msg'; - + const urlRegex = /(https?:\/\/[^\s]+)/g; let content = escapeHtml(msg.content); - + content = content.replace(urlRegex, (url) => { if (url.match(/\.(jpeg|jpg|gif|png|webp|svg)(\?.*)?$/i)) { return `${url}`; } - const ytMatch = url.match(/(?:https?:\/\/)?(?:www\.)?(?:youtube\.com\/watch\?v=|youtu\.be\/)([a-zA-Z0-9_-]{11})/i); + const ytMatch = url.match( + /(?:https?:\/\/)?(?:www\.)?(?:youtube\.com\/watch\?v=|youtu\.be\/)([a-zA-Z0-9_-]{11})/i + ); if (ytMatch) { return `${url}`; } @@ -407,7 +488,7 @@ export class ListeningPartyManager { } return `${url}`; }); - + div.innerHTML = `
${escapeHtml(msg.sender_name)}
@@ -421,10 +502,15 @@ export class ListeningPartyManager { async sendChatMessage() { const input = document.getElementById('party-chat-input'); if (!input || !input.value.trim()) return; - const content = input.value.trim(); input.value = ''; + const content = input.value.trim(); + input.value = ''; const profile = await this.getMemberProfile(); const f_id = authManager.user ? authManager.user.$id : 'guest'; - try { await pb.collection('party_messages').create({ party: this.currentParty.id, sender_name: profile.name, content }, { f_id }); } catch (e) {} + try { + await pb + .collection('party_messages') + .create({ party: this.currentParty.id, sender_name: profile.name, content }, { f_id }); + } catch (e) {} } async requestSong(track) { @@ -433,13 +519,18 @@ export class ListeningPartyManager { const f_id = authManager.user ? authManager.user.$id : 'guest'; try { const minifiedTrack = syncManager._minifyItem('track', track); - await pb.collection('party_requests').create({ - party: this.currentParty.id, - track: minifiedTrack, - requested_by: profile.name - }, { f_id }); + await pb.collection('party_requests').create( + { + party: this.currentParty.id, + track: minifiedTrack, + requested_by: profile.name, + }, + { f_id } + ); showNotification(`Requested "${track.title}"`); - } catch (e) { console.error('Request error:', e); } + } catch (e) { + console.error('Request error:', e); + } } async syncWithHost(party) { @@ -448,18 +539,23 @@ export class ListeningPartyManager { try { const player = Player.instance; const el = player.activeElement; - if (!party.current_track) { if (player.currentTrack) el.pause(); return; } - + if (!party.current_track) { + if (player.currentTrack) el.pause(); + return; + } + const currentId = String(player.currentTrack?.id || ''); const targetId = String(party.current_track.id || ''); if (currentId !== targetId) { const cleanedTrack = { ...party.current_track }; - delete cleanedTrack.audioUrl; delete cleanedTrack.streamUrl; delete cleanedTrack.remoteUrl; + delete cleanedTrack.audioUrl; + delete cleanedTrack.streamUrl; + delete cleanedTrack.remoteUrl; player.setQueue([cleanedTrack], 0); await player.playTrackFromQueue(party.playback_time); if (!party.is_playing) el.pause(); - return; + return; } if (party.is_playing) { @@ -473,17 +569,57 @@ export class ListeningPartyManager { if (!el.paused) el.pause(); if (Math.abs(el.currentTime - party.playback_time) > 0.5) el.currentTime = party.playback_time; } - } catch (e) { console.error('Sync error:', e); } finally { this.isInternalSync = false; } + } catch (e) { + console.error('Sync error:', e); + } finally { + this.isInternalSync = false; + } } lockControls() { - const selectors = ['.play-pause-btn', '#next-btn', '#prev-btn', '#shuffle-btn', '#repeat-btn', '#progress-bar', '#fs-play-pause-btn', '#fs-next-btn', '#fs-prev-btn', '#fs-shuffle-btn', '#fs-repeat-btn', '#fs-progress-bar']; - selectors.forEach(s => document.querySelectorAll(s).forEach(el => { el.style.opacity = '0.5'; el.style.pointerEvents = 'none'; })); + const selectors = [ + '.play-pause-btn', + '#next-btn', + '#prev-btn', + '#shuffle-btn', + '#repeat-btn', + '#progress-bar', + '#fs-play-pause-btn', + '#fs-next-btn', + '#fs-prev-btn', + '#fs-shuffle-btn', + '#fs-repeat-btn', + '#fs-progress-bar', + ]; + selectors.forEach((s) => + document.querySelectorAll(s).forEach((el) => { + el.style.opacity = '0.5'; + el.style.pointerEvents = 'none'; + }) + ); } unlockControls() { - const selectors = ['.play-pause-btn', '#next-btn', '#prev-btn', '#shuffle-btn', '#repeat-btn', '#progress-bar', '#fs-play-pause-btn', '#fs-next-btn', '#fs-prev-btn', '#fs-shuffle-btn', '#fs-repeat-btn', '#fs-progress-bar']; - selectors.forEach(s => document.querySelectorAll(s).forEach(el => { el.style.opacity = '1'; el.style.pointerEvents = 'auto'; })); + const selectors = [ + '.play-pause-btn', + '#next-btn', + '#prev-btn', + '#shuffle-btn', + '#repeat-btn', + '#progress-bar', + '#fs-play-pause-btn', + '#fs-next-btn', + '#fs-prev-btn', + '#fs-shuffle-btn', + '#fs-repeat-btn', + '#fs-progress-bar', + ]; + selectors.forEach((s) => + document.querySelectorAll(s).forEach((el) => { + el.style.opacity = '1'; + el.style.pointerEvents = 'auto'; + }) + ); } setupHostPlayerSync() { @@ -493,16 +629,20 @@ export class ListeningPartyManager { const el = player.activeElement; const sharedTrack = player.currentTrack ? syncManager._minifyItem('track', player.currentTrack) : null; try { - await pb.collection('parties').update(this.currentParty.id, { - current_track: sharedTrack, - is_playing: !el.paused, - playback_time: el.currentTime, - playback_timestamp: Date.now(), - queue: player.queue?.map(t => syncManager._minifyItem('track', t)) || [] - }, { f_id: authManager.user?.$id }); + await pb.collection('parties').update( + this.currentParty.id, + { + current_track: sharedTrack, + is_playing: !el.paused, + playback_time: el.currentTime, + playback_timestamp: Date.now(), + queue: player.queue?.map((t) => syncManager._minifyItem('track', t)) || [], + }, + { f_id: authManager.user?.$id } + ); } catch (e) {} }; - ['play', 'pause', 'seeked'].forEach(ev => { + ['play', 'pause', 'seeked'].forEach((ev) => { player.audio.addEventListener(ev, updateParty); if (player.video) player.video.addEventListener(ev, updateParty); }); @@ -520,7 +660,12 @@ export class ListeningPartyManager { const originalPlayTrackFromQueue = player.playTrackFromQueue.bind(player); player.playTrackFromQueue = async (...args) => { if (this.currentParty && !this.isHost && !this.isInternalSync) { - const leave = await Modal.confirm('Leave Party?', 'Playing a song will cause you to leave the listening party. Are you sure?', 'Leave and Play', 'danger'); + const leave = await Modal.confirm( + 'Leave Party?', + 'Playing a song will cause you to leave the listening party. Are you sure?', + 'Leave and Play', + 'danger' + ); if (!leave) return; this.leaveParty(); } @@ -531,39 +676,60 @@ export class ListeningPartyManager { startHeartbeat() { this.heartbeatInterval = setInterval(async () => { if (!this.memberId) return; - try { await pb.collection('party_members').update(this.memberId, { last_seen: Date.now() }, { f_id: authManager.user?.$id || 'guest' }); } catch (e) {} + try { + await pb + .collection('party_members') + .update(this.memberId, { last_seen: Date.now() }, { f_id: authManager.user?.$id || 'guest' }); + } catch (e) {} }, 30000); } async leaveParty(shouldCleanup = true) { const f_id = authManager.user?.$id || 'guest'; if (this.isHost && shouldCleanup) { - const end = await Modal.confirm('End Party?', 'Leaving will end the party for everyone. Are you sure?', 'End Party', 'danger'); + const end = await Modal.confirm( + 'End Party?', + 'Leaving will end the party for everyone. Are you sure?', + 'End Party', + 'danger' + ); if (!end) return; try { const cleanup = async (coll) => { - const items = await pb.collection(coll).getFullList({ filter: `party = "${this.currentParty.id}"`, f_id }); + const items = await pb + .collection(coll) + .getFullList({ filter: `party = "${this.currentParty.id}"`, f_id }); for (const i of items) await pb.collection(coll).delete(i.id, { f_id }); }; - await cleanup('party_members'); await cleanup('party_messages'); await cleanup('party_requests'); + await cleanup('party_members'); + await cleanup('party_messages'); + await cleanup('party_requests'); await pb.collection('parties').delete(this.currentParty.id, { f_id }); } catch (e) {} } else if (this.memberId) { - try { await pb.collection('party_members').delete(this.memberId, { f_id }); } catch (e) {} + try { + await pb.collection('party_members').delete(this.memberId, { f_id }); + } catch (e) {} } this.restorePlayerMethods(); this.unlockControls(); - this.unsubscribeFunctions.forEach(unsub => unsub()); + this.unsubscribeFunctions.forEach((unsub) => unsub()); this.unsubscribeFunctions = []; - clearInterval(this.syncInterval); clearInterval(this.heartbeatInterval); - this.currentParty = null; this.isHost = false; this.memberId = null; + clearInterval(this.syncInterval); + clearInterval(this.heartbeatInterval); + this.currentParty = null; + this.isHost = false; + this.memberId = null; document.getElementById('party-indicator')?.remove(); navigate('/parties'); } restorePlayerMethods() { const player = Player.instance; - if (this.originalSafePlay) { player.safePlay = this.originalSafePlay; this.originalSafePlay = null; } + if (this.originalSafePlay) { + player.safePlay = this.originalSafePlay; + this.originalSafePlay = null; + } } copyInviteLink() { @@ -580,7 +746,7 @@ export class ListeningPartyManager { document.body.appendChild(indicator); indicator.onclick = () => navigate(`/party/${this.currentParty.id}`); } - + indicator.innerHTML = `
Listening Party diff --git a/styles.css b/styles.css index 71ecf74..8399b70 100644 --- a/styles.css +++ b/styles.css @@ -9180,8 +9180,14 @@ body:has(#side-panel.active) #close-fullscreen-cover-btn { } @keyframes fadeIn { - from { opacity: 0; transform: translateY(5px); } - to { opacity: 1; transform: translateY(0); } + from { + opacity: 0; + transform: translateY(5px); + } + to { + opacity: 1; + transform: translateY(0); + } } .party-status-badge { From ffb57e932fafedd3e73034d0bef13ca90e6d731d Mon Sep 17 00:00:00 2001 From: edideaur <182119792+edideaur@users.noreply.github.com> Date: Thu, 2 Apr 2026 07:28:17 +0000 Subject: [PATCH 03/51] style: auto-fix linting issues --- styles.css | 1 + 1 file changed, 1 insertion(+) diff --git a/styles.css b/styles.css index 8399b70..11cc31b 100644 --- a/styles.css +++ b/styles.css @@ -9184,6 +9184,7 @@ body:has(#side-panel.active) #close-fullscreen-cover-btn { opacity: 0; transform: translateY(5px); } + to { opacity: 1; transform: translateY(0); From 6e98830fdde0d3906a2a39fe97b98b6cea761042 Mon Sep 17 00:00:00 2001 From: Daniel <790119+DanTheMan827@users.noreply.github.com> Date: Wed, 1 Apr 2026 12:34:00 -0500 Subject: [PATCH 04/51] fix(downloads): change FLAC ffmpegArgs to use 'copy' codec --- js/ffmpegFormats.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/ffmpegFormats.ts b/js/ffmpegFormats.ts index eeb27a1..2d45089 100644 --- a/js/ffmpegFormats.ts +++ b/js/ffmpegFormats.ts @@ -151,7 +151,7 @@ if (import.meta.env.DEV) { export const containerFormats: Record = { flac: { displayName: 'FLAC', - ffmpegArgs: ['-vn', '-map_metadata', '-1', '-map', '0:a', '-c:a', 'flac'], + ffmpegArgs: ['-vn', '-map_metadata', '-1', '-map', '0:a', '-c:a', 'copy'], outputFilename: 'output.flac', outputMime: 'audio/flac', extension: 'flac', From d4d1fe8494b113c8d7d1217e4264c6efc8d63c11 Mon Sep 17 00:00:00 2001 From: tryptz Date: Wed, 1 Apr 2026 16:49:42 -0400 Subject: [PATCH 05/51] feat: AutoEQ and speaker EQ enhancements Adds AutoEQ integration with interactive parametric EQ graph, speaker/room correction with shelf filters, and improved EQ persistence via IndexedDB. --- .github/workflows/lint.yml | 1 + index.html | 978 +++++--- js/api.js | 8 +- js/audio-context.js | 221 +- js/autoeq-data.js | 4396 ++++++++++++++++++++++++++++++++++++ js/autoeq-engine.js | 221 ++ js/autoeq-importer.js | 219 ++ js/equalizer.js | 26 +- js/player.js | 30 +- js/settings.js | 4122 +++++++++++++++++++++++++-------- js/storage.js | 228 +- js/ui.js | 52 - styles.css | 1693 +++++++++----- 13 files changed, 10305 insertions(+), 1890 deletions(-) create mode 100644 js/autoeq-data.js create mode 100644 js/autoeq-engine.js create mode 100644 js/autoeq-importer.js diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index bf36fb4..f177d13 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -19,6 +19,7 @@ jobs: uses: actions/checkout@v4 with: fetch-depth: 1 + repository: ${{ github.event.pull_request.head.repo.full_name || github.repository }} ref: ${{ github.head_ref || github.ref }} - name: Setup Bun diff --git a/index.html b/index.html index 6deece3..4b8e51a 100644 --- a/index.html +++ b/index.html @@ -3871,32 +3871,6 @@
-
-
- Fullscreen Cover Tilt - 3D tilt effect on album cover in fullscreen view -
- -
-
-
- Preload Next Track - Seconds before track ends to start loading next -
- -
@@ -3995,9 +3969,9 @@
- Equalizer + AutoEQ 16-band parametric equalizer for fine audio controlPrecision headphone correction & parametric equalizer
-
-
- No content blocked yet. -
- - - - - - -

{ const filter = this.audioContext.createBiquadFilter(); - filter.type = 'peaking'; + filter.type = (this.currentTypes && this.currentTypes[index]) || 'peaking'; filter.frequency.value = freq; - filter.Q.value = this._calculateQ(index); + filter.Q.value = + this.currentQs && this.currentQs[index] > 0 ? this.currentQs[index] : this._calculateQ(index); filter.gain.value = this.currentGains[index] || 0; return filter; }); @@ -312,10 +313,10 @@ class AudioContextManager { try { this.audioContext = new AudioContext(highResOptions); console.log(`[AudioContext] Created with high-res settings: ${this.audioContext.sampleRate}Hz`); - } catch (e) { + } catch { try { this.audioContext = new AudioContext({ latencyHint: 'playback' }); - } catch (e2) { + } catch { this.audioContext = new AudioContext(); } } @@ -358,7 +359,9 @@ class AudioContextManager { if (this.source) { try { this.source.disconnect(); - } catch (e) {} + } catch { + // node may already be disconnected + } } this.audio = audioElement; @@ -386,7 +389,9 @@ class AudioContextManager { // Disconnect everything first try { this.source.disconnect(); - } catch (e) {} + } catch { + // node may already be disconnected + } this.outputNode.disconnect(); if (this.volumeNode) { this.volumeNode.disconnect(); @@ -405,16 +410,23 @@ class AudioContextManager { // Apply mono audio if enabled if (this.isMonoAudioEnabled && this.monoMergerNode) { - // Create a gain node to mix channels before the merger - const monoGain = this.audioContext.createGain(); - monoGain.gain.value = 0.5; // Reduce volume to prevent clipping when mixing + // Reuse persistent gain node to avoid leaking AudioNodes + if (!this.monoGainNode) { + this.monoGainNode = this.audioContext.createGain(); + this.monoGainNode.gain.value = 0.5; // Reduce volume to prevent clipping when mixing + } + try { + this.monoGainNode.disconnect(); + } catch { + /* not connected */ + } // Connect source to mono gain - this.source.connect(monoGain); + this.source.connect(this.monoGainNode); // Connect mono gain to both inputs of the merger - monoGain.connect(this.monoMergerNode, 0, 0); - monoGain.connect(this.monoMergerNode, 0, 1); + this.monoGainNode.connect(this.monoMergerNode, 0, 0); + this.monoGainNode.connect(this.monoMergerNode, 0, 1); lastNode = this.monoMergerNode; console.log('[AudioContext] Mono audio enabled'); @@ -573,6 +585,57 @@ class AudioContextManager { return equalizerSettings.getRange(); } + /** + * Calculate biquad filter magnitude response in dB at a given frequency + */ + _biquadResponseDb(f, band, sr) { + if (!band.enabled || !band.type) return 0; + const w = (2 * Math.PI * band.freq) / sr; + const p = (2 * Math.PI * f) / sr; + const s = Math.sin(w) / (2 * band.q); + const A = Math.pow(10, band.gain / 40); + const c = Math.cos(w); + let b0, b1, b2, a0, a1, a2; + const t = band.type[0]; + if (t === 'p') { + b0 = 1 + s * A; + b1 = -2 * c; + b2 = 1 - s * A; + a0 = 1 + s / A; + a1 = -2 * c; + a2 = 1 - s / A; + } else if (t === 'l') { + const sq = 2 * Math.sqrt(A) * s; + b0 = A * (A + 1 - (A - 1) * c + sq); + b1 = 2 * A * (A - 1 - (A + 1) * c); + b2 = A * (A + 1 - (A - 1) * c - sq); + a0 = A + 1 + (A - 1) * c + sq; + a1 = -2 * (A - 1 + (A + 1) * c); + a2 = A + 1 + (A - 1) * c - sq; + } else if (t === 'h') { + const sq = 2 * Math.sqrt(A) * s; + b0 = A * (A + 1 + (A - 1) * c + sq); + b1 = -2 * A * (A - 1 + (A + 1) * c); + b2 = A * (A + 1 + (A - 1) * c - sq); + a0 = A + 1 - (A - 1) * c + sq; + a1 = 2 * (A - 1 - (A + 1) * c); + a2 = A + 1 - (A - 1) * c - sq; + } else { + return 0; + } + const _a0 = 1 / a0; + const b0n = b0 * _a0, + b1n = b1 * _a0, + b2n = b2 * _a0; + const a1n = a1 * _a0, + a2n = a2 * _a0; + const cp = Math.cos(p), + c2p = Math.cos(2 * p); + const n = b0n * b0n + b1n * b1n + b2n * b2n + 2 * (b0n * b1n + b1n * b2n) * cp + 2 * b0n * b2n * c2p; + const d = 1 + a1n * a1n + a2n * a2n + 2 * (a1n + a1n * a2n) * cp + 2 * a2n * c2p; + return 10 * Math.log10(n / d); + } + /** * Clamp gain to valid range */ @@ -667,6 +730,8 @@ class AudioContextManager { this.freqRange = equalizerSettings.getFreqRange(); this.frequencies = generateFrequencies(this.bandCount, this.freqRange.min, this.freqRange.max); this.currentGains = equalizerSettings.getGains(this.bandCount); + this.currentTypes = equalizerSettings.getBandTypes(this.bandCount); + this.currentQs = equalizerSettings.getBandQs(this.bandCount); this.isMonoAudioEnabled = monoAudioSettings.isEnabled(); this.preamp = equalizerSettings.getPreamp(); } @@ -697,7 +762,88 @@ class AudioContextManager { } /** - * Called when the app enters the background (screen lock, app switch). + * Apply AutoEQ-generated bands to the equalizer + * Unlike regular presets, AutoEQ bands have specific frequencies, gains, and Q values + * @param {Array<{id: number, type: string, freq: number, gain: number, q: number, enabled: boolean}>} bands + * @returns {string} Exported text representation of the applied EQ + */ + applyAutoEQBands(bands, skipPreamp = false) { + if (!bands || bands.length === 0) return ''; + + const enabledBands = bands.filter((b) => b.enabled); + const count = Math.max(equalizerSettings.MIN_BANDS, Math.min(equalizerSettings.MAX_BANDS, enabledBands.length)); + + // Calculate preamp: negative of cumulative peak gain across all bands to prevent clipping + let cumulativePeak = 0; + if (!skipPreamp) { + const sr = this.audioContext?.sampleRate ?? 48000; + // Sweep log-spaced frequencies (24 points/octave from 20-20kHz) to catch narrow peaks + for (let f = 20; f <= 20000; f *= Math.pow(2, 1 / 24)) { + let sum = 0; + for (const b of enabledBands) { + sum += this._biquadResponseDb(f, b, sr); + } + if (sum > cumulativePeak) cumulativePeak = sum; + } + } + const preamp = skipPreamp + ? equalizerSettings.getPreamp() + : cumulativePeak > 0 + ? -Math.round(cumulativePeak * 10) / 10 + : 0; + + // Sort bands by frequency so index order is deterministic + const sortedBands = [...enabledBands].sort((a, b) => a.freq - b.freq); + + // Build normalized band descriptor arrays + const newFrequencies = sortedBands + .slice(0, count) + .map((b) => Math.round(Math.min(b.freq, (this.audioContext?.sampleRate ?? 48000) / 2 - 1))); + const newTypes = sortedBands.slice(0, count).map((b) => b.type || 'peaking'); + const newQs = sortedBands.slice(0, count).map((b) => b.q); + const newGains = sortedBands.slice(0, count).map((b) => this._clampGain(b.gain)); + + // Update band count via class setter to trigger equalizer-band-count-changed event + if (count !== this.bandCount) { + this.setBandCount(count); + } + + // Override frequencies, types, and Qs with band-specific values + this.frequencies = newFrequencies; + this.currentTypes = newTypes; + this.currentQs = newQs; + this.currentGains = newGains; + + // Rebuild EQ so _createEQ picks up the new types/Qs + if (this.isInitialized && this.audioContext) { + this._destroyEQ(); + this._createEQ(); + this._connectGraph(); + } + + // Apply preamp (skip if caller manages preamp externally) + if (!skipPreamp) { + this.setPreamp(preamp); + } + + // Persist normalized band descriptors to settings store + equalizerSettings.setGains(this.currentGains); + equalizerSettings.setBandTypes(this.currentTypes); + equalizerSettings.setBandQs(this.currentQs); + + // Generate export text using clamped gain values + const lines = [`Preamp: ${preamp.toFixed(1)} dB`]; + sortedBands.forEach((band, index) => { + if (index >= count) return; + const filterType = band.type === 'lowshelf' ? 'LS' : band.type === 'highshelf' ? 'HS' : 'PK'; + lines.push( + `Filter ${index + 1}: ON ${filterType} Fc ${newFrequencies[index]} Hz Gain ${newGains[index].toFixed(1)} dB Q ${newQs[index].toFixed(2)}` + ); + }); + + return lines.join('\n'); + } + /** * Export equalizer settings to text format * @returns {string} Exported settings in text format @@ -709,8 +855,13 @@ class AudioContextManager { this.frequencies.forEach((freq, index) => { const gain = this.currentGains[index] || 0; + const type = (this.currentTypes && this.currentTypes[index]) || 'peaking'; + const filterType = type === 'lowshelf' ? 'LS' : type === 'highshelf' ? 'HS' : 'PK'; + const q = this.currentQs && this.currentQs[index] > 0 ? this.currentQs[index] : this._calculateQ(index); const filterNum = index + 1; - lines.push(`Filter ${filterNum}: ON PK Fc ${freq} Hz Gain ${gain.toFixed(1)} dB Q 0.71`); + lines.push( + `Filter ${filterNum}: ON ${filterType} Fc ${freq} Hz Gain ${gain.toFixed(1)} dB Q ${q.toFixed(2)}` + ); }); return lines.join('\n'); @@ -760,24 +911,42 @@ class AudioContextManager { this.setPreamp(preamp); // If different number of bands, adjust - if (filters.length !== this.bandCount) { - const newCount = Math.max( - equalizerSettings.MIN_BANDS, - Math.min(equalizerSettings.MAX_BANDS, filters.length) - ); + const newCount = Math.max( + equalizerSettings.MIN_BANDS, + Math.min(equalizerSettings.MAX_BANDS, filters.length) + ); + if (newCount !== this.bandCount) { this.setBandCount(newCount); } - // Extract gains from filters - const gains = filters.slice(0, this.bandCount).map((f) => f.gain); - this.setAllGains(gains); + // Apply per-band frequencies, types, Qs, and gains from import + const sliced = filters.slice(0, this.bandCount); + const typeMap = { + PK: 'peaking', + LS: 'lowshelf', + LSC: 'lowshelf', + LSF: 'lowshelf', + HS: 'highshelf', + HSC: 'highshelf', + HSF: 'highshelf', + }; + this.frequencies = sliced.map((f) => f.freq); + this.currentTypes = sliced.map((f) => typeMap[f.type] || 'peaking'); + this.currentQs = sliced.map((f) => f.q); + this.currentGains = sliced.map((f) => this._clampGain(f.gain)); - // Store filter frequencies if different - const newFreqs = filters.slice(0, this.bandCount).map((f) => f.freq); - if (JSON.stringify(newFreqs) !== JSON.stringify(this.frequencies)) { - equalizerSettings.setFreqRange(newFreqs[0], newFreqs[newFreqs.length - 1]); + // Rebuild EQ chain to apply new frequencies, types, and Qs + if (this.isInitialized && this.audioContext) { + this._destroyEQ(); + this._createEQ(); + this._connectGraph(); } + // Persist all band settings + equalizerSettings.setGains(this.currentGains); + equalizerSettings.setBandTypes(this.currentTypes); + equalizerSettings.setBandQs(this.currentQs); + return true; } catch (e) { console.warn('[AudioContext] Failed to import EQ settings:', e); diff --git a/js/autoeq-data.js b/js/autoeq-data.js new file mode 100644 index 0000000..784fbfb --- /dev/null +++ b/js/autoeq-data.js @@ -0,0 +1,4396 @@ +// js/autoeq-data.js +// Target Curves & Data Parser - Ported from Seap Engine +// Contains target frequency response curves and raw data parser + + +// Raw content synchronized with features/autoeq/Targets/ directory + +const RAW_HARMAN_OE_2018 = ` +20.00 78.964 +20.36 78.969 +20.73 78.975 +21.11 78.980 +21.50 78.985 +21.89 78.990 +22.29 78.994 +22.69 78.998 +23.10 79.001 +23.52 79.004 +23.95 79.005 +24.39 79.006 +24.83 79.006 +25.28 79.005 +25.74 79.003 +26.21 79.000 +26.69 78.996 +27.18 78.992 +27.67 78.986 +28.17 78.980 +28.69 78.972 +29.21 78.964 +29.74 78.955 +30.28 78.945 +30.83 78.934 +31.39 78.923 +31.97 78.911 +32.55 78.898 +33.14 78.884 +33.74 78.870 +34.36 78.854 +34.98 78.838 +35.62 78.822 +36.27 78.804 +36.93 78.786 +37.60 78.766 +38.28 78.746 +38.98 78.725 +39.69 78.703 +40.41 78.680 +41.15 78.655 +41.90 78.630 +42.66 78.603 +43.44 78.575 +44.23 78.546 +45.03 78.516 +45.85 78.485 +46.68 78.452 +47.53 78.418 +48.40 78.383 +49.28 78.346 +50.18 78.309 +51.09 78.270 +52.02 78.230 +52.97 78.189 +53.93 78.147 +54.91 78.104 +55.91 78.060 +56.93 78.015 +57.97 77.969 +59.02 77.923 +60.09 77.875 +61.19 77.826 +62.30 77.776 +63.44 77.725 +64.59 77.674 +65.77 77.621 +66.96 77.567 +68.18 77.513 +69.42 77.457 +70.69 77.400 +71.97 77.342 +73.28 77.283 +74.62 77.223 +75.97 77.161 +77.36 77.099 +78.76 77.034 +80.20 76.969 +81.66 76.901 +83.14 76.833 +84.66 76.762 +86.20 76.690 +87.77 76.616 +89.36 76.541 +90.99 76.464 +92.65 76.385 +94.33 76.304 +96.05 76.222 +97.80 76.138 +99.58 76.053 +101.39 75.966 +103.23 75.878 +105.11 75.790 +107.03 75.700 +108.97 75.609 +110.96 75.518 +112.98 75.427 +115.03 75.335 +117.13 75.244 +119.26 75.152 +121.43 75.062 +123.64 74.972 +125.89 74.882 +128.18 74.794 +130.51 74.706 +132.89 74.620 +135.31 74.535 +137.77 74.452 +140.28 74.369 +142.83 74.288 +145.43 74.208 +148.07 74.130 +150.77 74.053 +153.51 73.977 +156.31 73.902 +159.15 73.828 +162.05 73.756 +165.00 73.685 +168.00 73.616 +171.06 73.549 +174.17 73.483 +177.34 73.420 +180.57 73.360 +183.86 73.302 +187.20 73.247 +190.61 73.196 +194.08 73.149 +197.61 73.105 +201.21 73.067 +204.87 73.032 +208.60 73.003 +212.39 72.979 +216.26 72.960 +220.19 72.946 +224.20 72.937 +228.28 72.934 +232.44 72.935 +236.67 72.942 +240.97 72.952 +245.36 72.968 +249.82 72.987 +254.37 73.010 +259.00 73.036 +263.71 73.065 +268.51 73.097 +273.40 73.132 +278.38 73.168 +283.44 73.205 +288.60 73.244 +293.85 73.283 +299.20 73.323 +304.65 73.364 +310.19 73.404 +315.84 73.443 +321.59 73.482 +327.44 73.520 +333.40 73.558 +339.47 73.593 +345.64 73.628 +351.93 73.661 +358.34 73.692 +364.86 73.722 +371.50 73.751 +378.26 73.778 +385.15 73.805 +392.16 73.830 +399.29 73.854 +406.56 73.877 +413.96 73.900 +421.49 73.923 +429.16 73.946 +436.97 73.969 +444.93 73.993 +453.02 74.017 +461.27 74.041 +469.66 74.067 +478.21 74.093 +486.91 74.119 +495.78 74.147 +504.80 74.175 +513.99 74.204 +523.34 74.234 +532.87 74.263 +542.56 74.293 +552.44 74.324 +562.49 74.354 +572.73 74.384 +583.15 74.415 +593.77 74.444 +604.57 74.474 +615.57 74.503 +626.78 74.531 +638.18 74.559 +649.80 74.585 +661.63 74.611 +673.67 74.635 +685.93 74.658 +698.41 74.680 +711.12 74.701 +724.06 74.720 +737.24 74.738 +750.66 74.754 +764.32 74.769 +778.23 74.783 +792.39 74.796 +806.82 74.808 +821.50 74.819 +836.45 74.830 +851.67 74.841 +867.17 74.852 +882.96 74.863 +899.02 74.876 +915.39 74.891 +932.05 74.907 +949.01 74.926 +966.28 74.948 +983.87 74.974 +1001.77 75.003 +1020.00 75.038 +1038.57 75.077 +1057.47 75.122 +1076.71 75.172 +1096.31 75.229 +1116.26 75.291 +1136.58 75.360 +1157.26 75.435 +1178.32 75.517 +1199.77 75.604 +1221.60 75.698 +1243.84 75.797 +1266.47 75.901 +1289.52 76.011 +1312.99 76.125 +1336.89 76.244 +1361.22 76.367 +1385.99 76.495 +1411.22 76.626 +1436.90 76.763 +1463.05 76.904 +1489.68 77.050 +1516.79 77.202 +1544.40 77.360 +1572.50 77.524 +1601.12 77.695 +1630.26 77.873 +1659.93 78.057 +1690.14 78.249 +1720.90 78.446 +1752.22 78.649 +1784.11 78.858 +1816.58 79.070 +1849.64 79.285 +1883.30 79.501 +1917.58 79.718 +1952.48 79.935 +1988.01 80.149 +2024.19 80.360 +2061.03 80.567 +2098.54 80.770 +2136.73 80.967 +2175.62 81.158 +2215.22 81.343 +2255.53 81.521 +2296.58 81.692 +2338.38 81.855 +2380.94 82.011 +2424.27 82.159 +2468.39 82.298 +2513.31 82.430 +2559.05 82.554 +2605.63 82.670 +2653.05 82.778 +2701.33 82.879 +2750.50 82.974 +2800.55 83.061 +2851.52 83.142 +2903.42 83.217 +2956.26 83.285 +3010.06 83.346 +3064.85 83.401 +3120.62 83.449 +3177.42 83.490 +3235.25 83.522 +3294.13 83.546 +3354.08 83.560 +3415.12 83.565 +3477.27 83.560 +3540.56 83.545 +3605.00 83.519 +3670.60 83.483 +3737.41 83.437 +3805.43 83.379 +3874.68 83.312 +3945.20 83.234 +4017.00 83.146 +4090.11 83.048 +4164.55 82.940 +4240.34 82.822 +4317.51 82.696 +4396.09 82.562 +4476.10 82.421 +4557.56 82.274 +4640.50 82.122 +4724.96 81.968 +4810.95 81.812 +4898.51 81.656 +4987.66 81.501 +5078.43 81.349 +5170.86 81.200 +5264.97 81.055 +5360.79 80.914 +5458.35 80.776 +5557.69 80.641 +5658.84 80.616 +5761.82 80.598 +5866.69 80.583 +5973.46 80.574 +6082.17 80.572 +6192.87 80.576 +6305.57 80.584 +6420.33 80.595 +6537.18 80.605 +6656.15 80.615 +6777.29 80.626 +6900.63 80.637 +7026.22 80.652 +7154.10 80.667 +7284.30 80.685 +7416.87 80.702 +7551.85 80.714 +7689.29 80.716 +7829.23 80.696 +7971.72 80.644 +8116.80 80.555 +8264.53 80.432 +8414.94 80.285 +8568.09 80.118 +8724.02 79.943 +8882.79 79.766 +9044.46 79.593 +9209.06 79.424 +9376.66 79.258 +9547.31 79.094 +9721.07 78.929 +9897.99 78.759 +10078.13 78.583 +10261.55 78.397 +10448.30 78.201 +10638.45 77.994 +10832.07 77.777 +11029.21 77.556 +11229.94 77.331 +11434.32 77.102 +11642.41 76.871 +11854.30 76.637 +12070.04 76.400 +12289.71 76.157 +12513.38 75.908 +12741.12 75.657 +12973.00 75.407 +13209.10 75.162 +13449.50 74.922 +13694.28 74.687 +13943.51 74.454 +14197.27 74.223 +14455.66 73.992 +14718.74 73.763 +14986.62 73.531 +15259.37 73.299 +15537.08 73.067 +15819.85 72.833 +16107.76 72.601 +16400.92 72.367 +16699.41 72.134 +17003.33 71.901 +17312.78 71.668 +17627.86 71.435 +17948.68 71.200 +18275.34 70.963 +18607.94 70.722 +18946.60 70.474 +19291.42 70.219 +19642.52 69.959 +20000.00 69.695 +`; + +const RAW_HARMAN_IE_2019 = ` +20.00 82.649 +20.36 82.656 +20.73 82.664 +21.11 82.671 +21.50 82.678 +21.89 82.685 +22.29 82.692 +22.69 82.699 +23.10 82.705 +23.52 82.711 +23.95 82.717 +24.39 82.722 +24.83 82.727 +25.28 82.731 +25.74 82.734 +26.21 82.737 +26.69 82.739 +27.18 82.741 +27.67 82.741 +28.17 82.741 +28.69 82.739 +29.21 82.737 +29.74 82.733 +30.28 82.728 +30.83 82.722 +31.39 82.714 +31.97 82.705 +32.55 82.694 +33.14 82.681 +33.74 82.666 +34.36 82.649 +34.98 82.630 +35.62 82.608 +36.27 82.584 +36.93 82.557 +37.60 82.528 +38.28 82.496 +38.98 82.461 +39.69 82.423 +40.41 82.382 +41.15 82.338 +41.90 82.291 +42.66 82.242 +43.44 82.189 +44.23 82.133 +45.03 82.074 +45.85 82.013 +46.68 81.949 +47.53 81.881 +48.40 81.812 +49.28 81.740 +50.18 81.665 +51.09 81.588 +52.02 81.510 +52.97 81.429 +53.93 81.346 +54.91 81.261 +55.91 81.174 +56.93 81.086 +57.97 80.995 +59.02 80.904 +60.09 80.811 +61.19 80.716 +62.30 80.620 +63.44 80.523 +64.59 80.425 +65.77 80.326 +66.96 80.225 +68.18 80.124 +69.42 80.022 +70.69 79.919 +71.97 79.816 +73.28 79.712 +74.62 79.607 +75.97 79.502 +77.36 79.396 +78.76 79.290 +80.20 79.183 +81.66 79.076 +83.14 78.968 +84.66 78.860 +86.20 78.751 +87.77 78.641 +89.36 78.531 +90.99 78.420 +92.65 78.309 +94.33 78.197 +96.05 78.085 +97.80 77.972 +99.58 77.859 +101.39 77.746 +103.23 77.633 +105.11 77.519 +107.03 77.406 +108.97 77.293 +110.96 77.180 +112.98 77.068 +115.03 76.956 +117.13 76.844 +119.26 76.733 +121.43 76.624 +123.64 76.515 +125.89 76.407 +128.18 76.300 +130.51 76.195 +132.89 76.091 +135.31 75.988 +137.77 75.887 +140.28 75.787 +142.83 75.688 +145.43 75.590 +148.07 75.494 +150.77 75.399 +153.51 75.304 +156.31 75.211 +159.15 75.119 +162.05 75.027 +165.00 74.937 +168.00 74.847 +171.06 74.759 +174.17 74.671 +177.34 74.585 +180.57 74.501 +183.86 74.418 +187.20 74.336 +190.61 74.257 +194.08 74.181 +197.61 74.106 +201.21 74.035 +204.87 73.966 +208.60 73.901 +212.39 73.838 +216.26 73.779 +220.19 73.724 +224.20 73.672 +228.28 73.623 +232.44 73.578 +236.67 73.537 +240.97 73.499 +245.36 73.465 +249.82 73.435 +254.37 73.407 +259.00 73.383 +263.71 73.362 +268.51 73.344 +273.40 73.329 +278.38 73.317 +283.44 73.307 +288.60 73.299 +293.85 73.293 +299.20 73.290 +304.65 73.288 +310.19 73.289 +315.84 73.290 +321.59 73.293 +327.44 73.297 +333.40 73.303 +339.47 73.309 +345.64 73.317 +351.93 73.325 +358.34 73.334 +364.86 73.344 +371.50 73.354 +378.26 73.365 +385.15 73.377 +392.16 73.389 +399.29 73.401 +406.56 73.414 +413.96 73.428 +421.49 73.442 +429.16 73.456 +436.97 73.472 +444.93 73.487 +453.02 73.504 +461.27 73.520 +469.66 73.538 +478.21 73.556 +486.91 73.574 +495.78 73.593 +504.80 73.613 +513.99 73.633 +523.34 73.653 +532.87 73.675 +542.56 73.696 +552.44 73.719 +562.49 73.742 +572.73 73.766 +583.15 73.791 +593.77 73.817 +604.57 73.843 +615.57 73.871 +626.78 73.899 +638.18 73.928 +649.80 73.959 +661.63 73.990 +673.67 74.022 +685.93 74.054 +698.41 74.088 +711.12 74.122 +724.06 74.157 +737.24 74.193 +750.66 74.229 +764.32 74.266 +778.23 74.305 +792.39 74.344 +806.82 74.384 +821.50 74.425 +836.45 74.468 +851.67 74.512 +867.17 74.558 +882.96 74.606 +899.02 74.655 +915.39 74.707 +932.05 74.762 +949.01 74.819 +966.28 74.878 +983.87 74.941 +1001.77 75.007 +1020.00 75.076 +1038.57 75.149 +1057.47 75.226 +1076.71 75.308 +1096.31 75.394 +1116.26 75.484 +1136.58 75.579 +1157.26 75.679 +1178.32 75.784 +1199.77 75.894 +1221.60 76.008 +1243.84 76.126 +1266.47 76.249 +1289.52 76.375 +1312.99 76.505 +1336.89 76.638 +1361.22 76.773 +1385.99 76.912 +1411.22 77.054 +1436.90 77.198 +1463.05 77.346 +1489.68 77.499 +1516.79 77.655 +1544.40 77.817 +1572.50 77.984 +1601.12 78.157 +1630.26 78.337 +1659.93 78.524 +1690.14 78.717 +1720.90 78.918 +1752.22 79.125 +1784.11 79.338 +1816.58 79.556 +1849.64 79.779 +1883.30 80.006 +1917.58 80.235 +1952.48 80.466 +1988.01 80.697 +2024.19 80.928 +2061.03 81.157 +2098.54 81.384 +2136.73 81.608 +2175.62 81.828 +2215.22 82.042 +2255.53 82.252 +2296.58 82.454 +2338.38 82.650 +2380.94 82.837 +2424.27 83.016 +2468.39 83.185 +2513.31 83.344 +2559.05 83.492 +2605.63 83.630 +2653.05 83.757 +2701.33 83.872 +2750.50 83.976 +2800.55 84.069 +2851.52 84.150 +2903.42 84.220 +2956.26 84.280 +3010.06 84.328 +3064.85 84.366 +3120.62 84.393 +3177.42 84.410 +3235.25 84.417 +3294.13 84.414 +3354.08 84.402 +3415.12 84.380 +3477.27 84.350 +3540.56 84.312 +3605.00 84.266 +3670.60 84.214 +3737.41 84.156 +3805.43 84.093 +3874.68 84.025 +3945.20 83.952 +4017.00 83.875 +4090.11 83.795 +4164.55 83.711 +4240.34 83.624 +4317.51 83.534 +4396.09 83.442 +4476.10 83.347 +4557.56 83.251 +4640.50 83.154 +4724.96 83.056 +4810.95 82.958 +4898.51 82.860 +4987.66 82.763 +5078.43 82.667 +5170.86 82.572 +5264.97 82.477 +5360.79 82.383 +5458.35 82.288 +5557.69 82.192 +5658.84 82.093 +5761.82 81.991 +5866.69 81.884 +5973.46 81.771 +6082.17 81.651 +6192.87 81.523 +6305.57 81.386 +6420.33 81.239 +6537.18 81.083 +6656.15 80.915 +6777.29 80.737 +6900.63 80.547 +7026.22 80.345 +7154.10 80.132 +7284.30 79.908 +7416.87 79.674 +7551.85 79.429 +7689.29 79.174 +7829.23 78.910 +7971.72 78.634 +8116.80 78.348 +8264.53 78.050 +8414.94 77.740 +8568.09 77.418 +8724.02 77.084 +8882.79 76.740 +9044.46 76.385 +9209.06 76.021 +9376.66 75.649 +9547.31 75.272 +9721.07 74.891 +9897.99 74.507 +10078.13 74.123 +10261.55 73.739 +10448.30 73.355 +10638.45 72.971 +10832.07 72.588 +11029.21 72.205 +11229.94 71.823 +11434.32 71.442 +11642.41 71.063 +11854.30 70.687 +12070.04 70.315 +12289.71 69.946 +12513.38 69.583 +12741.12 69.225 +12973.00 68.875 +13209.10 68.535 +13449.50 68.206 +13694.28 67.894 +13943.51 67.600 +14197.27 67.328 +14455.66 67.083 +14718.74 66.865 +14986.62 66.675 +15259.37 66.509 +15537.08 66.358 +15819.85 66.205 +16107.76 66.020 +16400.92 65.757 +16699.41 65.348 +17003.33 64.704 +17312.78 63.707 +17627.86 62.214 +17948.68 60.060 +18275.34 57.070 +18607.94 53.077 +18946.60 47.955 +19291.42 41.677 +19642.52 34.385 +20000.00 26.435 +`; + +const RAW_DIFFUSE_FIELD = ` +20.00 71.058 +20.36 71.058 +20.73 71.058 +21.11 71.058 +21.50 71.058 +21.89 71.058 +22.29 71.058 +22.69 71.058 +23.10 71.058 +23.52 71.058 +23.95 71.058 +24.39 71.058 +24.83 71.058 +25.28 71.058 +25.74 71.058 +26.21 71.058 +26.69 71.058 +27.18 71.058 +27.67 71.058 +28.17 71.058 +28.69 71.058 +29.21 71.058 +29.74 71.058 +30.28 71.058 +30.83 71.058 +31.39 71.058 +31.97 71.058 +32.55 71.058 +33.14 71.058 +33.74 71.058 +34.36 71.058 +34.98 71.058 +35.62 71.058 +36.27 71.058 +36.93 71.058 +37.60 71.058 +38.28 71.058 +38.98 71.058 +39.69 71.058 +40.41 71.058 +41.15 71.058 +41.90 71.058 +42.66 71.058 +43.44 71.058 +44.23 71.058 +45.03 71.058 +45.85 71.058 +46.68 71.058 +47.53 71.058 +48.40 71.058 +49.28 71.058 +50.18 71.058 +51.09 71.058 +52.02 71.058 +52.97 71.058 +53.93 71.058 +54.91 71.058 +55.91 71.058 +56.93 71.058 +57.97 71.058 +59.02 71.058 +60.09 71.058 +61.19 71.058 +62.30 71.058 +63.44 71.058 +64.59 71.058 +65.77 71.058 +66.96 71.058 +68.18 71.058 +69.42 71.058 +70.69 71.058 +71.97 71.058 +73.28 71.058 +74.62 71.058 +75.97 71.058 +77.36 71.058 +78.76 71.058 +80.20 71.058 +81.66 71.058 +83.14 71.058 +84.66 71.057 +86.20 71.057 +87.77 71.057 +89.36 71.056 +90.99 71.056 +92.65 71.055 +94.33 71.055 +96.05 71.055 +97.80 71.056 +99.58 71.058 +101.39 71.061 +103.23 71.066 +105.11 71.072 +107.03 71.081 +108.97 71.091 +110.96 71.102 +112.98 71.113 +115.03 71.124 +117.13 71.134 +119.26 71.142 +121.43 71.149 +123.64 71.155 +125.89 71.161 +128.18 71.167 +130.51 71.174 +132.89 71.182 +135.31 71.192 +137.77 71.204 +140.28 71.216 +142.83 71.229 +145.43 71.241 +148.07 71.254 +150.77 71.267 +153.51 71.281 +156.31 71.297 +159.15 71.313 +162.05 71.331 +165.00 71.349 +168.00 71.368 +171.06 71.388 +174.17 71.408 +177.34 71.429 +180.57 71.449 +183.86 71.469 +187.20 71.489 +190.61 71.509 +194.08 71.529 +197.61 71.549 +201.21 71.568 +204.87 71.587 +208.60 71.607 +212.39 71.627 +216.26 71.646 +220.19 71.665 +224.20 71.684 +228.28 71.704 +232.44 71.724 +236.67 71.745 +240.97 71.765 +245.36 71.786 +249.82 71.808 +254.37 71.831 +259.00 71.854 +263.71 71.877 +268.51 71.900 +273.40 71.924 +278.38 71.947 +283.44 71.969 +288.60 71.991 +293.85 72.011 +299.20 72.031 +304.65 72.049 +310.19 72.065 +315.84 72.081 +321.59 72.096 +327.44 72.110 +333.40 72.123 +339.47 72.135 +345.64 72.147 +351.93 72.158 +358.34 72.170 +364.86 72.184 +371.50 72.200 +378.26 72.219 +385.15 72.242 +392.16 72.269 +399.29 72.301 +406.56 72.340 +413.96 72.385 +421.49 72.436 +429.16 72.493 +436.97 72.554 +444.93 72.619 +453.02 72.684 +461.27 72.750 +469.66 72.815 +478.21 72.879 +486.91 72.942 +495.78 73.002 +504.80 73.060 +513.99 73.115 +523.34 73.169 +532.87 73.221 +542.56 73.273 +552.44 73.325 +562.49 73.377 +572.73 73.430 +583.15 73.483 +593.77 73.537 +604.57 73.592 +615.57 73.646 +626.78 73.701 +638.18 73.756 +649.80 73.812 +661.63 73.868 +673.67 73.925 +685.93 73.982 +698.41 74.039 +711.12 74.095 +724.06 74.151 +737.24 74.205 +750.66 74.258 +764.32 74.309 +778.23 74.358 +792.39 74.404 +806.82 74.446 +821.50 74.486 +836.45 74.524 +851.67 74.561 +867.17 74.597 +882.96 74.635 +899.02 74.674 +915.39 74.717 +932.05 74.764 +949.01 74.815 +966.28 74.873 +983.87 74.937 +1001.77 75.008 +1020.00 75.088 +1038.57 75.175 +1057.47 75.271 +1076.71 75.373 +1096.31 75.482 +1116.26 75.595 +1136.58 75.714 +1157.26 75.836 +1178.32 75.962 +1199.77 76.092 +1221.60 76.226 +1243.84 76.364 +1266.47 76.505 +1289.52 76.649 +1312.99 76.795 +1336.89 76.942 +1361.22 77.092 +1385.99 77.242 +1411.22 77.396 +1436.90 77.553 +1463.05 77.714 +1489.68 77.882 +1516.79 78.058 +1544.40 78.243 +1572.50 78.440 +1601.12 78.650 +1630.26 78.873 +1659.93 79.109 +1690.14 79.357 +1720.90 79.617 +1752.22 79.886 +1784.11 80.162 +1816.58 80.445 +1849.64 80.734 +1883.30 81.025 +1917.58 81.320 +1952.48 81.618 +1988.01 81.918 +2024.19 82.221 +2061.03 82.528 +2098.54 82.840 +2136.73 83.158 +2175.62 83.482 +2215.22 83.811 +2255.53 84.142 +2296.58 84.472 +2338.38 84.794 +2380.94 85.105 +2424.27 85.398 +2468.39 85.667 +2513.31 85.910 +2559.05 86.121 +2605.63 86.298 +2653.05 86.442 +2701.33 86.552 +2750.50 86.631 +2800.55 86.683 +2851.52 86.710 +2903.42 86.713 +2956.26 86.695 +3010.06 86.656 +3064.85 86.596 +3120.62 86.515 +3177.42 86.415 +3235.25 86.294 +3294.13 86.154 +3354.08 85.998 +3415.12 85.828 +3477.27 85.648 +3540.56 85.458 +3605.00 85.262 +3670.60 85.060 +3737.41 84.854 +3805.43 84.645 +3874.68 84.434 +3945.20 84.222 +4017.00 84.008 +4090.11 83.795 +4164.55 83.582 +4240.34 83.370 +4317.51 83.160 +4396.09 82.954 +4476.10 82.754 +4557.56 82.560 +4640.50 82.376 +4724.96 82.201 +4810.95 82.037 +4898.51 81.886 +4987.66 81.746 +5078.43 81.619 +5170.86 81.503 +5264.97 81.397 +5360.79 81.298 +5458.35 81.206 +5557.69 81.119 +5658.84 81.037 +5761.82 80.961 +5866.69 80.891 +5973.46 80.826 +6082.17 80.768 +6192.87 80.718 +6305.57 80.674 +6420.33 80.636 +6537.18 80.604 +6656.15 80.576 +6777.29 80.551 +6900.63 80.530 +7026.22 80.510 +7154.10 80.489 +7284.30 80.466 +7416.87 80.436 +7551.85 80.397 +7689.29 80.345 +7829.23 80.277 +7971.72 80.190 +8116.80 80.082 +8264.53 79.953 +8414.94 79.806 +8568.09 79.643 +8724.02 79.470 +8882.79 79.288 +9044.46 79.102 +9209.06 78.915 +9376.66 78.726 +9547.31 78.535 +9721.07 78.343 +9897.99 78.148 +10078.13 77.949 +10261.55 77.745 +10448.30 77.536 +10638.45 77.321 +10832.07 77.100 +11029.21 76.874 +11229.94 76.642 +11434.32 76.407 +11642.41 76.167 +11854.30 75.924 +12070.04 75.678 +12289.71 75.430 +12513.38 75.179 +12741.12 74.927 +12973.00 74.675 +13209.10 74.425 +13449.50 74.178 +13694.28 73.933 +13943.51 73.692 +14197.27 73.452 +14455.66 73.215 +14718.74 72.978 +14986.62 72.742 +15259.37 72.507 +15537.08 72.272 +15819.85 72.037 +16107.76 71.801 +16400.92 71.566 +16699.41 71.329 +17003.33 71.092 +17312.78 70.854 +17627.86 70.614 +17948.68 70.373 +18275.34 70.130 +18607.94 69.885 +18946.60 69.639 +19291.42 69.390 +19642.52 69.141 +20000.00 68.891 +`; + +const RAW_KNOWLES = ` +20.00 82.754 +20.36 82.754 +20.73 82.755 +21.11 82.755 +21.50 82.755 +21.89 82.756 +22.29 82.756 +22.69 82.757 +23.10 82.758 +23.52 82.759 +23.95 82.760 +24.39 82.761 +24.83 82.762 +25.28 82.762 +25.74 82.763 +26.21 82.763 +26.69 82.763 +27.18 82.762 +27.67 82.761 +28.17 82.760 +28.69 82.758 +29.21 82.756 +29.74 82.753 +30.28 82.750 +30.83 82.746 +31.39 82.741 +31.97 82.736 +32.55 82.731 +33.14 82.725 +33.74 82.719 +34.36 82.712 +34.98 82.702 +35.62 82.689 +36.27 82.670 +36.93 82.647 +37.60 82.620 +38.28 82.591 +38.98 82.563 +39.69 82.533 +40.41 82.502 +41.15 82.469 +41.90 82.433 +42.66 82.394 +43.44 82.348 +44.23 82.294 +45.03 82.231 +45.85 82.160 +46.68 82.081 +47.53 81.996 +48.40 81.908 +49.28 81.819 +50.18 81.732 +51.09 81.649 +52.02 81.570 +52.97 81.493 +53.93 81.417 +54.91 81.341 +55.91 81.262 +56.93 81.180 +57.97 81.094 +59.02 81.004 +60.09 80.911 +61.19 80.817 +62.30 80.721 +63.44 80.623 +64.59 80.521 +65.77 80.414 +66.96 80.305 +68.18 80.196 +69.42 80.091 +70.69 79.989 +71.97 79.890 +73.28 79.791 +74.62 79.691 +75.97 79.591 +77.36 79.488 +78.76 79.383 +80.20 79.275 +81.66 79.165 +83.14 79.055 +84.66 78.946 +86.20 78.836 +87.77 78.727 +89.36 78.618 +90.99 78.508 +92.65 78.397 +94.33 78.286 +96.05 78.172 +97.80 78.058 +99.58 77.942 +101.39 77.825 +103.23 77.709 +105.11 77.592 +107.03 77.477 +108.97 77.364 +110.96 77.253 +112.98 77.142 +115.03 77.031 +117.13 76.917 +119.26 76.803 +121.43 76.691 +123.64 76.583 +125.89 76.483 +128.18 76.389 +130.51 76.297 +132.89 76.207 +135.31 76.116 +137.77 76.024 +140.28 75.929 +142.83 75.833 +145.43 75.734 +148.07 75.632 +150.77 75.528 +153.51 75.423 +156.31 75.319 +159.15 75.219 +162.05 75.123 +165.00 75.030 +168.00 74.941 +171.06 74.854 +174.17 74.769 +177.34 74.686 +180.57 74.603 +183.86 74.521 +187.20 74.438 +190.61 74.355 +194.08 74.271 +197.61 74.188 +201.21 74.107 +204.87 74.028 +208.60 73.951 +212.39 73.875 +216.26 73.801 +220.19 73.730 +224.20 73.667 +228.28 73.618 +232.44 73.581 +236.67 73.555 +240.97 73.537 +245.36 73.524 +249.82 73.514 +254.37 73.506 +259.00 73.496 +263.71 73.484 +268.51 73.471 +273.40 73.456 +278.38 73.440 +283.44 73.423 +288.60 73.407 +293.85 73.390 +299.20 73.374 +304.65 73.359 +310.19 73.345 +315.84 73.333 +321.59 73.324 +327.44 73.321 +333.40 73.324 +339.47 73.335 +345.64 73.355 +351.93 73.381 +358.34 73.409 +364.86 73.439 +371.50 73.466 +378.26 73.488 +385.15 73.504 +392.16 73.514 +399.29 73.521 +406.56 73.525 +413.96 73.527 +421.49 73.529 +429.16 73.533 +436.97 73.541 +444.93 73.554 +453.02 73.571 +461.27 73.590 +469.66 73.610 +478.21 73.628 +486.91 73.642 +495.78 73.652 +504.80 73.660 +513.99 73.666 +523.34 73.672 +532.87 73.680 +542.56 73.691 +552.44 73.705 +562.49 73.724 +572.73 73.748 +583.15 73.777 +593.77 73.809 +604.57 73.845 +615.57 73.883 +626.78 73.924 +638.18 73.964 +649.80 74.003 +661.63 74.037 +673.67 74.067 +685.93 74.095 +698.41 74.121 +711.12 74.147 +724.06 74.174 +737.24 74.202 +750.66 74.232 +764.32 74.264 +778.23 74.298 +792.39 74.332 +806.82 74.365 +821.50 74.400 +836.45 74.436 +851.67 74.474 +867.17 74.516 +882.96 74.563 +899.02 74.614 +915.39 74.671 +932.05 74.732 +949.01 74.797 +966.28 74.865 +983.87 74.935 +1001.77 75.007 +1020.00 75.079 +1038.57 75.152 +1057.47 75.227 +1076.71 75.307 +1096.31 75.393 +1116.26 75.487 +1136.58 75.586 +1157.26 75.689 +1178.32 75.797 +1199.77 75.908 +1221.60 76.022 +1243.84 76.139 +1266.47 76.260 +1289.52 76.385 +1312.99 76.514 +1336.89 76.649 +1361.22 76.790 +1385.99 76.935 +1411.22 77.087 +1436.90 77.244 +1463.05 77.402 +1489.68 77.559 +1516.79 77.712 +1544.40 77.863 +1572.50 78.014 +1601.12 78.167 +1630.26 78.324 +1659.93 78.490 +1690.14 78.666 +1720.90 78.856 +1752.22 79.059 +1784.11 79.276 +1816.58 79.505 +1849.64 79.744 +1883.30 79.988 +1917.58 80.236 +1952.48 80.483 +1988.01 80.726 +2024.19 80.967 +2061.03 81.205 +2098.54 81.441 +2136.73 81.675 +2175.62 81.908 +2215.22 82.138 +2255.53 82.364 +2296.58 82.580 +2338.38 82.780 +2380.94 82.966 +2424.27 83.141 +2468.39 83.310 +2513.31 83.473 +2559.05 83.631 +2605.63 83.779 +2653.05 83.915 +2701.33 84.038 +2750.50 84.149 +2800.55 84.247 +2851.52 84.334 +2903.42 84.410 +2956.26 84.475 +3010.06 84.532 +3064.85 84.581 +3120.62 84.619 +3177.42 84.645 +3235.25 84.660 +3294.13 84.662 +3354.08 84.654 +3415.12 84.637 +3477.27 84.610 +3540.56 84.575 +3605.00 84.529 +3670.60 84.473 +3737.41 84.410 +3805.43 84.342 +3874.68 84.274 +3945.20 84.207 +4017.00 84.140 +4090.11 84.072 +4164.55 84.001 +4240.34 83.927 +4317.51 83.852 +4396.09 83.775 +4476.10 83.697 +4557.56 83.618 +4640.50 83.536 +4724.96 83.450 +4810.95 83.361 +4898.51 83.271 +4987.66 83.182 +5078.43 83.097 +5170.86 83.015 +5264.97 82.937 +5360.79 82.864 +5458.35 82.795 +5557.69 82.731 +5658.84 82.670 +5761.82 82.610 +5866.69 82.548 +5973.46 82.478 +6082.17 82.401 +6192.87 82.315 +6305.57 82.223 +6420.33 82.122 +6537.18 82.011 +6656.15 81.889 +6777.29 81.754 +6900.63 81.607 +7026.22 81.450 +7154.10 81.285 +7284.30 81.115 +7416.87 80.939 +7551.85 80.758 +7689.29 80.573 +7829.23 80.381 +7971.72 80.181 +8116.80 79.970 +8264.53 79.744 +8414.94 79.504 +8568.09 79.251 +8724.02 78.986 +8882.79 78.715 +9044.46 78.444 +9209.06 78.175 +9376.66 77.913 +9547.31 77.658 +9721.07 77.413 +9897.99 77.181 +10078.13 76.963 +10261.55 76.760 +10448.30 76.567 +10638.45 76.381 +10832.07 76.202 +11029.21 76.032 +11229.94 75.870 +11434.32 75.722 +11642.41 75.594 +11854.30 75.493 +12070.04 75.424 +12289.71 75.391 +12513.38 75.397 +12741.12 75.443 +12973.00 75.528 +13209.10 75.650 +13449.50 75.804 +13694.28 75.985 +13943.51 76.181 +14197.27 76.380 +14455.66 76.567 +14718.74 76.723 +14986.62 76.824 +15259.37 76.846 +15537.08 76.765 +15819.85 76.557 +16107.76 76.196 +16400.92 75.664 +16699.41 74.955 +17003.33 74.069 +17312.78 73.022 +17627.86 71.842 +17948.68 70.566 +18275.34 69.231 +18607.94 67.864 +18946.60 66.480 +19291.42 65.085 +19642.52 63.676 +20000.00 62.254 +`; + +const RAW_MOONDROP_VDSF = ` +20.00 77.103 +20.36 77.093 +20.73 77.083 +21.11 77.073 +21.50 77.062 +21.89 77.051 +22.29 77.039 +22.69 77.026 +23.10 77.013 +23.52 76.999 +23.95 76.985 +24.39 76.971 +24.83 76.957 +25.28 76.943 +25.74 76.930 +26.21 76.917 +26.69 76.906 +27.18 76.895 +27.67 76.884 +28.17 76.875 +28.69 76.867 +29.21 76.859 +29.74 76.852 +30.28 76.845 +30.83 76.839 +31.39 76.834 +31.97 76.829 +32.55 76.824 +33.14 76.819 +33.74 76.815 +34.36 76.811 +34.98 76.807 +35.62 76.803 +36.27 76.799 +36.93 76.795 +37.60 76.791 +38.28 76.787 +38.98 76.783 +39.69 76.779 +40.41 76.774 +41.15 76.770 +41.90 76.765 +42.66 76.759 +43.44 76.753 +44.23 76.747 +45.03 76.740 +45.85 76.731 +46.68 76.722 +47.53 76.711 +48.40 76.698 +49.28 76.684 +50.18 76.667 +51.09 76.649 +52.02 76.628 +52.97 76.604 +53.93 76.579 +54.91 76.551 +55.91 76.521 +56.93 76.489 +57.97 76.455 +59.02 76.420 +60.09 76.385 +61.19 76.349 +62.30 76.312 +63.44 76.277 +64.59 76.242 +65.77 76.208 +66.96 76.176 +68.18 76.146 +69.42 76.118 +70.69 76.091 +71.97 76.067 +73.28 76.044 +74.62 76.023 +75.97 76.004 +77.36 75.986 +78.76 75.969 +80.20 75.952 +81.66 75.935 +83.14 75.918 +84.66 75.900 +86.20 75.881 +87.77 75.861 +89.36 75.839 +90.99 75.816 +92.65 75.790 +94.33 75.763 +96.05 75.733 +97.80 75.702 +99.58 75.668 +101.39 75.633 +103.23 75.597 +105.11 75.559 +107.03 75.520 +108.97 75.481 +110.96 75.442 +112.98 75.404 +115.03 75.366 +117.13 75.329 +119.26 75.294 +121.43 75.260 +123.64 75.228 +125.89 75.198 +128.18 75.170 +130.51 75.143 +132.89 75.118 +135.31 75.093 +137.77 75.069 +140.28 75.045 +142.83 75.020 +145.43 74.993 +148.07 74.965 +150.77 74.934 +153.51 74.901 +156.31 74.863 +159.15 74.822 +162.05 74.777 +165.00 74.728 +168.00 74.674 +171.06 74.616 +174.17 74.555 +177.34 74.490 +180.57 74.423 +183.86 74.355 +187.20 74.286 +190.61 74.219 +194.08 74.153 +197.61 74.091 +201.21 74.033 +204.87 73.981 +208.60 73.934 +212.39 73.894 +216.26 73.860 +220.19 73.832 +224.20 73.810 +228.28 73.793 +232.44 73.782 +236.67 73.775 +240.97 73.772 +245.36 73.772 +249.82 73.776 +254.37 73.784 +259.00 73.794 +263.71 73.806 +268.51 73.822 +273.40 73.840 +278.38 73.860 +283.44 73.883 +288.60 73.906 +293.85 73.931 +299.20 73.957 +304.65 73.982 +310.19 74.007 +315.84 74.030 +321.59 74.051 +327.44 74.071 +333.40 74.088 +339.47 74.102 +345.64 74.115 +351.93 74.125 +358.34 74.134 +364.86 74.141 +371.50 74.148 +378.26 74.153 +385.15 74.157 +392.16 74.162 +399.29 74.165 +406.56 74.169 +413.96 74.173 +421.49 74.176 +429.16 74.179 +436.97 74.183 +444.93 74.187 +453.02 74.191 +461.27 74.191 +469.66 74.201 +478.21 74.208 +486.91 74.216 +495.78 74.226 +504.80 74.239 +513.99 74.254 +523.34 74.273 +532.87 74.294 +542.56 74.320 +552.44 74.348 +562.49 74.380 +572.73 74.414 +583.15 74.451 +593.77 74.489 +604.57 74.528 +615.57 74.568 +626.78 74.607 +638.18 74.646 +649.80 74.683 +661.63 74.717 +673.67 74.749 +685.93 74.777 +698.41 74.802 +711.12 74.823 +724.06 74.841 +737.24 74.855 +750.66 74.867 +764.32 74.877 +778.23 74.885 +792.39 74.891 +806.82 74.896 +821.50 74.901 +836.45 74.906 +851.67 74.910 +867.17 74.914 +882.96 74.919 +899.02 74.925 +915.39 74.891 +932.05 74.907 +949.01 74.926 +966.28 74.948 +983.87 74.974 +1001.77 75.002 +1020.00 75.026 +1038.57 75.055 +1057.47 75.088 +1076.71 75.126 +1096.31 75.169 +1116.26 75.218 +1136.58 75.272 +1157.26 75.332 +1178.32 75.400 +1199.77 75.475 +1221.60 75.559 +1243.84 75.652 +1266.47 75.754 +1289.52 75.866 +1312.99 75.987 +1336.89 76.116 +1361.22 76.253 +1385.99 76.396 +1411.22 76.546 +1436.90 76.700 +1463.05 76.859 +1489.68 77.021 +1516.79 77.187 +1544.40 77.357 +1572.50 77.530 +1601.12 77.707 +1630.26 77.887 +1659.93 78.071 +1690.14 78.257 +1720.90 78.446 +1752.22 78.636 +1784.11 78.829 +1816.58 79.024 +1849.64 79.220 +1883.30 79.419 +1917.58 79.620 +1952.48 79.823 +1988.01 80.029 +2024.19 80.237 +2061.03 80.447 +2098.54 80.658 +2136.73 80.869 +2175.62 81.077 +2215.22 81.282 +2255.53 81.481 +2296.58 81.673 +2338.38 81.857 +2380.94 82.030 +2424.27 82.193 +2468.39 82.344 +2513.31 82.483 +2559.05 82.609 +2605.63 82.724 +2653.05 82.826 +2701.33 82.915 +2750.50 82.993 +2800.55 83.059 +2851.52 83.112 +2903.42 83.154 +2956.26 83.182 +3010.06 83.198 +3064.85 83.198 +3120.62 83.184 +3177.42 83.153 +3235.25 83.106 +3294.13 83.041 +3354.08 82.959 +3415.12 82.861 +3477.27 82.749 +3540.56 82.623 +3605.00 82.487 +3670.60 82.343 +3737.41 82.194 +3805.43 82.041 +3874.68 81.887 +3945.20 81.732 +4017.00 81.578 +4090.11 81.422 +4164.55 81.266 +4240.34 81.107 +4317.51 80.944 +4396.09 80.776 +4476.10 80.603 +4557.56 80.423 +4640.50 80.237 +4724.96 80.044 +4810.95 79.846 +4898.51 79.644 +4987.66 79.439 +5078.43 79.232 +5170.86 79.025 +5264.97 78.819 +5360.79 78.616 +5458.35 78.416 +5557.69 78.220 +5658.84 78.029 +5761.82 77.843 +5866.69 77.660 +5973.46 77.482 +6082.17 77.306 +6192.87 77.133 +6305.57 76.961 +6420.33 76.791 +6537.18 76.620 +6656.15 76.451 +6777.29 76.282 +6900.63 76.114 +7026.22 75.951 +7154.10 75.793 +7284.30 75.644 +7416.87 75.508 +7551.85 75.388 +7689.29 75.287 +7829.23 75.208 +7971.72 75.154 +8116.80 75.124 +8264.53 75.117 +8414.94 75.131 +8568.09 75.160 +8724.02 75.198 +8882.79 75.237 +9044.46 75.268 +9209.06 75.282 +9376.66 75.270 +9547.31 75.221 +9721.07 75.129 +9897.99 74.985 +10078.13 74.787 +10261.55 74.530 +10448.30 74.217 +10638.45 73.851 +10832.07 73.438 +11029.21 72.988 +11229.94 72.513 +11434.32 72.025 +11642.41 71.538 +11854.30 71.065 +12070.04 70.618 +12289.71 70.205 +12513.38 69.833 +12741.12 69.503 +12973.00 69.214 +13209.10 68.963 +13449.50 68.744 +13694.28 68.548 +13943.51 68.369 +14197.27 68.200 +14455.66 68.034 +14718.74 67.867 +14986.62 67.694 +15259.37 67.511 +15537.08 67.312 +15819.85 67.090 +16107.76 66.834 +16400.92 66.527 +16699.41 66.147 +17003.33 65.661 +17312.78 65.026 +17627.86 64.186 +17948.68 63.076 +18275.34 61.615 +18607.94 59.722 +18946.60 57.322 +19291.42 54.366 +19642.52 50.866 +20000.00 46.930 +`; + +const RAW_HIFI_ENDGAME_2026 = ` +20.00 84.736 +20.36 84.724 +20.73 84.712 +21.11 84.699 +21.50 84.687 +21.89 84.674 +22.29 84.662 +22.69 84.649 +23.10 84.635 +23.52 84.622 +23.95 84.608 +24.39 84.593 +24.83 84.579 +25.28 84.563 +25.74 84.548 +26.21 84.532 +26.69 84.515 +27.18 84.498 +27.67 84.480 +28.17 84.461 +28.69 84.442 +29.21 84.423 +29.74 84.403 +30.28 84.382 +30.83 84.360 +31.39 84.338 +31.97 84.315 +32.55 84.292 +33.14 84.268 +33.74 84.243 +34.36 84.217 +34.98 84.191 +35.62 84.164 +36.27 84.136 +36.93 84.107 +37.60 84.078 +38.28 84.047 +38.98 84.016 +39.69 83.984 +40.41 83.951 +41.15 83.918 +41.90 83.883 +42.66 83.848 +43.44 83.811 +44.23 83.774 +45.03 83.736 +45.85 83.696 +46.68 83.656 +47.53 83.615 +48.40 83.573 +49.28 83.530 +50.18 83.486 +51.09 83.441 +52.02 83.395 +52.97 83.347 +53.93 83.299 +54.91 83.250 +55.91 83.200 +56.93 83.149 +57.97 83.096 +59.02 83.043 +60.09 82.988 +61.19 82.933 +62.30 82.877 +63.44 82.819 +64.59 82.760 +65.77 82.701 +66.96 82.640 +68.18 82.578 +69.42 82.516 +70.69 82.452 +71.97 82.387 +73.28 82.321 +74.62 82.254 +75.97 82.187 +77.36 82.118 +78.76 82.048 +80.20 81.977 +81.66 81.905 +83.14 81.833 +84.66 81.759 +86.20 81.685 +87.77 81.610 +89.36 81.534 +90.99 81.458 +92.65 81.381 +94.33 81.304 +96.05 81.227 +97.80 81.149 +99.58 81.072 +101.39 80.995 +103.23 80.919 +105.11 80.842 +107.03 80.767 +108.97 80.692 +110.96 80.616 +112.98 80.541 +115.03 80.465 +117.13 80.388 +119.26 80.310 +121.43 80.232 +123.64 80.153 +125.89 80.073 +128.18 79.993 +130.51 79.914 +132.89 79.835 +135.31 79.756 +137.77 79.678 +140.28 79.601 +142.83 79.524 +145.43 79.447 +148.07 79.370 +150.77 79.294 +153.51 79.219 +156.31 79.144 +159.15 79.071 +162.05 78.997 +165.00 78.925 +168.00 78.852 +171.06 78.780 +174.17 78.709 +177.34 78.637 +180.57 78.566 +183.86 78.494 +187.20 78.423 +190.61 78.352 +194.08 78.280 +197.61 78.209 +201.21 78.137 +204.87 78.065 +208.60 77.994 +212.39 77.922 +216.26 77.850 +220.19 77.779 +224.20 77.707 +228.28 77.636 +232.44 77.565 +236.67 77.495 +240.97 77.425 +245.36 77.355 +249.82 77.286 +254.37 77.218 +259.00 77.150 +263.71 77.082 +268.51 77.015 +273.40 76.948 +278.38 76.880 +283.44 76.813 +288.60 76.744 +293.85 76.675 +299.20 76.605 +304.65 76.534 +310.19 76.462 +315.84 76.389 +321.59 76.315 +327.44 76.240 +333.40 76.165 +339.47 76.089 +345.64 76.013 +351.93 75.937 +358.34 75.863 +364.86 75.790 +371.50 75.720 +378.26 75.653 +385.15 75.591 +392.16 75.533 +399.29 75.481 +406.56 75.436 +413.96 75.398 +421.49 75.366 +429.16 75.341 +436.97 75.321 +444.93 75.304 +453.02 75.290 +461.27 75.276 +469.66 75.263 +478.21 75.249 +486.91 75.234 +495.78 75.217 +504.80 75.198 +513.99 75.178 +523.34 75.157 +532.87 75.135 +542.56 75.114 +552.44 75.094 +562.49 75.075 +572.73 75.059 +583.15 75.044 +593.77 75.030 +604.57 75.018 +615.57 75.007 +626.78 74.997 +638.18 74.988 +649.80 74.981 +661.63 74.976 +673.67 74.973 +685.93 74.971 +698.41 74.971 +711.12 74.971 +724.06 74.972 +737.24 74.973 +750.66 74.974 +764.32 74.974 +778.23 74.973 +792.39 74.970 +806.82 74.964 +821.50 74.956 +836.45 74.946 +851.67 74.935 +867.17 74.925 +882.96 74.917 +899.02 74.912 +915.39 74.912 +932.05 74.917 +949.01 74.927 +966.28 74.944 +983.87 74.969 +1001.77 75.004 +1020.00 75.049 +1038.57 75.103 +1057.47 75.168 +1076.71 75.240 +1096.31 75.319 +1116.26 75.402 +1136.58 75.489 +1157.26 75.579 +1178.32 75.673 +1199.77 75.771 +1221.60 75.874 +1243.84 75.983 +1266.47 76.097 +1289.52 76.215 +1312.99 76.336 +1336.89 76.456 +1361.22 76.577 +1385.99 76.696 +1411.22 76.816 +1436.90 76.935 +1463.05 77.057 +1489.68 77.182 +1516.79 77.312 +1544.40 77.453 +1572.50 77.605 +1601.12 77.773 +1630.26 77.956 +1659.93 78.153 +1690.14 78.363 +1720.90 78.583 +1752.22 78.811 +1784.11 79.043 +1816.58 79.278 +1849.64 79.513 +1883.30 79.747 +1917.58 79.977 +1952.48 80.201 +1988.01 80.419 +2024.19 80.631 +2061.03 80.840 +2098.54 81.049 +2136.73 81.265 +2175.62 81.492 +2215.22 81.732 +2255.53 81.981 +2296.58 82.235 +2338.38 82.486 +2380.94 82.726 +2424.27 82.949 +2468.39 83.147 +2513.31 83.315 +2559.05 83.446 +2605.63 83.537 +2653.05 83.588 +2701.33 83.609 +2750.50 83.612 +2800.55 83.606 +2851.52 83.600 +2903.42 83.598 +2956.26 83.603 +3010.06 83.614 +3064.85 83.626 +3120.62 83.635 +3177.42 83.635 +3235.25 83.621 +3294.13 83.593 +3354.08 83.554 +3415.12 83.514 +3477.27 83.478 +3540.56 83.448 +3605.00 83.423 +3670.60 83.400 +3737.41 83.376 +3805.43 83.349 +3874.68 83.321 +3945.20 83.291 +4017.00 83.259 +4090.11 83.223 +4164.55 83.183 +4240.34 83.137 +4317.51 83.085 +4396.09 83.028 +4476.10 82.973 +4557.56 82.923 +4640.50 82.877 +4724.96 82.835 +4810.95 82.797 +4898.51 82.770 +4987.66 82.760 +5078.43 82.769 +5170.86 82.793 +5264.97 82.821 +5360.79 82.848 +5458.35 82.870 +5557.69 82.887 +5658.84 82.899 +5761.82 82.909 +5866.69 82.921 +5973.46 82.938 +6082.17 82.964 +6192.87 83.003 +6305.57 83.053 +6420.33 83.107 +6537.18 83.155 +6656.15 83.198 +6777.29 83.237 +6900.63 83.279 +7026.22 83.329 +7154.10 83.385 +7284.30 83.440 +7416.87 83.490 +7551.85 83.536 +7689.29 83.582 +7829.23 83.616 +7971.72 83.619 +8116.80 83.571 +8264.53 83.471 +8414.94 83.335 +8568.09 83.182 +8724.02 83.024 +8882.79 82.868 +9044.46 82.724 +9209.06 82.596 +9376.66 82.477 +9547.31 82.356 +9721.07 82.227 +9897.99 82.087 +10078.13 81.939 +10261.55 81.784 +10448.30 81.620 +10638.45 81.442 +10832.07 81.251 +11029.21 81.052 +11229.94 80.850 +11434.32 80.648 +11642.41 80.446 +11854.30 80.245 +12070.04 80.042 +12289.71 79.832 +12513.38 79.609 +12741.12 79.371 +12973.00 79.126 +13209.10 78.887 +13449.50 78.664 +13694.28 78.460 +13943.51 78.261 +14197.27 78.058 +14455.66 77.848 +14718.74 77.634 +14986.62 77.419 +15259.37 77.203 +15537.08 76.986 +15819.85 76.769 +16107.76 76.551 +16400.92 76.333 +16699.41 76.113 +17003.33 75.893 +17312.78 75.672 +17627.86 75.451 +17948.68 75.230 +18275.34 75.007 +18607.94 74.775 +18946.60 74.529 +19291.42 74.275 +19642.52 74.036 +20000.00 73.830 +`; +const RAW_HIFI_ENDGAME_2026_MKII = ` +20.00 84.284 +20.36 84.272 +20.73 84.260 +21.11 84.249 +21.50 84.237 +21.89 84.225 +22.29 84.212 +22.69 84.200 +23.10 84.187 +23.52 84.174 +23.95 84.161 +24.39 84.147 +24.83 84.133 +25.28 84.118 +25.74 84.103 +26.21 84.087 +26.69 84.071 +27.18 84.054 +27.67 84.037 +28.17 84.019 +28.69 84.001 +29.21 83.982 +29.74 83.962 +30.28 83.942 +30.83 83.921 +31.39 83.900 +31.97 83.877 +32.55 83.854 +33.14 83.831 +33.74 83.806 +34.36 83.781 +34.98 83.755 +35.62 83.728 +36.27 83.701 +36.93 83.672 +37.60 83.643 +38.28 83.613 +38.98 83.582 +39.69 83.551 +40.41 83.518 +41.15 83.484 +41.90 83.450 +42.66 83.414 +43.44 83.377 +44.23 83.340 +45.03 83.301 +45.85 83.262 +46.68 83.221 +47.53 83.179 +48.40 83.136 +49.28 83.092 +50.18 83.047 +51.09 83.001 +52.02 82.954 +52.97 82.905 +53.93 82.856 +54.91 82.805 +55.91 82.753 +56.93 82.699 +57.97 82.645 +59.02 82.589 +60.09 82.532 +61.19 82.474 +62.30 82.414 +63.44 82.353 +64.59 82.291 +65.77 82.228 +66.96 82.163 +68.18 82.097 +69.42 82.030 +70.69 81.961 +71.97 81.892 +73.28 81.820 +74.62 81.748 +75.97 81.674 +77.36 81.599 +78.76 81.522 +80.20 81.444 +81.66 81.365 +83.14 81.285 +84.66 81.204 +86.20 81.121 +87.77 81.037 +89.36 80.952 +90.99 80.867 +92.65 80.780 +94.33 80.693 +96.05 80.605 +97.80 80.517 +99.58 80.428 +101.39 80.340 +103.23 80.251 +105.11 80.163 +107.03 80.075 +108.97 79.986 +110.96 79.898 +112.98 79.809 +115.03 79.719 +117.13 79.628 +119.26 79.536 +121.43 79.442 +123.64 79.348 +125.89 79.253 +128.18 79.157 +130.51 79.062 +132.89 78.967 +135.31 78.872 +137.77 78.778 +140.28 78.684 +142.83 78.590 +145.43 78.496 +148.07 78.403 +150.77 78.310 +153.51 78.218 +156.31 78.126 +159.15 78.035 +162.05 77.945 +165.00 77.856 +168.00 77.767 +171.06 77.678 +174.17 77.590 +177.34 77.502 +180.57 77.415 +183.86 77.328 +187.20 77.241 +190.61 77.155 +194.08 77.069 +197.61 76.983 +201.21 76.898 +204.87 76.813 +208.60 76.728 +212.39 76.644 +216.26 76.561 +220.19 76.478 +224.20 76.397 +228.28 76.316 +232.44 76.236 +236.67 76.157 +240.97 76.079 +245.36 76.002 +249.82 75.927 +254.37 75.854 +259.00 75.781 +263.71 75.710 +268.51 75.640 +273.40 75.571 +278.38 75.503 +283.44 75.435 +288.60 75.368 +293.85 75.301 +299.20 75.234 +304.65 75.166 +310.19 75.099 +315.84 75.032 +321.59 74.965 +327.44 74.898 +333.40 74.832 +339.47 74.766 +345.64 74.700 +351.93 74.636 +358.34 74.574 +364.86 74.515 +371.50 74.459 +378.26 74.408 +385.15 74.361 +392.16 74.320 +399.29 74.286 +406.56 74.260 +413.96 74.241 +421.49 74.230 +429.16 74.226 +436.97 74.227 +444.93 74.233 +453.02 74.241 +461.27 74.251 +469.66 74.262 +478.21 74.273 +486.91 74.282 +495.78 74.291 +504.80 74.298 +513.99 74.304 +523.34 74.310 +532.87 74.315 +542.56 74.321 +552.44 74.328 +562.49 74.337 +572.73 74.348 +583.15 74.361 +593.77 74.375 +604.57 74.391 +615.57 74.407 +626.78 74.425 +638.18 74.444 +649.80 74.464 +661.63 74.486 +673.67 74.509 +685.93 74.534 +698.41 74.560 +711.12 74.586 +724.06 74.613 +737.24 74.639 +750.66 74.665 +764.32 74.690 +778.23 74.712 +792.39 74.732 +806.82 74.749 +821.50 74.763 +836.45 74.774 +851.67 74.784 +867.17 74.794 +882.96 74.805 +899.02 74.819 +915.39 74.837 +932.05 74.859 +949.01 74.886 +966.28 74.918 +983.87 74.958 +1001.77 75.006 +1020.00 75.064 +1038.57 75.131 +1057.47 75.206 +1076.71 75.289 +1096.31 75.377 +1116.26 75.469 +1136.58 75.564 +1157.26 75.660 +1178.32 75.759 +1199.77 75.861 +1221.60 75.968 +1243.84 76.080 +1266.47 76.195 +1289.52 76.314 +1312.99 76.434 +1336.89 76.553 +1361.22 76.671 +1385.99 76.787 +1411.22 76.901 +1436.90 77.015 +1463.05 77.130 +1489.68 77.246 +1516.79 77.368 +1544.40 77.498 +1572.50 77.639 +1601.12 77.794 +1630.26 77.963 +1659.93 78.145 +1690.14 78.339 +1720.90 78.541 +1752.22 78.750 +1784.11 78.962 +1816.58 79.175 +1849.64 79.388 +1883.30 79.598 +1917.58 79.802 +1952.48 80.000 +1988.01 80.190 +2024.19 80.373 +2061.03 80.550 +2098.54 80.728 +2136.73 80.910 +2175.62 81.103 +2215.22 81.306 +2255.53 81.519 +2296.58 81.735 +2338.38 81.947 +2380.94 82.147 +2424.27 82.329 +2468.39 82.487 +2513.31 82.614 +2559.05 82.704 +2605.63 82.755 +2653.05 82.767 +2701.33 82.752 +2750.50 82.720 +2800.55 82.682 +2851.52 82.647 +2903.42 82.621 +2956.26 82.606 +3010.06 82.600 +3064.85 82.600 +3120.62 82.601 +3177.42 82.598 +3235.25 82.584 +3294.13 82.558 +3354.08 82.526 +3415.12 82.494 +3477.27 82.468 +3540.56 82.449 +3605.00 82.437 +3670.60 82.426 +3737.41 82.415 +3805.43 82.401 +3874.68 82.384 +3945.20 82.365 +4017.00 82.343 +4090.11 82.318 +4164.55 82.286 +4240.34 82.248 +4317.51 82.203 +4396.09 82.152 +4476.10 82.102 +4557.56 82.055 +4640.50 82.013 +4724.96 81.972 +4810.95 81.935 +4898.51 81.908 +4987.66 81.898 +5078.43 81.906 +5170.86 81.928 +5264.97 81.953 +5360.79 81.976 +5458.35 81.995 +5557.69 82.007 +5658.84 82.013 +5761.82 82.018 +5866.69 82.023 +5973.46 82.033 +6082.17 82.052 +6192.87 82.084 +6305.57 82.126 +6420.33 82.171 +6537.18 82.211 +6656.15 82.244 +6777.29 82.274 +6900.63 82.307 +7026.22 82.347 +7154.10 82.393 +7284.30 82.438 +7416.87 82.477 +7551.85 82.513 +7689.29 82.548 +7829.23 82.572 +7971.72 82.564 +8116.80 82.505 +8264.53 82.395 +8414.94 82.249 +8568.09 82.085 +8724.02 81.915 +8882.79 81.749 +9044.46 81.595 +9209.06 81.456 +9376.66 81.326 +9547.31 81.195 +9721.07 81.055 +9897.99 80.905 +10078.13 80.747 +10261.55 80.582 +10448.30 80.407 +10638.45 80.220 +10832.07 80.019 +11029.21 79.810 +11229.94 79.599 +11434.32 79.388 +11642.41 79.177 +11854.30 78.967 +12070.04 78.755 +12289.71 78.537 +12513.38 78.306 +12741.12 78.059 +12973.00 77.806 +13209.10 77.559 +13449.50 77.329 +13694.28 77.117 +13943.51 76.911 +14197.27 76.700 +14455.66 76.484 +14718.74 76.263 +14986.62 76.041 +15259.37 75.819 +15537.08 75.596 +15819.85 75.374 +16107.76 75.150 +16400.92 74.926 +16699.41 74.701 +17003.33 74.476 +17312.78 74.250 +17627.86 74.025 +17948.68 73.799 +18275.34 73.572 +18607.94 73.336 +18946.60 73.086 +19291.42 72.828 +19642.52 72.587 +20000.00 72.378 +`; + + +const RAW_PEQDB_ULTRA = ` +20.00 83.153 +20.36 83.149 +20.73 83.144 +21.11 83.140 +21.50 83.136 +21.89 83.132 +22.29 83.127 +22.69 83.122 +23.10 83.117 +23.52 83.112 +23.95 83.106 +24.39 83.101 +24.83 83.095 +25.28 83.089 +25.74 83.082 +26.21 83.076 +26.69 83.069 +27.18 83.062 +27.67 83.054 +28.17 83.046 +28.69 83.038 +29.21 83.030 +29.74 83.021 +30.28 83.012 +30.83 83.002 +31.39 82.992 +31.97 82.982 +32.55 82.971 +33.14 82.960 +33.74 82.948 +34.36 82.936 +34.98 82.923 +35.62 82.910 +36.27 82.896 +36.93 82.881 +37.60 82.866 +38.28 82.851 +38.98 82.834 +39.69 82.817 +40.41 82.800 +41.15 82.781 +41.90 82.762 +42.66 82.742 +43.44 82.721 +44.23 82.699 +45.03 82.676 +45.85 82.652 +46.68 82.627 +47.53 82.601 +48.40 82.574 +49.28 82.546 +50.18 82.517 +51.09 82.486 +52.02 82.455 +52.97 82.421 +53.93 82.387 +54.91 82.351 +55.91 82.313 +56.93 82.274 +57.97 82.234 +59.02 82.192 +60.09 82.148 +61.19 82.102 +62.30 82.054 +63.44 82.005 +64.59 81.954 +65.77 81.900 +66.96 81.845 +68.18 81.788 +69.42 81.728 +70.69 81.666 +71.97 81.602 +73.28 81.536 +74.62 81.468 +75.97 81.397 +77.36 81.323 +78.76 81.248 +80.20 81.169 +81.66 81.089 +83.14 81.005 +84.66 80.920 +86.20 80.831 +87.77 80.740 +89.36 80.647 +90.99 80.551 +92.65 80.452 +94.33 80.350 +96.05 80.246 +97.80 80.139 +99.58 80.030 +101.39 79.919 +103.23 79.808 +105.11 79.698 +107.03 79.591 +108.97 79.487 +110.96 79.385 +112.98 79.282 +115.03 79.171 +117.13 79.053 +119.26 78.929 +121.43 78.800 +123.64 78.668 +125.89 78.535 +128.18 78.401 +130.51 78.270 +132.89 78.142 +135.31 78.019 +137.77 77.900 +140.28 77.782 +142.83 77.660 +145.43 77.533 +148.07 77.404 +150.77 77.275 +153.51 77.149 +156.31 77.025 +159.15 76.904 +162.05 76.787 +165.00 76.673 +168.00 76.561 +171.06 76.451 +174.17 76.342 +177.34 76.230 +180.57 76.117 +183.86 76.006 +187.20 75.898 +190.61 75.796 +194.08 75.697 +197.61 75.598 +201.21 75.498 +204.87 75.400 +208.60 75.305 +212.39 75.215 +216.26 75.128 +220.19 75.045 +224.20 74.964 +228.28 74.884 +232.44 74.804 +236.67 74.724 +240.97 74.648 +245.36 74.577 +249.82 74.512 +254.37 74.453 +259.00 74.396 +263.71 74.340 +268.51 74.285 +273.40 74.230 +278.38 74.174 +283.44 74.120 +288.60 74.069 +293.85 74.024 +299.20 73.980 +304.65 73.936 +310.19 73.891 +315.84 73.846 +321.59 73.805 +327.44 73.769 +333.40 73.736 +339.47 73.701 +345.64 73.665 +351.93 73.629 +358.34 73.596 +364.86 73.567 +371.50 73.542 +378.26 73.517 +385.15 73.492 +392.16 73.471 +399.29 73.460 +406.56 73.461 +413.96 73.475 +421.49 73.498 +429.16 73.530 +436.97 73.568 +444.93 73.607 +453.02 73.647 +461.27 73.687 +469.66 73.729 +478.21 73.771 +486.91 73.811 +495.78 73.846 +504.80 73.873 +513.99 73.894 +523.34 73.914 +532.87 73.936 +542.56 73.960 +552.44 73.988 +562.49 74.020 +572.73 74.055 +583.15 74.092 +593.77 74.129 +604.57 74.165 +615.57 74.199 +626.78 74.231 +638.18 74.262 +649.80 74.298 +661.63 74.340 +673.67 74.386 +685.93 74.431 +698.41 74.470 +711.12 74.506 +724.06 74.540 +737.24 74.575 +750.66 74.610 +764.32 74.642 +778.23 74.672 +792.39 74.697 +806.82 74.718 +821.50 74.735 +836.45 74.749 +851.67 74.762 +867.17 74.775 +882.96 74.791 +899.02 74.810 +915.39 74.834 +932.05 74.863 +949.01 74.894 +966.28 74.927 +983.87 74.963 +1001.77 75.005 +1020.00 75.055 +1038.57 75.114 +1057.47 75.181 +1076.71 75.256 +1096.31 75.338 +1116.26 75.422 +1136.58 75.508 +1157.26 75.594 +1178.32 75.681 +1199.77 75.769 +1221.60 75.861 +1243.84 75.958 +1266.47 76.061 +1289.52 76.168 +1312.99 76.276 +1336.89 76.381 +1361.22 76.482 +1385.99 76.582 +1411.22 76.680 +1436.90 76.778 +1463.05 76.877 +1489.68 76.978 +1516.79 77.083 +1544.40 77.196 +1572.50 77.320 +1601.12 77.457 +1630.26 77.606 +1659.93 77.768 +1690.14 77.942 +1720.90 78.123 +1752.22 78.310 +1784.11 78.499 +1816.58 78.690 +1849.64 78.880 +1883.30 79.070 +1917.58 79.256 +1952.48 79.437 +1988.01 79.614 +2024.19 79.787 +2061.03 79.958 +2098.54 80.130 +2136.73 80.311 +2175.62 80.506 +2215.22 80.716 +2255.53 80.939 +2296.58 81.172 +2338.38 81.410 +2380.94 81.645 +2424.27 81.870 +2468.39 82.079 +2513.31 82.266 +2559.05 82.429 +2605.63 82.568 +2653.05 82.689 +2701.33 82.795 +2750.50 82.894 +2800.55 82.990 +2851.52 83.086 +2903.42 83.186 +2956.26 83.285 +3010.06 83.383 +3064.85 83.475 +3120.62 83.556 +3177.42 83.621 +3235.25 83.668 +3294.13 83.698 +3354.08 83.711 +3415.12 83.709 +3477.27 83.697 +3540.56 83.677 +3605.00 83.652 +3670.60 83.622 +3737.41 83.585 +3805.43 83.543 +3874.68 83.496 +3945.20 83.444 +4017.00 83.387 +4090.11 83.326 +4164.55 83.260 +4240.34 83.190 +4317.51 83.116 +4396.09 83.039 +4476.10 82.960 +4557.56 82.882 +4640.50 82.809 +4724.96 82.743 +4810.95 82.686 +4898.51 82.642 +4987.66 82.610 +5078.43 82.588 +5170.86 82.574 +5264.97 82.564 +5360.79 82.555 +5458.35 82.546 +5557.69 82.536 +5658.84 82.525 +5761.82 82.516 +5866.69 82.510 +5973.46 82.509 +6082.17 82.514 +6192.87 82.525 +6305.57 82.540 +6420.33 82.557 +6537.18 82.573 +6656.15 82.588 +6777.29 82.605 +6900.63 82.621 +7026.22 82.640 +7154.10 82.660 +7284.30 82.682 +7416.87 82.703 +7551.85 82.719 +7689.29 82.724 +7829.23 82.708 +7971.72 82.659 +8116.80 82.573 +8264.53 82.453 +8414.94 82.309 +8568.09 82.145 +8724.02 81.972 +8882.79 81.797 +9044.46 81.627 +9209.06 81.460 +9376.66 81.296 +9547.31 81.134 +9721.07 80.971 +9897.99 80.803 +10078.13 80.628 +10261.55 80.444 +10448.30 80.250 +10638.45 80.044 +10832.07 79.829 +11029.21 79.609 +11229.94 79.385 +11434.32 79.158 +11642.41 78.928 +11854.30 78.695 +12070.04 78.459 +12289.71 78.217 +12513.38 77.969 +12741.12 77.719 +12973.00 77.470 +13209.10 77.226 +13449.50 76.987 +13694.28 76.752 +13943.51 76.520 +14197.27 76.290 +14455.66 76.060 +14718.74 75.831 +14986.62 75.600 +15259.37 75.369 +15537.08 75.137 +15819.85 74.904 +16107.76 74.672 +16400.92 74.439 +16699.41 74.206 +17003.33 73.974 +17312.78 73.741 +17627.86 73.508 +17948.68 73.274 +18275.34 73.037 +18607.94 72.796 +18946.60 72.549 +19291.42 72.294 +19642.52 72.034 +20000.00 71.771 +`; +const RAW_PEQDB_DIAMOND_BETA = ` +20.00 82.982 +20.36 82.975 +20.73 82.968 +21.11 82.960 +21.50 82.953 +21.89 82.945 +22.29 82.936 +22.69 82.927 +23.10 82.918 +23.52 82.909 +23.95 82.899 +24.39 82.888 +24.83 82.877 +25.28 82.866 +25.74 82.854 +26.21 82.842 +26.69 82.829 +27.18 82.816 +27.67 82.802 +28.17 82.788 +28.69 82.773 +29.21 82.757 +29.74 82.741 +30.28 82.724 +30.83 82.706 +31.39 82.687 +31.97 82.668 +32.55 82.648 +33.14 82.627 +33.74 82.605 +34.36 82.582 +34.98 82.558 +35.62 82.533 +36.27 82.508 +36.93 82.480 +37.60 82.452 +38.28 82.423 +38.98 82.392 +39.69 82.360 +40.41 82.327 +41.15 82.292 +41.90 82.256 +42.66 82.219 +43.44 82.179 +44.23 82.139 +45.03 82.096 +45.85 82.052 +46.68 82.006 +47.53 81.958 +48.40 81.908 +49.28 81.856 +50.18 81.802 +51.09 81.746 +52.02 81.688 +52.97 81.628 +53.93 81.566 +54.91 81.501 +55.91 81.434 +56.93 81.364 +57.97 81.292 +59.02 81.217 +60.09 81.140 +61.19 81.061 +62.30 80.978 +63.44 80.894 +64.59 80.806 +65.77 80.716 +66.96 80.623 +68.18 80.528 +69.42 80.430 +70.69 80.329 +71.97 80.225 +73.28 80.119 +74.62 80.011 +75.97 79.900 +77.36 79.786 +78.76 79.670 +80.20 79.552 +81.66 79.432 +83.14 79.309 +84.66 79.183 +86.20 79.056 +87.77 78.928 +89.36 78.796 +90.99 78.664 +92.65 78.530 +94.33 78.395 +96.05 78.258 +97.80 78.122 +99.58 77.986 +101.39 77.849 +103.23 77.715 +105.11 77.580 +107.03 77.449 +108.97 77.318 +110.96 77.188 +112.98 77.058 +115.03 76.928 +117.13 76.798 +119.26 76.667 +121.43 76.535 +123.64 76.404 +125.89 76.273 +128.18 76.143 +130.51 76.016 +132.89 75.892 +135.31 75.771 +137.77 75.653 +140.28 75.538 +142.83 75.425 +145.43 75.314 +148.07 75.205 +150.77 75.099 +153.51 74.997 +156.31 74.898 +159.15 74.802 +162.05 74.711 +165.00 74.622 +168.00 74.536 +171.06 74.454 +174.17 74.375 +177.34 74.300 +180.57 74.226 +183.86 74.155 +187.20 74.086 +190.61 74.020 +194.08 73.957 +197.61 73.896 +201.21 73.837 +204.87 73.780 +208.60 73.727 +212.39 73.676 +216.26 73.626 +220.19 73.579 +224.20 73.535 +228.28 73.493 +232.44 73.454 +236.67 73.418 +240.97 73.383 +245.36 73.351 +249.82 73.322 +254.37 73.295 +259.00 73.271 +263.71 73.248 +268.51 73.228 +273.40 73.209 +278.38 73.192 +283.44 73.175 +288.60 73.159 +293.85 73.143 +299.20 73.128 +304.65 73.113 +310.19 73.097 +315.84 73.082 +321.59 73.067 +327.44 73.053 +333.40 73.038 +339.47 73.024 +345.64 73.010 +351.93 72.996 +358.34 72.985 +364.86 72.976 +371.50 72.970 +378.26 72.967 +385.15 72.970 +392.16 72.977 +399.29 72.990 +406.56 73.010 +413.96 73.037 +421.49 73.071 +429.16 73.111 +436.97 73.156 +444.93 73.205 +453.02 73.255 +461.27 73.306 +469.66 73.356 +478.21 73.406 +486.91 73.455 +495.78 73.501 +504.80 73.546 +513.99 73.588 +523.34 73.629 +532.87 73.669 +542.56 73.709 +552.44 73.748 +562.49 73.789 +572.73 73.830 +583.15 73.871 +593.77 73.914 +604.57 73.957 +615.57 74.000 +626.78 74.043 +638.18 74.087 +649.80 74.132 +661.63 74.177 +673.67 74.222 +685.93 74.268 +698.41 74.313 +711.12 74.358 +724.06 74.402 +737.24 74.445 +750.66 74.486 +764.32 74.525 +778.23 74.561 +792.39 74.595 +806.82 74.624 +821.50 74.651 +836.45 74.676 +851.67 74.700 +867.17 74.722 +882.96 74.745 +899.02 74.770 +915.39 74.798 +932.05 74.829 +949.01 74.864 +966.28 74.906 +983.87 74.953 +1001.77 75.006 +1020.00 75.067 +1038.57 75.136 +1057.47 75.212 +1076.71 75.293 +1096.31 75.381 +1116.26 75.472 +1136.58 75.568 +1157.26 75.666 +1178.32 75.767 +1199.77 75.870 +1221.60 75.977 +1243.84 76.087 +1266.47 76.198 +1289.52 76.310 +1312.99 76.424 +1336.89 76.537 +1361.22 76.651 +1385.99 76.763 +1411.22 76.878 +1436.90 76.993 +1463.05 77.111 +1489.68 77.234 +1516.79 77.362 +1544.40 77.497 +1572.50 77.641 +1601.12 77.796 +1630.26 77.961 +1659.93 78.137 +1690.14 78.322 +1720.90 78.515 +1752.22 78.715 +1784.11 78.919 +1816.58 79.127 +1849.64 79.337 +1883.30 79.547 +1917.58 79.757 +1952.48 79.968 +1988.01 80.179 +2024.19 80.390 +2061.03 80.604 +2098.54 80.821 +2136.73 81.043 +2175.62 81.272 +2215.22 81.507 +2255.53 81.745 +2296.58 81.986 +2338.38 82.223 +2380.94 82.455 +2424.27 82.676 +2468.39 82.882 +2513.31 83.072 +2559.05 83.242 +2605.63 83.390 +2653.05 83.520 +2701.33 83.630 +2750.50 83.725 +2800.55 83.808 +2851.52 83.881 +2903.42 83.944 +2956.26 84.001 +3010.06 84.049 +3064.85 84.088 +3120.62 84.116 +3177.42 84.134 +3235.25 84.139 +3294.13 84.130 +3354.08 84.111 +3415.12 84.080 +3477.27 84.042 +3540.56 83.996 +3605.00 83.944 +3670.60 83.886 +3737.41 83.823 +3805.43 83.757 +3874.68 83.686 +3945.20 83.613 +4017.00 83.536 +4090.11 83.457 +4164.55 83.375 +4240.34 83.293 +4317.51 83.209 +4396.09 83.126 +4476.10 83.047 +4557.56 82.971 +4640.50 82.902 +4724.96 82.839 +4810.95 82.784 +4898.51 82.739 +4987.66 82.702 +5078.43 82.675 +5170.86 82.656 +5264.97 82.644 +5360.79 82.635 +5458.35 82.631 +5557.69 82.629 +5658.84 82.628 +5761.82 82.631 +5866.69 82.637 +5973.46 82.644 +6082.17 82.656 +6192.87 82.673 +6305.57 82.693 +6420.33 82.716 +6537.18 82.742 +6656.15 82.770 +6777.29 82.798 +6900.63 82.828 +7026.22 82.856 +7154.10 82.880 +7284.30 82.901 +7416.87 82.912 +7551.85 82.912 +7689.29 82.897 +7829.23 82.864 +7971.72 82.810 +8116.80 82.733 +8264.53 82.634 +8414.94 82.515 +8568.09 82.378 +8724.02 82.230 +8882.79 82.072 +9044.46 81.908 +9209.06 81.742 +9376.66 81.573 +9547.31 81.400 +9721.07 81.226 +9897.99 81.047 +10078.13 80.864 +10261.55 80.674 +10448.30 80.479 +10638.45 80.277 +10832.07 80.068 +11029.21 79.853 +11229.94 79.632 +11434.32 79.407 +11642.41 79.177 +11854.30 78.943 +12070.04 78.705 +12289.71 78.465 +12513.38 78.222 +12741.12 77.977 +12973.00 77.732 +13209.10 77.488 +13449.50 77.247 +13694.28 77.007 +13943.51 76.772 +14197.27 76.537 +14455.66 76.304 +14718.74 76.071 +14986.62 75.840 +15259.37 75.608 +15537.08 75.377 +15819.85 75.145 +16107.76 74.913 +16400.92 74.681 +16699.41 74.446 +17003.33 74.212 +17312.78 73.976 +17627.86 73.739 +17948.68 73.500 +18275.34 73.259 +18607.94 73.016 +18946.60 72.771 +19291.42 72.524 +19642.52 72.276 +20000.00 72.028 +`; + + +const RAW_SEAP = ` +20.00 82.982 +20.36 82.975 +20.73 82.969 +21.11 82.962 +21.50 82.957 +21.89 82.951 +22.29 82.943 +22.69 82.936 +23.10 82.928 +23.52 82.921 +23.95 82.912 +24.39 82.905 +24.83 82.896 +25.28 82.887 +25.74 82.876 +26.21 82.867 +26.69 82.857 +27.18 82.847 +27.67 82.835 +28.17 82.823 +28.69 82.810 +29.21 82.798 +29.74 82.784 +30.28 82.770 +30.83 82.755 +31.39 82.739 +31.97 82.723 +32.55 82.706 +33.14 82.681 +33.74 82.666 +34.36 82.649 +34.98 82.628 +35.62 82.607 +36.27 82.584 +36.93 82.559 +37.60 82.534 +38.28 82.508 +38.98 82.480 +39.69 82.452 +40.41 82.421 +41.15 82.390 +41.90 82.356 +42.66 82.321 +43.44 82.285 +44.23 82.246 +45.03 82.206 +45.85 82.163 +46.68 82.119 +47.53 82.072 +48.40 82.024 +49.28 81.974 +50.18 81.922 +51.09 81.865 +52.02 81.808 +52.97 81.747 +53.93 81.684 +54.91 81.618 +55.91 81.549 +56.93 81.477 +57.97 81.404 +59.02 81.325 +60.09 81.245 +61.19 81.161 +62.30 81.073 +63.44 80.986 +64.59 80.893 +65.77 80.797 +66.96 80.699 +68.18 80.599 +69.42 80.495 +70.69 80.390 +71.97 80.282 +73.28 80.171 +74.62 80.060 +75.97 79.946 +77.36 79.830 +78.76 79.712 +80.20 79.593 +81.66 79.473 +83.14 79.350 +84.66 79.228 +86.20 79.103 +87.77 78.978 +89.36 78.852 +90.99 78.725 +92.65 78.596 +94.33 78.466 +96.05 78.336 +97.80 78.205 +99.58 78.074 +101.39 77.942 +103.23 77.812 +105.11 77.684 +107.03 77.562 +108.97 77.443 +110.96 77.329 +112.98 77.215 +115.03 77.094 +117.13 76.968 +119.26 76.837 +121.43 76.701 +123.64 76.565 +125.89 76.428 +128.18 76.291 +130.51 76.158 +132.89 76.029 +135.31 75.905 +137.77 75.786 +140.28 75.669 +142.83 75.549 +145.43 75.423 +148.07 75.297 +150.77 75.170 +153.51 75.047 +156.31 74.926 +159.15 74.809 +162.05 74.696 +165.00 74.585 +168.00 74.477 +171.06 74.372 +174.17 74.265 +177.34 74.158 +180.57 74.049 +183.86 73.942 +187.20 73.837 +190.61 73.740 +194.08 73.644 +197.61 73.548 +201.21 73.452 +204.87 73.357 +208.60 73.265 +212.39 73.178 +216.26 73.094 +220.19 73.013 +224.20 72.934 +228.28 72.857 +232.44 72.780 +236.67 72.702 +240.97 72.626 +245.36 72.557 +249.82 72.494 +254.37 72.437 +259.00 72.380 +263.71 72.326 +268.51 72.272 +273.40 72.217 +278.38 72.162 +283.44 72.109 +288.60 72.058 +293.85 72.013 +299.20 71.970 +304.65 71.926 +310.19 71.881 +315.84 71.835 +321.59 71.794 +327.44 71.759 +333.40 71.725 +339.47 71.690 +345.64 71.653 +351.93 71.617 +358.34 71.583 +364.86 71.554 +371.50 71.528 +378.26 71.502 +385.15 71.476 +392.16 71.456 +399.29 71.444 +406.56 71.444 +413.96 71.457 +421.49 71.479 +429.16 71.511 +436.97 71.548 +444.93 71.586 +453.02 71.626 +461.27 71.664 +469.66 71.705 +478.21 71.747 +486.91 71.786 +495.78 71.821 +504.80 71.846 +513.99 71.867 +523.34 71.887 +532.87 71.907 +542.56 71.931 +552.44 71.959 +562.49 71.990 +572.73 72.024 +583.15 72.060 +593.77 72.097 +604.57 72.132 +615.57 72.166 +626.78 72.197 +638.18 72.228 +649.80 72.263 +661.63 72.306 +673.67 72.351 +685.93 72.395 +698.41 72.434 +711.12 72.470 +724.06 72.504 +737.24 72.538 +750.66 72.573 +764.32 72.605 +778.23 72.635 +792.39 72.659 +806.82 72.680 +821.50 72.697 +836.45 72.711 +851.67 72.723 +867.17 72.736 +882.96 72.752 +899.02 72.771 +915.39 72.795 +932.05 72.823 +949.01 72.855 +966.28 72.888 +983.87 72.923 +1001.77 72.965 +1020.00 73.015 +1038.57 73.074 +1057.47 73.141 +1076.71 73.216 +1096.31 73.298 +1116.26 73.381 +1136.58 73.467 +1157.26 73.553 +1178.32 73.642 +1199.77 73.730 +1221.60 73.823 +1243.84 73.920 +1266.47 74.025 +1289.52 74.134 +1312.99 74.244 +1336.89 74.353 +1361.22 74.459 +1385.99 74.565 +1411.22 74.670 +1436.90 74.776 +1463.05 74.886 +1489.68 75.000 +1516.79 75.120 +1544.40 75.251 +1572.50 75.396 +1601.12 75.557 +1630.26 75.734 +1659.93 75.928 +1690.14 76.139 +1720.90 76.360 +1752.22 76.591 +1784.11 76.829 +1816.58 77.074 +1849.64 77.323 +1883.30 77.578 +1917.58 77.834 +1952.48 78.092 +1988.01 78.351 +2024.19 78.614 +2061.03 78.882 +2098.54 79.158 +2136.73 79.451 +2175.62 79.764 +2215.22 80.098 +2255.53 80.450 +2296.58 80.813 +2338.38 81.179 +2380.94 81.534 +2424.27 81.868 +2468.39 82.167 +2513.31 82.421 +2559.05 82.624 +2605.63 82.774 +2653.05 82.878 +2701.33 82.943 +2750.50 82.979 +2800.55 82.997 +2851.52 83.003 +2903.42 83.005 +2956.26 83.001 +3010.06 82.995 +3064.85 82.983 +3120.62 82.962 +3177.42 82.929 +3235.25 82.882 +3294.13 82.822 +3354.08 82.751 +3415.12 82.670 +3477.27 82.585 +3540.56 82.496 +3605.00 82.408 +3670.60 82.319 +3737.41 82.228 +3805.43 82.137 +3874.68 82.044 +3945.20 81.949 +4017.00 81.853 +4090.11 81.756 +4164.55 81.657 +4240.34 81.556 +4317.51 81.454 +4396.09 81.351 +4476.10 81.248 +4557.56 81.147 +4640.50 81.054 +4724.96 80.968 +4810.95 80.894 +4898.51 80.833 +4987.66 80.786 +5078.43 80.749 +5170.86 80.722 +5264.97 80.699 +5360.79 80.679 +5458.35 80.659 +5557.69 80.638 +5658.84 80.618 +5761.82 80.600 +5866.69 80.585 +5973.46 80.576 +6082.17 80.574 +6192.87 80.578 +6305.57 80.586 +6420.33 80.597 +6537.18 80.607 +6656.15 80.617 +6777.29 80.628 +6900.63 80.639 +7026.22 80.654 +7154.10 80.669 +7284.30 80.687 +7416.87 80.704 +7551.85 80.716 +7689.29 80.718 +7829.23 80.698 +7971.72 80.646 +8116.80 80.557 +8264.53 80.434 +8414.94 80.287 +8568.09 80.120 +8724.02 79.945 +8882.79 79.768 +9044.46 79.595 +9209.06 79.426 +9376.66 79.260 +9547.31 79.096 +9721.07 78.931 +9897.99 78.761 +10078.13 78.585 +10261.55 78.399 +10448.30 78.203 +10638.45 77.996 +10832.07 77.779 +11029.21 77.558 +11229.94 77.333 +11434.32 77.104 +11642.41 76.873 +11854.30 76.639 +12070.04 76.402 +12289.71 76.159 +12513.38 75.910 +12741.12 75.659 +12973.00 75.409 +13209.10 75.164 +13449.50 74.924 +13694.28 74.689 +13943.51 74.456 +14197.27 74.225 +14455.66 73.994 +14718.74 73.765 +14986.62 73.533 +15259.37 73.301 +15537.08 73.069 +15819.85 72.835 +16107.76 72.603 +16400.92 72.369 +16699.41 72.136 +17003.33 71.903 +17312.78 71.670 +17627.86 71.437 +17948.68 71.202 +18275.34 70.965 +18607.94 70.724 +18946.60 70.476 +19291.42 70.221 +19642.52 69.961 +20000.00 69.695 +`; + +const RAW_SEAP_BASS = ` +20.00 84.785 +20.36 84.777 +20.73 84.768 +21.11 84.759 +21.50 84.751 +21.89 84.741 +22.29 84.730 +22.69 84.719 +23.10 84.707 +23.52 84.694 +23.95 84.680 +24.39 84.667 +24.83 84.651 +25.28 84.634 +25.74 84.616 +26.21 84.599 +26.69 84.581 +27.18 84.560 +27.67 84.539 +28.17 84.516 +28.69 84.492 +29.21 84.468 +29.74 84.441 +30.28 84.413 +30.83 84.384 +31.39 84.353 +31.97 84.321 +32.55 84.287 +33.14 84.251 +33.74 84.213 +34.36 84.174 +34.98 84.132 +35.62 84.090 +36.27 84.044 +36.93 83.995 +37.60 83.945 +38.28 83.894 +38.98 83.838 +39.69 83.781 +40.41 83.722 +41.15 83.659 +41.90 83.595 +42.66 83.527 +43.44 83.457 +44.23 83.383 +45.03 83.307 +45.85 83.229 +46.68 83.147 +47.53 83.064 +48.40 82.978 +49.28 82.890 +50.18 82.800 +51.09 82.706 +52.02 82.612 +52.97 82.516 +53.93 82.418 +54.91 82.318 +55.91 82.217 +56.93 82.114 +57.97 82.012 +59.02 81.906 +60.09 81.800 +61.19 81.692 +62.30 81.582 +63.44 81.474 +64.59 81.361 +65.77 81.247 +66.96 81.130 +68.18 81.012 +69.42 80.889 +70.69 80.765 +71.97 80.638 +73.28 80.507 +74.62 80.374 +75.97 80.235 +77.36 80.093 +78.76 79.947 +80.20 79.797 +81.66 79.642 +83.14 79.482 +84.66 79.319 +86.20 79.149 +87.77 78.975 +89.36 78.796 +90.99 78.612 +92.65 78.422 +94.33 78.225 +96.05 78.024 +97.80 77.817 +99.58 77.604 +101.39 77.386 +103.23 77.164 +105.11 76.940 +107.03 76.718 +108.97 76.494 +110.96 76.270 +112.98 76.044 +115.03 75.808 +117.13 75.566 +119.26 75.318 +121.43 75.067 +123.64 74.817 +125.89 74.572 +128.18 74.334 +130.51 74.107 +132.89 73.895 +135.31 73.701 +137.77 73.527 +140.28 73.369 +142.83 73.225 +145.43 73.092 +148.07 72.975 +150.77 72.874 +153.51 72.791 +156.31 72.724 +159.15 72.672 +162.05 72.633 +165.00 72.604 +168.00 72.584 +171.06 72.569 +174.17 72.557 +177.34 72.543 +180.57 72.528 +183.86 72.512 +187.20 72.497 +190.61 72.486 +194.08 72.473 +197.61 72.456 +201.21 72.436 +204.87 72.412 +208.60 72.387 +212.39 72.364 +216.26 72.339 +220.19 72.314 +224.20 72.287 +228.28 72.259 +232.44 72.227 +236.67 72.191 +240.97 72.155 +245.36 72.122 +249.82 72.092 +254.37 72.066 +259.00 72.039 +263.71 72.011 +268.51 71.982 +273.40 71.951 +278.38 71.916 +283.44 71.883 +288.60 71.850 +293.85 71.822 +299.20 71.795 +304.65 71.765 +310.19 71.733 +315.84 71.700 +321.59 71.670 +327.44 71.646 +333.40 71.622 +339.47 71.595 +345.64 71.567 +351.93 71.538 +358.34 71.511 +364.86 71.488 +371.50 71.468 +378.26 71.448 +385.15 71.427 +392.16 71.411 +399.29 71.403 +406.56 71.407 +413.96 71.424 +421.49 71.449 +429.16 71.484 +436.97 71.524 +444.93 71.565 +453.02 71.607 +461.27 71.648 +469.66 71.691 +478.21 71.734 +486.91 71.775 +495.78 71.811 +504.80 71.838 +513.99 71.860 +523.34 71.880 +532.87 71.902 +542.56 71.927 +552.44 71.955 +562.49 71.987 +572.73 72.022 +583.15 72.059 +593.77 72.096 +604.57 72.132 +615.57 72.166 +626.78 72.198 +638.18 72.228 +649.80 72.264 +661.63 72.307 +673.67 72.352 +685.93 72.397 +698.41 72.435 +711.12 72.472 +724.06 72.505 +737.24 72.540 +750.66 72.575 +764.32 72.607 +778.23 72.637 +792.39 72.661 +806.82 72.683 +821.50 72.699 +836.45 72.713 +851.67 72.725 +867.17 72.739 +882.96 72.754 +899.02 72.773 +915.39 72.797 +932.05 72.825 +949.01 72.856 +966.28 72.890 +983.87 72.925 +1001.77 72.967 +1020.00 73.017 +1038.57 73.075 +1057.47 73.142 +1076.71 73.217 +1096.31 73.299 +1116.26 73.382 +1136.58 73.468 +1157.26 73.555 +1178.32 73.643 +1199.77 73.731 +1221.60 73.824 +1243.84 73.921 +1266.47 74.026 +1289.52 74.135 +1312.99 74.245 +1336.89 74.354 +1361.22 74.459 +1385.99 74.565 +1411.22 74.670 +1436.90 74.776 +1463.05 74.886 +1489.68 75.000 +1516.79 75.120 +1544.40 75.251 +1572.50 75.396 +1601.12 75.557 +1630.26 75.734 +1659.93 75.928 +1690.14 76.138 +1720.90 76.359 +1752.22 76.590 +1784.11 76.828 +1816.58 77.073 +1849.64 77.322 +1883.30 77.577 +1917.58 77.833 +1952.48 78.091 +1988.01 78.350 +2024.19 78.613 +2061.03 78.881 +2098.54 79.157 +2136.73 79.450 +2175.62 79.763 +2215.22 80.097 +2255.53 80.449 +2296.58 80.812 +2338.38 81.178 +2380.94 81.533 +2424.27 81.867 +2468.39 82.166 +2513.31 82.419 +2559.05 82.622 +2605.63 82.772 +2653.05 82.876 +2701.33 82.941 +2750.50 82.977 +2800.55 82.995 +2851.52 83.001 +2903.42 83.003 +2956.26 82.999 +3010.06 82.993 +3064.85 82.981 +3120.62 82.960 +3177.42 82.927 +3235.25 82.880 +3294.13 82.820 +3354.08 82.749 +3415.12 82.668 +3477.27 82.583 +3540.56 82.494 +3605.00 82.406 +3670.60 82.317 +3737.41 82.226 +3805.43 82.135 +3874.68 82.042 +3945.20 81.947 +4017.00 81.851 +4090.11 81.754 +4164.55 81.655 +4240.34 81.554 +4317.51 81.452 +4396.09 81.349 +4476.10 81.246 +4557.56 81.145 +4640.50 81.052 +4724.96 80.966 +4810.95 80.892 +4898.51 80.831 +4987.66 80.784 +5078.43 80.747 +5170.86 80.720 +5264.97 80.697 +5360.79 80.677 +5458.35 80.657 +5557.69 80.636 +5658.84 80.616 +5761.82 80.598 +5866.69 80.583 +5973.46 80.574 +6082.17 80.572 +6192.87 80.576 +6305.57 80.584 +6420.33 80.595 +6537.18 80.605 +6656.15 80.615 +6777.29 80.626 +6900.63 80.637 +7026.22 80.652 +7154.10 80.667 +7284.30 80.685 +7416.87 80.702 +7551.85 80.714 +7689.29 80.716 +7829.23 80.696 +7971.72 80.644 +8116.80 80.555 +8264.53 80.432 +8414.94 80.285 +8568.09 80.118 +8724.02 79.943 +8882.79 79.766 +9044.46 79.593 +9209.06 79.424 +9376.66 79.258 +9547.31 79.094 +9721.07 78.929 +9897.99 78.759 +10078.13 78.583 +10261.55 78.397 +10448.30 78.201 +10638.45 77.994 +10832.07 77.777 +11029.21 77.556 +11229.94 77.331 +11434.32 77.102 +11642.41 76.871 +11854.30 76.637 +12070.04 76.400 +12289.71 76.157 +12513.38 75.908 +12741.12 75.657 +12973.00 75.407 +13209.10 75.162 +13449.50 74.922 +13694.28 74.687 +13943.51 74.454 +14197.27 74.223 +14455.66 73.992 +14718.74 73.763 +14986.62 73.531 +15259.37 73.299 +15537.08 73.067 +15819.85 72.833 +16107.76 72.601 +16400.92 72.367 +16699.41 72.134 +17003.33 71.901 +17312.78 71.668 +17627.86 71.435 +17948.68 71.200 +18275.34 70.963 +18607.94 70.722 +18946.60 70.474 +19291.42 70.219 +19642.52 69.959 +20000.00 69.695 +`; + +const RAW_HARMAN_SPEAKER = ` +20.00 81.000 +25.00 81.000 +31.50 81.000 +40.00 81.000 +50.00 81.000 +63.00 81.000 +80.00 81.000 +100.00 80.800 +125.00 80.300 +160.00 79.500 +200.00 78.500 +250.00 77.500 +315.00 76.500 +400.00 75.800 +500.00 75.400 +630.00 75.100 +800.00 75.050 +1000.00 75.000 +1250.00 74.700 +1600.00 74.300 +2000.00 73.800 +2500.00 73.200 +3150.00 72.500 +4000.00 71.700 +5000.00 70.900 +6300.00 70.000 +8000.00 69.000 +10000.00 68.000 +12500.00 67.000 +16000.00 66.000 +20000.00 65.000 +`; + +const RAW_FLAT_LINE = ` +20.00 75.000 +20000.00 75.000 +`; + + +// --- PARSER --- +/** + * Parse raw frequency/gain text data into [{freq, gain}] arrays + * Supports semicolon, comma, tab, and whitespace delimiters + * Handles European decimal format and header detection + * @param {string} raw - Raw text data + * @returns {Array<{freq: number, gain: number}>} + */ +function parseRawData(raw) { + if (!raw) return []; + + const lines = raw.trim().split('\n'); + if (lines.length === 0) return []; + + const points = []; + const firstLine = lines[0].trim(); + + // Determine separator + let delimiter = /\s+/; + if (firstLine.indexOf(';') > -1) delimiter = ';'; + else if (firstLine.indexOf(',') > -1) delimiter = ','; + else if (firstLine.indexOf('\t') > -1) delimiter = '\t'; + + // Determine column indices + let freqIdx = 0; + let gainIdx = 1; + + const hasHeader = /[a-zA-Z]/.test(firstLine); + if (hasHeader) { + const headers = firstLine.split(delimiter).map(h => h.trim().toLowerCase().replace(/['"]+/g, '')); + const fIdx = headers.findIndex(h => h.includes('freq') || h === 'f'); + if (fIdx > -1) freqIdx = fIdx; + + const rIdx = headers.findIndex(h => h === 'raw'); + if (rIdx > -1) { + gainIdx = rIdx; + } else { + const splIdx = headers.findIndex(h => h.includes('spl') || h.includes('gain') || h.includes('db') || h.includes('mag')); + if (splIdx > -1 && splIdx !== freqIdx) gainIdx = splIdx; + } + } + + for (const line of lines) { + const cleanLine = line.trim(); + if (!cleanLine) continue; + if (!/^[\d\-.]/u.test(cleanLine)) continue; + + const parts = typeof delimiter === 'string' ? cleanLine.split(delimiter) : cleanLine.split(delimiter); + if (parts.length <= Math.max(freqIdx, gainIdx)) continue; + + let freqStr = parts[freqIdx].trim(); + let gainStr = parts[gainIdx].trim(); + + if (delimiter !== ',') { + if (freqStr.includes(',')) freqStr = freqStr.replace(',', '.'); + if (gainStr.includes(',')) gainStr = gainStr.replace(',', '.'); + } + + const freq = parseFloat(freqStr); + const gain = parseFloat(gainStr); + + if (!isNaN(freq) && !isNaN(gain)) { + points.push({ freq, gain }); + } + } + + return points.sort((a, b) => a.freq - b.freq); +} + +// --- PARSED TARGET CURVES --- +const TARGETS = [ + { id: 'harman_oe_2018', label: 'Harman Over-Ear 2018', data: parseRawData(RAW_HARMAN_OE_2018) }, + { id: 'harman_ie_2019', label: 'Harman In-Ear 2019', data: parseRawData(RAW_HARMAN_IE_2019) }, + { id: 'diffuse_field', label: 'Diffuse Field', data: parseRawData(RAW_DIFFUSE_FIELD) }, + { id: 'knowles', label: 'Knowles', data: parseRawData(RAW_KNOWLES) }, + { id: 'moondrop', label: 'Moondrop VDSF', data: parseRawData(RAW_MOONDROP_VDSF) }, + { id: 'hifi_endgame', label: 'HiFi Endgame 2026', data: parseRawData(RAW_HIFI_ENDGAME_2026) }, + { id: 'hifi_endgame_mkii', label: 'HiFi Endgame 2026 MKII', data: parseRawData(RAW_HIFI_ENDGAME_2026_MKII) }, + { id: 'peqdb_ultra', label: 'PEQdB Ultra', data: parseRawData(RAW_PEQDB_ULTRA) }, + { id: 'peqdb_diamond_beta', label: 'PEQdB Diamond β', data: parseRawData(RAW_PEQDB_DIAMOND_BETA) }, + { id: 'seap', label: 'SEAP', data: parseRawData(RAW_SEAP) }, + { id: 'seap_bass', label: 'SEAP Bass', data: parseRawData(RAW_SEAP_BASS) }, + { id: 'flat', label: 'Flat (Calibration)', data: parseRawData(RAW_FLAT_LINE) }, +]; + +const SPEAKER_TARGETS = [ + { id: 'harman_room', label: 'Harman In-Room (2013)', data: parseRawData(RAW_HARMAN_SPEAKER) }, + { id: 'seap_bass', label: 'SEAP Bass (Room)', data: parseRawData(RAW_SEAP_BASS) }, + { id: 'flat', label: 'Flat', data: parseRawData(RAW_FLAT_LINE) }, +]; + +export { parseRawData, TARGETS, SPEAKER_TARGETS }; diff --git a/js/autoeq-engine.js b/js/autoeq-engine.js new file mode 100644 index 0000000..6a9fa7f --- /dev/null +++ b/js/autoeq-engine.js @@ -0,0 +1,221 @@ +// js/autoeq-engine.js +// AutoEQ Algorithm - Ported from Seap Engine AutoEqEngine.ts +// Iterative peak-flattening parametric EQ optimization + +// Constants +const MAX_BOOST = 30.0; +const MAX_CUT = 30.0; +const MIN_Q = 0.6; +const DEFAULT_SR = 48000; +const PI = Math.PI; +const DB_BASE = 10; +const DB_DIVISOR = 40; + +/** + * Calculate biquad filter magnitude response at a given frequency + * @param {number} f - Frequency to evaluate (Hz) + * @param {object} band - EQ band {type, freq, gain, q, enabled} + * @param {number} sr - Sample rate + * @returns {number} Magnitude in dB + */ +function calculateBiquadResponse(f, band, sr = DEFAULT_SR) { + if (!band.enabled) return 0; + if (!band.type || band.type.length === 0) return 0; + const w = 2 * PI * band.freq / sr; + const p = 2 * PI * f / sr; + const s = Math.sin(w) / (2 * band.q); + const A = Math.pow(DB_BASE, band.gain / DB_DIVISOR); + const c = Math.cos(w); + let b0 = 0, b1 = 0, b2 = 0, a0 = 0, a1 = 0, a2 = 0; + + const t = band.type[0]; + + if (t === 'p') { + b0 = 1 + s * A; b1 = -2 * c; b2 = 1 - s * A; + a0 = 1 + s / A; a1 = -2 * c; a2 = 1 - s / A; + } else if (t === 'l') { + const sq = 2 * Math.sqrt(A) * s; + b0 = A * ((A + 1) - (A - 1) * c + sq); + b1 = 2 * A * ((A - 1) - (A + 1) * c); + b2 = A * ((A + 1) - (A - 1) * c - sq); + a0 = (A + 1) + (A - 1) * c + sq; + a1 = -2 * ((A - 1) + (A + 1) * c); + a2 = (A + 1) + (A - 1) * c - sq; + } else if (t === 'h') { + const sq = 2 * Math.sqrt(A) * s; + b0 = A * ((A + 1) + (A - 1) * c + sq); + b1 = -2 * A * ((A - 1) + (A + 1) * c); + b2 = A * ((A + 1) + (A - 1) * c - sq); + a0 = (A + 1) - (A - 1) * c + sq; + a1 = 2 * ((A - 1) - (A + 1) * c); + a2 = (A + 1) - (A - 1) * c - sq; + } else { + return 0; + } + + const _a0 = 1 / a0; + const b0n = b0 * _a0, b1n = b1 * _a0, b2n = b2 * _a0; + const a1n = a1 * _a0, a2n = a2 * _a0; + const cp = Math.cos(p), c2p = Math.cos(2 * p); + const n = b0n * b0n + b1n * b1n + b2n * b2n + 2 * (b0n * b1n + b1n * b2n) * cp + 2 * b0n * b2n * c2p; + const d = 1 + a1n * a1n + a2n * a2n + 2 * (a1n + a1n * a2n) * cp + 2 * a2n * c2p; + return 10 * Math.log10(n / d); +} + +/** + * Linear interpolation on frequency response data + * @param {number} freq - Frequency to interpolate at + * @param {Array<{freq: number, gain: number}>} data - Frequency response data + * @returns {number} Interpolated gain value + */ +function interpolate(freq, data) { + if (data.length === 0) return 0; + if (freq <= data[0].freq) return data[0].gain; + if (freq >= data[data.length - 1].freq) return data[data.length - 1].gain; + for (let i = 0; i < data.length - 1; i++) { + if (freq >= data[i].freq && freq <= data[i + 1].freq) { + return data[i].gain + (freq - data[i].freq) / (data[i + 1].freq - data[i].freq) * (data[i + 1].gain - data[i].gain); + } + } + return 0; +} + +/** + * Calculate normalization offset based on midrange average (250-2500 Hz) + * @param {Array<{freq: number, gain: number}>} data - Frequency response data + * @returns {number} Average gain in midrange + */ +function getNormalizationOffset(data) { + let sum = 0, count = 0; + for (const p of data) { + if (p.freq >= 250 && p.freq <= 2500) { + sum += p.gain; + count++; + } + } + return count > 0 ? sum / count : interpolate(1000, data); +} + +/** + * Run the AutoEQ algorithm to generate parametric EQ bands + * Iterative peak-flattening: finds largest error, places a corrective filter, repeats + * + * @param {Array<{freq: number, gain: number}>} measurement - Headphone frequency response + * @param {Array<{freq: number, gain: number}>} target - Target frequency response curve + * @param {number} bandCount - Number of EQ bands to generate + * @param {number} maxFreq - Maximum frequency limit (Hz) + * @param {number} minFreq - Minimum frequency limit (Hz) + * @param {number} maxQ - Maximum Q factor + * @returns {Array<{id: number, type: string, freq: number, gain: number, q: number, enabled: boolean}>} + */ +function runAutoEqAlgorithm(measurement, target, bandCount, maxFreq = 16000, minFreq = 20, maxQ = 5.0, sampleRate = DEFAULT_SR) { + if (minFreq > maxFreq) return []; + const off = getNormalizationOffset(target) - getNormalizationOffset(measurement); + let err = measurement.map(p => ({ freq: p.freq, gain: (p.gain + off) - interpolate(p.freq, target) })); + + const hasInRangePoints = err.some(p => p.freq >= minFreq && p.freq <= maxFreq); + if (!hasInRangePoints) return []; + + const out = []; + + for (let i = 0; i < bandCount; i++) { + let maxDev = 0, maxWeightedDev = 0, peakFreq = 1000, peakIdx = 0; + + // Scan for maximum weighted error + for (let j = 0; j < err.length; j++) { + const p = err[j]; + if (p.freq < minFreq || p.freq > maxFreq) continue; + + // 3-point smoothing + let v = p.gain; + if (j > 0 && j < err.length - 1) { + v = (err[j - 1].gain + v + err[j + 1].gain) / 3; + } + + // Frequency-dependent weighting + let w = 1.0; + if (p.freq < 300) w = 1.5; + else if (p.freq < 4000) w = 1.0; + else if (p.freq < 8000) w = 0.5; + else w = 0.25; + + if (Math.abs(v * w) > Math.abs(maxWeightedDev)) { + maxWeightedDev = Math.abs(v * w); + maxDev = v; + peakFreq = p.freq; + peakIdx = j; + } + } + + let gain = -maxDev; + + // Safety clamps - reduce max boost at higher frequencies + let safeBoost = MAX_BOOST; + if (peakFreq > 3000) safeBoost = 6.0; + if (peakFreq > 6000) safeBoost = 3.0; + if (gain > safeBoost) gain = safeBoost; + if (gain < -MAX_CUT) gain = -MAX_CUT; + if (Math.abs(gain) < 0.2) break; + + // Q factor calculation from error bandwidth (half-gain points) + let upperFreq = peakFreq, lowerFreq = peakFreq; + let foundLower = false, foundUpper = false; + const thresholdError = maxDev / 2; + for (let k = peakIdx; k >= 0; k--) { + if (Math.abs(err[k].gain) < Math.abs(thresholdError)) { + lowerFreq = err[k].freq; + foundLower = true; + break; + } + } + for (let k = peakIdx; k < err.length; k++) { + if (Math.abs(err[k].gain) < Math.abs(thresholdError)) { + upperFreq = err[k].freq; + foundUpper = true; + break; + } + } + + // If half-gain boundary not found on one side, mirror the other side + // to avoid degenerate bandwidth = 0 producing extremely narrow filters + if (!foundLower && foundUpper) { + lowerFreq = peakFreq * peakFreq / upperFreq; + } else if (!foundUpper && foundLower) { + upperFreq = peakFreq * peakFreq / lowerFreq; + } else if (!foundLower && !foundUpper) { + // Neither boundary found — use 1 octave default + lowerFreq = peakFreq / Math.SQRT2; + upperFreq = peakFreq * Math.SQRT2; + } + + let bandwidth = Math.log2(upperFreq / Math.max(1, lowerFreq)); + if (bandwidth < 0.1) bandwidth = 0.1; + let q = Math.sqrt(Math.pow(2, bandwidth)) / (Math.pow(2, bandwidth) - 1); + q = Math.max(MIN_Q, Math.min(maxQ, q)); + if (peakFreq > 5000 && q > 3.0) q = 3.0; + if (gain > 0 && q > 2.0) q = 2.0; + + const newBand = { id: i, type: 'peaking', freq: peakFreq, gain, q, enabled: true }; + + // Check cumulative gain at the peak frequency across all existing bands + this one + let cumulativeGain = gain; + for (const existing of out) { + cumulativeGain += calculateBiquadResponse(peakFreq, existing, sampleRate); + } + // If cumulative boost exceeds safe limits, reduce this band's gain + const cumulativeLimit = MAX_BOOST; + if (cumulativeGain > cumulativeLimit) { + newBand.gain = gain - (cumulativeGain - cumulativeLimit); + if (newBand.gain < 0.2) continue; + } + + out.push(newBand); + + // Update error curve by applying the new band's response + err = err.map(p => ({ ...p, gain: p.gain + calculateBiquadResponse(p.freq, newBand, sampleRate) })); + } + + return out.sort((a, b) => a.freq - b.freq).map((b, i) => ({ ...b, id: i })); +} + +export { calculateBiquadResponse, interpolate, getNormalizationOffset, runAutoEqAlgorithm }; diff --git a/js/autoeq-importer.js b/js/autoeq-importer.js new file mode 100644 index 0000000..0cd1cf0 --- /dev/null +++ b/js/autoeq-importer.js @@ -0,0 +1,219 @@ +// js/autoeq-importer.js +// Headphone Database Browser - Fetches from AutoEq GitHub repository +// Provides access to 4000+ headphone measurement profiles + +import { parseRawData } from './autoeq-data.js'; +import { db } from './db.js'; + +const CACHE_KEY = 'autoeq_index_v4'; +const OLD_LS_CACHE_KEY = 'monochrome_autoeq_index_v4'; +const CACHE_EXPIRY = 24 * 60 * 60 * 1000; // 24 hours + +// 5 most popular headphones — pre-loaded as defaults and shown in the headphone select +// All measured on Rtings B&K 5128 rig for consistency +const POPULAR_HEADPHONES = [ + { name: 'Sony WH-1000XM5 (Rtings)', type: 'over-ear', path: 'Rtings/Bruel & Kjaer 5128 over-ear/Sony WH-1000XM5', fileName: 'Sony WH-1000XM5.csv' }, + { name: 'Apple AirPods Pro2 (Rtings)', type: 'in-ear', path: 'Rtings/Bruel & Kjaer 5128 in-ear/Apple AirPods Pro2', fileName: 'Apple AirPods Pro2.csv' }, + { name: 'Sony WF-1000XM5 (Rtings)', type: 'in-ear', path: 'Rtings/Bruel & Kjaer 5128 in-ear/Sony WF-1000XM5', fileName: 'Sony WF-1000XM5.csv' }, + { name: 'Samsung Galaxy Buds3 Pro (Rtings)', type: 'in-ear', path: 'Rtings/Bruel & Kjaer 5128 in-ear/Samsung Galaxy Buds3 Pro', fileName: 'Samsung Galaxy Buds3 Pro.csv' }, + { name: 'Sennheiser HD 600 (Rtings)', type: 'over-ear', path: 'Rtings/Bruel & Kjaer 5128 over-ear/Sennheiser HD 600', fileName: 'Sennheiser HD 600.csv' }, +]; + +// Static fallback list in case GitHub API fails — popular picks + additional well-known models +const FALLBACK_INDEX = [ + ...POPULAR_HEADPHONES, + { name: 'Sennheiser HD 600 (Filk)', type: 'over-ear', path: 'Filk/over-ear/Sennheiser HD 600', fileName: 'Sennheiser HD 600.csv' }, + { name: 'Sennheiser HD 600 (Innerfidelity)', type: 'over-ear', path: 'Innerfidelity/over-ear/Sennheiser HD 600', fileName: 'Sennheiser HD 600.csv' }, + { name: 'Samsung Galaxy Buds2 Pro (Rtings)', type: 'in-ear', path: 'Rtings/Bruel & Kjaer 5128 in-ear/Samsung Galaxy Buds2 Pro', fileName: 'Samsung Galaxy Buds2 Pro.csv' }, + { name: 'Sony WF-1000XM5 (Kazi)', type: 'in-ear', path: 'Kazi/in-ear/Sony WF-1000XM5', fileName: 'Sony WF-1000XM5.csv' }, + { name: 'Samsung Galaxy Buds3 Pro (DHRME)', type: 'in-ear', path: 'DHRME/in-ear/Samsung Galaxy Buds3 Pro', fileName: 'Samsung Galaxy Buds3 Pro.csv' }, + { name: 'Apple AirPods Pro (Super Review)', type: 'in-ear', path: 'Super Review/in-ear/Apple AirPods Pro', fileName: 'Apple AirPods Pro.csv' }, + { name: 'Sennheiser HD 600 (2020) (Kuulokenurkka)', type: 'over-ear', path: 'Kuulokenurkka/over-ear/Sennheiser HD 600 (2020)', fileName: 'Sennheiser HD 600 (2020).csv' }, +]; + +/** + * Fetch the full AutoEq headphone index from GitHub + * Uses GitHub API to get the repository tree, then parses it for measurement files + * Caches results in localStorage for 24 hours + * @returns {Promise>} + */ +async function fetchAutoEqIndex() { + // Migrate: remove old localStorage cache to free quota + try { localStorage.removeItem(OLD_LS_CACHE_KEY); } catch { /* ignore */ } + + // 1. Try loading from IndexedDB cache + try { + const cached = await db.getSetting(CACHE_KEY); + if (cached && cached.timestamp && cached.data) { + if (Date.now() - cached.timestamp < CACHE_EXPIRY) { + console.log('[AutoEQ] Loaded index from cache'); + return cached.data; + } + } + } catch (e) { + console.warn('[AutoEQ] Failed to read cache:', e); + } + + // 2. Fetch from GitHub API + try { + console.log('[AutoEQ] Fetching index from GitHub...'); + const controller = new AbortController(); + const timeoutId = setTimeout(() => controller.abort(), 8000); + let response; + try { + response = await fetch('https://api.github.com/repos/jaakkopasanen/AutoEq/git/trees/master?recursive=1', { signal: controller.signal }); + } finally { + clearTimeout(timeoutId); + } + + if (!response.ok) { + try { + const cached = await db.getSetting(CACHE_KEY); + if (cached?.data) { + console.warn('[AutoEQ] GitHub API limit reached. Using stale cache.'); + return cached.data; + } + } catch { /* ignore */ } + console.warn('[AutoEQ] GitHub API error. Using fallback.'); + return FALLBACK_INDEX; + } + + const data = await response.json(); + const entries = []; + + for (const item of data.tree) { + if (!item.path.startsWith('results/')) continue; + if (!item.path.endsWith('.csv') && !item.path.endsWith('.txt')) continue; + + const parts = item.path.split('/'); + if (parts.length < 4) continue; + + const fileName = parts.pop(); + const fileNameLower = fileName.toLowerCase(); + + // Skip non-measurement files (EQ presets, not raw frequency response) + if (fileNameLower.includes('parametriceq') || + fileNameLower.includes('fixedbandeq') || + fileNameLower.includes('graphiceq') || + fileNameLower.includes('convolution') || + fileNameLower.includes('fixed band eq') || + fileNameLower.includes('parametric eq') || + fileNameLower.includes('graphic eq')) { + continue; + } + + const headphoneName = parts[parts.length - 1]; + const folderPath = parts.slice(1).join('/'); + const source = parts[1]; + + let type = 'over-ear'; + const lowerPath = item.path.toLowerCase(); + if (lowerPath.includes('in-ear') || lowerPath.includes('iem')) { + type = 'in-ear'; + } else if (lowerPath.includes('earbud')) { + type = 'in-ear'; + } + + entries.push({ + name: `${headphoneName} (${source})`, + type, + path: folderPath, + fileName, + }); + } + + if (entries.length === 0) return FALLBACK_INDEX; + + const sortedEntries = entries.sort((a, b) => a.name.localeCompare(b.name)); + + // 3. Save to IndexedDB cache + try { + await db.saveSetting(CACHE_KEY, { + timestamp: Date.now(), + data: sortedEntries, + }); + console.log(`[AutoEQ] Cached ${sortedEntries.length} entries`); + } catch (e) { + console.warn('[AutoEQ] Failed to save cache:', e); + } + + return sortedEntries; + } catch (err) { + if (err.name === 'AbortError') { + console.warn('[AutoEQ] GitHub API request timed out. Falling back to cache or fallback index.'); + try { + const cached = await db.getSetting(CACHE_KEY); + if (cached?.data) return cached.data; + } catch { /* ignore */ } + } else { + console.error('[AutoEQ] Failed to fetch index:', err); + } + return FALLBACK_INDEX; + } +} + +/** + * Fetch the frequency response measurement data for a specific headphone + * Tries raw GitHub first, falls back to jsDelivr CDN + * @param {object} entry - AutoEq entry {name, type, path, fileName} + * @returns {Promise>} + */ +async function fetchHeadphoneData(entry) { + const encodedPath = entry.path.split('/').map(encodeURIComponent).join('/'); + const encodedFileName = encodeURIComponent(entry.fileName); + + const urls = [ + `https://raw.githubusercontent.com/jaakkopasanen/AutoEq/master/results/${encodedPath}/${encodedFileName}`, + `https://cdn.jsdelivr.net/gh/jaakkopasanen/AutoEq@master/results/${encodedPath}/${encodedFileName}`, + ]; + + for (const url of urls) { + try { + const controller = new AbortController(); + const timeoutId = setTimeout(() => controller.abort(), 8000); + let response; + try { + response = await fetch(url, { signal: controller.signal }); + } finally { + clearTimeout(timeoutId); + } + if (!response.ok) continue; + + const text = await response.text(); + // Validate it's not an HTML error page + if (text.trim().startsWith(' 0) return points; + } catch (e) { + console.warn(`[AutoEQ] Fetch failed for ${url}:`, e); + } + } + + throw new Error(`Failed to fetch data for ${entry.name}`); +} + +/** + * Search/filter headphone entries by query and optional type filter + * @param {string} query - Search query + * @param {Array} entries - Full list of entries + * @param {string} typeFilter - Optional type filter ('all', 'over-ear', 'in-ear') + * @param {number} limit - Maximum results to return + * @returns {Array} + */ +function searchHeadphones(query, entries, typeFilter = 'all', limit = 100) { + let filtered = entries; + + if (typeFilter !== 'all') { + filtered = filtered.filter(e => e.type === typeFilter); + } + + if (query && query.trim()) { + const lower = query.toLowerCase().trim(); + filtered = filtered.filter(e => e.name.toLowerCase().includes(lower)); + } + + return filtered.slice(0, limit); +} + +export { fetchAutoEqIndex, fetchHeadphoneData, searchHeadphones, POPULAR_HEADPHONES }; diff --git a/js/equalizer.js b/js/equalizer.js index 09b70d8..cc2cd86 100644 --- a/js/equalizer.js +++ b/js/equalizer.js @@ -621,8 +621,9 @@ export class Equalizer { this.frequencies.forEach((freq, index) => { const gain = this.currentGains[index] || 0; + const q = this.filters[index] ? this.filters[index].Q.value : this._calculateQ(index); const filterNum = index + 1; - lines.push(`Filter ${filterNum}: ON PK Fc ${freq} Hz Gain ${gain.toFixed(1)} dB Q 0.71`); + lines.push(`Filter ${filterNum}: ON PK Fc ${freq} Hz Gain ${gain.toFixed(1)} dB Q ${q.toFixed(2)}`); }); return lines.join('\n'); @@ -680,16 +681,25 @@ export class Equalizer { this.setBandCount(newCount); } - // Extract gains from filters - const gains = filters.slice(0, this.bandCount).map((f) => f.gain); - this.setAllGains(gains); + // Apply imported filter frequencies directly instead of regenerating + const sliced = filters.slice(0, this.bandCount); + const newFreqs = sliced.map((f) => f.freq); + this.frequencies = newFreqs; + this.frequencyLabels = generateFrequencyLabels(newFreqs); - // Store filter frequencies if different - const newFreqs = filters.slice(0, this.bandCount).map((f) => f.freq); - if (JSON.stringify(newFreqs) !== JSON.stringify(this.frequencies)) { - equalizerSettings.setFreqRange(newFreqs[0], newFreqs[newFreqs.length - 1]); + // Update filter frequencies on the actual biquad nodes + if (this.filters.length === newFreqs.length) { + newFreqs.forEach((freq, i) => { + if (this.filters[i]) { + this.filters[i].frequency.value = freq; + } + }); } + // Extract and apply gains + const gains = sliced.map((f) => f.gain); + this.setAllGains(gains); + return true; } catch (e) { console.warn('[Equalizer] Failed to import settings:', e); diff --git a/js/player.js b/js/player.js index 4e0ed01..de0b850 100644 --- a/js/player.js +++ b/js/player.js @@ -16,7 +16,6 @@ import { exponentialVolumeSettings, audioEffectsSettings, radioSettings, - playbackSettings, } from './storage.js'; import { audioContextManager } from './audio-context.js'; import { isIos, isSafari } from './platform-detection.js'; @@ -49,7 +48,6 @@ export class Player { this.repeatMode = REPEAT_MODE.OFF; this.preloadCache = new Map(); this.preloadAbortController = null; - this._lastPreloadTime = null; this.currentTrack = null; this.currentRgValues = null; this.userVolume = parseFloat(localStorage.getItem('volume') || '0.7'); @@ -108,6 +106,7 @@ export class Player { bufferingGoal: 30, rebufferingGoal: 2, bufferBehind: 30, + jumpLargeGaps: true, }, abr: { enabled: true, @@ -151,6 +150,7 @@ export class Player { document.addEventListener('visibilitychange', () => { const el = this.activeElement; if (document.visibilityState === 'visible' && !el.paused) { + // Ensure audio context is resumed when user returns to the app if (!audioContextManager.isReady()) { audioContextManager.init(el); } @@ -162,17 +162,6 @@ export class Player { } }); - // Time-based preload trigger for Safari background playback - this._timeUpdateHandler = this._handleTimeUpdateForPreload.bind(this); - this.audio.addEventListener('timeupdate', this._timeUpdateHandler); - if (this.video) { - this.video.addEventListener('timeupdate', this._timeUpdateHandler); - } - - window.addEventListener('preload-time-change', () => { - this._lastPreloadTime = null; - }); - this._setupVideoSync(); } @@ -527,21 +516,6 @@ export class Player { } } - _handleTimeUpdateForPreload() { - const el = this.activeElement; - if (!el || !el.duration || el.paused) return; - - const preloadTime = playbackSettings.getPreloadTime(); - const timeRemaining = el.duration - el.currentTime; - if (timeRemaining <= preloadTime && timeRemaining > 0) { - const now = Date.now(); - if (!this._lastPreloadTime || now - this._lastPreloadTime > 5000) { - this._lastPreloadTime = now; - this.preloadNextTracks(); - } - } - } - async setupHlsVideo(video, result, fallbackImg) { const url = result.videoUrl || result.hlsUrl || result; const Hls = (await import('hls.js')).default; diff --git a/js/settings.js b/js/settings.js index 7cf8e44..40c6d6b 100644 --- a/js/settings.js +++ b/js/settings.js @@ -18,7 +18,6 @@ import { visualizerSettings, playlistSettings, equalizerSettings, - playbackSettings, listenBrainzSettings, malojaSettings, libreFmSettings, @@ -37,7 +36,10 @@ import { modalSettings, preferDolbyAtmosSettings, } from './storage.js'; -import { audioContextManager, EQ_PRESETS } from './audio-context.js'; +import { audioContextManager, getPresetsForBandCount } from './audio-context.js'; +import { calculateBiquadResponse, interpolate, getNormalizationOffset, runAutoEqAlgorithm } from './autoeq-engine.js'; +import { parseRawData, TARGETS, SPEAKER_TARGETS } from './autoeq-data.js'; +import { fetchAutoEqIndex, fetchHeadphoneData, searchHeadphones, POPULAR_HEADPHONES } from './autoeq-importer.js'; import { db } from './db.js'; import { authManager } from './accounts/auth.js'; import { syncManager } from './accounts/pocketbase.js'; @@ -49,6 +51,9 @@ async function getButterchurnPresets(...args) { return butterchurnModule.getButterchurnPresets(...args); } +// Module-level state for AutoEQ (persists across re-initializations) +let _autoeqIndex = []; + export async function initializeSettings(scrobbler, player, api, ui) { // Restore last active settings tab const savedTab = settingsUiState.getActiveTab(); @@ -1112,27 +1117,6 @@ export async function initializeSettings(scrobbler, player, api, ui) { }); } - // Fullscreen Cover Tilt Toggle - const fullscreenTiltToggle = document.getElementById('fullscreen-tilt-toggle'); - if (fullscreenTiltToggle) { - fullscreenTiltToggle.checked = playbackSettings.isFullscreenTiltEnabled(); - fullscreenTiltToggle.addEventListener('change', (e) => { - playbackSettings.setFullscreenTiltEnabled(e.target.checked); - window.dispatchEvent(new CustomEvent('fullscreen-tilt-toggle', { detail: { enabled: e.target.checked } })); - }); - } - - // Preload Time Input - const preloadTimeInput = document.getElementById('preload-time-input'); - if (preloadTimeInput) { - preloadTimeInput.value = playbackSettings.getPreloadTime(); - preloadTimeInput.addEventListener('change', (e) => { - const val = Math.max(5, Math.min(60, parseInt(e.target.value, 10) || 15)); - playbackSettings.setPreloadTime(val); - window.dispatchEvent(new CustomEvent('preload-time-change', { detail: { seconds: val } })); - }); - } - // ReplayGain Settings const replayGainMode = document.getElementById('replay-gain-mode'); if (replayGainMode) { @@ -1244,1042 +1228,3288 @@ export async function initializeSettings(scrobbler, player, api, ui) { } // ======================================== - // Parametric Equalizer Settings (3-32 bands with custom ranges) + // Precision AutoEQ — Redesigned Equalizer // ======================================== const eqToggle = document.getElementById('equalizer-enabled-toggle'); const eqContainer = document.getElementById('equalizer-container'); - const eqPresetSelect = document.getElementById('equalizer-preset-select'); - const eqResetBtn = document.getElementById('equalizer-reset-btn'); - const eqBandsContainer = document.getElementById('equalizer-bands'); - const customPresetsOptgroup = document.getElementById('custom-presets-optgroup'); - const customPresetNameInput = document.getElementById('custom-preset-name'); - const saveCustomPresetBtn = document.getElementById('save-custom-preset-btn'); - const deleteCustomPresetBtn = document.getElementById('delete-custom-preset-btn'); - const eqBandCountInput = document.getElementById('eq-band-count'); - const eqRangeMinInput = document.getElementById('eq-range-min'); - const eqRangeMaxInput = document.getElementById('eq-range-max'); - const applyEqRangeBtn = document.getElementById('apply-eq-range-btn'); - const eqFreqMinInput = document.getElementById('eq-freq-min'); - const eqFreqMaxInput = document.getElementById('eq-freq-max'); - const applyEqFreqBtn = document.getElementById('apply-eq-freq-btn'); - const resetEqFreqBtn = document.getElementById('reset-eq-freq-btn'); - const resetEqRangeBtn = document.getElementById('reset-eq-range-btn'); - const eqScaleContainer = document.querySelector('.equalizer-scale'); const eqPreampSlider = document.getElementById('eq-preamp-slider'); - const eqPreampInput = document.getElementById('eq-preamp-input'); - const eqExportBtn = document.getElementById('eq-export-btn'); - const eqImportBtn = document.getElementById('eq-import-btn'); - const eqImportFile = document.getElementById('eq-import-file'); - // Current settings - let currentBandCount = equalizerSettings.getBandCount(); - let currentRange = equalizerSettings.getRange(); - let currentFreqRange = equalizerSettings.getFreqRange(); + // AutoEQ State (kept when switching modes) + let autoeqSelectedMeasurement = null; + let autoeqSelectedEntry = null; + let autoeqCurrentBands = null; // AutoEQ-generated bands + let autoeqCorrectedCurve = null; let currentPreamp = equalizerSettings.getPreamp(); - /** - * Generate frequency labels for given band count and frequency range - */ - const generateFreqLabels = (count, minFreq = currentFreqRange.min, maxFreq = currentFreqRange.max) => { - const labels = []; - const safeMin = Math.max(10, minFreq); - const safeMax = Math.min(96000, maxFreq); + // Parametric EQ State (separate from AutoEQ, kept when switching modes) + let parametricBands = null; - for (let i = 0; i < count; i++) { - const t = i / (count - 1); - const freq = safeMin * Math.pow(safeMax / safeMin, t); - const rounded = Math.round(freq); + // Interactive graph state + let draggedNode = null; + let hoveredNode = null; + let graphAnimFrame = null; - if (rounded < 1000) { - labels.push(rounded.toString()); - } else if (rounded < 10000) { - labels.push((rounded / 1000).toFixed(rounded % 1000 === 0 ? 0 : 1) + 'K'); - } else { - labels.push((rounded / 1000).toFixed(0) + 'K'); + // dB zoom state (half-range values, user-adjustable via scroll on Y axis) + let graphDbHalfAutoEQ = 25; + let graphDbHalfParametric = 35; + + /** Get the active bands for the current mode */ + const getActiveBands = () => { + if (currentMode === 'parametric') return parametricBands; + if (currentMode === 'speaker') return speakerChannels[speakerActiveChannel]?.bands || null; + return autoeqCurrentBands; + }; + /** Set the active bands for the current mode */ + const setActiveBands = (bands) => { + if (currentMode === 'parametric') parametricBands = bands; + else if (currentMode === 'speaker') speakerChannels[speakerActiveChannel].bands = bands; + else autoeqCurrentBands = bands; + }; + + // DOM Elements + const autoeqCanvas = document.getElementById('autoeq-response-canvas'); + const autoeqGraphWrapper = document.getElementById('autoeq-graph-wrapper'); + const autoeqHeadphoneSelect = document.getElementById('autoeq-headphone-select'); + const autoeqTargetSelect = document.getElementById('autoeq-target-select'); + const autoeqBandCount = document.getElementById('autoeq-band-count'); + const autoeqMaxFreq = document.getElementById('autoeq-max-freq'); + const autoeqSampleRate = document.getElementById('autoeq-sample-rate'); + + // Safely set band count dropdown, ensuring the value matches an available option + const setAutoeqBandCount = (count, bands) => { + if (!autoeqBandCount) return; + const val = String(count || (bands && bands.length) || 10); + autoeqBandCount.value = val; + // If value didn't match any option (dropdown shows blank), add it or fall back + if (autoeqBandCount.value !== val) { + // Try using actual band count from the bands array + if (bands && bands.length) { + const bandsVal = String(bands.length); + autoeqBandCount.value = bandsVal; + if (autoeqBandCount.value === bandsVal) return; } - } - - return labels; - }; - - /** - * Generate EQ bands HTML - */ - const generateEQBands = ( - count, - rangeMin = currentRange.min, - rangeMax = currentRange.max, - freqMin = currentFreqRange.min, - freqMax = currentFreqRange.max - ) => { - if (!eqBandsContainer) return; - - const labels = generateFreqLabels(count, freqMin, freqMax); - eqBandsContainer.innerHTML = ''; - - for (let i = 0; i < count; i++) { - const bandEl = document.createElement('div'); - bandEl.className = 'eq-band'; - bandEl.dataset.band = i; - - bandEl.innerHTML = ` - - 0 - ${labels[i]} - `; - - eqBandsContainer.appendChild(bandEl); - } - - // Re-initialize band sliders - initializeBandSliders(); - }; - - /** - * Update EQ scale display - */ - const updateEQScale = (min, max) => { - if (!eqScaleContainer) return; - const spans = eqScaleContainer.querySelectorAll('span'); - if (spans.length >= 3) { - spans[0].textContent = `+${max} dB`; - spans[1].textContent = '0 dB'; - spans[2].textContent = `${min} dB`; + // Fall back to default + autoeqBandCount.value = '10'; } }; + const autoeqRunBtn = document.getElementById('autoeq-run-btn'); + const autoeqDownloadBtn = document.getElementById('autoeq-download-btn'); + const autoeqStatus = document.getElementById('autoeq-status'); + const autoeqImportBtn = document.getElementById('autoeq-import-measurement-btn'); + const autoeqImportFile = document.getElementById('autoeq-import-measurement-file'); + const autoeqSavedGrid = document.getElementById('autoeq-saved-grid'); + const autoeqSavedCount = document.getElementById('autoeq-saved-count'); + const autoeqProfileNameInput = document.getElementById('autoeq-profile-name'); + const autoeqSaveBtn = document.getElementById('autoeq-save-btn'); + const autoeqSavedCollapse = document.getElementById('autoeq-saved-collapse'); + const autoeqDatabaseList = document.getElementById('autoeq-database-list'); + const autoeqDatabaseCount = document.getElementById('autoeq-database-count'); + const autoeqFiltersToggle = document.getElementById('autoeq-filters-toggle'); + const autoeqFiltersContent = document.getElementById('autoeq-filters-content'); + const autoeqFiltersCollapse = document.getElementById('autoeq-filters-collapse'); + const autoeqBandsList = document.getElementById('autoeq-bands-list'); + const autoeqPreampValue = document.getElementById('autoeq-preamp-value'); - /** - * Update the visual display of a band value - */ - const updateBandValueDisplay = (bandEl, value) => { - const valueEl = bandEl.querySelector('.eq-value'); - if (!valueEl) return; - - const displayValue = value > 0 ? `+${value}` : value.toString(); - valueEl.textContent = displayValue; - - // Add color classes based on value - valueEl.classList.remove('positive', 'negative'); - if (value > 0) { - valueEl.classList.add('positive'); - } else if (value < 0) { - valueEl.classList.add('negative'); + // Populate headphone select with popular models + if (autoeqHeadphoneSelect) { + const optgroup = document.createElement('optgroup'); + optgroup.label = 'Popular'; + for (const hp of POPULAR_HEADPHONES) { + const opt = document.createElement('option'); + opt.value = hp.name; + opt.textContent = hp.name.replace(/\s*\([^)]*\)\s*$/, ''); // strip source suffix for clean display + opt.dataset.type = hp.type; + optgroup.appendChild(opt); } - }; + // Insert after the placeholder option + autoeqHeadphoneSelect.appendChild(optgroup); - /** - * Update all band sliders and displays from an array of gains - */ - const updateAllBandUI = (gains) => { - const eqBands = eqBandsContainer?.querySelectorAll('.eq-band'); - if (!eqBands) return; - - eqBands.forEach((bandEl, index) => { - const slider = bandEl.querySelector('.eq-slider'); - if (slider && gains[index] !== undefined) { - slider.value = gains[index]; - updateBandValueDisplay(bandEl, gains[index]); + // When user picks a popular headphone from the dropdown, load it + autoeqHeadphoneSelect.addEventListener('change', () => { + const selected = autoeqHeadphoneSelect.value; + if (!selected) return; + const popularEntry = POPULAR_HEADPHONES.find((hp) => hp.name === selected); + if (popularEntry && (!autoeqSelectedEntry || autoeqSelectedEntry.name !== selected)) { + loadHeadphoneEntry(popularEntry); } }); + } - // Redraw the EQ curve after updating all bands - drawEQCurve(); + // ======================================== + // Frequency Response Graph Renderer + // ======================================== + const FREQ_MIN = 20; + const FREQ_MAX = 20000; + const GRAPH_FREQS = [20, 50, 100, 200, 500, 1000, 2000, 5000, 10000, 20000]; + const LOG_MIN = Math.log10(FREQ_MIN); + const LOG_MAX = Math.log10(FREQ_MAX); + const LOG_RANGE = LOG_MAX - LOG_MIN; + + const freqToX = (freq, width) => ((Math.log10(Math.max(FREQ_MIN, freq)) - LOG_MIN) / LOG_RANGE) * width; + const xToFreq = (x, width) => Math.pow(10, (x / width) * LOG_RANGE + LOG_MIN); + const dbToY = (db, height, dbMin, dbMax) => height - ((db - dbMin) / (dbMax - dbMin)) * height; + const yToDb = (y, height, dbMin, dbMax) => dbMin + (1 - y / height) * (dbMax - dbMin); + + const formatFreq = (freq) => { + if (freq >= 1000) return (freq / 1000).toFixed(freq % 1000 === 0 ? 0 : 1) + 'k'; + return Math.round(freq).toString(); }; /** - * Toggle EQ container visibility + * Draw the frequency response graph with Original, Target, and Corrected curves */ - const updateEQContainerVisibility = (enabled) => { - if (eqContainer) { - eqContainer.style.display = enabled ? 'block' : 'none'; - if (enabled) { - // Redraw curve when container becomes visible - requestAnimationFrame(drawEQCurve); + const drawAutoEQGraph = () => { + if (!autoeqCanvas) return; + const activeBands = getActiveBands(); + const ctx = autoeqCanvas.getContext('2d'); + const dpr = window.devicePixelRatio || 1; + const rect = autoeqCanvas.getBoundingClientRect(); + if (rect.width === 0 || rect.height === 0) return; + + autoeqCanvas.width = rect.width * dpr; + autoeqCanvas.height = rect.height * dpr; + ctx.scale(dpr, dpr); + + const padLeft = 40, + padRight = 10, + padTop = 10, + padBottom = 30; + const w = rect.width - padLeft - padRight; + const h = rect.height - padTop - padBottom; + + ctx.clearRect(0, 0, rect.width, rect.height); + + // dB scale: fixed 75dB center for AutoEQ, 0dB center for Parametric + const isParametricMode = currentMode === 'parametric'; + const dbCenter = isParametricMode ? 0 : 75; + const dbHalfRange = isParametricMode ? graphDbHalfParametric : graphDbHalfAutoEQ; + const dbMin = dbCenter - dbHalfRange; + const dbMax = dbCenter + dbHalfRange; + + // Helper mappings (local to graph area) + const gx = (freq) => padLeft + freqToX(freq, w); + const gy = (db) => padTop + dbToY(db, h, dbMin, dbMax); + + // Fixed curve colors (work across all themes) + const gridColor = 'rgba(255,255,255,0.06)'; + const textColor = 'rgba(255,255,255,0.4)'; + const originalColor = '#3b82f6'; // Blue + const targetColor = 'rgba(255,255,255,0.5)'; // White/gray dashed + const correctedColor = '#f472b6'; // Pink + + // Draw grid + ctx.strokeStyle = gridColor; + ctx.lineWidth = 1; + // Horizontal grid lines (dB) + for (let db = dbMin; db <= dbMax; db += 5) { + const y = gy(db); + ctx.beginPath(); + ctx.moveTo(padLeft, y); + ctx.lineTo(padLeft + w, y); + ctx.stroke(); + } + // Vertical grid lines (freq) + for (const freq of GRAPH_FREQS) { + const x = gx(freq); + ctx.beginPath(); + ctx.moveTo(x, padTop); + ctx.lineTo(x, padTop + h); + ctx.stroke(); + } + + // Y axis labels + ctx.fillStyle = textColor; + ctx.font = '10px system-ui, sans-serif'; + ctx.textAlign = 'right'; + ctx.textBaseline = 'middle'; + for (let db = dbMin; db <= dbMax; db += 5) { + ctx.fillText(db.toString(), padLeft - 5, gy(db)); + } + + // X axis labels + ctx.textAlign = 'center'; + ctx.textBaseline = 'top'; + for (const freq of GRAPH_FREQS) { + ctx.fillText(formatFreq(freq), gx(freq), padTop + h + 8); + } + + // Draw curve helper + const drawCurve = (data, color, lineWidth, dashed = false) => { + if (!data || data.length < 2) return; + ctx.save(); + ctx.beginPath(); + ctx.strokeStyle = color; + ctx.lineWidth = lineWidth; + if (dashed) ctx.setLineDash([6, 4]); + let started = false; + for (const p of data) { + if (p.freq < FREQ_MIN || p.freq > FREQ_MAX) continue; + const x = gx(p.freq); + const y = gy(p.gain); + if (!started) { + ctx.moveTo(x, y); + started = true; + } else ctx.lineTo(x, y); + } + ctx.stroke(); + ctx.restore(); + }; + + // Normalize all data to center around dbCenter + let targetId, targetEntry, targetData, graphMeasurement; + if (currentMode === 'speaker') { + const sCh = speakerChannels[speakerActiveChannel]; + targetId = sCh?.targetId || 'harman_room'; + targetEntry = SPEAKER_TARGETS.find((t) => t.id === targetId); + targetData = targetEntry?.data; + graphMeasurement = sCh?.measurement; + } else { + targetId = autoeqTargetSelect ? autoeqTargetSelect.value : 'harman_oe_2018'; + targetEntry = TARGETS.find((t) => t.id === targetId); + targetData = targetEntry?.data; + graphMeasurement = autoeqSelectedMeasurement; + } + + let graphShift = 0; + + if (isParametricMode) { + // Parametric mode: flat 0dB reference line + ctx.strokeStyle = 'rgba(255,255,255,0.2)'; + ctx.lineWidth = 1; + ctx.beginPath(); + ctx.moveTo(padLeft, gy(0)); + ctx.lineTo(padLeft + w, gy(0)); + ctx.stroke(); + + if (activeBands && activeBands.length > 0) { + const sampleRate = autoeqSampleRate ? parseInt(autoeqSampleRate.value, 10) : 48000; + const nodeColors = [ + '#f472b6', + '#fb923c', + '#facc15', + '#4ade80', + '#22d3ee', + '#818cf8', + '#c084fc', + '#f87171', + '#34d399', + '#60a5fa', + '#a78bfa', + '#fb7185', + '#fbbf24', + '#2dd4bf', + '#38bdf8', + '#a3e635', + ]; + + // Draw individual band bell curves (filled) + activeBands.forEach((band, i) => { + if (!band.enabled || Math.abs(band.gain) < 0.1) return; + const color = nodeColors[i % nodeColors.length]; + const r = parseInt(color.slice(1, 3), 16); + const g2 = parseInt(color.slice(3, 5), 16); + const b2 = parseInt(color.slice(5, 7), 16); + + // Draw filled bell shape + ctx.save(); + ctx.beginPath(); + ctx.moveTo(padLeft, gy(0)); + for (let f = FREQ_MIN; f <= FREQ_MAX; f *= 1.02) { + const resp = calculateBiquadResponse(f, band, sampleRate); + ctx.lineTo(gx(f), gy(resp)); + } + ctx.lineTo(padLeft + w, gy(0)); + ctx.closePath(); + ctx.fillStyle = `rgba(${r},${g2},${b2},0.12)`; + ctx.fill(); + + // Draw bell curve outline + ctx.beginPath(); + let started = false; + for (let f = FREQ_MIN; f <= FREQ_MAX; f *= 1.02) { + const resp = calculateBiquadResponse(f, band, sampleRate); + const bx = gx(f); + const by = gy(resp); + if (!started) { + ctx.moveTo(bx, by); + started = true; + } else ctx.lineTo(bx, by); + } + ctx.strokeStyle = `rgba(${r},${g2},${b2},0.5)`; + ctx.lineWidth = 1; + ctx.stroke(); + ctx.restore(); + }); + + // Draw combined EQ response curve (sum of all bands) + const eqCurve = []; + for (let f = FREQ_MIN; f <= FREQ_MAX; f *= 1.02) { + let totalGain = 0; + for (const band of activeBands) { + if (band.enabled) totalGain += calculateBiquadResponse(f, band, sampleRate); + } + eqCurve.push({ freq: f, gain: totalGain }); + } + drawCurve(eqCurve, 'rgba(255,255,255,0.8)', 2); + } + } else { + // AutoEQ / Speaker mode: draw measurement, target, corrected + if (targetData) { + const targetMidAvg = getNormalizationOffset(targetData); + graphShift = dbCenter - targetMidAvg; + } else if (graphMeasurement) { + const measMidAvg = getNormalizationOffset(graphMeasurement); + graphShift = dbCenter - measMidAvg; + } + + // Draw Target curve (shifted) + if (targetData) { + const shiftedTarget = targetData.map((p) => ({ freq: p.freq, gain: p.gain + graphShift })); + drawCurve(shiftedTarget, targetColor, 1.5, true); + } + + // Draw Original measurement (normalized + shifted) + if (graphMeasurement) { + const normOff = targetData + ? getNormalizationOffset(targetData) - getNormalizationOffset(graphMeasurement) + : 0; + const normalized = graphMeasurement.map((p) => ({ freq: p.freq, gain: p.gain + normOff + graphShift })); + drawCurve(normalized, originalColor, 1.5); + } + + // Draw Corrected curve (shifted) + if (autoeqCorrectedCurve) { + const shiftedCorrected = autoeqCorrectedCurve.map((p) => ({ freq: p.freq, gain: p.gain + graphShift })); + drawCurve(shiftedCorrected, correctedColor, 2); } } - }; - /** - * Populate custom presets in the dropdown - */ - const populateCustomPresets = () => { - if (!customPresetsOptgroup) return; + // Speaker EQ: draw bass limit & room limit markers + if (currentMode === 'speaker') { + const bassHz = speakerBassCutoff ? parseInt(speakerBassCutoff.value, 10) : 40; + const roomHz = speakerRoomLimit ? parseInt(speakerRoomLimit.value, 10) : 500; - // Clear existing custom presets - customPresetsOptgroup.innerHTML = ''; + // Shaded regions outside EQ range + ctx.fillStyle = 'rgba(34, 211, 238, 0.04)'; + ctx.fillRect(padLeft, padTop, gx(bassHz) - padLeft, h); + ctx.fillStyle = 'rgba(245, 158, 11, 0.04)'; + ctx.fillRect(gx(roomHz), padTop, padLeft + w - gx(roomHz), h); - const customPresets = equalizerSettings.getCustomPresets(); - const presetIds = Object.keys(customPresets); + // Bass limit line (cyan dashed) + ctx.save(); + ctx.beginPath(); + ctx.setLineDash([4, 4]); + ctx.strokeStyle = 'rgba(34, 211, 238, 0.6)'; + ctx.lineWidth = 1.5; + ctx.moveTo(gx(bassHz), padTop); + ctx.lineTo(gx(bassHz), padTop + h); + ctx.stroke(); + ctx.restore(); - if (presetIds.length === 0) { - const emptyOption = document.createElement('option'); - emptyOption.value = ''; - emptyOption.textContent = 'No custom presets saved'; - emptyOption.disabled = true; - customPresetsOptgroup.appendChild(emptyOption); - } else { - presetIds.forEach((presetId) => { - const preset = customPresets[presetId]; - const option = document.createElement('option'); - option.value = presetId; - option.textContent = preset.name; - customPresetsOptgroup.appendChild(option); + // Bass limit label + ctx.save(); + ctx.font = 'bold 9px system-ui'; + ctx.fillStyle = 'rgba(34, 211, 238, 0.7)'; + ctx.textAlign = 'center'; + ctx.fillText(bassHz + ' Hz', gx(bassHz), padTop - 2); + ctx.restore(); + + // Room limit line (amber dashed) + ctx.save(); + ctx.beginPath(); + ctx.setLineDash([4, 4]); + ctx.strokeStyle = 'rgba(245, 158, 11, 0.6)'; + ctx.lineWidth = 1.5; + ctx.moveTo(gx(roomHz), padTop); + ctx.lineTo(gx(roomHz), padTop + h); + ctx.stroke(); + ctx.restore(); + + // Room limit label + ctx.save(); + ctx.font = 'bold 9px system-ui'; + ctx.fillStyle = 'rgba(245, 158, 11, 0.7)'; + ctx.textAlign = 'center'; + ctx.fillText(roomHz + ' Hz', gx(roomHz), padTop - 2); + ctx.restore(); + } + + // Draw interactive nodes + if (activeBands && activeBands.length > 0 && (autoeqCorrectedCurve || isParametricMode)) { + const sampleRate = autoeqSampleRate ? parseInt(autoeqSampleRate.value, 10) : 48000; + activeBands.forEach((band, i) => { + if (!band.enabled) return; + const x = gx(band.freq); + // In parametric mode: node Y = band's individual response at its freq (basically its gain) + // In AutoEQ mode: node Y = corrected curve value at band freq (shifted) + let nodeGain; + if (isParametricMode) { + // Sum all bands' response at this frequency + let totalGain = 0; + for (const b of activeBands) { + if (b.enabled) totalGain += calculateBiquadResponse(band.freq, b, sampleRate); + } + nodeGain = totalGain; + } else { + nodeGain = interpolate(band.freq, autoeqCorrectedCurve) + graphShift; + } + const y = gy(nodeGain); + + // Draw node circle with unique color per band + const nodeColors = [ + '#f472b6', + '#fb923c', + '#facc15', + '#4ade80', + '#22d3ee', + '#818cf8', + '#c084fc', + '#f87171', + '#34d399', + '#60a5fa', + '#a78bfa', + '#fb7185', + '#fbbf24', + '#2dd4bf', + '#38bdf8', + '#a3e635', + ]; + const nodeColor = nodeColors[i % nodeColors.length]; + const isHovered = i === hoveredNode; + const isDragged = i === draggedNode; + const radius = isDragged ? 9 : isHovered ? 7 : 5; + + // Glow effect on hover/drag + if (isHovered || isDragged) { + ctx.save(); + ctx.beginPath(); + ctx.arc(x, y, radius + 4, 0, Math.PI * 2); + ctx.fillStyle = nodeColor.replace(')', ', 0.25)').replace('rgb', 'rgba').replace('#', ''); + // Use hex to rgba + const r2 = parseInt(nodeColor.slice(1, 3), 16); + const g2 = parseInt(nodeColor.slice(3, 5), 16); + const b2 = parseInt(nodeColor.slice(5, 7), 16); + ctx.fillStyle = `rgba(${r2},${g2},${b2},0.25)`; + ctx.fill(); + ctx.restore(); + } + + ctx.beginPath(); + ctx.arc(x, y, radius, 0, Math.PI * 2); + ctx.fillStyle = isDragged ? '#fff' : nodeColor; + ctx.fill(); + ctx.strokeStyle = isDragged ? nodeColor : 'rgba(0,0,0,0.5)'; + ctx.lineWidth = 1.5; + ctx.stroke(); + + // Show tooltip on drag + if (isDragged) { + ctx.save(); + ctx.fillStyle = 'rgba(0,0,0,0.8)'; + const txt = `${Math.round(band.freq)} Hz ${band.gain > 0 ? '+' : ''}${band.gain.toFixed(1)} dB Q${band.q.toFixed(2)}`; + ctx.font = 'bold 11px system-ui, sans-serif'; + const tw = ctx.measureText(txt).width + 12; + const tx = Math.min(x - tw / 2, rect.width - tw - 5); + const ty = y - 28; + ctx.fillRect(tx, ty, tw, 20); + ctx.fillStyle = '#fff'; + ctx.textAlign = 'center'; + ctx.textBaseline = 'middle'; + ctx.fillText(txt, tx + tw / 2, ty + 10); + ctx.restore(); + } }); } }; /** - * Check if a preset ID is a custom preset + * Compute corrected curve from measurement + bands */ - const isCustomPreset = (presetId) => { - return presetId && presetId.startsWith('custom_'); + const computeCorrectedCurve = () => { + let measurement, bands, tId, tList; + if (currentMode === 'speaker') { + const sCh = speakerChannels[speakerActiveChannel]; + measurement = sCh?.measurement; + bands = sCh?.bands; + tId = sCh?.targetId || 'harman_room'; + tList = SPEAKER_TARGETS; + } else { + measurement = autoeqSelectedMeasurement; + bands = autoeqCurrentBands; + tId = autoeqTargetSelect ? autoeqTargetSelect.value : 'harman_oe_2018'; + tList = TARGETS; + } + + if (!measurement || !bands) { + autoeqCorrectedCurve = null; + return; + } + const targetEntry = tList.find((t) => t.id === tId); + const targetData = targetEntry?.data; + const normOff = targetData ? getNormalizationOffset(targetData) - getNormalizationOffset(measurement) : 0; + const sampleRate = autoeqSampleRate ? parseInt(autoeqSampleRate.value, 10) : 48000; + + autoeqCorrectedCurve = measurement.map((p) => { + let correction = 0; + for (const band of bands) { + if (band.enabled) correction += calculateBiquadResponse(p.freq, band, sampleRate); + } + return { freq: p.freq, gain: p.gain + normOff + correction }; + }); }; /** - * Update delete button visibility based on selected preset + * Get canvas coordinates from mouse event */ - const updateDeleteButtonVisibility = () => { - if (!deleteCustomPresetBtn || !eqPresetSelect) return; - const isCustom = isCustomPreset(eqPresetSelect.value); - deleteCustomPresetBtn.style.display = isCustom ? 'flex' : 'none'; + const getCanvasCoords = (e) => { + const rect = autoeqCanvas.getBoundingClientRect(); + return { x: e.clientX - rect.left, y: e.clientY - rect.top }; }; /** - * Draw smooth EQ response curve on canvas + * Find closest node to coordinates */ - const drawEQCurve = () => { - const canvas = document.getElementById('eq-response-canvas'); + const findClosestNode = (mx, my, threshold = 15) => { + const activeBands = getActiveBands(); + if (!activeBands || !autoeqCanvas) return -1; + const isParam = currentMode === 'parametric'; + if (!isParam && !autoeqCorrectedCurve) return -1; + + const rect = autoeqCanvas.getBoundingClientRect(); + const padLeft = 40, + padRight = 10, + padTop = 10, + padBottom = 30; + const w = rect.width - padLeft - padRight; + const h = rect.height - padTop - padBottom; + + const dbCenter = isParam ? 0 : 75; + const dbHalfRange = isParam ? graphDbHalfParametric : graphDbHalfAutoEQ; + const dbMin = dbCenter - dbHalfRange; + const dbMax = dbCenter + dbHalfRange; + + let graphShift = 0; + if (!isParam) { + let tId, tList, meas; + if (currentMode === 'speaker') { + const sCh = speakerChannels[speakerActiveChannel]; + tId = sCh?.targetId || 'harman_room'; + tList = SPEAKER_TARGETS; + meas = sCh?.measurement; + } else { + tId = autoeqTargetSelect ? autoeqTargetSelect.value : 'harman_oe_2018'; + tList = TARGETS; + meas = autoeqSelectedMeasurement; + } + const targetEntry = tList.find((t) => t.id === tId); + const targetData = targetEntry?.data; + if (targetData) graphShift = 75 - getNormalizationOffset(targetData); + else if (meas) graphShift = 75 - getNormalizationOffset(meas); + } + + const sampleRate = autoeqSampleRate ? parseInt(autoeqSampleRate.value, 10) : 48000; + let closest = -1, + closestDist = Infinity; + activeBands.forEach((band, i) => { + if (!band.enabled) return; + const x = padLeft + freqToX(band.freq, w); + let nodeGain; + if (isParam) { + nodeGain = 0; + for (const b of activeBands) { + if (b.enabled) nodeGain += calculateBiquadResponse(band.freq, b, sampleRate); + } + } else { + nodeGain = interpolate(band.freq, autoeqCorrectedCurve) + graphShift; + } + const y = padTop + dbToY(nodeGain, h, dbMin, dbMax); + const dist = Math.sqrt((mx - x) ** 2 + (my - y) ** 2); + if (dist < threshold && dist < closestDist) { + closest = i; + closestDist = dist; + } + }); + return closest; + }; + + /** + * Auto preamp compensation state + */ + let autoPreampEnabled = false; + const autoPreampToggle = document.getElementById('autoeq-auto-preamp-toggle'); + + /** + * Apply current bands to audio engine + */ + const applyBandsToAudio = (bands) => { + if (bands && bands.length > 0) { + // Pass skipPreamp=true when auto preamp is off so the engine doesn't override manual preamp + audioContextManager.applyAutoEQBands(bands, !autoPreampEnabled); + currentPreamp = equalizerSettings.getPreamp(); + if (eqPreampSlider) eqPreampSlider.value = currentPreamp; + if (autoeqPreampValue) autoeqPreampValue.textContent = `${currentPreamp} dB`; + } + }; + + // ======================================== + // Interactive Graph Mouse/Touch Handlers + // ======================================== + if (autoeqCanvas) { + autoeqCanvas.addEventListener('mousedown', (e) => { + const coords = getCanvasCoords(e); + const nodeIdx = findClosestNode(coords.x, coords.y, 18); + if (nodeIdx >= 0) { + draggedNode = nodeIdx; + autoeqCanvas.style.cursor = 'grabbing'; + e.preventDefault(); + } + }); + + autoeqCanvas.addEventListener('mousemove', (e) => { + const coords = getCanvasCoords(e); + const bands = getActiveBands(); + if (draggedNode !== null && bands) { + const rect = autoeqCanvas.getBoundingClientRect(); + const padLeft = 40, + padRight = 10, + padTop = 10, + padBottom = 30; + const w = rect.width - padLeft - padRight; + const h = rect.height - padTop - padBottom; + + const isParam = currentMode === 'parametric'; + const dbCenter = isParam ? 0 : 75; + const dbHalf = isParam ? graphDbHalfParametric : graphDbHalfAutoEQ; + const dbMin = dbCenter - dbHalf; + const dbMax = dbCenter + dbHalf; + + const freq = xToFreq(coords.x - padLeft, w); + bands[draggedNode].freq = Math.max(20, Math.min(20000, freq)); + + if (isParam) { + const newGain = yToDb(coords.y - padTop, h, dbMin, dbMax); + bands[draggedNode].gain = Math.max(-30, Math.min(30, Math.round(newGain * 10) / 10)); + } else { + const corrGain = interpolate(bands[draggedNode].freq, autoeqCorrectedCurve || []); + const newDb = yToDb(coords.y - padTop, h, dbMin, dbMax); + const gainDelta = newDb - corrGain; + bands[draggedNode].gain = Math.max(-30, Math.min(30, bands[draggedNode].gain + gainDelta * 0.3)); + } + + if (!graphAnimFrame) { + graphAnimFrame = requestAnimationFrame(() => { + computeCorrectedCurve(); + applyBandsToAudio(bands); + drawAutoEQGraph(); + renderBandControls(bands); + graphAnimFrame = null; + }); + } + } else { + const padLeft = 40; + if (coords.x <= padLeft + 10) { + autoeqCanvas.style.cursor = 'ns-resize'; + if (hoveredNode !== null) { + hoveredNode = null; + drawAutoEQGraph(); + } + } else { + const newHovered = findClosestNode(coords.x, coords.y, 18); + if (newHovered !== hoveredNode) { + hoveredNode = newHovered; + autoeqCanvas.style.cursor = hoveredNode >= 0 ? 'grab' : 'crosshair'; + drawAutoEQGraph(); + } + } + } + }); + + autoeqCanvas.addEventListener('mouseup', () => { + draggedNode = null; + autoeqCanvas.style.cursor = hoveredNode >= 0 ? 'grab' : 'crosshair'; + }); + + autoeqCanvas.addEventListener('mouseleave', () => { + draggedNode = null; + hoveredNode = null; + autoeqCanvas.style.cursor = 'crosshair'; + drawAutoEQGraph(); + }); + + autoeqCanvas.addEventListener('dblclick', (e) => { + e.preventDefault(); + const coords = getCanvasCoords(e); + const isParam = currentMode === 'parametric'; + + // getActiveBands() returns null in autoeq mode before first run — init to empty array + let bands = getActiveBands(); + if (!bands) { + if (currentMode === 'autoeq') { + autoeqCurrentBands = []; + bands = autoeqCurrentBands; + } else return; + } + + // findClosestNode needs autoeqCorrectedCurve in non-parametric modes. + // Fall back to frequency-only (X-axis) matching when corrected curve is absent. + let nodeIdx = findClosestNode(coords.x, coords.y, 18); + if (nodeIdx < 0 && !isParam && !autoeqCorrectedCurve && bands.length > 0) { + const rect2 = autoeqCanvas.getBoundingClientRect(); + const w2 = rect2.width - 40 - 10; + let best = Infinity; + bands.forEach((band, i) => { + const dx = Math.abs(coords.x - (40 + freqToX(band.freq, w2))); + if (dx < 18 && dx < best) { + best = dx; + nodeIdx = i; + } + }); + } + + if (nodeIdx >= 0) { + bands.splice(nodeIdx, 1); + bands.forEach((b, i) => { + b.id = i; + }); + draggedNode = null; + hoveredNode = null; + } else { + if (bands.length >= 32) return; + const rect = autoeqCanvas.getBoundingClientRect(); + const padLeft = 40, + padRight = 10, + padTop = 10, + padBottom = 30; + const w = rect.width - padLeft - padRight; + const h = rect.height - padTop - padBottom; + const dbCenter = isParam ? 0 : 75; + const dbHalf = isParam ? graphDbHalfParametric : graphDbHalfAutoEQ; + const dbMin = dbCenter - dbHalf; + const dbMax = dbCenter + dbHalf; + const freq = Math.max(20, Math.min(20000, Math.round(xToFreq(coords.x - padLeft, w)))); + const gain = Math.max( + -30, + Math.min(30, Math.round((yToDb(coords.y - padTop, h, dbMin, dbMax) - dbCenter) * 10) / 10) + ); + bands.push({ id: bands.length, type: 'peaking', freq, gain, q: 1.0, enabled: true }); + } + + setActiveBands(bands); + computeCorrectedCurve(); + applyBandsToAudio(bands); + renderBandControls(bands); + drawAutoEQGraph(); + }); + + autoeqCanvas.addEventListener( + 'wheel', + (e) => { + const coords = getCanvasCoords(e); + const padLeft = 40; + + // Scroll on Y axis area (left edge) = dB zoom + if (coords.x <= padLeft + 10) { + e.preventDefault(); + const zoomStep = e.deltaY > 0 ? 2 : -2; // scroll down = zoom out (wider range), scroll up = zoom in + if (currentMode === 'parametric') { + graphDbHalfParametric = Math.max(5, Math.min(60, graphDbHalfParametric + zoomStep)); + } else { + graphDbHalfAutoEQ = Math.max(5, Math.min(60, graphDbHalfAutoEQ + zoomStep)); + } + drawAutoEQGraph(); + return; + } + + // Scroll on a node = Q adjust + const wBands = getActiveBands(); + if (hoveredNode >= 0 && wBands && wBands[hoveredNode]) { + e.preventDefault(); + const band = wBands[hoveredNode]; + const delta = e.deltaY > 0 ? -0.15 : 0.15; + band.q = Math.max(0.1, Math.min(10, (band.q || 1) + delta)); + computeCorrectedCurve(); + applyBandsToAudio(wBands); + drawAutoEQGraph(); + renderBandControls(wBands); + } + }, + { passive: false } + ); + + // Touch support + let touchNodeIdx = -1; + autoeqCanvas.addEventListener( + 'touchstart', + (e) => { + const touch = e.touches[0]; + const coords = { + x: touch.clientX - autoeqCanvas.getBoundingClientRect().left, + y: touch.clientY - autoeqCanvas.getBoundingClientRect().top, + }; + touchNodeIdx = findClosestNode(coords.x, coords.y, 25); + if (touchNodeIdx >= 0) { + draggedNode = touchNodeIdx; + e.preventDefault(); + } + }, + { passive: false } + ); + + autoeqCanvas.addEventListener( + 'touchmove', + (e) => { + const tBands = getActiveBands(); + if (draggedNode !== null && tBands) { + const touch = e.touches[0]; + const rect = autoeqCanvas.getBoundingClientRect(); + const coords = { x: touch.clientX - rect.left, y: touch.clientY - rect.top }; + const padLeft = 40, + padRight = 10, + padTop = 10, + padBottom = 30; + const w = rect.width - padLeft - padRight; + const h = rect.height - padTop - padBottom; + + const freq = xToFreq(coords.x - padLeft, w); + tBands[draggedNode].freq = Math.max(20, Math.min(20000, freq)); + + if (currentMode === 'parametric') { + const newGain = yToDb(coords.y - padTop, h, -graphDbHalfParametric, graphDbHalfParametric); + tBands[draggedNode].gain = Math.max(-30, Math.min(30, Math.round(newGain * 10) / 10)); + } + + computeCorrectedCurve(); + applyBandsToAudio(tBands); + if (!graphAnimFrame) { + graphAnimFrame = requestAnimationFrame(() => { + drawAutoEQGraph(); + renderBandControls(tBands); + graphAnimFrame = null; + }); + } + e.preventDefault(); + } + }, + { passive: false } + ); + + autoeqCanvas.addEventListener('touchend', () => { + draggedNode = null; + touchNodeIdx = -1; + }); + + // Resize observer for graph + if (autoeqGraphWrapper) { + const ro = new ResizeObserver(() => { + drawAutoEQGraph(); + }); + ro.observe(autoeqGraphWrapper); + } + } + + // ======================================== + // Per-Band Parametric EQ Controls + // ======================================== + const renderBandControls = (bands) => { + if (!autoeqBandsList) return; + autoeqBandsList.innerHTML = ''; + if (!bands || bands.length === 0) return; + + bands.forEach((band, i) => { + const control = document.createElement('div'); + control.className = 'autoeq-band-control'; + control.dataset.band = i; + const currentType = band.type || 'peaking'; + control.innerHTML = ` +

+ ${i + 1} + +
+ Freq + ${formatFreq(band.freq)} Hz +
+
+ Gain + ${band.gain > 0 ? '+' : ''}${band.gain.toFixed(1)} dB +
+
+ Q + ${band.q.toFixed(2)} +
+
+
+ + + +
+ `; + autoeqBandsList.appendChild(control); + + // Attach slider event listeners + const freqSlider = control.querySelector('.autoeq-freq-slider'); + const gainSlider = control.querySelector('.autoeq-gain-slider'); + const qSlider = control.querySelector('.autoeq-q-slider'); + const freqVal = control.querySelector('.autoeq-freq-val'); + const gainVal = control.querySelector('.autoeq-gain-val'); + const qVal = control.querySelector('.autoeq-q-val'); + + freqSlider.addEventListener('input', () => { + const bands = getActiveBands(); + if (!bands || !bands[i]) return; + bands[i].freq = parseFloat(freqSlider.value); + freqVal.textContent = `${formatFreq(bands[i].freq)} Hz`; + computeCorrectedCurve(); + applyBandsToAudio(bands); + drawAutoEQGraph(); + }); + + gainSlider.addEventListener('input', () => { + const bands = getActiveBands(); + if (!bands || !bands[i]) return; + bands[i].gain = parseFloat(gainSlider.value); + gainVal.textContent = `${bands[i].gain > 0 ? '+' : ''}${bands[i].gain.toFixed(1)} dB`; + computeCorrectedCurve(); + applyBandsToAudio(bands); + drawAutoEQGraph(); + }); + + qSlider.addEventListener('input', () => { + const bands = getActiveBands(); + if (!bands || !bands[i]) return; + bands[i].q = parseFloat(qSlider.value); + qVal.textContent = bands[i].q.toFixed(2); + computeCorrectedCurve(); + applyBandsToAudio(bands); + drawAutoEQGraph(); + }); + + const typeSelect = control.querySelector('.autoeq-type-select'); + typeSelect.addEventListener('change', () => { + const bands = getActiveBands(); + if (!bands || !bands[i]) return; + bands[i].type = typeSelect.value; + computeCorrectedCurve(); + applyBandsToAudio(bands); + drawAutoEQGraph(); + }); + }); + }; + + // ======================================== + // EQ Toggle + Container Visibility + // ======================================== + /** + * Ensure parametric bands exist - creates default 10 log-spaced bands if none + */ + const ensureParametricBands = () => { + if (!parametricBands || parametricBands.length === 0) { + const defaultBands = []; + for (let i = 0; i < 10; i++) { + const freq = 20 * Math.pow(20000 / 20, i / 9); + defaultBands.push({ id: i, type: 'peaking', freq: Math.round(freq), gain: 0, q: 1.0, enabled: true }); + } + parametricBands = defaultBands; + applyBandsToAudio(parametricBands); + } + }; + + const updateEQContainerVisibility = (enabled) => { + if (eqContainer) { + eqContainer.style.display = enabled ? 'flex' : 'none'; + if (enabled) { + // Ensure bands exist when EQ is enabled (fixes parametric mode without AutoEQ) + if (currentMode === 'parametric') { + ensureParametricBands(); + applyBandsToAudio(parametricBands); + renderBandControls(parametricBands); + } + requestAnimationFrame(drawAutoEQGraph); + } + } + }; + + // ======================================== + // Collapsible Sections + // ======================================== + // Saved Profiles collapse + if (autoeqSavedCollapse) { + const savedGrid = document.getElementById('autoeq-saved-grid'); + autoeqSavedCollapse.addEventListener('click', (e) => { + e.stopPropagation(); + autoeqSavedCollapse.classList.toggle('collapsed'); + if (savedGrid) + savedGrid.style.display = autoeqSavedCollapse.classList.contains('collapsed') ? 'none' : 'flex'; + }); + } + + // Parametric EQ Filters collapse + if (autoeqFiltersToggle) { + autoeqFiltersToggle.addEventListener('click', () => { + if (autoeqFiltersCollapse) autoeqFiltersCollapse.classList.toggle('collapsed'); + if (autoeqFiltersContent) + autoeqFiltersContent.style.display = autoeqFiltersContent.style.display === 'none' ? 'flex' : 'none'; + }); + } + + // ======================================== + // Set Status Message + // ======================================== + const setAutoEQStatus = (msg, type = '') => { + if (!autoeqStatus) return; + autoeqStatus.textContent = msg; + autoeqStatus.className = 'autoeq-status' + (type ? ' ' + type : ''); + }; + + // ======================================== + // Downsample curve for profile storage + // ======================================== + const downsampleCurve = (data, maxPoints = 80) => { + if (!data || data.length <= maxPoints) return data ? [...data] : []; + const result = []; + const step = data.length / maxPoints; + for (let i = 0; i < maxPoints; i++) { + result.push({ ...data[Math.floor(i * step)] }); + } + return result; + }; + + // ======================================== + // Mini-Graph Renderer for Profile Cards + // ======================================== + const drawMiniGraph = (canvas, measurementData, targetData, correctedData) => { if (!canvas) return; - const ctx = canvas.getContext('2d'); const dpr = window.devicePixelRatio || 1; const rect = canvas.getBoundingClientRect(); + if (rect.width === 0) { + // Canvas not laid out yet — retry when it becomes visible + const obs = new IntersectionObserver((entries, observer) => { + if (entries[0].isIntersecting) { + observer.disconnect(); + drawMiniGraph(canvas, measurementData, targetData, correctedData); + } + }); + obs.observe(canvas); + return; + } - // Skip if canvas has no size (not visible yet) - if (rect.width === 0 || rect.height === 0) return; - - // Set canvas size accounting for DPR canvas.width = rect.width * dpr; - canvas.height = rect.height * dpr; + canvas.height = (rect.height || 60) * dpr; ctx.scale(dpr, dpr); + const w = rect.width; + const h = rect.height || 60; - const width = rect.width; - const height = rect.height; + ctx.clearRect(0, 0, w, h); - // Clear canvas - ctx.clearRect(0, 0, width, height); + const drawMiniFill = (data, colors) => { + if (!data || data.length < 2) return; + const allGains = data.map((p) => p.gain); + const dMin = Math.min(...allGains) - 2; + const dMax = Math.max(...allGains) + 2; + const dRange = dMax - dMin || 1; - // Get all current gain values - const eqBands = eqBandsContainer?.querySelectorAll('.eq-band'); - if (!eqBands || eqBands.length === 0) return; + const gradient = ctx.createLinearGradient(0, 0, w, 0); + colors.forEach((c, i) => gradient.addColorStop(i / (colors.length - 1), c)); - // Get the actual highlight color from CSS - const tempEl = document.createElement('div'); - tempEl.style.color = 'rgb(var(--highlight-rgb))'; - document.body.appendChild(tempEl); - const highlightColor = getComputedStyle(tempEl).color; - document.body.removeChild(tempEl); + ctx.beginPath(); + ctx.moveTo(0, h); + for (let i = 0; i < data.length; i++) { + const x = freqToX(data[i].freq, w); + const y = h - ((data[i].gain - dMin) / dRange) * h * 0.8 - h * 0.1; + if (i === 0) ctx.lineTo(x, y); + else ctx.lineTo(x, y); + } + ctx.lineTo(w, h); + ctx.closePath(); + ctx.fillStyle = gradient; + ctx.globalAlpha = 0.4; + ctx.fill(); + ctx.globalAlpha = 1; - const gains = []; - const positions = []; - const range = currentRange; - const rangeTotal = range.max - range.min; - const canvasRect = canvas.getBoundingClientRect(); + // Draw line + ctx.beginPath(); + ctx.strokeStyle = gradient; + ctx.lineWidth = 1.5; + for (let i = 0; i < data.length; i++) { + const x = freqToX(data[i].freq, w); + const y = h - ((data[i].gain - dMin) / dRange) * h * 0.8 - h * 0.1; + if (i === 0) ctx.moveTo(x, y); + else ctx.lineTo(x, y); + } + ctx.stroke(); + }; - eqBands.forEach((bandEl) => { - const slider = bandEl.querySelector('.eq-slider'); - const gain = slider ? parseFloat(slider.value) : 0; - gains.push(gain); + if (measurementData) drawMiniFill(measurementData, ['#3b82f6', '#06b6d4', '#8b5cf6']); + if (targetData) drawMiniFill(targetData, ['#f472b6', '#a855f7', '#6366f1']); + if (correctedData) drawMiniFill(correctedData, ['#22c55e', '#06b6d4', '#3b82f6']); + }; - // Get actual center position of the band element relative to canvas - const bandRect = bandEl.getBoundingClientRect(); - const x = bandRect.left + bandRect.width / 2 - canvasRect.left; - positions.push(x); + const BAND_PREVIEW_COLORS = [ + '#f472b6', + '#fb923c', + '#facc15', + '#4ade80', + '#22d3ee', + '#818cf8', + '#c084fc', + '#f87171', + '#34d399', + '#60a5fa', + ]; + + const drawBandsPreview = (canvas, bands, sampleRate) => { + if (!canvas || !bands || bands.length === 0) return; + const ctx = canvas.getContext('2d'); + const dpr = window.devicePixelRatio || 1; + const rect = canvas.getBoundingClientRect(); + if (rect.width === 0) { + const obs = new IntersectionObserver((entries, observer) => { + if (entries[0].isIntersecting) { + observer.disconnect(); + drawBandsPreview(canvas, bands, sampleRate); + } + }); + obs.observe(canvas); + return; + } + const sr = sampleRate || 48000; + const ph = rect.height || 100; + canvas.width = rect.width * dpr; + canvas.height = ph * dpr; + ctx.scale(dpr, dpr); + const pw = rect.width; + ctx.clearRect(0, 0, pw, ph); + const mid = ph / 2; + const dbRange = 12; // -12dB to +12dB + + // Draw each band as a filled blob + bands.forEach((band, bi) => { + if (!band.enabled || Math.abs(band.gain) < 0.1) return; + const color = BAND_PREVIEW_COLORS[bi % BAND_PREVIEW_COLORS.length]; + const pts = []; + for (let f = 20; f <= 20000; f *= 1.04) { + const resp = calculateBiquadResponse(f, band, sr); + pts.push({ + x: freqToX(f, pw), + y: mid - (Math.max(-dbRange, Math.min(dbRange, resp)) / dbRange) * mid * 0.9, + }); + } + if (!pts.length) return; + + ctx.beginPath(); + ctx.moveTo(pts[0].x, mid); + pts.forEach((p) => ctx.lineTo(p.x, p.y)); + ctx.lineTo(pts[pts.length - 1].x, mid); + ctx.closePath(); + const grad = ctx.createLinearGradient(0, 0, pw, 0); + grad.addColorStop(0, color + '18'); + grad.addColorStop(0.5, color + '55'); + grad.addColorStop(1, color + '18'); + ctx.fillStyle = grad; + ctx.fill(); + + ctx.beginPath(); + pts.forEach((p, i) => (i === 0 ? ctx.moveTo(p.x, p.y) : ctx.lineTo(p.x, p.y))); + ctx.strokeStyle = color; + ctx.lineWidth = 1.5; + ctx.globalAlpha = 0.85; + ctx.stroke(); + ctx.globalAlpha = 1; }); - // Calculate y positions - account for slider thumb size (18px) - // The track is 120px, but thumb center moves within (120 - 18) = 102px range - const trackHeight = height; - const thumbSize = 18; - const usableTrack = trackHeight - thumbSize; - const trackOffset = thumbSize / 2; - - const getY = (gain) => { - const normalized = (gain - range.min) / rangeTotal; - // Invert because canvas Y=0 is at top, slider max is at top - return trackOffset + (1 - normalized) * usableTrack; - }; - - // Create points array - const points = gains.map((gain, i) => ({ - x: positions[i], - y: getY(gain), - })); - - if (points.length < 2) return; - - // Parse RGB values from color string - const rgbMatch = highlightColor.match(/\d+/g); - const r = rgbMatch ? parseInt(rgbMatch[0]) : 128; - const g = rgbMatch ? parseInt(rgbMatch[1]) : 128; - const b = rgbMatch ? parseInt(rgbMatch[2]) : 128; - - // Calculate control points for smooth curve - const getControlPoints = (i) => { - const p0 = points[i === 0 ? i : i - 1]; - const p1 = points[i]; - const p2 = points[i + 1]; - const p3 = points[i + 2] || p2; - - const cp1x = p1.x + (p2.x - p0.x) / 6; - const cp1y = p1.y + (p2.y - p0.y) / 6; - const cp2x = p2.x - (p3.x - p1.x) / 6; - const cp2y = p2.y - (p3.y - p1.y) / 6; - - return { cp1x, cp1y, cp2x, cp2y }; - }; - - // Draw filled area from curve to bottom - const gradient = ctx.createLinearGradient(0, 0, 0, height); - gradient.addColorStop(0, `rgba(${r}, ${g}, ${b}, 0.3)`); - gradient.addColorStop(1, `rgba(${r}, ${g}, ${b}, 0.05)`); - + // Combined curve on top ctx.beginPath(); - ctx.moveTo(points[0].x, height); - ctx.lineTo(points[0].x, points[0].y); - - for (let i = 0; i < points.length - 1; i++) { - const { cp1x, cp1y, cp2x, cp2y } = getControlPoints(i); - const p2 = points[i + 1]; - ctx.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, p2.x, p2.y); + let first = true; + for (let f = 20; f <= 20000; f *= 1.04) { + let total = 0; + for (const b of bands) { + if (b.enabled) total += calculateBiquadResponse(f, b, sr); + } + const x = freqToX(f, pw); + const y = mid - (Math.max(-dbRange, Math.min(dbRange, total)) / dbRange) * mid * 0.9; + first ? (ctx.moveTo(x, y), (first = false)) : ctx.lineTo(x, y); } - - ctx.lineTo(points[points.length - 1].x, height); - ctx.closePath(); - ctx.fillStyle = gradient; - ctx.fill(); - - // Draw the curve line - ctx.beginPath(); - ctx.moveTo(points[0].x, points[0].y); - - for (let i = 0; i < points.length - 1; i++) { - const { cp1x, cp1y, cp2x, cp2y } = getControlPoints(i); - const p2 = points[i + 1]; - ctx.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, p2.x, p2.y); - } - - ctx.strokeStyle = `rgb(${r}, ${g}, ${b})`; + ctx.strokeStyle = 'rgba(255,255,255,0.9)'; ctx.lineWidth = 2; - ctx.lineCap = 'round'; - ctx.lineJoin = 'round'; ctx.stroke(); + }; - // Draw dots at each band point - points.forEach((point) => { - ctx.beginPath(); - ctx.arc(point.x, point.y, 4, 0, Math.PI * 2); - ctx.fillStyle = `rgb(${r}, ${g}, ${b})`; - ctx.fill(); + // ======================================== + // Saved Profiles Rendering + // ======================================== + const renderSavedProfiles = () => { + if (!autoeqSavedGrid) return; + const profiles = equalizerSettings.getAutoEQProfiles(); + const activeId = equalizerSettings.getActiveAutoEQProfile(); + const keys = Object.keys(profiles); - // Add white center to dots for visibility - ctx.beginPath(); - ctx.arc(point.x, point.y, 2, 0, Math.PI * 2); - ctx.fillStyle = 'rgba(255, 255, 255, 0.8)'; - ctx.fill(); + if (autoeqSavedCount) autoeqSavedCount.textContent = keys.length; + autoeqSavedGrid.innerHTML = ''; + + if (keys.length === 0) return; + + keys.forEach((id) => { + const profile = profiles[id]; + const card = document.createElement('div'); + card.className = 'autoeq-profile-card' + (id === activeId ? ' active' : ''); + card.dataset.profileId = id; + + const preview = document.createElement('canvas'); + preview.className = 'autoeq-profile-preview'; + card.appendChild(preview); + + const info = document.createElement('div'); + info.className = 'autoeq-profile-info'; + info.innerHTML = ` + + ${profile.name || 'Unnamed'} + ${profile.bandCount || '?'} bands · ${profile.targetLabel || ''} + `; + card.appendChild(info); + + const delBtn = document.createElement('button'); + delBtn.className = 'autoeq-profile-delete'; + delBtn.innerHTML = '🗑'; + delBtn.title = 'Delete profile'; + delBtn.addEventListener('click', (e) => { + e.stopPropagation(); + equalizerSettings.deleteAutoEQProfile(id); + renderSavedProfiles(); + }); + card.appendChild(delBtn); + + // Click to load profile + card.addEventListener('click', () => { + loadAutoEQProfile(id); + }); + + autoeqSavedGrid.appendChild(card); + + // Draw mini preview using filter bands + requestAnimationFrame(() => { + drawBandsPreview(preview, profile.bands, profile.sampleRate); + }); + }); + }; + + // ======================================== + // Profile Save/Load + // ======================================== + const saveAutoEQProfile = (name) => { + if (!autoeqCurrentBands || !autoeqSelectedMeasurement) return; + + const targetId = autoeqTargetSelect ? autoeqTargetSelect.value : 'harman_oe_2018'; + const targetEntry = TARGETS.find((t) => t.id === targetId); + + const profile = { + id: 'autoeq_' + Date.now(), + name: name || (autoeqSelectedEntry ? autoeqSelectedEntry.name : 'Custom'), + headphoneName: autoeqSelectedEntry ? autoeqSelectedEntry.name : 'Custom', + headphoneType: autoeqSelectedEntry ? autoeqSelectedEntry.type : 'over-ear', + targetId, + targetLabel: targetEntry ? targetEntry.label : targetId, + bandCount: + (autoeqBandCount && autoeqBandCount.value ? parseInt(autoeqBandCount.value, 10) : null) || + autoeqCurrentBands.length || + 10, + maxFreq: autoeqMaxFreq ? parseInt(autoeqMaxFreq.value, 10) : 16000, + sampleRate: autoeqSampleRate ? parseInt(autoeqSampleRate.value, 10) : 48000, + bands: autoeqCurrentBands.map((b) => ({ ...b })), + gains: audioContextManager.getGains ? audioContextManager.getGains() : [], + preamp: equalizerSettings.getPreamp(), + measurementData: downsampleCurve(autoeqSelectedMeasurement), + targetData: downsampleCurve(targetEntry?.data), + correctedData: downsampleCurve(autoeqCorrectedCurve), + createdAt: Date.now(), + }; + + const id = equalizerSettings.saveAutoEQProfile(profile); + equalizerSettings.setActiveAutoEQProfile(id); + renderSavedProfiles(); + setAutoEQStatus(`Profile "${name}" saved`, 'success'); + }; + + const loadAutoEQProfile = (profileId) => { + const profiles = equalizerSettings.getAutoEQProfiles(); + const profile = profiles[profileId]; + if (!profile) return; + + autoeqCurrentBands = profile.bands.map((b) => ({ ...b })); + autoeqCorrectedCurve = profile.correctedData ? [...profile.correctedData] : null; + autoeqSelectedMeasurement = profile.measurementData ? [...profile.measurementData] : null; + autoeqSelectedEntry = { name: profile.headphoneName, type: profile.headphoneType }; + + // Update headphone select dropdown + if (autoeqHeadphoneSelect) { + let opt = autoeqHeadphoneSelect.querySelector(`option[value="${profile.headphoneName}"]`); + if (!opt) { + opt = document.createElement('option'); + opt.value = profile.headphoneName; + opt.textContent = profile.headphoneName.replace(/\s*\([^)]*\)\s*$/, ''); + autoeqHeadphoneSelect.appendChild(opt); + } + autoeqHeadphoneSelect.value = profile.headphoneName; + } + + // Update UI selects + if (autoeqTargetSelect) autoeqTargetSelect.value = profile.targetId || 'harman_oe_2018'; + setAutoeqBandCount(profile.bandCount, profile.bands); + if (autoeqMaxFreq) autoeqMaxFreq.value = profile.maxFreq || 16000; + if (autoeqSampleRate) autoeqSampleRate.value = profile.sampleRate || 48000; + + // Apply to audio + applyBandsToAudio(autoeqCurrentBands); + + equalizerSettings.setActiveAutoEQProfile(profileId); + renderSavedProfiles(); + renderBandControls(autoeqCurrentBands); + drawAutoEQGraph(); + setAutoEQStatus(`Loaded "${profile.name}"`, 'success'); + }; + + // Save button + if (autoeqSaveBtn) { + autoeqSaveBtn.addEventListener('click', () => { + const name = autoeqProfileNameInput ? autoeqProfileNameInput.value.trim() : ''; + if (!name) { + setAutoEQStatus('Enter a profile name', 'error'); + return; + } + saveAutoEQProfile(name); + if (autoeqProfileNameInput) autoeqProfileNameInput.value = ''; + }); + } + + // ======================================== + + // ======================================== + // Database Browser + // ======================================== + /** + * Load a headphone measurement entry + */ + const loadHeadphoneEntry = async (entry) => { + setAutoEQStatus('Loading measurement...', ''); + try { + const data = await fetchHeadphoneData(entry); + autoeqSelectedMeasurement = data; + autoeqSelectedEntry = entry; + + if (autoeqHeadphoneSelect) { + let opt = autoeqHeadphoneSelect.querySelector(`option[value="${entry.name}"]`); + if (!opt) { + opt = document.createElement('option'); + opt.value = entry.name; + opt.textContent = entry.name; + autoeqHeadphoneSelect.appendChild(opt); + } + autoeqHeadphoneSelect.value = entry.name; + } + + if (autoeqTargetSelect && entry.type === 'in-ear') { + autoeqTargetSelect.value = 'harman_ie_2019'; + } + + if (autoeqRunBtn) autoeqRunBtn.disabled = false; + drawAutoEQGraph(); + setAutoEQStatus(`Loaded ${data.length} points for ${entry.name}`, 'success'); + + // Persist for reload + equalizerSettings.setLastHeadphone(entry, data); + } catch (err) { + setAutoEQStatus('Failed: ' + err.message, 'error'); + } + }; + + /** + * Render database list with expandable headphone groups + */ + const renderDatabaseResults = (entries, append = false) => { + if (!autoeqDatabaseList) return; + if (!append) autoeqDatabaseList.innerHTML = ''; + + if (entries.length === 0 && !append) { + autoeqDatabaseList.innerHTML = + '
No results found
'; + return; + } + + // Group by base model name (strip source suffix like "(crinacle)") + const modelMap = new Map(); + entries.forEach((entry) => { + const baseName = entry.name.replace(/\s*\([^)]*\)\s*$/, '').trim() || entry.name; + if (!modelMap.has(baseName)) { + modelMap.set(baseName, []); + } + modelMap.get(baseName).push(entry); + }); + + modelMap.forEach((variants, name) => { + const wrapper = document.createElement('div'); + const rawFirstChar = name[0]?.toUpperCase() || '#'; + const firstLetter = /^[A-Z]$/.test(rawFirstChar) ? rawFirstChar : '#'; + wrapper.dataset.letter = firstLetter; + + const item = document.createElement('div'); + item.className = 'autoeq-db-item'; + item.dataset.name = name; + + item.innerHTML = ` + +
+ ${name} + ${variants.length} profile${variants.length > 1 ? 's' : ''} +
+ + `; + + wrapper.appendChild(item); + + // Sub-list for multiple profiles + if (variants.length > 1) { + const subList = document.createElement('div'); + subList.className = 'autoeq-db-sub-list'; + + variants.forEach((entry) => { + const subItem = document.createElement('div'); + subItem.className = 'autoeq-db-sub-item'; + // Extract source from parentheses + const sourceMatch = entry.name.match(/\(([^)]+)\)\s*$/); + const source = sourceMatch ? sourceMatch[1] : entry.type; + subItem.innerHTML = `${entry.name}${source}`; + subItem.addEventListener('click', (e) => { + e.stopPropagation(); + loadHeadphoneEntry(entry); + }); + subList.appendChild(subItem); + }); + + wrapper.appendChild(subList); + + item.addEventListener('click', () => { + item.classList.toggle('expanded'); + subList.classList.toggle('visible'); + }); + } else { + // Single profile - load directly + item.addEventListener('click', () => loadHeadphoneEntry(variants[0])); + } + + autoeqDatabaseList.appendChild(wrapper); }); }; /** - * Initialize band slider event listeners + * Render the A-Z alphabet index */ - const initializeBandSliders = () => { - const eqBands = eqBandsContainer?.querySelectorAll('.eq-band'); - if (!eqBands || eqBands.length === 0) return; + const renderAlphaIndex = () => { + const alphaContainer = document.getElementById('autoeq-alpha-index'); + if (!alphaContainer) return; + alphaContainer.innerHTML = ''; - const savedGains = equalizerSettings.getGains(currentBandCount); - - // FL Studio-style absolute position drag state - let isDragging = false; - - eqBands.forEach((bandEl) => { - const bandIndex = parseInt(bandEl.dataset.band, 10); - const slider = bandEl.querySelector('.eq-slider'); - - if (slider && !isNaN(bandIndex)) { - // Set initial value from saved settings - const initialGain = savedGains[bandIndex] ?? 0; - slider.value = initialGain; - updateBandValueDisplay(bandEl, initialGain); - - // Handle slider input - slider.addEventListener('input', (e) => { - const gain = parseFloat(e.target.value); - audioContextManager.setBandGain(bandIndex, gain); - updateBandValueDisplay(bandEl, gain); - drawEQCurve(); - - // When manually adjusting, check if we should clear preset - if (eqPresetSelect && eqPresetSelect.value !== 'flat') { - const currentGains = audioContextManager.getGains(); - const builtInPresets = EQ_PRESETS; - const currentPreset = builtInPresets[eqPresetSelect.value]; - if (currentPreset) { - const matches = currentPreset.gains.every((g, i) => Math.abs(g - currentGains[i]) < 0.01); - if (!matches) { - // User has deviated from preset - } - } - } + const letters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ#'.split(''); + letters.forEach((letter) => { + const btn = document.createElement('button'); + btn.textContent = letter; + btn.addEventListener('click', () => { + // Find the index of the first entry starting with this letter + const targetIdx = _dbFilteredEntries.findIndex((e) => { + const first = e.name[0].toUpperCase(); + return letter === '#' ? !/[A-Z]/.test(first) : first === letter; }); - // Double-click to reset individual band to 0 - slider.addEventListener('dblclick', () => { - slider.value = 0; - audioContextManager.setBandGain(bandIndex, 0); - updateBandValueDisplay(bandEl, 0); - drawEQCurve(); - }); + if (targetIdx < 0) return; // No entries for this letter - // FL Studio-style absolute drag: mousedown starts drag mode - bandEl.addEventListener('mousedown', (e) => { - // Only handle left mouse button - if (e.button !== 0) return; - - isDragging = true; - document.body.style.cursor = 'ns-resize'; - e.preventDefault(); - }); - } - }); - - // Global mousemove: whichever band is under cursor, set slider to cursor Y position - document.addEventListener('mousemove', (e) => { - if (!isDragging) return; - - // Find which band is under the cursor - const elementUnderCursor = document.elementFromPoint(e.clientX, e.clientY); - const bandUnderCursor = elementUnderCursor?.closest('.eq-band'); - - if (bandUnderCursor) { - const slider = bandUnderCursor.querySelector('.eq-slider'); - - if (slider) { - const rect = slider.getBoundingClientRect(); - const min = parseFloat(slider.min); - const max = parseFloat(slider.max); - const step = parseFloat(slider.step) || 0.5; - - // Calculate relative Y position within slider (0 = bottom, 1 = top) - const relativeY = (rect.bottom - e.clientY) / rect.height; - const clampedY = Math.max(0, Math.min(1, relativeY)); - - // Map to slider value range - let newValue = min + clampedY * (max - min); - - // Round to step - newValue = Math.round(newValue / step) * step; - - // Only update if value changed - if (parseFloat(slider.value) !== newValue) { - slider.value = newValue; - const bandIndex = parseInt(bandUnderCursor.dataset.band, 10); - audioContextManager.setBandGain(bandIndex, newValue); - updateBandValueDisplay(bandUnderCursor, newValue); - drawEQCurve(); - } + // Render all entries up to and past the target so the DOM element exists + while (_dbRenderedCount <= targetIdx + DB_BATCH_SIZE && _dbRenderedCount < _dbFilteredEntries.length) { + renderNextDatabaseBatch(); } - } - }); - // Global mouseup: stop dragging - document.addEventListener('mouseup', () => { - if (isDragging) { - isDragging = false; - document.body.style.cursor = ''; - } + // Now find and scroll to the element + requestAnimationFrame(() => { + const target = autoeqDatabaseList?.querySelector(`[data-letter="${letter}"]`); + if (target) target.scrollIntoView({ behavior: 'smooth', block: 'start' }); + }); + }); + alphaContainer.appendChild(btn); }); - - // Initial curve draw with delay to ensure canvas has proper dimensions - setTimeout(() => { - drawEQCurve(); - }, 100); }; - // Initialize EQ toggle - if (eqToggle) { - const isEnabled = equalizerSettings.isEnabled(); - eqToggle.checked = isEnabled; - updateEQContainerVisibility(isEnabled); + /** + * Load and display the full headphone database + */ + // Lazy-loading state for database list + let _dbFilteredEntries = []; + let _dbRenderedCount = 0; + const DB_BATCH_SIZE = 80; - eqToggle.addEventListener('change', (e) => { - const enabled = e.target.checked; - audioContextManager.toggleEQ(enabled); - updateEQContainerVisibility(enabled); - - // Redraw curve after a brief delay to allow container to become visible - if (enabled) { - setTimeout(() => { - drawEQCurve(); - }, 50); - } - }); - } - - // Initialize band count input - if (eqBandCountInput) { - eqBandCountInput.value = currentBandCount; - - eqBandCountInput.addEventListener('change', (e) => { - const newCount = parseInt(e.target.value, 10); - if (newCount >= equalizerSettings.MIN_BANDS && newCount <= equalizerSettings.MAX_BANDS) { - currentBandCount = newCount; - - // Save new band count and update audio context (interpolates gains automatically) - equalizerSettings.setBandCount(newCount); - audioContextManager.setBandCount?.(newCount); - - // Regenerate UI - generateEQBands( - newCount, - currentRange.min, - currentRange.max, - currentFreqRange.min, - currentFreqRange.max - ); - - // Get interpolated gains from audio context - const interpolatedGains = audioContextManager.getGains?.() || equalizerSettings.getGains(newCount); - updateAllBandUI(interpolatedGains); - - // Keep current preset or set to custom if modified - if (eqPresetSelect) { - const currentPreset = eqPresetSelect.value; - if (!currentPreset.startsWith('custom_')) { - eqPresetSelect.value = 'custom'; - } - } - updateDeleteButtonVisibility(); - - // Show brief feedback - const originalText = eqBandCountInput.style.backgroundColor; - eqBandCountInput.style.backgroundColor = 'var(--highlight)'; - setTimeout(() => { - eqBandCountInput.style.backgroundColor = originalText; - }, 300); - } - }); - } - - // Initialize preset selector - if (eqPresetSelect) { - populateCustomPresets(); - eqPresetSelect.value = equalizerSettings.getPreset(); - updateDeleteButtonVisibility(); - - eqPresetSelect.addEventListener('change', (e) => { - const presetKey = e.target.value; - - // Check if it's a custom preset - if (isCustomPreset(presetKey)) { - const customPresets = equalizerSettings.getCustomPresets(); - const customPreset = customPresets[presetKey]; - if (customPreset && customPreset.gains) { - // Check if preset has different band count - const presetBands = customPreset.bandCount || customPreset.gains.length; - if (presetBands !== currentBandCount) { - // Update band count to match preset - currentBandCount = presetBands; - equalizerSettings.setBandCount(presetBands); - if (eqBandCountInput) eqBandCountInput.value = presetBands; - generateEQBands( - presetBands, - currentRange.min, - currentRange.max, - currentFreqRange.min, - currentFreqRange.max - ); - } - audioContextManager.setAllGains(customPreset.gains); - updateAllBandUI(customPreset.gains); - equalizerSettings.setPreset(presetKey); - } - } else { - // Built-in preset - use current band count - const presets = EQ_PRESETS; - const preset = presets[presetKey]; - if (preset) { - audioContextManager.applyPreset(presetKey); - updateAllBandUI(preset.gains); - } - } - updateDeleteButtonVisibility(); - }); - } - - // Initialize reset button - if (eqResetBtn) { - eqResetBtn.addEventListener('click', () => { - audioContextManager.reset(); - updateAllBandUI(new Array(currentBandCount).fill(0)); - if (eqPresetSelect) { - eqPresetSelect.value = 'flat'; - updateDeleteButtonVisibility(); - } - }); - } - - // Initialize save custom preset button - if (saveCustomPresetBtn && customPresetNameInput) { - saveCustomPresetBtn.addEventListener('click', () => { - const name = customPresetNameInput.value.trim(); - if (!name) { - alert('Please enter a name for your preset'); - return; - } - - const currentGains = audioContextManager.getGains(); - const presetId = equalizerSettings.saveCustomPreset(name, currentGains); - - if (presetId) { - populateCustomPresets(); - if (eqPresetSelect) { - eqPresetSelect.value = presetId; - equalizerSettings.setPreset(presetId); - updateDeleteButtonVisibility(); - } - customPresetNameInput.value = ''; - - // Show feedback - const originalText = saveCustomPresetBtn.textContent; - saveCustomPresetBtn.textContent = 'Saved!'; - setTimeout(() => { - saveCustomPresetBtn.textContent = originalText; - }, 1500); - } else { - alert('Failed to save preset. Please try again.'); - } - }); - - // Allow saving with Enter key - customPresetNameInput.addEventListener('keypress', (e) => { - if (e.key === 'Enter') { - saveCustomPresetBtn.click(); - } - }); - } - - // Initialize delete custom preset button - if (deleteCustomPresetBtn) { - deleteCustomPresetBtn.addEventListener('click', () => { - if (!eqPresetSelect) return; - - const presetId = eqPresetSelect.value; - if (!isCustomPreset(presetId)) return; - - const customPresets = equalizerSettings.getCustomPresets(); - const presetName = customPresets[presetId]?.name || 'this preset'; - - if (confirm(`Are you sure you want to delete "${presetName}"?`)) { - const success = equalizerSettings.deleteCustomPreset(presetId); - if (success) { - populateCustomPresets(); - eqPresetSelect.value = 'flat'; - audioContextManager.reset(); - updateAllBandUI(new Array(currentBandCount).fill(0)); - equalizerSettings.setPreset('flat'); - updateDeleteButtonVisibility(); - } else { - alert('Failed to delete preset. Please try again.'); - } - } - }); - } - - // Initialize range inputs - if (eqRangeMinInput) { - eqRangeMinInput.value = currentRange.min; - } - if (eqRangeMaxInput) { - eqRangeMaxInput.value = currentRange.max; - } - updateEQScale(currentRange.min, currentRange.max); - - // Initialize apply range button - if (applyEqRangeBtn && eqRangeMinInput && eqRangeMaxInput) { - applyEqRangeBtn.addEventListener('click', () => { - const newMin = parseInt(eqRangeMinInput.value, 10); - const newMax = parseInt(eqRangeMaxInput.value, 10); - - // Validate range - if (isNaN(newMin) || isNaN(newMax)) { - alert('Please enter valid numbers for the range'); - return; - } - - if (newMin >= 0 || newMax <= 0) { - alert('Minimum must be negative and maximum must be positive'); - return; - } - - if (newMin < equalizerSettings.ABSOLUTE_MIN || newMax > equalizerSettings.ABSOLUTE_MAX) { - alert( - `Range must be between ${equalizerSettings.ABSOLUTE_MIN} and ${equalizerSettings.ABSOLUTE_MAX} dB` - ); - return; - } - - // Save new range - equalizerSettings.setRange(newMin, newMax); - currentRange = { min: newMin, max: newMax }; - - // Regenerate bands with new range - generateEQBands(currentBandCount, newMin, newMax); - - // Update scale display - updateEQScale(newMin, newMax); - - // Reset gains to flat - const flatGains = new Array(currentBandCount).fill(0); - audioContextManager.setAllGains(flatGains); - updateAllBandUI(flatGains); - - // Reset to flat preset - if (eqPresetSelect) { - eqPresetSelect.value = 'flat'; - equalizerSettings.setPreset('flat'); - } - - // Show feedback - const originalText = applyEqRangeBtn.textContent; - applyEqRangeBtn.textContent = 'Applied!'; - setTimeout(() => { - applyEqRangeBtn.textContent = originalText; - }, 1500); - }); - } - - // Initialize reset DB range button - if (resetEqRangeBtn) { - resetEqRangeBtn.addEventListener('click', () => { - // Reset to default values - const defaultMin = equalizerSettings.DEFAULT_RANGE_MIN; - const defaultMax = equalizerSettings.DEFAULT_RANGE_MAX; - - // Update inputs - if (eqRangeMinInput) eqRangeMinInput.value = defaultMin; - if (eqRangeMaxInput) eqRangeMaxInput.value = defaultMax; - - // Save new range - equalizerSettings.setRange(defaultMin, defaultMax); - currentRange = { min: defaultMin, max: defaultMax }; - - // Regenerate bands with new range - generateEQBands(currentBandCount, defaultMin, defaultMax, currentFreqRange.min, currentFreqRange.max); - - // Update scale display - updateEQScale(defaultMin, defaultMax); - - // Reset gains to flat - const flatGains = new Array(currentBandCount).fill(0); - audioContextManager.setAllGains(flatGains); - updateAllBandUI(flatGains); - - // Reset to flat preset - if (eqPresetSelect) { - eqPresetSelect.value = 'flat'; - equalizerSettings.setPreset('flat'); - } - - // Show feedback - const originalText = resetEqRangeBtn.textContent; - resetEqRangeBtn.textContent = 'Reset!'; - setTimeout(() => { - resetEqRangeBtn.textContent = originalText; - }, 1500); - }); - } - - // Initialize frequency range inputs - if (eqFreqMinInput) { - eqFreqMinInput.value = currentFreqRange.min; - } - if (eqFreqMaxInput) { - eqFreqMaxInput.value = currentFreqRange.max; - } - - // Initialize apply frequency range button - if (applyEqFreqBtn && eqFreqMinInput && eqFreqMaxInput) { - applyEqFreqBtn.addEventListener('click', () => { - const newMin = parseInt(eqFreqMinInput.value, 10); - const newMax = parseInt(eqFreqMaxInput.value, 10); - - // Validate range - if (isNaN(newMin) || isNaN(newMax)) { - alert('Please enter valid numbers for the frequency range'); - return; - } - - if (newMin < equalizerSettings.ABSOLUTE_FREQ_MIN || newMax > equalizerSettings.ABSOLUTE_FREQ_MAX) { - alert( - `Frequency range must be between ${equalizerSettings.ABSOLUTE_FREQ_MIN} Hz and ${equalizerSettings.ABSOLUTE_FREQ_MAX} Hz` - ); - return; - } - - if (newMin >= newMax) { - alert('Minimum frequency must be less than maximum frequency'); - return; - } - - // Save new frequency range - equalizerSettings.setFreqRange(newMin, newMax); - currentFreqRange = { min: newMin, max: newMax }; - - // Update audio context - audioContextManager.setFreqRange(newMin, newMax); - - // Regenerate bands with new frequency range - generateEQBands(currentBandCount, currentRange.min, currentRange.max, newMin, newMax); - - // Reset gains to flat - const flatGains = new Array(currentBandCount).fill(0); - audioContextManager.setAllGains(flatGains); - updateAllBandUI(flatGains); - - // Reset to flat preset - if (eqPresetSelect) { - eqPresetSelect.value = 'flat'; - equalizerSettings.setPreset('flat'); - } - - // Show feedback - const originalText = applyEqFreqBtn.textContent; - applyEqFreqBtn.textContent = 'Applied!'; - setTimeout(() => { - applyEqFreqBtn.textContent = originalText; - }, 1500); - }); - } - - // Initialize reset frequency range button - if (resetEqFreqBtn) { - resetEqFreqBtn.addEventListener('click', () => { - // Reset to default values - const defaultMin = equalizerSettings.DEFAULT_FREQ_MIN; - const defaultMax = equalizerSettings.DEFAULT_FREQ_MAX; - - // Update inputs - if (eqFreqMinInput) eqFreqMinInput.value = defaultMin; - if (eqFreqMaxInput) eqFreqMaxInput.value = defaultMax; - - // Save new frequency range - equalizerSettings.setFreqRange(defaultMin, defaultMax); - currentFreqRange = { min: defaultMin, max: defaultMax }; - - // Update audio context - audioContextManager.setFreqRange(defaultMin, defaultMax); - - // Regenerate bands with new frequency range - generateEQBands(currentBandCount, currentRange.min, currentRange.max, defaultMin, defaultMax); - - // Reset gains to flat - const flatGains = new Array(currentBandCount).fill(0); - audioContextManager.setAllGains(flatGains); - updateAllBandUI(flatGains); - - // Reset to flat preset - if (eqPresetSelect) { - eqPresetSelect.value = 'flat'; - equalizerSettings.setPreset('flat'); - } - - // Show feedback - const originalText = resetEqFreqBtn.textContent; - resetEqFreqBtn.textContent = 'Reset!'; - setTimeout(() => { - resetEqFreqBtn.textContent = originalText; - }, 1500); - }); - } - - // Initialize preamp control - const updatePreampUI = (value) => { - currentPreamp = value; - if (eqPreampSlider) eqPreampSlider.value = value; - if (eqPreampInput) eqPreampInput.value = value; - audioContextManager.setPreamp?.(value); + const renderNextDatabaseBatch = () => { + if (_dbRenderedCount >= _dbFilteredEntries.length) return; + const end = Math.min(_dbRenderedCount + DB_BATCH_SIZE, _dbFilteredEntries.length); + const batch = _dbFilteredEntries.slice(_dbRenderedCount, end); + renderDatabaseResults(batch, true); // append mode + _dbRenderedCount = end; }; - if (eqPreampSlider) { - // Set initial value - eqPreampSlider.value = currentPreamp; + const resetDatabaseList = (entries) => { + _dbFilteredEntries = entries; + _dbRenderedCount = 0; + if (autoeqDatabaseList) autoeqDatabaseList.innerHTML = ''; + renderNextDatabaseBatch(); + }; - // Handle slider input - eqPreampSlider.addEventListener('input', (e) => { - const value = parseFloat(e.target.value); - updatePreampUI(value); - }); - } - - if (eqPreampInput) { - // Set initial value - eqPreampInput.value = currentPreamp; - - // Handle text input - eqPreampInput.addEventListener('change', (e) => { - let value = parseFloat(e.target.value); - // Clamp to valid range - value = Math.max(-20, Math.min(20, value || 0)); - updatePreampUI(value); - }); - - // Handle enter key - eqPreampInput.addEventListener('keypress', (e) => { - if (e.key === 'Enter') { - e.target.blur(); + // Infinite scroll on database list + if (autoeqDatabaseList) { + autoeqDatabaseList.addEventListener('scroll', () => { + const el = autoeqDatabaseList; + if (el.scrollTop + el.clientHeight >= el.scrollHeight - 60) { + renderNextDatabaseBatch(); } }); } - // Initialize import/export controls - if (eqExportBtn) { - eqExportBtn.addEventListener('click', () => { - const text = audioContextManager.exportEQToText?.(); - if (text) { - navigator.clipboard - .writeText(text) - .then(() => { - eqExportBtn.textContent = 'Copied!'; - setTimeout(() => { - eqExportBtn.textContent = 'Export'; - }, 1500); - }) - .catch(() => { - // Fallback: create and download file - const blob = new Blob([text], { type: 'text/plain' }); - const url = URL.createObjectURL(blob); - const a = document.createElement('a'); - a.href = url; - a.download = 'equalizer-settings.txt'; - document.body.appendChild(a); - a.click(); - document.body.removeChild(a); - URL.revokeObjectURL(url); - }); + const loadFullDatabase = async () => { + if (_autoeqIndex.length === 0) { + setAutoEQStatus('Loading headphone database...', ''); + try { + _autoeqIndex = await fetchAutoEqIndex(); + setAutoEQStatus(`Loaded ${_autoeqIndex.length} headphones`, 'success'); + } catch { + setAutoEQStatus('Failed to load database', 'error'); + return; } + } + if (autoeqDatabaseCount) autoeqDatabaseCount.textContent = `${_autoeqIndex.length} models`; + resetDatabaseList(_autoeqIndex); + renderAlphaIndex(); + }; + + // Search input with debounce + { + const searchEl = document.getElementById('autoeq-headphone-search'); + + if (searchEl && !searchEl._autoeqBound) { + searchEl._autoeqBound = true; + let timer = null; + + const doSearch = async () => { + const query = searchEl.value.trim(); + if (!query) { + resetDatabaseList(_autoeqIndex); + return; + } + + if (_autoeqIndex.length === 0) await loadFullDatabase(); + + const results = searchHeadphones(query, _autoeqIndex, 'all', 500); + resetDatabaseList(results); + }; + + searchEl.addEventListener('input', () => { + clearTimeout(timer); + timer = setTimeout(doSearch, 300); + }); + } + } + + // ======================================== + // AutoEQ Run + // ======================================== + if (autoeqRunBtn) { + autoeqRunBtn.addEventListener('click', () => { + if (!autoeqSelectedMeasurement) return; + + setAutoEQStatus('Running AutoEQ...', ''); + autoeqRunBtn.disabled = true; + + setTimeout(() => { + try { + const targetId = autoeqTargetSelect ? autoeqTargetSelect.value : 'harman_oe_2018'; + const targetEntry = TARGETS.find((t) => t.id === targetId); + if (!targetEntry || !targetEntry.data || targetEntry.data.length === 0) { + setAutoEQStatus('Invalid target curve', 'error'); + autoeqRunBtn.disabled = false; + return; + } + + const bandCount = autoeqBandCount ? parseInt(autoeqBandCount.value, 10) : 10; + const maxFreq = autoeqMaxFreq ? parseInt(autoeqMaxFreq.value, 10) : 16000; + const sampleRate = autoeqSampleRate ? parseInt(autoeqSampleRate.value, 10) : 48000; + + const bands = runAutoEqAlgorithm( + autoeqSelectedMeasurement, + targetEntry.data, + bandCount, + maxFreq, + 20, + 5.0, + sampleRate + ); + + if (!bands || bands.length === 0) { + setAutoEQStatus('No correction needed', 'success'); + autoeqRunBtn.disabled = false; + return; + } + + autoeqCurrentBands = bands; + computeCorrectedCurve(); + applyBandsToAudio(autoeqCurrentBands); + drawAutoEQGraph(); + renderBandControls(autoeqCurrentBands); + + const headphoneName = autoeqSelectedEntry ? autoeqSelectedEntry.name : 'Custom'; + setAutoEQStatus(`Applied ${bands.length} bands for ${headphoneName}`, 'success'); + autoeqRunBtn.disabled = false; + } catch (err) { + console.error('[AutoEQ] Algorithm failed:', err); + setAutoEQStatus('Error: ' + err.message, 'error'); + autoeqRunBtn.disabled = false; + } + }, 50); }); } - if (eqImportBtn && eqImportFile) { - eqImportBtn.addEventListener('click', () => { - eqImportFile.click(); + // ======================================== + // Import Measurement File + // ======================================== + if (autoeqImportBtn && autoeqImportFile) { + autoeqImportBtn.addEventListener('click', () => { + autoeqImportFile.click(); }); - eqImportFile.addEventListener('change', (e) => { + autoeqImportFile.addEventListener('change', (e) => { const file = e.target.files[0]; if (!file) return; const reader = new FileReader(); reader.onload = (event) => { - const text = event.target.result; - const success = audioContextManager.importEQFromText?.(text); - if (success) { - // Update UI - currentPreamp = equalizerSettings.getPreamp(); - updatePreampUI(currentPreamp); - - // Update band count if changed - currentBandCount = equalizerSettings.getBandCount(); - if (eqBandCountInput) eqBandCountInput.value = currentBandCount; - - // Regenerate bands and update UI - generateEQBands( - currentBandCount, - currentRange.min, - currentRange.max, - currentFreqRange.min, - currentFreqRange.max - ); - const gains = audioContextManager.getGains?.() || equalizerSettings.getGains(currentBandCount); - updateAllBandUI(gains); - - eqImportBtn.textContent = 'Imported!'; - setTimeout(() => { - eqImportBtn.textContent = 'Import'; - }, 1500); - } else { - eqImportBtn.textContent = 'Invalid!'; - setTimeout(() => { - eqImportBtn.textContent = 'Import'; - }, 1500); + try { + const data = parseRawData(event.target.result); + if (data.length === 0) { + setAutoEQStatus('Invalid measurement file', 'error'); + return; + } + autoeqSelectedMeasurement = data; + autoeqSelectedEntry = { name: file.name.replace(/\.(txt|csv)$/i, ''), type: 'over-ear' }; + if (autoeqRunBtn) autoeqRunBtn.disabled = false; + drawAutoEQGraph(); + setAutoEQStatus(`Imported ${data.length} points from ${file.name}`, 'success'); + } catch { + setAutoEQStatus('Failed to parse file', 'error'); } }; reader.readAsText(file); - - // Reset file input e.target.value = ''; }); } - // Generate initial EQ bands with current ranges - generateEQBands(currentBandCount, currentRange.min, currentRange.max, currentFreqRange.min, currentFreqRange.max); + // ======================================== + // Import Target Button + // ======================================== + const autoeqImportTargetBtn = document.getElementById('autoeq-import-target-btn'); + const autoeqImportTargetFile = document.getElementById('autoeq-import-target-file'); - // Listen for band count changes from other sources - window.addEventListener('equalizer-band-count-changed', (e) => { - if (e.detail && e.detail.bandCount) { - currentBandCount = e.detail.bandCount; - if (eqBandCountInput) eqBandCountInput.value = currentBandCount; - generateEQBands( - currentBandCount, - currentRange.min, - currentRange.max, - currentFreqRange.min, - currentFreqRange.max - ); + if (autoeqImportTargetBtn && autoeqImportTargetFile) { + autoeqImportTargetBtn.addEventListener('click', () => autoeqImportTargetFile.click()); + + autoeqImportTargetFile.addEventListener('change', (e) => { + const file = e.target.files[0]; + if (!file) return; + + const reader = new FileReader(); + reader.onload = (event) => { + try { + const data = parseRawData(event.target.result); + if (data.length === 0) { + setAutoEQStatus('Invalid target file', 'error'); + return; + } + + const customId = 'custom_target'; + const customLabel = file.name.replace(/\.(txt|csv)$/i, ''); + + // Inject or update in TARGETS array + const existing = TARGETS.findIndex((t) => t.id === customId); + if (existing > -1) { + TARGETS[existing] = { id: customId, label: customLabel, data }; + } else { + TARGETS.push({ id: customId, label: customLabel, data }); + } + + // Add/update option in select + if (autoeqTargetSelect) { + let opt = autoeqTargetSelect.querySelector('option[value="custom_target"]'); + if (!opt) { + opt = document.createElement('option'); + opt.value = customId; + autoeqTargetSelect.appendChild(opt); + } + opt.textContent = customLabel; + autoeqTargetSelect.value = customId; + } + + computeCorrectedCurve(); + drawAutoEQGraph(); + setAutoEQStatus(`Target "${customLabel}" imported`, 'success'); + } catch { + setAutoEQStatus('Failed to parse target file', 'error'); + } + }; + reader.readAsText(file); + e.target.value = ''; + }); + } + + // ======================================== + // Download/Export Button + // ======================================== + if (autoeqDownloadBtn) { + autoeqDownloadBtn.addEventListener('click', () => { + if (!autoeqCurrentBands || autoeqCurrentBands.length === 0) { + setAutoEQStatus('No EQ to export', 'error'); + return; + } + // Build EqualizerAPO / Peace format + let lines = [`Preamp: ${currentPreamp} dB`]; + autoeqCurrentBands.forEach((band, i) => { + if (!band.enabled) return; + const type = band.type === 'peaking' ? 'PK' : band.type === 'lowshelf' ? 'LSC' : 'HSC'; + lines.push( + `Filter ${i + 1}: ON ${type} Fc ${Math.round(band.freq)} Hz Gain ${band.gain.toFixed(1)} dB Q ${band.q.toFixed(2)}` + ); + }); + const exportText = lines.join('\n'); + const blob = new Blob([exportText], { type: 'text/plain' }); + const url = URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + a.download = `autoeq-${autoeqSelectedEntry?.name || 'custom'}.txt`; + a.click(); + URL.revokeObjectURL(url); + setAutoEQStatus('Exported', 'success'); + }); + } + + // ======================================== + // Auto Preamp Compensation Toggle + // ======================================== + if (autoPreampToggle) { + autoPreampToggle.addEventListener('change', () => { + autoPreampEnabled = autoPreampToggle.checked; + if (autoPreampEnabled) { + // Recalculate and apply auto preamp immediately + const bands = getActiveBands(); + if (bands && bands.length > 0) { + const maxGain = Math.max(0, ...bands.filter((b) => b.enabled).map((b) => b.gain)); + const autoPreamp = maxGain > 0 ? -Math.round(maxGain * 10) / 10 : 0; + currentPreamp = autoPreamp; + equalizerSettings.setPreamp(autoPreamp); + if (audioContextManager.setPreamp) audioContextManager.setPreamp(autoPreamp); + if (eqPreampSlider) eqPreampSlider.value = autoPreamp; + if (autoeqPreampValue) autoeqPreampValue.textContent = `${autoPreamp} dB`; + } + } else { + // Reset preamp to 0 dB + currentPreamp = 0; + equalizerSettings.setPreamp(0); + if (audioContextManager.setPreamp) audioContextManager.setPreamp(0); + if (eqPreampSlider) eqPreampSlider.value = 0; + if (autoeqPreampValue) autoeqPreampValue.textContent = '0 dB'; + } + }); + } + + // ======================================== + // Preamp Slider + // ======================================== + if (eqPreampSlider) { + eqPreampSlider.value = currentPreamp; + if (autoeqPreampValue) autoeqPreampValue.textContent = `${currentPreamp} dB`; + + eqPreampSlider.addEventListener('input', () => { + // Manual preamp adjustment disables auto compensation + if (autoPreampEnabled) { + autoPreampEnabled = false; + if (autoPreampToggle) autoPreampToggle.checked = false; + } + const val = parseFloat(eqPreampSlider.value); + currentPreamp = val; + equalizerSettings.setPreamp(val); + if (autoeqPreampValue) autoeqPreampValue.textContent = `${val} dB`; + if (audioContextManager.setPreamp) audioContextManager.setPreamp(val); + }); + } + + // ======================================== + // Speaker EQ State + // ======================================== + const SPEAKER_CONFIGS = { + '2.0': ['FL', 'FR'], + 5.1: ['FL', 'FR', 'C', 'LFE', 'SL', 'SR'], + 7.1: ['FL', 'FR', 'C', 'LFE', 'SL', 'SR', 'SBL', 'SBR'], + }; + const SPEAKER_CHANNEL_LABELS = { + FL: 'Front L', + FR: 'Front R', + C: 'Center', + LFE: 'Sub', + SL: 'Surr L', + SR: 'Surr R', + SBL: 'Back L', + SBR: 'Back R', + }; + let speakerConfig = '2.0'; + let speakerActiveChannel = 'FL'; + const speakerChannels = {}; + // Initialize all channels + Object.keys(SPEAKER_CHANNEL_LABELS).forEach((id) => { + speakerChannels[id] = { + measurement: null, + targetId: 'harman_room', + bands: Array.from({ length: 10 }, (_, i) => ({ + id: i, + type: 'peaking', + freq: Math.round(100 * Math.pow(2, i)), + gain: 0, + q: 1.41, + enabled: true, + })), + preamp: 0, + }; + }); + + // ======================================== + // Mode Toggle: AutoEQ vs Parametric EQ vs Speaker EQ + // ======================================== + const modeButtons = document.querySelectorAll('.autoeq-mode-btn'); + const EQ_MODE_KEY = 'eq-active-mode'; + let currentMode = 'autoeq'; + + const speakerSection = document.getElementById('speaker-eq-section'); + + const setEQMode = (mode) => { + currentMode = mode; + localStorage.setItem(EQ_MODE_KEY, mode); + modeButtons.forEach((b) => b.classList.toggle('active', b.dataset.mode === mode)); + + const graphSection = document.querySelector('.autoeq-graph-section'); + const controlsSection = document.querySelector('.autoeq-controls-section'); + const savedSection = document.getElementById('autoeq-saved-section'); + const databaseSection = document.getElementById('autoeq-database-section'); + const filtersSection = document.getElementById('autoeq-filters-section'); + const filtersContent = document.getElementById('autoeq-filters-content'); + const presetRow = document.getElementById('autoeq-preset-row'); + const parametricProfiles = document.getElementById('autoeq-parametric-profiles'); + const speakerSavedSection = document.getElementById('speaker-saved-section'); + + // Reset interactive state on switch + draggedNode = null; + hoveredNode = null; + + // Graph always visible in all modes + if (graphSection) graphSection.style.display = ''; + + // Hide all mode-specific sections first + if (controlsSection) controlsSection.style.display = 'none'; + if (savedSection) savedSection.style.display = 'none'; + if (databaseSection) databaseSection.style.display = 'none'; + if (filtersSection) filtersSection.style.display = 'none'; + if (presetRow) presetRow.style.display = 'none'; + if (parametricProfiles) parametricProfiles.style.display = 'none'; + if (speakerSection) speakerSection.style.display = 'none'; + if (speakerSavedSection) speakerSavedSection.style.display = 'none'; + + if (mode === 'autoeq') { + if (controlsSection) controlsSection.style.display = ''; + if (savedSection) savedSection.style.display = ''; + if (databaseSection) databaseSection.style.display = ''; + if (filtersSection) filtersSection.style.display = ''; + + if (autoeqCurrentBands && autoeqCurrentBands.length > 0) { + applyBandsToAudio(autoeqCurrentBands); + renderBandControls(autoeqCurrentBands); + } + computeCorrectedCurve(); + drawAutoEQGraph(); + } else if (mode === 'parametric') { + if (filtersSection) filtersSection.style.display = ''; + if (filtersContent) filtersContent.style.display = 'flex'; + if (autoeqFiltersCollapse) autoeqFiltersCollapse.classList.remove('collapsed'); + if (presetRow) presetRow.style.display = ''; + if (parametricProfiles) parametricProfiles.style.display = ''; + + if (!parametricBands || parametricBands.length === 0) { + const defaultBands = []; + for (let i = 0; i < 10; i++) { + const freq = 20 * Math.pow(20000 / 20, i / 9); + defaultBands.push({ + id: i, + type: 'peaking', + freq: Math.round(freq), + gain: 0, + q: 1.0, + enabled: true, + }); + } + parametricBands = defaultBands; + } + applyBandsToAudio(parametricBands); + renderBandControls(parametricBands); + renderParametricProfiles(); + computeCorrectedCurve(); + drawAutoEQGraph(); + } else if (mode === 'speaker') { + if (speakerSection) speakerSection.style.display = ''; + if (speakerSavedSection) speakerSavedSection.style.display = ''; + if (filtersSection) filtersSection.style.display = ''; + if (filtersContent) filtersContent.style.display = 'flex'; + if (autoeqFiltersCollapse) autoeqFiltersCollapse.classList.remove('collapsed'); + + // Apply active speaker channel bands + const ch = speakerChannels[speakerActiveChannel]; + if (ch && ch.bands.length > 0) { + applyBandsToAudio(ch.bands); + renderBandControls(ch.bands); + } + renderSpeakerChannelTabs(); + renderSpeakerProfiles(); + computeCorrectedCurve(); + drawAutoEQGraph(); } - }); - // Listen for frequency range changes from other sources - window.addEventListener('equalizer-freq-range-changed', (e) => { - if (e.detail && e.detail.min !== undefined && e.detail.max !== undefined) { - currentFreqRange = { min: e.detail.min, max: e.detail.max }; - if (eqFreqMinInput) eqFreqMinInput.value = currentFreqRange.min; - if (eqFreqMaxInput) eqFreqMaxInput.value = currentFreqRange.max; - generateEQBands( - currentBandCount, - currentRange.min, - currentRange.max, - currentFreqRange.min, - currentFreqRange.max - ); + // Update tutorial tab if visible + const hp = document.getElementById('eq-howto-panel'); + if (hp && hp.style.display !== 'none') { + const tabs = { + autoeq: document.getElementById('eq-howto-autoeq'), + parametric: document.getElementById('eq-howto-parametric'), + speaker: document.getElementById('eq-howto-speaker'), + }; + Object.values(tabs).forEach((t) => { + if (t) t.style.display = 'none'; + }); + if (tabs[mode]) tabs[mode].style.display = ''; } + }; + + modeButtons.forEach((btn) => { + btn.addEventListener('click', () => setEQMode(btn.dataset.mode)); }); - // Redraw EQ curve on window resize - window.addEventListener('resize', () => { - requestAnimationFrame(drawEQCurve); - }); + // ======================================== + // How-To Tutorial Panel + // ======================================== + const howtoBtn = document.getElementById('eq-howto-btn'); + const howtoPanel = document.getElementById('eq-howto-panel'); + const howtoClose = document.getElementById('eq-howto-close'); + const howtoTabs = { + autoeq: document.getElementById('eq-howto-autoeq'), + parametric: document.getElementById('eq-howto-parametric'), + speaker: document.getElementById('eq-howto-speaker'), + }; + + const updateHowtoTab = () => { + Object.values(howtoTabs).forEach((t) => { + if (t) t.style.display = 'none'; + }); + const active = howtoTabs[currentMode]; + if (active) active.style.display = ''; + }; + + if (howtoBtn && howtoPanel) { + howtoBtn.addEventListener('click', () => { + const visible = howtoPanel.style.display !== 'none'; + howtoPanel.style.display = visible ? 'none' : ''; + if (!visible) updateHowtoTab(); + }); + } + if (howtoClose && howtoPanel) { + howtoClose.addEventListener('click', () => { + howtoPanel.style.display = 'none'; + }); + } + + // ======================================== + // Redraw graph when target/settings change + // ======================================== + if (autoeqTargetSelect) { + autoeqTargetSelect.addEventListener('change', () => { + if (autoeqCurrentBands && autoeqSelectedMeasurement) { + computeCorrectedCurve(); + } + drawAutoEQGraph(); + }); + } + + if (autoeqBandCount) { + autoeqBandCount.addEventListener('change', () => drawAutoEQGraph()); + } + if (autoeqMaxFreq) { + autoeqMaxFreq.addEventListener('change', () => drawAutoEQGraph()); + } + if (autoeqSampleRate) { + autoeqSampleRate.addEventListener('change', () => { + if (autoeqCurrentBands && autoeqSelectedMeasurement) { + computeCorrectedCurve(); + } + drawAutoEQGraph(); + }); + } + + // ======================================== + // Parametric EQ Preset Selector + // ======================================== + const parametricPresetSelect = document.getElementById('parametric-preset-select'); + if (parametricPresetSelect) { + parametricPresetSelect.addEventListener('change', () => { + const presetKey = parametricPresetSelect.value; + if (!presetKey) return; // "Custom" selected + + ensureParametricBands(); + const bandCount = parametricBands.length; + const presets = getPresetsForBandCount(bandCount); + const preset = presets[presetKey]; + if (!preset) return; + + parametricBands.forEach((band, i) => { + band.gain = preset.gains[i] || 0; + }); + + applyBandsToAudio(parametricBands); + renderBandControls(parametricBands); + computeCorrectedCurve(); + drawAutoEQGraph(); + }); + } + + // ======================================== + // Parametric EQ Profile Save/Load/Render + // ======================================== + const PARAMETRIC_PROFILES_KEY = 'parametric-eq-profiles'; + const PARAMETRIC_ACTIVE_KEY = 'parametric-eq-active-profile'; + + const getParametricProfiles = () => { + try { + return JSON.parse(localStorage.getItem(PARAMETRIC_PROFILES_KEY)) || {}; + } catch { + return {}; + } + }; + + const renderParametricProfiles = () => { + const grid = document.getElementById('parametric-saved-grid'); + const countEl = document.getElementById('parametric-saved-count'); + if (!grid) return; + + const profiles = getParametricProfiles(); + const activeId = localStorage.getItem(PARAMETRIC_ACTIVE_KEY); + const keys = Object.keys(profiles); + if (countEl) countEl.textContent = keys.length; + grid.innerHTML = ''; + + keys.forEach((id) => { + const profile = profiles[id]; + const card = document.createElement('div'); + card.className = 'autoeq-profile-card' + (id === activeId ? ' active' : ''); + card.dataset.profileId = id; + + const preview = document.createElement('canvas'); + preview.className = 'autoeq-profile-preview'; + preview.style.height = '80px'; + card.appendChild(preview); + + const info = document.createElement('div'); + info.className = 'autoeq-profile-info'; + info.innerHTML = ` + + ${profile.name || 'Unnamed'} + ${profile.bandCount || '?'} bands + `; + card.appendChild(info); + + const delBtn = document.createElement('button'); + delBtn.className = 'autoeq-profile-delete'; + delBtn.innerHTML = '🗑'; + delBtn.title = 'Delete profile'; + delBtn.addEventListener('click', (e) => { + e.stopPropagation(); + const all = getParametricProfiles(); + delete all[id]; + localStorage.setItem(PARAMETRIC_PROFILES_KEY, JSON.stringify(all)); + if (localStorage.getItem(PARAMETRIC_ACTIVE_KEY) === id) localStorage.removeItem(PARAMETRIC_ACTIVE_KEY); + renderParametricProfiles(); + }); + card.appendChild(delBtn); + + card.addEventListener('click', () => { + parametricBands = profile.bands.map((b) => ({ ...b })); + applyBandsToAudio(parametricBands); + renderBandControls(parametricBands); + computeCorrectedCurve(); + drawAutoEQGraph(); + localStorage.setItem(PARAMETRIC_ACTIVE_KEY, id); + if (parametricPresetSelect) parametricPresetSelect.value = ''; + renderParametricProfiles(); + }); + + grid.appendChild(card); + + // Draw mini graph + requestAnimationFrame(() => { + drawBandsPreview(preview, profile.bands); + }); + }); + }; + + // Save parametric profile + const parametricSaveBtn = document.getElementById('parametric-save-btn'); + const parametricProfileName = document.getElementById('parametric-profile-name'); + if (parametricSaveBtn) { + parametricSaveBtn.addEventListener('click', () => { + if (!parametricBands || parametricBands.length === 0) return; + const name = parametricProfileName ? parametricProfileName.value.trim() : ''; + if (!name) return; + + const profiles = getParametricProfiles(); + const id = 'peq_' + Date.now(); + profiles[id] = { + name, + bands: parametricBands.map((b) => ({ ...b })), + bandCount: parametricBands.length, + preamp: equalizerSettings.getPreamp(), + createdAt: Date.now(), + }; + localStorage.setItem(PARAMETRIC_PROFILES_KEY, JSON.stringify(profiles)); + localStorage.setItem(PARAMETRIC_ACTIVE_KEY, id); + if (parametricProfileName) parametricProfileName.value = ''; + renderParametricProfiles(); + }); + } + + // ======================================== + // Parametric EQ Import/Export + // ======================================== + const parametricExportBtn = document.getElementById('parametric-export-btn'); + const parametricImportBtn = document.getElementById('parametric-import-btn'); + const parametricImportFile = document.getElementById('parametric-import-file'); + + if (parametricExportBtn) { + parametricExportBtn.addEventListener('click', () => { + if (!parametricBands || parametricBands.length === 0) return; + const preamp = equalizerSettings.getPreamp(); + const lines = [`Preamp: ${preamp.toFixed(1)} dB`]; + parametricBands.forEach((band, i) => { + const ft = band.type === 'lowshelf' ? 'LS' : band.type === 'highshelf' ? 'HS' : 'PK'; + lines.push( + `Filter ${i + 1}: ON ${ft} Fc ${Math.round(band.freq)} Hz Gain ${band.gain.toFixed(1)} dB Q ${band.q.toFixed(2)}` + ); + }); + const text = lines.join('\n'); + const blob = new Blob([text], { type: 'text/plain' }); + const url = URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + a.download = 'parametric-eq.txt'; + a.click(); + URL.revokeObjectURL(url); + }); + } + + if (parametricImportBtn && parametricImportFile) { + parametricImportBtn.addEventListener('click', () => parametricImportFile.click()); + parametricImportFile.addEventListener('change', (e) => { + const file = e.target.files[0]; + if (!file) return; + const reader = new FileReader(); + reader.onload = (event) => { + try { + const text = event.target.result; + const bands = []; + let preamp = 0; + const lines = text.split('\n'); + for (const line of lines) { + const preampMatch = line.match(/Preamp:\s*([-\d.]+)\s*dB/i); + if (preampMatch) { + preamp = parseFloat(preampMatch[1]); + continue; + } + const filterMatch = line.match( + /Filter\s+\d+:\s*ON\s+(\w+)\s+Fc\s+([\d.]+)\s*Hz\s+Gain\s+([-\d.]+)\s*dB\s+Q\s+([\d.]+)/i + ); + if (filterMatch) { + const typeMap = { + PK: 'peaking', + LS: 'lowshelf', + LSC: 'lowshelf', + LSF: 'lowshelf', + HS: 'highshelf', + HSC: 'highshelf', + HSF: 'highshelf', + }; + bands.push({ + id: bands.length, + type: typeMap[filterMatch[1].toUpperCase()] || 'peaking', + freq: parseFloat(filterMatch[2]), + gain: parseFloat(filterMatch[3]), + q: parseFloat(filterMatch[4]), + enabled: true, + }); + } + } + if (bands.length === 0) return; + parametricBands = bands; + applyBandsToAudio(parametricBands); + equalizerSettings.setPreamp(preamp); + if (eqPreampSlider) eqPreampSlider.value = preamp; + if (autoeqPreampValue) autoeqPreampValue.textContent = `${preamp} dB`; + renderBandControls(parametricBands); + computeCorrectedCurve(); + drawAutoEQGraph(); + if (parametricPresetSelect) parametricPresetSelect.value = ''; + } catch (err) { + console.error('[PEQ Import] Failed:', err); + } + }; + reader.readAsText(file); + e.target.value = ''; + }); + } + + // ======================================== + // Speaker EQ Logic + // ======================================== + const speakerConfigSelect = document.getElementById('speaker-config-select'); + const speakerChannelTabsEl = document.getElementById('speaker-channel-tabs'); + const speakerMeasStatus = document.getElementById('speaker-measurement-status'); + const speakerImportMeasBtn = document.getElementById('speaker-import-measurement-btn'); + const speakerImportMeasFile = document.getElementById('speaker-import-measurement-file'); + const speakerClearMeasBtn = document.getElementById('speaker-clear-measurement-btn'); + const speakerTargetSelect = document.getElementById('speaker-target-select'); + const speakerImportTargetBtn = document.getElementById('speaker-import-target-btn'); + const speakerImportTargetFile = document.getElementById('speaker-import-target-file'); + const speakerBandCountSelect = document.getElementById('speaker-band-count'); + const speakerBassCutoff = document.getElementById('speaker-bass-cutoff'); + const speakerBassCutoffValue = document.getElementById('speaker-bass-cutoff-value'); + const speakerRoomLimit = document.getElementById('speaker-room-limit'); + const speakerRoomLimitValue = document.getElementById('speaker-room-limit-value'); + const speakerAutoEqBtn = document.getElementById('speaker-autoeq-btn'); + const speakerEqStatus = document.getElementById('speaker-eq-status'); + const speakerExportBtn = document.getElementById('speaker-export-btn'); + + const getSpeakerChannel = () => speakerChannels[speakerActiveChannel]; + + const renderSpeakerChannelTabs = () => { + if (!speakerChannelTabsEl) return; + const ids = SPEAKER_CONFIGS[speakerConfig]; + speakerChannelTabsEl.innerHTML = ''; + ids.forEach((id) => { + const btn = document.createElement('button'); + btn.className = 'speaker-channel-tab' + (id === speakerActiveChannel ? ' active' : ''); + btn.textContent = id; + btn.title = SPEAKER_CHANNEL_LABELS[id]; + if (speakerChannels[id].measurement) btn.classList.add('has-data'); + btn.addEventListener('click', () => { + speakerActiveChannel = id; + renderSpeakerChannelTabs(); + updateSpeakerUI(); + // Apply this channel's bands to audio + graph + const ch = getSpeakerChannel(); + applyBandsToAudio(ch.bands); + renderBandControls(ch.bands); + drawAutoEQGraph(); + }); + speakerChannelTabsEl.appendChild(btn); + }); + }; + + const updateSpeakerUI = () => { + const ch = getSpeakerChannel(); + // Measurement status + if (speakerMeasStatus) { + speakerMeasStatus.textContent = ch.measurement ? `${ch.measurement.length} pts` : 'No measurement'; + speakerMeasStatus.classList.toggle('loaded', !!ch.measurement); + } + if (speakerClearMeasBtn) speakerClearMeasBtn.style.display = ch.measurement ? '' : 'none'; + if (speakerAutoEqBtn) speakerAutoEqBtn.disabled = !ch.measurement; + // Target + if (speakerTargetSelect) speakerTargetSelect.value = ch.targetId; + // Preamp + }; + + // Config change + if (speakerConfigSelect) { + speakerConfigSelect.addEventListener('change', () => { + speakerConfig = speakerConfigSelect.value; + const ids = SPEAKER_CONFIGS[speakerConfig]; + if (!ids.includes(speakerActiveChannel)) speakerActiveChannel = ids[0]; + renderSpeakerChannelTabs(); + updateSpeakerUI(); + }); + } + + // Import measurement + if (speakerImportMeasBtn && speakerImportMeasFile) { + speakerImportMeasBtn.addEventListener('click', () => speakerImportMeasFile.click()); + speakerImportMeasFile.addEventListener('change', (e) => { + const file = e.target.files[0]; + if (!file) return; + const reader = new FileReader(); + reader.onload = (ev) => { + const data = parseRawData(ev.target.result); + if (data.length > 0) { + getSpeakerChannel().measurement = data; + updateSpeakerUI(); + renderSpeakerChannelTabs(); + drawAutoEQGraph(); + } + }; + reader.readAsText(file); + e.target.value = ''; + }); + } + + // Clear measurement + if (speakerClearMeasBtn) { + speakerClearMeasBtn.addEventListener('click', () => { + getSpeakerChannel().measurement = null; + updateSpeakerUI(); + renderSpeakerChannelTabs(); + drawAutoEQGraph(); + }); + } + + // Pink noise room measurement + const speakerMeasureBtn = document.getElementById('speaker-measure-btn'); + if (speakerMeasureBtn) { + speakerMeasureBtn.addEventListener('click', async () => { + speakerMeasureBtn.disabled = true; + if (speakerMeasStatus) { + speakerMeasStatus.textContent = 'Requesting mic...'; + speakerMeasStatus.classList.remove('loaded'); + } + + let measCtx, stream; + try { + // 1. Get mic with processing disabled + stream = await navigator.mediaDevices.getUserMedia({ + audio: { echoCancellation: false, noiseSuppression: false, autoGainControl: false }, + }); + + measCtx = new (window.AudioContext || window.webkitAudioContext)({ sampleRate: 48000 }); + const sr = measCtx.sampleRate; + const duration = 5; + + // 2. Generate pink noise buffer (Voss algorithm approximation) + const bufLen = sr * duration; + const buffer = measCtx.createBuffer(1, bufLen, sr); + const data = buffer.getChannelData(0); + // Paul Kellet's refined pink noise filter coefficients + let b0 = 0, + b1 = 0, + b2 = 0, + b3 = 0, + b4 = 0, + b5 = 0, + b6 = 0; + for (let i = 0; i < bufLen; i++) { + const white = Math.random() * 2 - 1; + b0 = 0.99886 * b0 + white * 0.0555179; + b1 = 0.99332 * b1 + white * 0.0750759; + b2 = 0.969 * b2 + white * 0.153852; + b3 = 0.8665 * b3 + white * 0.3104856; + b4 = 0.55 * b4 + white * 0.5329522; + b5 = -0.7616 * b5 - white * 0.016898; + let pink = b0 + b1 + b2 + b3 + b4 + b5 + b6 + white * 0.5362; + b6 = white * 0.115926; + // Fade in/out envelope (100ms) + let env = 1; + const t = i / sr; + if (t < 0.1) env = t / 0.1; + else if (t > duration - 0.1) env = (duration - t) / 0.1; + data[i] = pink * 0.04 * env; // low amplitude + } + + // 3. Play pink noise + const noiseSource = measCtx.createBufferSource(); + noiseSource.buffer = buffer; + noiseSource.connect(measCtx.destination); + + // 4. Setup mic analyser + const micSource = measCtx.createMediaStreamSource(stream); + const analyser = measCtx.createAnalyser(); + analyser.fftSize = 8192; + analyser.smoothingTimeConstant = 0.3; + micSource.connect(analyser); + + const freqBinCount = analyser.frequencyBinCount; + const binHz = sr / analyser.fftSize; + const fftData = new Float32Array(freqBinCount); + const accumulator = new Float64Array(freqBinCount); + let frameCount = 0; + + // 5. Start playback + capture loop + noiseSource.start(); + const startTime = measCtx.currentTime; + + await new Promise((resolve) => { + const tick = () => { + const elapsed = measCtx.currentTime - startTime; + if (elapsed >= duration) { + resolve(); + return; + } + + // Update progress + const pct = Math.round((elapsed / duration) * 100); + if (speakerMeasStatus) speakerMeasStatus.textContent = `Measuring... ${pct}%`; + + // Skip first 0.3s (let noise settle) + if (elapsed > 0.3) { + analyser.getFloatFrequencyData(fftData); + for (let j = 0; j < freqBinCount; j++) { + const val = fftData[j]; + if (val !== -Infinity) accumulator[j] += val; + } + frameCount++; + } + requestAnimationFrame(tick); + }; + requestAnimationFrame(tick); + }); + + noiseSource.stop(); + + // 6. Post-process: average bins → log-spaced points + if (frameCount === 0) throw new Error('No frames captured'); + for (let j = 0; j < freqBinCount; j++) accumulator[j] /= frameCount; + + const points = []; + const ptsPerOctave = 24; + let freq = 20; + while (freq <= 20000) { + const binIdx = Math.round(freq / binHz); + if (binIdx >= 0 && binIdx < freqBinCount) { + // Average a few bins around target for smoothing + const lo = Math.max(0, binIdx - 2); + const hi = Math.min(freqBinCount - 1, binIdx + 2); + let sum = 0, + cnt = 0; + for (let k = lo; k <= hi; k++) { + sum += accumulator[k]; + cnt++; + } + points.push({ freq, gain: sum / cnt }); + } + freq *= Math.pow(2, 1 / ptsPerOctave); + } + + // Normalize: midrange (500-2000 Hz) average → 75 dB + const midPts = points.filter((p) => p.freq >= 500 && p.freq <= 2000); + const midAvg = midPts.length > 0 ? midPts.reduce((s, p) => s + p.gain, 0) / midPts.length : 0; + const offset = 75 - midAvg; + const normalized = points.map((p) => ({ freq: p.freq, gain: p.gain + offset })); + + // 7. Store result + getSpeakerChannel().measurement = normalized; + updateSpeakerUI(); + renderSpeakerChannelTabs(); + computeCorrectedCurve(); + drawAutoEQGraph(); + if (speakerMeasStatus) speakerMeasStatus.textContent = `${normalized.length} pts (measured)`; + } catch (err) { + console.error('[Speaker Measure]', err); + if (speakerMeasStatus) + speakerMeasStatus.textContent = err.name === 'NotAllowedError' ? 'Mic denied' : 'Measure failed'; + } finally { + // Cleanup + if (stream) stream.getTracks().forEach((t) => t.stop()); + if (measCtx && measCtx.state !== 'closed') measCtx.close().catch(() => {}); + speakerMeasureBtn.disabled = false; + } + }); + } + + // Measure All — plays pink noise once, assigns averaged measurement to all active channels + const speakerMeasureAllBtn = document.getElementById('speaker-measure-all-btn'); + if (speakerMeasureAllBtn) { + speakerMeasureAllBtn.addEventListener('click', async () => { + speakerMeasureAllBtn.disabled = true; + if (speakerMeasureBtn) speakerMeasureBtn.disabled = true; + if (speakerMeasStatus) { + speakerMeasStatus.textContent = 'Requesting mic...'; + speakerMeasStatus.classList.remove('loaded'); + } + + let measCtx, stream; + try { + stream = await navigator.mediaDevices.getUserMedia({ + audio: { echoCancellation: false, noiseSuppression: false, autoGainControl: false }, + }); + + measCtx = new (window.AudioContext || window.webkitAudioContext)({ sampleRate: 48000 }); + const sr = measCtx.sampleRate; + const duration = 5; + + // Generate pink noise buffer + const bufLen = sr * duration; + const buffer = measCtx.createBuffer(1, bufLen, sr); + const d = buffer.getChannelData(0); + let b0 = 0, + b1 = 0, + b2 = 0, + b3 = 0, + b4 = 0, + b5 = 0, + b6 = 0; + for (let i = 0; i < bufLen; i++) { + const white = Math.random() * 2 - 1; + b0 = 0.99886 * b0 + white * 0.0555179; + b1 = 0.99332 * b1 + white * 0.0750759; + b2 = 0.969 * b2 + white * 0.153852; + b3 = 0.8665 * b3 + white * 0.3104856; + b4 = 0.55 * b4 + white * 0.5329522; + b5 = -0.7616 * b5 - white * 0.016898; + let pink = b0 + b1 + b2 + b3 + b4 + b5 + b6 + white * 0.5362; + b6 = white * 0.115926; + let env = 1; + const t = i / sr; + if (t < 0.1) env = t / 0.1; + else if (t > duration - 0.1) env = (duration - t) / 0.1; + d[i] = pink * 0.04 * env; + } + + const noiseSource = measCtx.createBufferSource(); + noiseSource.buffer = buffer; + noiseSource.connect(measCtx.destination); + + const micSource = measCtx.createMediaStreamSource(stream); + const analyser = measCtx.createAnalyser(); + analyser.fftSize = 8192; + analyser.smoothingTimeConstant = 0.3; + micSource.connect(analyser); + + const freqBinCount = analyser.frequencyBinCount; + const binHz = sr / analyser.fftSize; + const fftData = new Float32Array(freqBinCount); + const accumulator = new Float64Array(freqBinCount); + let frameCount = 0; + + noiseSource.start(); + const startTime = measCtx.currentTime; + + await new Promise((resolve) => { + const tick = () => { + const elapsed = measCtx.currentTime - startTime; + if (elapsed >= duration) { + resolve(); + return; + } + const pct = Math.round((elapsed / duration) * 100); + if (speakerMeasStatus) speakerMeasStatus.textContent = `Measuring all... ${pct}%`; + if (elapsed > 0.3) { + analyser.getFloatFrequencyData(fftData); + for (let j = 0; j < freqBinCount; j++) { + const val = fftData[j]; + if (val !== -Infinity) accumulator[j] += val; + } + frameCount++; + } + requestAnimationFrame(tick); + }; + requestAnimationFrame(tick); + }); + + noiseSource.stop(); + + if (frameCount === 0) throw new Error('No frames captured'); + for (let j = 0; j < freqBinCount; j++) accumulator[j] /= frameCount; + + const points = []; + const ptsPerOctave = 24; + let freq = 20; + while (freq <= 20000) { + const binIdx = Math.round(freq / binHz); + if (binIdx >= 0 && binIdx < freqBinCount) { + const lo = Math.max(0, binIdx - 2); + const hi = Math.min(freqBinCount - 1, binIdx + 2); + let sum = 0, + cnt = 0; + for (let k = lo; k <= hi; k++) { + sum += accumulator[k]; + cnt++; + } + points.push({ freq, gain: sum / cnt }); + } + freq *= Math.pow(2, 1 / ptsPerOctave); + } + + const midPts = points.filter((p) => p.freq >= 500 && p.freq <= 2000); + const midAvg = midPts.length > 0 ? midPts.reduce((s, p) => s + p.gain, 0) / midPts.length : 0; + const offset = 75 - midAvg; + const normalized = points.map((p) => ({ freq: p.freq, gain: p.gain + offset })); + + // Assign to ALL active channels + const activeIds = SPEAKER_CONFIGS[speakerConfig]; + activeIds.forEach((id) => { + speakerChannels[id].measurement = normalized.map((p) => ({ ...p })); + }); + + updateSpeakerUI(); + renderSpeakerChannelTabs(); + computeCorrectedCurve(); + drawAutoEQGraph(); + if (speakerMeasStatus) + speakerMeasStatus.textContent = `${normalized.length} pts → ${activeIds.length} channels`; + } catch (err) { + console.error('[Speaker Measure All]', err); + if (speakerMeasStatus) + speakerMeasStatus.textContent = err.name === 'NotAllowedError' ? 'Mic denied' : 'Measure failed'; + } finally { + if (stream) stream.getTracks().forEach((t) => t.stop()); + if (measCtx && measCtx.state !== 'closed') measCtx.close().catch(() => {}); + speakerMeasureAllBtn.disabled = false; + if (speakerMeasureBtn) speakerMeasureBtn.disabled = false; + } + }); + } + + // AutoEQ All — runs AutoEQ on every active channel that has a measurement + const speakerAutoEqAllBtn = document.getElementById('speaker-autoeq-all-btn'); + if (speakerAutoEqAllBtn) { + speakerAutoEqAllBtn.addEventListener('click', () => { + const activeIds = SPEAKER_CONFIGS[speakerConfig]; + const measuredIds = activeIds.filter((id) => speakerChannels[id].measurement); + if (measuredIds.length === 0) return; + + speakerAutoEqAllBtn.disabled = true; + if (speakerAutoEqBtn) speakerAutoEqBtn.disabled = true; + if (speakerEqStatus) speakerEqStatus.textContent = 'Running all...'; - // Redraw EQ curve when a new track loads (audio metadata loaded) - const audioPlayer = document.getElementById('audio-player'); - if (audioPlayer) { - audioPlayer.addEventListener('loadedmetadata', () => { - // Small delay to ensure the visualizer and EQ are fully ready setTimeout(() => { - drawEQCurve(); + const bandCount = speakerBandCountSelect ? parseInt(speakerBandCountSelect.value, 10) : 10; + const bassCut = speakerBassCutoff ? parseInt(speakerBassCutoff.value, 10) : 40; + const roomLim = speakerRoomLimit ? parseInt(speakerRoomLimit.value, 10) : 500; + + measuredIds.forEach((id) => { + const ch = speakerChannels[id]; + const targetEntry = SPEAKER_TARGETS.find((t) => t.id === ch.targetId); + const targetData = targetEntry?.data || []; + + const bands = runAutoEqAlgorithm(ch.measurement, targetData, bandCount, roomLim, bassCut, 3.0); + + let maxGain = 0; + for (let f = 20; f <= 20000; f *= 1.1) { + let total = 0; + bands.forEach((b) => { + if (b.enabled) total += calculateBiquadResponse(f, b); + }); + if (total > maxGain) maxGain = total; + } + ch.bands = bands; + ch.preamp = maxGain > 0 ? parseFloat((-maxGain - 0.1).toFixed(1)) : 0; + }); + + // Refresh active channel UI + const ch = getSpeakerChannel(); + applyBandsToAudio(ch.bands); + renderBandControls(ch.bands); + updateSpeakerUI(); + renderSpeakerChannelTabs(); + computeCorrectedCurve(); + drawAutoEQGraph(); + + speakerAutoEqAllBtn.disabled = false; + if (speakerAutoEqBtn) speakerAutoEqBtn.disabled = !ch.measurement; + if (speakerEqStatus) speakerEqStatus.textContent = `${measuredIds.length} channels optimized`; + setTimeout(() => { + if (speakerEqStatus) speakerEqStatus.textContent = ''; + }, 3000); }, 100); }); } + // Target change + if (speakerTargetSelect) { + speakerTargetSelect.addEventListener('change', () => { + getSpeakerChannel().targetId = speakerTargetSelect.value; + drawAutoEQGraph(); + }); + } + + // Import custom speaker target + if (speakerImportTargetBtn && speakerImportTargetFile) { + speakerImportTargetBtn.addEventListener('click', () => speakerImportTargetFile.click()); + speakerImportTargetFile.addEventListener('change', (e) => { + const file = e.target.files[0]; + if (!file) return; + const reader = new FileReader(); + reader.onload = (ev) => { + const data = parseRawData(ev.target.result); + if (data.length === 0) return; + const customId = 'custom_speaker_target'; + const label = file.name.replace(/\.(txt|csv)$/i, ''); + const existing = SPEAKER_TARGETS.findIndex((t) => t.id === customId); + if (existing > -1) SPEAKER_TARGETS[existing] = { id: customId, label, data }; + else SPEAKER_TARGETS.push({ id: customId, label, data }); + let opt = speakerTargetSelect.querySelector('option[value="custom_speaker_target"]'); + if (!opt) { + opt = document.createElement('option'); + opt.value = customId; + speakerTargetSelect.appendChild(opt); + } + opt.textContent = label; + speakerTargetSelect.value = customId; + getSpeakerChannel().targetId = customId; + drawAutoEQGraph(); + }; + reader.readAsText(file); + e.target.value = ''; + }); + } + + // Slider labels + if (speakerBassCutoff) { + speakerBassCutoff.addEventListener('input', () => { + if (speakerBassCutoffValue) speakerBassCutoffValue.textContent = `${speakerBassCutoff.value} Hz`; + drawAutoEQGraph(); + }); + } + if (speakerRoomLimit) { + speakerRoomLimit.addEventListener('input', () => { + if (speakerRoomLimitValue) speakerRoomLimitValue.textContent = `${speakerRoomLimit.value} Hz`; + drawAutoEQGraph(); + }); + } + // AutoEQ per channel + if (speakerAutoEqBtn) { + speakerAutoEqBtn.addEventListener('click', () => { + const ch = getSpeakerChannel(); + if (!ch.measurement) return; + speakerAutoEqBtn.disabled = true; + if (speakerEqStatus) speakerEqStatus.textContent = 'Running...'; + + setTimeout(() => { + const targetEntry = SPEAKER_TARGETS.find((t) => t.id === ch.targetId); + const targetData = targetEntry?.data || []; + const bandCount = speakerBandCountSelect ? parseInt(speakerBandCountSelect.value, 10) : 10; + const bassCut = speakerBassCutoff ? parseInt(speakerBassCutoff.value, 10) : 40; + const roomLim = speakerRoomLimit ? parseInt(speakerRoomLimit.value, 10) : 500; + + const sampleRate = autoeqSampleRate ? parseInt(autoeqSampleRate.value, 10) : 48000; + const bands = runAutoEqAlgorithm( + ch.measurement, + targetData, + bandCount, + roomLim, + bassCut, + 3.0, + sampleRate + ); + + // Auto preamp + let maxGain = 0; + for (let f = 20; f <= 20000; f *= 1.1) { + let total = 0; + bands.forEach((b) => { + if (b.enabled) total += calculateBiquadResponse(f, b, sampleRate); + }); + if (total > maxGain) maxGain = total; + } + const autoPreamp = maxGain > 0 ? parseFloat((-maxGain - 0.1).toFixed(1)) : 0; + + ch.bands = bands; + ch.preamp = autoPreamp; + + applyBandsToAudio(bands); + renderBandControls(bands); + updateSpeakerUI(); + renderSpeakerChannelTabs(); + computeCorrectedCurve(); + drawAutoEQGraph(); + + speakerAutoEqBtn.disabled = false; + if (speakerEqStatus) speakerEqStatus.textContent = `${speakerActiveChannel} optimized`; + setTimeout(() => { + if (speakerEqStatus) speakerEqStatus.textContent = ''; + }, 3000); + }, 100); + }); + } + + // Export all channels as JSON + if (speakerExportBtn) { + speakerExportBtn.addEventListener('click', () => { + const activeIds = SPEAKER_CONFIGS[speakerConfig]; + const data = { + config: speakerConfig, + channels: activeIds.map((id) => { + const ch = speakerChannels[id]; + return { + id, + label: SPEAKER_CHANNEL_LABELS[id], + preamp: ch.preamp, + filters: ch.bands + .filter((b) => b.enabled) + .map((b) => ({ + type: b.type, + freq: b.freq, + gain: b.gain, + q: b.q, + })), + }; + }), + }; + const blob = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' }); + const url = URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + a.download = `SpeakerEQ_${speakerConfig}_${new Date().toISOString().slice(0, 10)}.json`; + a.click(); + URL.revokeObjectURL(url); + }); + } + + // Import EQ settings from JSON + const speakerImportBtn = document.getElementById('speaker-import-btn'); + const speakerImportFile = document.getElementById('speaker-import-file'); + if (speakerImportBtn && speakerImportFile) { + speakerImportBtn.addEventListener('click', () => speakerImportFile.click()); + speakerImportFile.addEventListener('change', async (e) => { + const file = e.target.files[0]; + if (!file) return; + try { + const text = await file.text(); + const data = JSON.parse(text); + if (!data.config || !Array.isArray(data.channels)) { + throw new Error('Invalid JSON format'); + } + // Change config if different + if (data.config !== speakerConfig) { + speakerConfig = data.config; + if (speakerConfigSelect) speakerConfigSelect.value = speakerConfig; + } + // Load channels + data.channels.forEach((ch) => { + if (speakerChannels[ch.id]) { + speakerChannels[ch.id].preamp = ch.preamp || 0; + speakerChannels[ch.id].bands = ch.filters.map((f) => ({ + enabled: true, + type: f.type, + freq: f.freq, + gain: f.gain, + q: f.q, + })); + } + }); + // Update UI + speakerActiveChannel = SPEAKER_CONFIGS[speakerConfig][0]; + renderSpeakerChannelTabs(); + setEQMode('speaker'); + if (speakerEqStatus) speakerEqStatus.textContent = `Loaded: ${data.channels.length} channels`; + setTimeout(() => { + if (speakerEqStatus) speakerEqStatus.textContent = ''; + }, 2000); + } catch (err) { + if (speakerEqStatus) speakerEqStatus.textContent = `Error: ${err.message}`; + } + speakerImportFile.value = ''; + }); + } + + // ======================================== + // Speaker Saved Profiles + // ======================================== + const SPEAKER_PROFILES_IDB_KEY = 'speaker-eq-profiles'; + const SPEAKER_ACTIVE_PROFILE_KEY = 'speaker-eq-active-profile'; + let _speakerProfilesCache = null; // in-memory cache backed by IndexedDB + + const getSpeakerProfiles = () => _speakerProfilesCache || {}; + + const loadSpeakerProfilesFromDB = async () => { + try { + // Migrate from localStorage if present + const lsData = localStorage.getItem('speaker-eq-profiles'); + if (lsData) { + const parsed = JSON.parse(lsData); + if (parsed && Object.keys(parsed).length > 0) { + await db.saveSetting(SPEAKER_PROFILES_IDB_KEY, parsed); + } + localStorage.removeItem('speaker-eq-profiles'); + } + } catch { + /* ignore migration errors */ + } + try { + _speakerProfilesCache = (await db.getSetting(SPEAKER_PROFILES_IDB_KEY)) || {}; + } catch { + _speakerProfilesCache = {}; + } + }; + + const saveSpeakerProfiles = async (profiles) => { + _speakerProfilesCache = profiles; + await db.saveSetting(SPEAKER_PROFILES_IDB_KEY, profiles); + }; + + await loadSpeakerProfilesFromDB(); + + const renderSpeakerProfiles = () => { + const grid = document.getElementById('speaker-saved-grid'); + const countEl = document.getElementById('speaker-saved-count'); + if (!grid) return; + + const profiles = getSpeakerProfiles(); + const activeId = localStorage.getItem(SPEAKER_ACTIVE_PROFILE_KEY); + const keys = Object.keys(profiles); + if (countEl) countEl.textContent = keys.length; + grid.innerHTML = ''; + + if (keys.length === 0) return; + + keys.forEach((id) => { + const profile = profiles[id]; + const card = document.createElement('div'); + card.className = 'autoeq-profile-card' + (id === activeId ? ' active' : ''); + + const preview = document.createElement('canvas'); + preview.className = 'autoeq-profile-preview'; + preview.style.height = '80px'; + card.appendChild(preview); + + const channelCount = profile.channels ? profile.channels.length : 0; + const info = document.createElement('div'); + info.className = 'autoeq-profile-info'; + info.innerHTML = ` + + ${profile.name || 'Unnamed'} + ${profile.config} · ${channelCount} ch + `; + card.appendChild(info); + + const delBtn = document.createElement('button'); + delBtn.className = 'autoeq-profile-delete'; + delBtn.innerHTML = '🗑'; + delBtn.title = 'Delete profile'; + delBtn.addEventListener('click', async (e) => { + e.stopPropagation(); + const all = getSpeakerProfiles(); + delete all[id]; + await saveSpeakerProfiles(all); + if (localStorage.getItem(SPEAKER_ACTIVE_PROFILE_KEY) === id) + localStorage.removeItem(SPEAKER_ACTIVE_PROFILE_KEY); + renderSpeakerProfiles(); + }); + card.appendChild(delBtn); + + // Click to load + card.addEventListener('click', () => { + loadSpeakerProfile(id); + }); + + grid.appendChild(card); + + // Draw mini preview from first channel's measurement + requestAnimationFrame(() => { + const firstCh = profile.channels?.[0]; + if (firstCh && firstCh.measurementPreview) { + const targetEntry = SPEAKER_TARGETS.find((t) => t.id === (firstCh.targetId || 'harman_room')); + drawMiniGraph( + preview, + firstCh.measurementPreview, + targetEntry?.data ? downsampleCurve(targetEntry.data) : null, + firstCh.correctedPreview || null + ); + } + }); + }); + }; + + const loadSpeakerProfile = (profileId) => { + const profiles = getSpeakerProfiles(); + const profile = profiles[profileId]; + if (!profile) return; + + // Switch config if different + if (profile.config && profile.config !== speakerConfig) { + speakerConfig = profile.config; + if (speakerConfigSelect) speakerConfigSelect.value = speakerConfig; + } + + // Load all channels + if (profile.channels) { + profile.channels.forEach((saved) => { + if (speakerChannels[saved.id]) { + speakerChannels[saved.id].measurement = saved.measurement || null; + speakerChannels[saved.id].targetId = saved.targetId || 'harman_room'; + speakerChannels[saved.id].preamp = saved.preamp || 0; + speakerChannels[saved.id].bands = saved.bands + ? saved.bands.map((b) => ({ ...b })) + : speakerChannels[saved.id].bands; + } + }); + } + + speakerActiveChannel = SPEAKER_CONFIGS[speakerConfig][0]; + const ch = getSpeakerChannel(); + applyBandsToAudio(ch.bands); + renderBandControls(ch.bands); + updateSpeakerUI(); + renderSpeakerChannelTabs(); + computeCorrectedCurve(); + drawAutoEQGraph(); + + localStorage.setItem(SPEAKER_ACTIVE_PROFILE_KEY, profileId); + renderSpeakerProfiles(); + if (speakerEqStatus) speakerEqStatus.textContent = `Loaded "${profile.name}"`; + setTimeout(() => { + if (speakerEqStatus) speakerEqStatus.textContent = ''; + }, 2000); + }; + + // Save button + const speakerSaveBtn = document.getElementById('speaker-save-btn'); + const speakerProfileNameInput = document.getElementById('speaker-profile-name'); + if (speakerSaveBtn) { + speakerSaveBtn.addEventListener('click', async () => { + try { + const name = speakerProfileNameInput?.value.trim() || `Speaker ${speakerConfig}`; + const activeIds = SPEAKER_CONFIGS[speakerConfig]; + const profiles = getSpeakerProfiles(); + const id = 'spk_' + Date.now(); + + profiles[id] = { + name, + config: speakerConfig, + channels: activeIds.map((chId) => { + const ch = speakerChannels[chId]; + return { + id: chId, + targetId: ch.targetId, + preamp: ch.preamp, + bands: ch.bands.map((b) => ({ ...b })), + measurement: ch.measurement + ? ch.measurement.map((p) => ({ freq: p.freq, gain: parseFloat(p.gain.toFixed(1)) })) + : null, + measurementPreview: ch.measurement ? downsampleCurve(ch.measurement) : null, + correctedPreview: + autoeqCorrectedCurve && chId === speakerActiveChannel + ? downsampleCurve(autoeqCorrectedCurve) + : null, + }; + }), + createdAt: Date.now(), + }; + + await saveSpeakerProfiles(profiles); + localStorage.setItem(SPEAKER_ACTIVE_PROFILE_KEY, id); + if (speakerProfileNameInput) speakerProfileNameInput.value = ''; + renderSpeakerProfiles(); + if (speakerEqStatus) speakerEqStatus.textContent = `Saved "${name}"`; + setTimeout(() => { + if (speakerEqStatus) speakerEqStatus.textContent = ''; + }, 2000); + } catch (err) { + console.error('[Speaker Save]', err); + if (speakerEqStatus) speakerEqStatus.textContent = `Save failed: ${err.message}`; + } + }); + } + + // Collapse toggle for speaker saved section + const speakerSavedCollapse = document.getElementById('speaker-saved-collapse'); + const speakerSavedGrid = document.getElementById('speaker-saved-grid'); + if (speakerSavedCollapse && speakerSavedGrid) { + speakerSavedCollapse.addEventListener('click', () => { + speakerSavedCollapse.classList.toggle('collapsed'); + speakerSavedGrid.style.display = speakerSavedCollapse.classList.contains('collapsed') ? 'none' : ''; + }); + } + + // ======================================== + // Add/Remove/Reset Band Buttons + // ======================================== + const addBandBtn = document.getElementById('autoeq-add-band-btn'); + const removeBandBtn = document.getElementById('autoeq-remove-band-btn'); + const resetBandsBtn = document.getElementById('autoeq-reset-bands-btn'); + + if (addBandBtn) { + addBandBtn.addEventListener('click', () => { + let bands = getActiveBands(); + if (!bands) { + bands = []; + setActiveBands(bands); + } + if (bands.length >= 32) return; + bands.push({ id: bands.length, type: 'peaking', freq: 1000, gain: 0, q: 1.0, enabled: true }); + applyBandsToAudio(bands); + renderBandControls(bands); + computeCorrectedCurve(); + drawAutoEQGraph(); + }); + } + + if (removeBandBtn) { + removeBandBtn.addEventListener('click', () => { + const bands = getActiveBands(); + if (!bands || bands.length <= 1) return; + bands.pop(); + applyBandsToAudio(bands); + renderBandControls(bands); + computeCorrectedCurve(); + drawAutoEQGraph(); + }); + } + + if (resetBandsBtn) { + resetBandsBtn.addEventListener('click', () => { + const bands = getActiveBands(); + if (!bands) return; + bands.forEach((b) => { + b.gain = 0; + }); + applyBandsToAudio(bands); + renderBandControls(bands); + computeCorrectedCurve(); + drawAutoEQGraph(); + }); + } + + // ======================================== + // EQ Toggle (enable/disable) + // ======================================== + if (eqToggle) { + eqToggle.checked = equalizerSettings.isEnabled(); + updateEQContainerVisibility(eqToggle.checked); + + eqToggle.addEventListener('change', (e) => { + const enabled = e.target.checked; + equalizerSettings.setEnabled(enabled); + updateEQContainerVisibility(enabled); + + audioContextManager.toggleEQ(enabled); + }); + } + + // Initial render of saved profiles + renderSavedProfiles(); + + // Hide parametric-only elements on startup (default mode is autoeq) + const initPresetRow = document.getElementById('autoeq-preset-row'); + const initParaProfiles = document.getElementById('autoeq-parametric-profiles'); + if (initPresetRow) initPresetRow.style.display = 'none'; + if (initParaProfiles) initParaProfiles.style.display = 'none'; + + // Auto-load headphone database + loadFullDatabase(); + + // Auto-load default popular headphone if no saved profile is active + const activeProfileId = equalizerSettings.getActiveAutoEQProfile(); + if (!activeProfileId) { + // Try restoring last selected headphone (persisted measurement + entry) + const lastHp = equalizerSettings.getLastHeadphone(); + if (lastHp) { + autoeqSelectedMeasurement = lastHp.measurementData; + autoeqSelectedEntry = lastHp.entry; + if (autoeqHeadphoneSelect) { + let opt = autoeqHeadphoneSelect.querySelector(`option[value="${lastHp.entry.name}"]`); + if (!opt) { + opt = document.createElement('option'); + opt.value = lastHp.entry.name; + opt.textContent = lastHp.entry.name.replace(/\s*\([^)]*\)\s*$/, ''); + autoeqHeadphoneSelect.appendChild(opt); + } + autoeqHeadphoneSelect.value = lastHp.entry.name; + } + if (autoeqRunBtn) autoeqRunBtn.disabled = false; + requestAnimationFrame(drawAutoEQGraph); + } else if (POPULAR_HEADPHONES.length > 0) { + loadHeadphoneEntry(POPULAR_HEADPHONES[0]); + } + } + + // Initial draw of graph (if EQ is enabled) + if (equalizerSettings.isEnabled()) { + requestAnimationFrame(drawAutoEQGraph); + } + + // Load active profile on startup + if (activeProfileId) { + const profiles = equalizerSettings.getAutoEQProfiles(); + if (profiles[activeProfileId]) { + // Restore state silently + const profile = profiles[activeProfileId]; + autoeqCurrentBands = profile.bands?.map((b) => ({ ...b })) || null; + autoeqCorrectedCurve = profile.correctedData ? [...profile.correctedData] : null; + autoeqSelectedMeasurement = profile.measurementData ? [...profile.measurementData] : null; + autoeqSelectedEntry = { name: profile.headphoneName, type: profile.headphoneType }; + // Restore headphone select dropdown + if (autoeqHeadphoneSelect) { + let opt = autoeqHeadphoneSelect.querySelector(`option[value="${profile.headphoneName}"]`); + if (!opt) { + opt = document.createElement('option'); + opt.value = profile.headphoneName; + opt.textContent = profile.headphoneName.replace(/\s*\([^)]*\)\s*$/, ''); + autoeqHeadphoneSelect.appendChild(opt); + } + autoeqHeadphoneSelect.value = profile.headphoneName; + } + if (autoeqTargetSelect) autoeqTargetSelect.value = profile.targetId || 'harman_oe_2018'; + setAutoeqBandCount(profile.bandCount, profile.bands); + if (autoeqMaxFreq) autoeqMaxFreq.value = profile.maxFreq || 16000; + if (autoeqSampleRate) autoeqSampleRate.value = profile.sampleRate || 48000; + if (autoeqRunBtn) autoeqRunBtn.disabled = false; + if (autoeqCurrentBands) renderBandControls(autoeqCurrentBands); + requestAnimationFrame(drawAutoEQGraph); + } + } + + // Restore parametric EQ active profile on startup + const activeParametricId = localStorage.getItem(PARAMETRIC_ACTIVE_KEY); + if (activeParametricId) { + const parametricProfiles = getParametricProfiles(); + const paraProfile = parametricProfiles[activeParametricId]; + if (paraProfile && paraProfile.bands) { + parametricBands = paraProfile.bands.map((b) => ({ ...b })); + } + } + + // Restore speaker EQ active profile on startup + const activeSpeakerId = localStorage.getItem(SPEAKER_ACTIVE_PROFILE_KEY); + if (activeSpeakerId) { + const speakerProfiles = getSpeakerProfiles(); + const spkProfile = speakerProfiles[activeSpeakerId]; + if (spkProfile) { + if (spkProfile.config) { + speakerConfig = spkProfile.config; + if (speakerConfigSelect) speakerConfigSelect.value = speakerConfig; + } + if (spkProfile.channels) { + spkProfile.channels.forEach((saved) => { + if (speakerChannels[saved.id]) { + speakerChannels[saved.id].measurement = saved.measurement || null; + speakerChannels[saved.id].targetId = saved.targetId || 'harman_room'; + speakerChannels[saved.id].preamp = saved.preamp || 0; + speakerChannels[saved.id].bands = saved.bands + ? saved.bands.map((b) => ({ ...b })) + : speakerChannels[saved.id].bands; + } + }); + } + speakerActiveChannel = SPEAKER_CONFIGS[speakerConfig][0]; + } + } + + // Restore EQ mode on startup + const savedEQMode = localStorage.getItem(EQ_MODE_KEY); + if (savedEQMode && ['autoeq', 'parametric', 'speaker'].includes(savedEQMode)) { + setEQMode(savedEQMode); + } + // Now Playing Mode const nowPlayingMode = document.getElementById('now-playing-mode'); if (nowPlayingMode) { @@ -3740,8 +5970,6 @@ function initializeBlockedContentManager() { e.stopPropagation(); const id = btn.dataset.id; const type = btn.dataset.type; - const itemLi = btn.closest('li'); - const itemName = itemLi ? itemLi.querySelector('.item-name').textContent : 'item'; if (type === 'artist') { contentBlockingSettings.unblockArtist(id); @@ -3751,10 +5979,6 @@ function initializeBlockedContentManager() { contentBlockingSettings.unblockTrack(id); } - if (typeof showNotification === 'function') { - showNotification(`Unblocked ${type}: ${itemName}`); - } - renderBlockedLists(); }); }); diff --git a/js/storage.js b/js/storage.js index e7b2035..f2097b4 100644 --- a/js/storage.js +++ b/js/storage.js @@ -999,39 +999,11 @@ export const visualizerSettings = { }, }; -export const playbackSettings = { - FULLSCREEN_TILT_KEY: 'playback-fullscreen-tilt', - PRELOAD_TIME_KEY: 'playback-preload-time', - - isFullscreenTiltEnabled() { - try { - return localStorage.getItem(this.FULLSCREEN_TILT_KEY) !== 'false'; - } catch { - return true; - } - }, - - setFullscreenTiltEnabled(enabled) { - localStorage.setItem(this.FULLSCREEN_TILT_KEY, enabled ? 'true' : 'false'); - }, - - getPreloadTime() { - try { - const val = localStorage.getItem(this.PRELOAD_TIME_KEY); - return val ? parseInt(val, 10) : 15; - } catch { - return 15; - } - }, - - setPreloadTime(seconds) { - localStorage.setItem(this.PRELOAD_TIME_KEY, seconds.toString()); - }, -}; - export const equalizerSettings = { ENABLED_KEY: 'equalizer-enabled', GAINS_KEY: 'equalizer-gains', + BAND_TYPES_KEY: 'equalizer-band-types', + BAND_QS_KEY: 'equalizer-band-qs', PRESET_KEY: 'equalizer-preset', CUSTOM_PRESETS_KEY: 'equalizer-custom-presets', BAND_COUNT_KEY: 'equalizer-band-count', @@ -1308,6 +1280,62 @@ export const equalizerSettings = { } }, + getBandTypes(bandCount) { + const count = bandCount || this.getBandCount(); + try { + const stored = localStorage.getItem(this.BAND_TYPES_KEY); + if (stored) { + const types = JSON.parse(stored); + if (Array.isArray(types) && types.length === count) { + return types; + } + } + } catch { + /* ignore */ + } + return new Array(count).fill('peaking'); + }, + + setBandTypes(types) { + try { + if (Array.isArray(types) && types.length >= this.MIN_BANDS && types.length <= this.MAX_BANDS) { + localStorage.setItem(this.BAND_TYPES_KEY, JSON.stringify(types)); + } + } catch (e) { + console.warn('[EQ] Failed to save band types:', e); + } + }, + + getBandQs(bandCount) { + const count = bandCount || this.getBandCount(); + try { + const stored = localStorage.getItem(this.BAND_QS_KEY); + if (stored) { + const qs = JSON.parse(stored); + if (Array.isArray(qs) && qs.length === count) { + return qs; + } + // Interpolate stored Qs to match requested band count instead of discarding + if (Array.isArray(qs) && qs.length >= this.MIN_BANDS) { + return this._interpolateGains(qs, count); + } + } + } catch { + /* ignore */ + } + return null; + }, + + setBandQs(qs) { + try { + if (Array.isArray(qs) && qs.length >= this.MIN_BANDS && qs.length <= this.MAX_BANDS) { + localStorage.setItem(this.BAND_QS_KEY, JSON.stringify(qs)); + } + } catch (e) { + console.warn('[EQ] Failed to save band Qs:', e); + } + }, + /** * Interpolate gains array to match target band count */ @@ -1440,6 +1468,130 @@ export const equalizerSettings = { return false; } }, + + // ======================================== + // AutoEQ Profile Storage + // ======================================== + AUTOEQ_PROFILES_KEY: 'autoeq-saved-profiles', + AUTOEQ_ACTIVE_PROFILE_KEY: 'autoeq-active-profile', + AUTOEQ_SAMPLE_RATE_KEY: 'autoeq-sample-rate', + + getAutoEQProfiles() { + try { + const stored = localStorage.getItem(this.AUTOEQ_PROFILES_KEY); + return stored ? JSON.parse(stored) : {}; + } catch { + return {}; + } + }, + + saveAutoEQProfile(profile) { + try { + const profiles = this.getAutoEQProfiles(); + const id = profile.id || 'autoeq_' + Date.now(); + const profileCopy = { ...profile, id }; + profiles[id] = profileCopy; + localStorage.setItem(this.AUTOEQ_PROFILES_KEY, JSON.stringify(profiles)); + return id; + } catch (e) { + console.warn('[AutoEQ] Failed to save profile:', e); + return false; + } + }, + + deleteAutoEQProfile(profileId) { + try { + const profiles = this.getAutoEQProfiles(); + if (profiles[profileId]) { + delete profiles[profileId]; + localStorage.setItem(this.AUTOEQ_PROFILES_KEY, JSON.stringify(profiles)); + if (this.getActiveAutoEQProfile() === profileId) { + localStorage.removeItem(this.AUTOEQ_ACTIVE_PROFILE_KEY); + } + return true; + } + return false; + } catch (e) { + console.warn('[AutoEQ] Failed to delete profile:', e); + return false; + } + }, + + getActiveAutoEQProfile() { + try { + return localStorage.getItem(this.AUTOEQ_ACTIVE_PROFILE_KEY) || null; + } catch { + return null; + } + }, + + setActiveAutoEQProfile(profileId) { + if (profileId) { + localStorage.setItem(this.AUTOEQ_ACTIVE_PROFILE_KEY, profileId); + } else { + localStorage.removeItem(this.AUTOEQ_ACTIVE_PROFILE_KEY); + } + }, + + getSampleRate() { + try { + const stored = localStorage.getItem(this.AUTOEQ_SAMPLE_RATE_KEY); + const val = parseInt(stored, 10); + return [44100, 48000, 96000].includes(val) ? val : 48000; + } catch { + return 48000; + } + }, + + setSampleRate(rate) { + localStorage.setItem(this.AUTOEQ_SAMPLE_RATE_KEY, rate.toString()); + }, + + // ======================================== + // Last Selected Headphone Persistence + // ======================================== + AUTOEQ_LAST_HEADPHONE_KEY: 'autoeq-last-headphone', + + /** + * Save the last selected headphone entry + its measurement data + * so it persists across page reloads without re-fetching from GitHub + * @param {object} entry - {name, type, path, fileName} + * @param {Array} measurementData - [{freq, gain}, ...] + */ + setLastHeadphone(entry, measurementData) { + try { + localStorage.setItem( + this.AUTOEQ_LAST_HEADPHONE_KEY, + JSON.stringify({ + entry, + measurementData, + savedAt: Date.now(), + }) + ); + } catch (e) { + console.warn('[AutoEQ] Failed to save last headphone:', e); + } + }, + + /** + * Retrieve the last selected headphone entry + cached measurement data + * @returns {{entry: object, measurementData: Array}|null} + */ + getLastHeadphone() { + try { + const stored = localStorage.getItem(this.AUTOEQ_LAST_HEADPHONE_KEY); + if (!stored) return null; + const parsed = JSON.parse(stored); + if (parsed && parsed.entry && parsed.measurementData) return parsed; + return null; + } catch { + return null; + } + }, + + clearLastHeadphone() { + localStorage.removeItem(this.AUTOEQ_LAST_HEADPHONE_KEY); + }, }; export const monoAudioSettings = { @@ -2573,7 +2725,7 @@ export const contentBlockingSettings = { isArtistBlocked(artistId) { if (!artistId) return false; - return this.getBlockedArtists().some((a) => a.id == artistId); + return this.getBlockedArtists().some((a) => String(a.id) === String(artistId)); }, blockArtist(artist) { @@ -2590,7 +2742,7 @@ export const contentBlockingSettings = { }, unblockArtist(artistId) { - const blocked = this.getBlockedArtists().filter((a) => a.id != artistId); + const blocked = this.getBlockedArtists().filter((a) => a.id !== artistId); this.setBlockedArtists(blocked); }, @@ -2610,13 +2762,13 @@ export const contentBlockingSettings = { isTrackBlocked(trackId) { if (!trackId) return false; - return this.getBlockedTracks().some((t) => t.id == trackId); + return this.getBlockedTracks().some((t) => t.id === trackId); }, blockTrack(track) { if (!track || !track.id) return; const blocked = this.getBlockedTracks(); - if (!blocked.some((t) => t.id == track.id)) { + if (!blocked.some((t) => t.id === track.id)) { blocked.push({ id: track.id, title: track.title || 'Unknown Track', @@ -2628,7 +2780,7 @@ export const contentBlockingSettings = { }, unblockTrack(trackId) { - const blocked = this.getBlockedTracks().filter((t) => t.id != trackId); + const blocked = this.getBlockedTracks().filter((t) => t.id !== trackId); this.setBlockedTracks(blocked); }, @@ -2648,13 +2800,13 @@ export const contentBlockingSettings = { isAlbumBlocked(albumId) { if (!albumId) return false; - return this.getBlockedAlbums().some((a) => a.id == albumId); + return this.getBlockedAlbums().some((a) => a.id === albumId); }, blockAlbum(album) { if (!album || !album.id) return; const blocked = this.getBlockedAlbums(); - if (!blocked.some((a) => a.id == album.id)) { + if (!blocked.some((a) => a.id === album.id)) { blocked.push({ id: album.id, title: album.title || 'Unknown Album', @@ -2666,7 +2818,7 @@ export const contentBlockingSettings = { }, unblockAlbum(albumId) { - const blocked = this.getBlockedAlbums().filter((a) => a.id != albumId); + const blocked = this.getBlockedAlbums().filter((a) => a.id !== albumId); this.setBlockedAlbums(blocked); }, diff --git a/js/ui.js b/js/ui.js index 4eb9f6f..1855ee5 100644 --- a/js/ui.js +++ b/js/ui.js @@ -26,7 +26,6 @@ import { fontSettings, contentBlockingSettings, settingsUiState, - playbackSettings, } from './storage.js'; import { db } from './db.js'; import { getVibrantColorFromImage } from './vibrant-color.js'; @@ -151,9 +150,6 @@ export class UIRenderer { this.lastRecommendedTracks = []; this.currentArtistId = null; - this._handleTiltMove = this._handleTiltMove.bind(this); - this._handleTiltLeave = this._handleTiltLeave.bind(this); - // Listen for dynamic color reset events window.addEventListener('reset-dynamic-color', () => { this.resetVibrantColor(); @@ -1231,14 +1227,6 @@ export class UIRenderer { overlay.style.display = 'flex'; - // Apply vanilla-tilt effect to fullscreen cover if enabled - this._applyFullscreenTilt(overlay); - - // Listen for tilt setting changes - window.addEventListener('fullscreen-tilt-toggle', (e) => { - this._applyFullscreenTilt(overlay, e.detail.enabled); - }); - const startVisualizer = async () => { if (!visualizerSettings.isEnabled()) { if (this.visualizer) this.visualizer.stop(); @@ -1332,46 +1320,6 @@ export class UIRenderer { clearTimeout(this.uiToggleMouseTimer); this.uiToggleMouseTimer = null; } - - // Clean up vanilla-tilt if applied - this._removeFullscreenTilt(); - } - - _applyFullscreenTilt(overlay, enabled = playbackSettings.isFullscreenTiltEnabled()) { - const image = document.getElementById('fullscreen-cover-image'); - if (!image) return; - - this._removeFullscreenTilt(); - - if (!enabled) return; - - image.addEventListener('mousemove', this._handleTiltMove); - image.addEventListener('mouseleave', this._handleTiltLeave); - } - - _handleTiltMove(e) { - const image = e.target; - const rect = image.getBoundingClientRect(); - const x = e.clientX - rect.left; - const y = e.clientY - rect.top; - const centerX = rect.width / 2; - const centerY = rect.height / 2; - const rotateX = ((y - centerY) / centerY) * -10; - const rotateY = ((x - centerX) / centerX) * 10; - - image.style.transform = `perspective(1000px) rotateX(${rotateX}deg) rotateY(${rotateY}deg) scale(1.02)`; - } - - _handleTiltLeave(e) { - e.target.style.transform = 'perspective(1000px) rotateX(0) rotateY(0) scale(1)'; - } - - _removeFullscreenTilt() { - const image = document.getElementById('fullscreen-cover-image'); - if (!image) return; - image.removeEventListener('mousemove', this._handleTiltMove); - image.removeEventListener('mouseleave', this._handleTiltLeave); - image.style.transform = ''; } setupUIToggleButton(overlay) { diff --git a/styles.css b/styles.css index 11cc31b..2150620 100644 --- a/styles.css +++ b/styles.css @@ -7712,6 +7712,9 @@ body:has(#side-panel.active) #close-fullscreen-cover-btn { transform: scale(0.97); } +/* ======================================== + Precision AutoEQ - Redesigned Equalizer + ======================================== */ .equalizer-container { margin-top: var(--spacing-md); padding: var(--spacing-lg); @@ -7719,330 +7722,1043 @@ body:has(#side-panel.active) #close-fullscreen-cover-btn { border: 1px solid var(--border); border-radius: var(--radius-lg); box-shadow: inset 0 1px 1px rgb(255, 255, 255, 0.05); + display: flex; + flex-direction: column; + gap: var(--spacing-lg); } -.equalizer-header { - margin-bottom: var(--spacing-lg); -} - -.equalizer-preset-row { +/* --- Mode Toggle --- */ +.autoeq-mode-row { display: flex; align-items: center; gap: var(--spacing-sm); +} + +.autoeq-mode-toggle { + display: flex; + flex: 1; + background: var(--input); + border: 1px solid var(--border); + border-radius: var(--radius); + overflow: hidden; +} + +.eq-howto-btn { + width: 30px; + height: 30px; + border-radius: 50%; + border: 1px solid var(--border); + background: var(--input); + color: var(--muted-foreground); + font-size: 0.8rem; + font-weight: 700; + cursor: pointer; + transition: all var(--transition-fast); + flex-shrink: 0; +} + +.eq-howto-btn:hover { + border-color: var(--primary); + color: var(--primary); + background: rgb(var(--highlight-rgb), 0.08); +} + +.eq-howto-panel { + border: 1px solid var(--border); + border-radius: var(--radius); + background: rgb(var(--highlight-rgb), 0.03); + padding: var(--spacing-md); + position: relative; + animation: fade-slide-in 0.2s ease; +} + +@keyframes fade-slide-in { + from { + opacity: 0; + transform: translateY(-6px); + } + + to { + opacity: 1; + transform: translateY(0); + } +} + +.eq-howto-close { + position: absolute; + top: var(--spacing-sm); + right: var(--spacing-sm); + width: 24px; + height: 24px; + border: none; + background: none; + color: var(--muted-foreground); + font-size: 1.2rem; + cursor: pointer; + border-radius: var(--radius-sm); + display: flex; + align-items: center; + justify-content: center; +} + +.eq-howto-close:hover { + color: var(--foreground); + background: rgb(var(--highlight-rgb), 0.1); +} + +/* stylelint-disable-next-line no-descending-specificity */ +.eq-howto-tab h4 { + font-size: 0.85rem; + font-weight: 700; + color: var(--foreground); + margin: 0 0 var(--spacing-sm) 0; +} + +.eq-howto-tab ol { + margin: 0; + padding-left: 1.4em; + font-size: 0.78rem; + color: var(--muted-foreground); + line-height: 1.7; +} + +.eq-howto-tab ol b { + color: var(--foreground); +} + +.eq-howto-tip { + margin: var(--spacing-sm) 0 0; + padding: var(--spacing-xs) var(--spacing-sm); + background: rgb(var(--highlight-rgb), 0.06); + border-radius: var(--radius-sm); + font-size: 0.72rem; + color: var(--primary); + font-weight: 500; +} + +.autoeq-mode-btn { + flex: 1; + padding: 0.5rem 1rem; + font-size: 0.8rem; + font-weight: 600; + background: transparent; + border: none; + color: var(--muted-foreground); + cursor: pointer; + transition: all var(--transition-fast); + text-align: center; +} + +.autoeq-mode-btn:hover { + color: var(--foreground); +} + +.autoeq-mode-btn.active { + background: var(--primary); + color: var(--primary-foreground); +} + +/* --- Graph Section --- */ +.autoeq-graph-section { + display: flex; + flex-direction: column; + gap: var(--spacing-sm); +} + +.autoeq-graph-header { + display: flex; + align-items: center; + justify-content: space-between; + flex-wrap: wrap; + gap: var(--spacing-sm); +} + +.autoeq-graph-title { + font-size: 0.8rem; + font-weight: 600; + color: var(--muted-foreground); + text-transform: uppercase; + letter-spacing: 0.05em; +} + +.autoeq-graph-legend { + display: flex; + gap: var(--spacing-md); + flex-wrap: wrap; +} + +.legend-item { + display: flex; + align-items: center; + gap: var(--spacing-xs); + font-size: 0.7rem; + color: var(--muted-foreground); +} + +.legend-dot { + width: 8px; + height: 8px; + border-radius: 50%; + display: inline-block; +} + +.legend-original .legend-dot { + background: #3b82f6; +} + +.legend-target .legend-dot { + background: rgb(255 255 255 / 0.5); + border: 1px dashed rgb(255 255 255 / 0.3); +} +.legend-corrected .legend-dot { + background: #f472b6; +} + +.autoeq-graph-wrapper { + position: relative; + width: 100%; + height: 300px; + background: var(--background); + border: 1px solid var(--border); + border-radius: var(--radius); + overflow: hidden; +} + +.autoeq-response-canvas { + display: block; + width: 100%; + height: 100%; + cursor: crosshair; +} + +.autoeq-auto-preamp { + display: flex; + align-items: center; + justify-content: space-between; + padding: var(--spacing-xs) 0; +} + +.autoeq-auto-preamp-label { + font-size: 0.75rem; + color: var(--muted-foreground); + cursor: pointer; + user-select: none; +} + +.toggle-switch-sm { + transform: scale(0.75); + transform-origin: right center; +} + +/* --- Controls Section --- */ +.autoeq-controls-section { + display: flex; + flex-direction: column; + gap: var(--spacing-md); +} + +.autoeq-control-group { + display: flex; + flex-direction: column; + gap: var(--spacing-xs); +} + +.autoeq-control-label { + font-size: 0.65rem; + text-transform: uppercase; + letter-spacing: 0.08em; + color: var(--primary); + font-weight: 600; +} + +.autoeq-select-wrapper { + display: flex; + gap: var(--spacing-xs); +} + +.autoeq-select-wrapper select { + flex: 1; + padding: 0.6rem 0.75rem; + background: var(--input); + border: 1px solid var(--border); + border-radius: var(--radius); + color: var(--foreground); + font-size: 0.85rem; + cursor: pointer; + transition: border-color var(--transition-fast); +} + +.autoeq-select-wrapper select:hover { + border-color: var(--primary); +} + +.autoeq-select-wrapper select:focus { + outline: none; + border-color: var(--ring); + box-shadow: 0 0 0 3px rgb(var(--highlight-rgb), 0.2); +} + +.autoeq-settings-btn { + width: 40px; + display: flex; + align-items: center; + justify-content: center; + background: var(--input); + border: 1px solid var(--border); + border-radius: var(--radius); + color: var(--muted-foreground); + cursor: pointer; + transition: all var(--transition-fast); + flex-shrink: 0; +} + +.autoeq-settings-btn:hover { + border-color: var(--primary); + color: var(--foreground); +} + +.autoeq-controls-row { + display: grid; + grid-template-columns: 1fr 1fr 1fr; + gap: var(--spacing-sm); +} + +.autoeq-control-mini { + display: flex; + flex-direction: column; + gap: var(--spacing-xs); +} + +/* stylelint-disable-next-line no-descending-specificity */ +.autoeq-control-mini select { + padding: 0.6rem 0.5rem; + background: var(--input); + border: 1px solid var(--border); + border-radius: var(--radius); + color: var(--foreground); + font-size: 0.85rem; + cursor: pointer; + transition: border-color var(--transition-fast); +} + +.autoeq-control-mini select:hover { + border-color: var(--primary); +} + +.autoeq-control-mini select:focus { + outline: none; + border-color: var(--ring); + box-shadow: 0 0 0 3px rgb(var(--highlight-rgb), 0.2); +} + +.autoeq-actions-row { + display: flex; + gap: var(--spacing-sm); + align-items: stretch; +} + +.autoeq-download-btn { + width: 48px; + display: flex; + align-items: center; + justify-content: center; + background: var(--input); + border: 1px solid var(--border); + border-radius: var(--radius); + color: var(--muted-foreground); + cursor: pointer; + transition: all var(--transition-fast); + flex-shrink: 0; +} + +.autoeq-download-btn:hover { + border-color: var(--primary); + color: var(--foreground); +} + +.autoeq-run-btn { + flex: 1; + padding: 0.75rem 1.5rem; + font-size: 1rem; + font-weight: 700; + background: var(--primary); + border: none; + border-radius: var(--radius); + color: var(--primary-foreground); + cursor: pointer; + transition: all var(--transition-fast); + letter-spacing: 0.02em; +} + +.autoeq-run-btn:disabled { + opacity: 0.5; + cursor: not-allowed; +} + +.autoeq-run-btn:hover:not(:disabled) { + filter: brightness(1.1); + box-shadow: 0 4px 16px rgb(var(--highlight-rgb), 0.3); +} + +.autoeq-status { + font-size: 0.75rem; + color: var(--muted-foreground); + font-style: italic; +} + +.autoeq-status.error { + color: var(--destructive); +} +.autoeq-status.success { + color: var(--primary); +} + +/* --- Saved Profiles --- */ +.autoeq-saved-section { + border: 1px solid var(--border); + border-radius: var(--radius); + overflow: hidden; +} + +.autoeq-saved-header { + display: flex; + align-items: center; + justify-content: space-between; + padding: var(--spacing-sm) var(--spacing-md); + background: rgb(var(--highlight-rgb), 0.04); + gap: var(--spacing-sm); flex-wrap: wrap; } -.equalizer-preset-row label { - font-size: 0.9rem; +.autoeq-saved-header-left { + display: flex; + align-items: center; + gap: var(--spacing-sm); +} + +.autoeq-saved-title { + font-size: 0.75rem; + font-weight: 700; + color: var(--primary); + text-transform: uppercase; + letter-spacing: 0.08em; +} + +.autoeq-saved-count { + display: inline-flex; + align-items: center; + justify-content: center; + min-width: 20px; + height: 20px; + padding: 0 6px; + font-size: 0.65rem; + font-weight: 700; + background: var(--primary); + color: var(--primary-foreground); + border-radius: var(--radius-full); +} + +.autoeq-saved-header-right { + display: flex; + align-items: center; + gap: var(--spacing-xs); +} + +.autoeq-profile-name-input { + width: 140px; + padding: 0.35rem 0.6rem; + background: var(--input); + border: 1px solid var(--border); + border-radius: var(--radius); + color: var(--foreground); + font-size: 0.8rem; + transition: border-color var(--transition-fast); +} + +.autoeq-profile-name-input:focus { + outline: none; + border-color: var(--ring); + box-shadow: 0 0 0 3px rgb(var(--highlight-rgb), 0.2); +} + +.autoeq-save-btn { + padding: 0.35rem 0.75rem; + font-size: 0.8rem; +} + +.autoeq-collapse-btn { + background: none; + border: none; + color: var(--muted-foreground); + cursor: pointer; + padding: 4px; + display: flex; + align-items: center; + transition: transform var(--transition-fast); +} + +.autoeq-collapse-btn.collapsed { + transform: rotate(180deg); +} + +.autoeq-saved-grid { + display: flex; + flex-direction: column; + gap: var(--spacing-md); +} + +.autoeq-profile-card { + display: flex; + flex-direction: column; + padding: 0; + background: var(--input); + border: 1px solid var(--border); + border-radius: var(--radius-lg, 12px); + cursor: pointer; + transition: + background var(--transition-fast), + border-color var(--transition-fast); + position: relative; + overflow: hidden; +} + +.autoeq-profile-card:last-child { + border-bottom: 1px solid var(--border); +} + +.autoeq-profile-card:hover { + border-color: var(--primary); + background: rgb(var(--highlight-rgb), 0.06); +} + +.autoeq-profile-card.active { + border-color: var(--primary); + background: rgb(var(--highlight-rgb), 0.08); +} + +.autoeq-profile-preview { + width: 100%; + height: 100px; + display: block; + background: rgb(0 0 0 / 0.25); +} + +.autoeq-profile-info { + display: flex; + flex-wrap: wrap; + align-items: center; + gap: 0.25rem var(--spacing-sm); + padding: var(--spacing-sm) var(--spacing-md) var(--spacing-md); +} + +.autoeq-profile-active-icon { + display: none; + width: 22px; + height: 22px; + border-radius: 50%; + background: var(--primary); + color: var(--primary-foreground); + font-size: 0.7rem; + font-weight: 700; + align-items: center; + justify-content: center; + flex-shrink: 0; +} + +.autoeq-profile-card.active .autoeq-profile-active-icon { + display: flex; +} + +.autoeq-profile-name { + font-size: 0.95rem; + font-weight: 600; + color: var(--foreground); +} + +.autoeq-profile-meta { + font-size: 0.75rem; + color: var(--muted-foreground); + width: 100%; + padding-left: 0; + margin-left: 0; +} + +.autoeq-profile-card.active .autoeq-profile-meta { + padding-left: calc(22px + var(--spacing-sm)); +} + +.autoeq-profile-delete { + position: absolute; + bottom: var(--spacing-sm); + right: var(--spacing-sm); + width: 32px; + height: 32px; + display: flex; + align-items: center; + justify-content: center; + background: rgb(var(--highlight-rgb), 0.1); + border: none; + border-radius: var(--radius); + color: var(--muted-foreground); + cursor: pointer; + opacity: 0; + transition: all var(--transition-fast); + font-size: 1rem; +} + +.autoeq-profile-delete:hover { + background: var(--destructive); + color: var(--destructive-foreground); +} + +.autoeq-profile-card:hover .autoeq-profile-delete { + opacity: 1; +} + +/* --- Database Browser --- */ +.autoeq-database-section { + border: 1px solid var(--border); + border-radius: var(--radius); + overflow: hidden; +} + +.autoeq-database-header { + display: flex; + align-items: center; + justify-content: space-between; + padding: var(--spacing-md); +} + +.autoeq-database-title { + font-size: 1rem; + font-weight: 700; + color: var(--foreground); + margin: 0; +} + +.autoeq-database-subtitle { + font-size: 0.7rem; + color: var(--muted-foreground); + display: block; +} + +.autoeq-database-count { + font-size: 0.75rem; color: var(--muted-foreground); font-weight: 500; } -.equalizer-preset-row select { - flex: 1; - min-width: 150px; - max-width: 250px; - padding: 0.5rem 1rem; - background: var(--input); - border: 1px solid var(--border); - border-radius: var(--radius); - color: var(--foreground); - font-size: 0.9rem; - cursor: pointer; - transition: - border-color var(--transition-fast), - box-shadow var(--transition-fast); -} - -.equalizer-preset-row select:hover { - border-color: var(--primary); -} - -.equalizer-preset-row select:focus { - outline: none; - border-color: var(--ring); - box-shadow: 0 0 0 3px rgb(var(--highlight-rgb), 0.2); -} - -.eq-band-count-input { - width: 60px; - padding: 0.5rem; - background: var(--input); - border: 1px solid var(--border); - border-radius: var(--radius); - color: var(--foreground); - font-size: 0.9rem; - text-align: center; - cursor: pointer; - transition: - border-color var(--transition-fast), - box-shadow var(--transition-fast); -} - -.eq-band-count-input:hover { - border-color: var(--primary); -} - -.eq-band-count-input:focus { - outline: none; - border-color: var(--ring); - box-shadow: 0 0 0 3px rgb(var(--highlight-rgb), 0.2); -} - -/* Hide number input arrows */ -.eq-band-count-input::-webkit-outer-spin-button, -.eq-band-count-input::-webkit-inner-spin-button { - appearance: none; - margin: 0; -} - -.eq-band-count-input[type='number'] { - appearance: textfield; -} - -#equalizer-reset-btn { - padding: 0.5rem; +.autoeq-database-search { display: flex; align-items: center; - justify-content: center; - border-radius: var(--radius); - background: var(--input); - border: 1px solid var(--border); - color: var(--muted-foreground); - transition: all var(--transition-fast); -} - -#equalizer-reset-btn:hover { - background: var(--card); - border-color: var(--primary); - color: var(--foreground); -} - -#equalizer-reset-btn svg { - transition: transform 0.3s ease; -} - -#equalizer-reset-btn:hover svg { - transform: rotate(-45deg); -} - -/* Custom Preset Controls */ -.custom-preset-controls { - margin-top: var(--spacing-md); - padding-top: var(--spacing-md); - border-top: 1px solid var(--border); -} - -.custom-preset-input-row { - display: flex; gap: var(--spacing-sm); - margin-bottom: var(--spacing-sm); + padding: 0 var(--spacing-md) var(--spacing-md); } -#custom-preset-name { +/* stylelint-disable-next-line no-descending-specificity */ +.autoeq-database-search svg { + color: var(--muted-foreground); + flex-shrink: 0; +} + +/* stylelint-disable-next-line no-descending-specificity */ +.autoeq-database-search input { flex: 1; padding: 0.5rem 0.75rem; background: var(--input); border: 1px solid var(--border); border-radius: var(--radius); color: var(--foreground); - font-size: 0.9rem; + font-size: 0.85rem; transition: border-color var(--transition-fast); } -#custom-preset-name:focus { +/* stylelint-disable-next-line no-descending-specificity */ +.autoeq-database-search input:focus { outline: none; border-color: var(--ring); box-shadow: 0 0 0 3px rgb(var(--highlight-rgb), 0.2); } -#save-custom-preset-btn { +.autoeq-database-content { + display: flex; + position: relative; +} + +.autoeq-database-list { + flex: 1; + max-height: 400px; + overflow-y: auto; +} + +.autoeq-database-alpha-index { + width: 22px; + display: flex; + flex-direction: column; + align-items: center; + padding: 2px 0; + flex-shrink: 0; + position: sticky; + top: 0; + align-self: flex-start; + max-height: 400px; + overflow: hidden; +} + +/* stylelint-disable-next-line no-descending-specificity */ +.autoeq-database-alpha-index button { + width: 18px; + height: 16px; + font-size: 0.5rem; + font-weight: 700; display: flex; align-items: center; - gap: var(--spacing-xs); - padding: 0.5rem 1rem; + justify-content: center; + color: var(--muted-foreground); + background: none; + border: none; + cursor: pointer; + border-radius: var(--radius-sm); + padding: 0; + line-height: 1; +} + +/* stylelint-disable-next-line no-descending-specificity */ +.autoeq-database-alpha-index button:hover { + color: var(--primary); + background: rgb(var(--highlight-rgb), 0.1); +} + +.autoeq-db-item { + display: flex; + align-items: center; + gap: var(--spacing-sm); + padding: var(--spacing-sm) var(--spacing-md); + border-top: 1px solid rgb(var(--highlight-rgb), 0.05); + cursor: pointer; + transition: background var(--transition-fast); +} + +.autoeq-db-item:hover { + background: rgb(var(--highlight-rgb), 0.08); +} + +/* stylelint-disable-next-line no-descending-specificity */ +.autoeq-db-item svg { + color: var(--muted-foreground); + flex-shrink: 0; +} + +.autoeq-db-item-info { + flex: 1; + min-width: 0; +} + +.autoeq-db-item-name { + font-size: 0.85rem; + font-weight: 500; + color: var(--primary); + display: block; + overflow: hidden; + text-overflow: ellipsis; white-space: nowrap; } -#save-custom-preset-btn svg { +.autoeq-db-item-meta { + font-size: 0.7rem; + color: var(--muted-foreground); + display: block; +} + +.autoeq-db-item-chevron { + color: var(--muted-foreground); flex-shrink: 0; + opacity: 0.5; + transition: transform var(--transition-fast); } -.delete-preset-btn { - display: flex; - align-items: center; - gap: var(--spacing-xs); - padding: 0.5rem 1rem; - font-size: 0.85rem; - color: var(--destructive); - border-color: var(--destructive); - opacity: 0.8; - transition: opacity var(--transition-fast); +.autoeq-db-item.expanded .autoeq-db-item-chevron { + transform: rotate(90deg); } -.delete-preset-btn:hover { - opacity: 1; - background: var(--destructive); - color: var(--destructive-foreground); +.autoeq-db-sub-list { + display: none; + padding-left: var(--spacing-lg); + background: rgb(var(--highlight-rgb), 0.02); } -.delete-preset-btn svg { - flex-shrink: 0; +.autoeq-db-sub-list.visible { + display: block; } -/* EQ Range Controls */ -.eq-range-controls { +.autoeq-db-sub-item { display: flex; align-items: center; gap: var(--spacing-sm); - margin-top: var(--spacing-sm); - padding-top: var(--spacing-sm); - border-top: 1px solid var(--border); - flex-wrap: wrap; -} - -.eq-range-controls label { - font-size: 0.9rem; + padding: var(--spacing-xs) var(--spacing-md); + border-top: 1px solid rgb(var(--highlight-rgb), 0.03); + cursor: pointer; + font-size: 0.8rem; color: var(--muted-foreground); - font-weight: 500; + transition: all var(--transition-fast); } -.eq-range-controls span { - font-size: 0.9rem; +.autoeq-db-sub-item:hover { + background: rgb(var(--highlight-rgb), 0.08); + color: var(--foreground); +} + +.autoeq-db-sub-item .sub-source { + font-size: 0.65rem; + padding: 1px 6px; + border-radius: var(--radius-sm); + background: rgb(var(--highlight-rgb), 0.08); color: var(--muted-foreground); } -.eq-range-input { - width: 60px; - padding: 0.4rem 0.5rem; - background: var(--input); +.autoeq-database-list::-webkit-scrollbar { + width: 6px; +} + +.autoeq-database-list::-webkit-scrollbar-track { + background: transparent; +} + +.autoeq-database-list::-webkit-scrollbar-thumb { + background: rgb(var(--highlight-rgb), 0.2); + border-radius: 3px; +} + +.autoeq-database-list::-webkit-scrollbar-thumb:hover { + background: rgb(var(--highlight-rgb), 0.4); +} + +/* --- Parametric EQ Filters Section --- */ +.autoeq-filters-section { border: 1px solid var(--border); border-radius: var(--radius); - color: var(--foreground); - font-size: 0.9rem; - text-align: center; - transition: border-color var(--transition-fast); + overflow: hidden; } -.eq-range-input:hover { - border-color: var(--primary); -} - -.eq-range-input:focus { - outline: none; - border-color: var(--ring); - box-shadow: 0 0 0 3px rgb(var(--highlight-rgb), 0.2); -} - -/* Hide number input arrows */ -.eq-range-input::-webkit-outer-spin-button, -.eq-range-input::-webkit-inner-spin-button { - appearance: none; - margin: 0; -} - -.eq-range-input[type='number'] { - appearance: textfield; -} - -#apply-eq-range-btn { - padding: 0.4rem 0.75rem; - font-size: 0.85rem; -} - -#reset-eq-range-btn { - padding: 0.4rem 0.75rem; - font-size: 0.85rem; - margin-left: var(--spacing-xs); -} - -/* EQ Frequency Range Controls */ -.eq-freq-controls { +.autoeq-filters-header { display: flex; align-items: center; - gap: var(--spacing-sm); - margin-top: var(--spacing-sm); - padding-top: var(--spacing-sm); - border-top: 1px solid var(--border); - flex-wrap: wrap; + justify-content: space-between; + padding: var(--spacing-sm) var(--spacing-md); + background: rgb(var(--highlight-rgb), 0.04); + cursor: pointer; + user-select: none; + font-size: 0.75rem; + font-weight: 700; + color: var(--primary); + text-transform: uppercase; + letter-spacing: 0.08em; + transition: background var(--transition-fast); } -.eq-freq-controls label { - font-size: 0.9rem; - color: var(--muted-foreground); - font-weight: 500; +.autoeq-filters-header:hover { + background: rgb(var(--highlight-rgb), 0.08); } -.eq-freq-controls span { - font-size: 0.9rem; - color: var(--muted-foreground); -} - -.eq-freq-input { - width: 70px; - padding: 0.4rem 0.5rem; - background: var(--input); - border: 1px solid var(--border); - border-radius: var(--radius); - color: var(--foreground); - font-size: 0.9rem; - text-align: center; - transition: border-color var(--transition-fast); -} - -.eq-freq-input:hover { - border-color: var(--primary); -} - -.eq-freq-input:focus { - outline: none; - border-color: var(--ring); - box-shadow: 0 0 0 3px rgb(var(--highlight-rgb), 0.2); -} - -/* Hide number input arrows */ -.eq-freq-input::-webkit-outer-spin-button, -.eq-freq-input::-webkit-inner-spin-button { - appearance: none; - margin: 0; -} - -.eq-freq-input[type='number'] { - appearance: textfield; -} - -#apply-eq-freq-btn { - padding: 0.4rem 0.75rem; - font-size: 0.85rem; -} - -#reset-eq-freq-btn { - padding: 0.4rem 0.75rem; - font-size: 0.85rem; - margin-left: var(--spacing-xs); -} - -/* EQ Preamp Controls */ -.eq-preamp-controls { +.autoeq-filters-content { + padding: var(--spacing-md); + display: flex; + flex-direction: column; + gap: var(--spacing-md); +} + +.autoeq-preset-row { display: flex; - align-items: center; gap: var(--spacing-sm); - margin-top: var(--spacing-sm); - padding-top: var(--spacing-sm); - border-top: 1px solid var(--border); - flex-wrap: wrap; } -.eq-preamp-controls label { - font-size: 0.9rem; - color: var(--muted-foreground); - font-weight: 500; -} - -#eq-preamp-slider { +.autoeq-preset-row .autoeq-control-group { + flex: 1; +} + +/* stylelint-disable-next-line no-descending-specificity */ +.autoeq-preset-row select { + width: 100%; + padding: 0.6rem 0.75rem; + background: var(--input); + border: 1px solid var(--border); + border-radius: var(--radius); + color: var(--foreground); + font-size: 0.85rem; + cursor: pointer; + transition: border-color var(--transition-fast); +} + +.autoeq-preset-row select:hover { + border-color: var(--primary); +} + +.autoeq-preset-row select:focus { + outline: none; + border-color: var(--ring); + box-shadow: 0 0 0 3px rgb(var(--highlight-rgb), 0.2); +} + +.autoeq-parametric-profiles { + border: 1px solid var(--border); + border-radius: var(--radius); + overflow: hidden; +} + +.autoeq-parametric-profiles .autoeq-saved-header { + padding: var(--spacing-xs) var(--spacing-md); +} + +.autoeq-filters-actions { + display: flex; + gap: var(--spacing-xs); + flex-wrap: wrap; + align-items: center; +} + +/* stylelint-disable-next-line no-descending-specificity */ +.autoeq-filters-actions button { + padding: 0.35rem 0.75rem; + font-size: 0.75rem; +} + +.autoeq-preamp-row { + display: flex; + align-items: center; + gap: var(--spacing-sm); +} + +.autoeq-preamp-label { + font-size: 0.8rem; + font-weight: 600; + color: var(--foreground); + min-width: 55px; +} + +.autoeq-preamp-slider { + flex: 1; + height: 6px; + appearance: none; + background: var(--border); + border-radius: 3px; + outline: none; + cursor: pointer; +} + +.autoeq-preamp-slider::-webkit-slider-thumb { + appearance: none; + width: 16px; + height: 16px; + border-radius: 50%; + background: var(--primary); + cursor: pointer; + transition: transform var(--transition-fast); +} + +.autoeq-preamp-slider::-webkit-slider-thumb:hover { + transform: scale(1.2); +} + +.autoeq-preamp-slider::-moz-range-thumb { + width: 16px; + height: 16px; + border-radius: 50%; + background: var(--primary); + cursor: pointer; + border: none; +} + +.autoeq-preamp-value { + font-size: 0.8rem; + font-weight: 600; + color: var(--primary); + min-width: 50px; + text-align: right; +} + +.autoeq-bands-list { + display: flex; + flex-direction: column; + gap: 2px; +} + +.autoeq-band-control { + padding: 0.4rem 0.6rem; + background: var(--input); + border: 1px solid var(--border); + border-radius: var(--radius); + display: flex; + flex-direction: column; + gap: 0.3rem; +} + +.autoeq-band-header { + display: flex; + align-items: center; + gap: 0.5rem; +} + +.autoeq-band-number { + font-size: 0.65rem; + font-weight: 700; + color: var(--muted-foreground); + min-width: 1rem; + text-align: center; + opacity: 0.6; +} + +.autoeq-type-select { + padding: 0.15rem 0.3rem; + font-size: 0.7rem; + font-weight: 600; + background: var(--background); + color: var(--primary); + border: 1px solid var(--border); + border-radius: var(--radius); + cursor: pointer; + outline: none; + flex-shrink: 0; +} + +.autoeq-type-select:hover { + border-color: var(--primary); +} + +.autoeq-type-select:focus { + border-color: var(--ring); +} + +.autoeq-band-param { + display: flex; + align-items: baseline; + gap: 0.25rem; + flex: 1; + justify-content: center; +} + +.autoeq-band-param-label { + font-size: 0.65rem; + color: var(--muted-foreground); + font-weight: 500; + text-transform: uppercase; + opacity: 0.7; +} + +.autoeq-band-value { + font-size: 0.75rem; + font-weight: 600; + color: var(--primary); + white-space: nowrap; +} + +.autoeq-band-sliders { + display: flex; + gap: 0.4rem; + align-items: center; +} + +.autoeq-band-slider { flex: 1; - min-width: 120px; - max-width: 200px; height: 4px; appearance: none; background: var(--border); @@ -8051,317 +8767,281 @@ body:has(#side-panel.active) #close-fullscreen-cover-btn { cursor: pointer; } -#eq-preamp-slider::-webkit-slider-thumb { +.autoeq-band-slider::-webkit-slider-thumb { appearance: none; - width: 16px; - height: 16px; + width: 12px; + height: 12px; border-radius: 50%; background: var(--primary); cursor: pointer; transition: transform var(--transition-fast); } -#eq-preamp-slider::-webkit-slider-thumb:hover { - transform: scale(1.2); +.autoeq-band-slider::-webkit-slider-thumb:hover { + transform: scale(1.3); } -#eq-preamp-slider::-moz-range-thumb { - width: 16px; - height: 16px; +.autoeq-band-slider::-moz-range-thumb { + width: 12px; + height: 12px; border-radius: 50%; background: var(--primary); cursor: pointer; border: none; - transition: transform var(--transition-fast); } -#eq-preamp-slider::-moz-range-thumb:hover { - transform: scale(1.2); +/* --- Speaker EQ --- */ +.speaker-eq-section { + display: flex; + flex-direction: column; + gap: var(--spacing-md); } -/* EQ Import/Export Buttons (now inline) */ -#eq-export-btn, -#eq-import-btn { - padding: 0.25rem 0.5rem; - font-size: 0.75rem; +.speaker-eq-header { + display: flex; + flex-direction: column; + gap: var(--spacing-sm); +} + +.speaker-eq-config-row { + display: flex; + align-items: center; + gap: var(--spacing-sm); +} + +.speaker-config-select { + padding: 0.5rem 0.75rem; + background: var(--input); + border: 1px solid var(--border); + border-radius: var(--radius); + color: var(--foreground); + font-size: 0.85rem; + font-weight: 600; + cursor: pointer; +} + +.speaker-config-select:hover { + border-color: var(--primary); +} + +.speaker-channel-tabs { + display: flex; + gap: 2px; + overflow-x: auto; + padding-bottom: 2px; +} + +.speaker-channel-tab { + padding: 0.4rem 0.8rem; + font-size: 0.7rem; + font-weight: 700; + text-transform: uppercase; + letter-spacing: 0.05em; + background: var(--input); + border: 1px solid var(--border); + border-radius: var(--radius-sm); + color: var(--muted-foreground); + cursor: pointer; + transition: all var(--transition-fast); + position: relative; +} + +.speaker-channel-tab:hover { + border-color: var(--primary); + color: var(--foreground); +} + +.speaker-channel-tab.active { + background: var(--primary); + border-color: var(--primary); + color: var(--primary-foreground); +} + +.speaker-channel-tab.has-data::after { + content: ''; + position: absolute; + top: 3px; + right: 3px; + width: 5px; + height: 5px; + border-radius: 50%; + background: #4ade80; +} + +.speaker-channel-tab.active.has-data::after { + background: var(--primary-foreground); +} + +.speaker-eq-controls { + display: flex; + flex-direction: column; + gap: var(--spacing-sm); + padding: var(--spacing-md); + background: rgb(var(--highlight-rgb), 0.03); + border: 1px solid var(--border); + border-radius: var(--radius); + overflow: hidden; +} + +.speaker-eq-measurement-row { + display: flex; + gap: var(--spacing-sm); + align-items: flex-end; + flex-wrap: wrap; +} + +.speaker-measurement-status { + flex: 1; + font-size: 0.8rem; + color: var(--muted-foreground); + padding: 0.5rem 0.75rem; + background: var(--input); + border: 1px solid var(--border); + border-radius: var(--radius); + overflow: hidden; + text-overflow: ellipsis; white-space: nowrap; +} + +.speaker-measurement-status.loaded { + color: #4ade80; + border-color: rgb(74 222 128 / 0.3); +} + +.speaker-eq-params-row { + display: flex; + gap: var(--spacing-sm); + flex-wrap: wrap; + align-items: flex-end; +} + +.speaker-eq-slider-control { + flex: 1; + min-width: 120px; +} + +.speaker-eq-slider-row { + display: flex; + align-items: center; gap: var(--spacing-xs); } -/* Equalizer preset dropdown styling */ -.equalizer-preset-row select optgroup { - font-weight: 600; - color: var(--foreground); - padding: var(--spacing-xs) 0; -} - -.equalizer-preset-row select optgroup option { - font-weight: 400; - padding-left: var(--spacing-sm); -} - -.equalizer-bands-wrapper { - position: relative; - padding: var(--spacing-md) 0; -} - -.equalizer-bands { - display: flex; - justify-content: space-between; - gap: 4px; - position: relative; -} - -/* Zero line indicator - positioned at center of slider tracks */ -.equalizer-bands::before { - content: ''; - position: absolute; - left: 0; - right: 0; - top: 60px; - height: 1px; - background: var(--border); - opacity: 0.5; - pointer-events: none; - z-index: 0; -} - -/* EQ Response Curve Canvas */ -.eq-response-canvas { - position: absolute; - top: var(--spacing-md); - left: 4px; - width: calc(100% - 8px); - height: 120px; - pointer-events: none; - z-index: 2; - display: block; -} - -.eq-band { - display: flex; - flex-direction: column; - align-items: center; - gap: 8px; +.speaker-eq-slider-row input[type='range'] { flex: 1; - min-width: 0; - position: relative; - z-index: 1; - cursor: ns-resize; - user-select: none; } -/* Vertical slider styling */ -.eq-slider { - appearance: none; - writing-mode: vertical-lr; - direction: rtl; - width: 8px; - height: 120px; - background: transparent; - cursor: pointer; - user-select: none; - position: relative; -} - -/* Track */ -.eq-slider::-webkit-slider-runnable-track { - width: 6px; - height: 100%; - background: linear-gradient(to top, var(--muted), var(--input)); - border-radius: var(--radius-full); - border: 1px solid var(--border); -} - -.eq-slider::-moz-range-track { - width: 6px; - height: 100%; - background: linear-gradient(to top, var(--muted), var(--input)); - border-radius: var(--radius-full); - border: 1px solid var(--border); -} - -/* Thumb */ -.eq-slider::-webkit-slider-thumb { - appearance: none; - width: 18px; - height: 18px; - background: linear-gradient(145deg, var(--primary), var(--highlight)); - border-radius: var(--radius-full); - cursor: grab; - margin-left: -6px; - box-shadow: - 0 2px 8px rgb(0, 0, 0, 0.3), - inset 0 1px 2px rgb(255, 255, 255, 0.3); - transition: - transform 0.15s ease, - box-shadow 0.15s ease; - border: 2px solid var(--background); -} - -.eq-slider::-moz-range-thumb { - width: 18px; - height: 18px; - background: linear-gradient(145deg, var(--primary), var(--highlight)); - border-radius: var(--radius-full); - cursor: grab; - box-shadow: - 0 2px 8px rgb(0, 0, 0, 0.3), - inset 0 1px 2px rgb(255, 255, 255, 0.3); - transition: - transform 0.15s ease, - box-shadow 0.15s ease; - border: 2px solid var(--background); -} - -.eq-slider::-webkit-slider-thumb:hover { - transform: scale(1.15); - box-shadow: - 0 4px 12px rgb(var(--highlight-rgb), 0.4), - inset 0 1px 2px rgb(255, 255, 255, 0.3); -} - -.eq-slider::-moz-range-thumb:hover { - transform: scale(1.15); - box-shadow: - 0 4px 12px rgb(var(--highlight-rgb), 0.4), - inset 0 1px 2px rgb(255, 255, 255, 0.3); -} - -.eq-slider::-webkit-slider-thumb:active { - cursor: grabbing; - transform: scale(1.1); -} - -.eq-slider::-moz-range-thumb:active { - cursor: grabbing; - transform: scale(1.1); -} - -.eq-slider:focus { - outline: none; -} - -.eq-slider:focus::-webkit-slider-thumb { - box-shadow: - 0 0 0 4px rgb(var(--highlight-rgb), 0.3), - 0 2px 8px rgb(0, 0, 0, 0.3); -} - -.eq-slider:focus::-moz-range-thumb { - box-shadow: - 0 0 0 4px rgb(var(--highlight-rgb), 0.3), - 0 2px 8px rgb(0, 0, 0, 0.3); -} - -.eq-value { +.speaker-eq-slider-value { font-size: 0.7rem; font-weight: 600; - color: var(--foreground); - min-width: 28px; - text-align: center; - padding: 2px 4px; - background: var(--input); - border-radius: var(--radius-sm); - transition: - color 0.2s ease, - background 0.2s ease; + color: var(--primary); + min-width: 45px; + text-align: right; } -.eq-value.positive { - color: var(--highlight); - background: rgb(var(--highlight-rgb), 0.15); +.speaker-eq-slider-value.bass { + color: #22d3ee; +} +.speaker-eq-slider-value.room { + color: #f59e0b; +} +.speaker-eq-section .autoeq-control-label.bass { + color: #22d3ee; +} +.speaker-eq-section .autoeq-control-label.room { + color: #f59e0b; } -.eq-value.negative { - color: #ef4444; - background: rgb(239, 68, 68, 0.15); +.speaker-measure-btn { + color: #f472b6; } -.eq-freq { - font-size: 0.65rem; - color: var(--muted-foreground); - text-align: center; - white-space: nowrap; - font-weight: 500; +.speaker-measure-btn:hover { + background: rgb(244 114 182 / 0.15); } -.equalizer-scale { +.speaker-measure-btn:disabled { + opacity: 0.5; + pointer-events: none; +} + +.speaker-eq-actions-row { display: flex; - justify-content: space-between; - padding-top: var(--spacing-sm); - border-top: 1px solid var(--border); + align-items: center; + gap: var(--spacing-sm); + flex-wrap: wrap; +} + +.speaker-all-btn { + background: var(--surface-2) !important; + color: var(--foreground) !important; + border: 1px solid var(--border); + font-size: 0.75rem; + padding: 0.35rem 0.75rem; + flex: 0 0 auto; + width: auto; +} + +.speaker-all-btn:hover { + background: var(--surface-3) !important; + border-color: var(--primary); +} + +/* Speaker Saved Profiles */ +.speaker-saved-section { margin-top: var(--spacing-sm); } -.equalizer-scale span { - font-size: 0.7rem; - color: var(--muted-foreground); - opacity: 0.7; -} - -/* Responsive adjustments */ +/* --- Responsive --- */ @media (max-width: 768px) { .equalizer-container { padding: var(--spacing-md); } - .equalizer-bands { - gap: 2px; - overflow-x: auto; - padding-bottom: var(--spacing-sm); - -webkit-overflow-scrolling: touch; + .autoeq-graph-wrapper { + height: 220px; } - .eq-band { - min-width: 36px; + .autoeq-controls-row { + grid-template-columns: 1fr 1fr 1fr; } - .eq-slider { - height: 100px; + .autoeq-database-list { + max-height: 300px; + } +} + +@media (max-width: 900px) { + .autoeq-graph-db-row { + flex-direction: column; } - .eq-slider::-webkit-slider-thumb { - width: 16px; - height: 16px; - margin-left: -5px; - } - - .eq-slider::-moz-range-thumb { - width: 16px; - height: 16px; - } - - .eq-freq { - font-size: 0.55rem; - } - - .eq-value { - font-size: 0.6rem; - min-width: 24px; + .autoeq-graph-db-row > .autoeq-database-section { + width: auto; } } @media (max-width: 480px) { - .equalizer-preset-row { + .autoeq-graph-wrapper { + height: 180px; + } + + .autoeq-graph-header { flex-direction: column; - align-items: stretch; + align-items: flex-start; } - .equalizer-preset-row select { - max-width: none; + .autoeq-saved-header { + flex-direction: column; + align-items: flex-start; } - .equalizer-preset-row label { - margin-bottom: -0.5rem; + .autoeq-saved-header-right { + width: 100%; } - .eq-slider { - height: 80px; - } - - .eq-band { - min-width: 30px; + .autoeq-profile-name-input { + flex: 1; + width: auto; } } @@ -8669,6 +9349,7 @@ body:has(#side-panel.active) #close-fullscreen-cover-btn { gap: 0.25rem; } +/* stylelint-disable-next-line no-descending-specificity */ .theme-editor-toolbar select { height: 24px; padding: 0 4px; From 77f9e10fdc55a47a5bebf766e521d250813e00eb Mon Sep 17 00:00:00 2001 From: tryptz <216453278+tryptz@users.noreply.github.com> Date: Wed, 1 Apr 2026 20:57:22 +0000 Subject: [PATCH 06/51] style: auto-fix linting issues --- js/autoeq-data.js | 14 +++--- js/autoeq-engine.js | 92 ++++++++++++++++++++++------------ js/autoeq-importer.js | 112 ++++++++++++++++++++++++++++++++++-------- styles.css | 5 ++ 4 files changed, 164 insertions(+), 59 deletions(-) diff --git a/js/autoeq-data.js b/js/autoeq-data.js index 784fbfb..e9015c7 100644 --- a/js/autoeq-data.js +++ b/js/autoeq-data.js @@ -2,7 +2,6 @@ // Target Curves & Data Parser - Ported from Seap Engine // Contains target frequency response curves and raw data parser - // Raw content synchronized with features/autoeq/Targets/ directory const RAW_HARMAN_OE_2018 = ` @@ -2713,7 +2712,6 @@ const RAW_HIFI_ENDGAME_2026_MKII = ` 20000.00 72.378 `; - const RAW_PEQDB_ULTRA = ` 20.00 83.153 20.36 83.149 @@ -3487,7 +3485,6 @@ const RAW_PEQDB_DIAMOND_BETA = ` 20000.00 72.028 `; - const RAW_SEAP = ` 20.00 82.982 20.36 82.975 @@ -4301,7 +4298,6 @@ const RAW_FLAT_LINE = ` 20000.00 75.000 `; - // --- PARSER --- /** * Parse raw frequency/gain text data into [{freq, gain}] arrays @@ -4331,15 +4327,17 @@ function parseRawData(raw) { const hasHeader = /[a-zA-Z]/.test(firstLine); if (hasHeader) { - const headers = firstLine.split(delimiter).map(h => h.trim().toLowerCase().replace(/['"]+/g, '')); - const fIdx = headers.findIndex(h => h.includes('freq') || h === 'f'); + const headers = firstLine.split(delimiter).map((h) => h.trim().toLowerCase().replace(/['"]+/g, '')); + const fIdx = headers.findIndex((h) => h.includes('freq') || h === 'f'); if (fIdx > -1) freqIdx = fIdx; - const rIdx = headers.findIndex(h => h === 'raw'); + const rIdx = headers.findIndex((h) => h === 'raw'); if (rIdx > -1) { gainIdx = rIdx; } else { - const splIdx = headers.findIndex(h => h.includes('spl') || h.includes('gain') || h.includes('db') || h.includes('mag')); + const splIdx = headers.findIndex( + (h) => h.includes('spl') || h.includes('gain') || h.includes('db') || h.includes('mag') + ); if (splIdx > -1 && splIdx !== freqIdx) gainIdx = splIdx; } } diff --git a/js/autoeq-engine.js b/js/autoeq-engine.js index 6a9fa7f..d7b7a5c 100644 --- a/js/autoeq-engine.js +++ b/js/autoeq-engine.js @@ -21,42 +21,55 @@ const DB_DIVISOR = 40; function calculateBiquadResponse(f, band, sr = DEFAULT_SR) { if (!band.enabled) return 0; if (!band.type || band.type.length === 0) return 0; - const w = 2 * PI * band.freq / sr; - const p = 2 * PI * f / sr; + const w = (2 * PI * band.freq) / sr; + const p = (2 * PI * f) / sr; const s = Math.sin(w) / (2 * band.q); const A = Math.pow(DB_BASE, band.gain / DB_DIVISOR); const c = Math.cos(w); - let b0 = 0, b1 = 0, b2 = 0, a0 = 0, a1 = 0, a2 = 0; + let b0 = 0, + b1 = 0, + b2 = 0, + a0 = 0, + a1 = 0, + a2 = 0; const t = band.type[0]; if (t === 'p') { - b0 = 1 + s * A; b1 = -2 * c; b2 = 1 - s * A; - a0 = 1 + s / A; a1 = -2 * c; a2 = 1 - s / A; + b0 = 1 + s * A; + b1 = -2 * c; + b2 = 1 - s * A; + a0 = 1 + s / A; + a1 = -2 * c; + a2 = 1 - s / A; } else if (t === 'l') { const sq = 2 * Math.sqrt(A) * s; - b0 = A * ((A + 1) - (A - 1) * c + sq); - b1 = 2 * A * ((A - 1) - (A + 1) * c); - b2 = A * ((A + 1) - (A - 1) * c - sq); - a0 = (A + 1) + (A - 1) * c + sq; - a1 = -2 * ((A - 1) + (A + 1) * c); - a2 = (A + 1) + (A - 1) * c - sq; + b0 = A * (A + 1 - (A - 1) * c + sq); + b1 = 2 * A * (A - 1 - (A + 1) * c); + b2 = A * (A + 1 - (A - 1) * c - sq); + a0 = A + 1 + (A - 1) * c + sq; + a1 = -2 * (A - 1 + (A + 1) * c); + a2 = A + 1 + (A - 1) * c - sq; } else if (t === 'h') { const sq = 2 * Math.sqrt(A) * s; - b0 = A * ((A + 1) + (A - 1) * c + sq); - b1 = -2 * A * ((A - 1) + (A + 1) * c); - b2 = A * ((A + 1) + (A - 1) * c - sq); - a0 = (A + 1) - (A - 1) * c + sq; - a1 = 2 * ((A - 1) - (A + 1) * c); - a2 = (A + 1) - (A - 1) * c - sq; + b0 = A * (A + 1 + (A - 1) * c + sq); + b1 = -2 * A * (A - 1 + (A + 1) * c); + b2 = A * (A + 1 + (A - 1) * c - sq); + a0 = A + 1 - (A - 1) * c + sq; + a1 = 2 * (A - 1 - (A + 1) * c); + a2 = A + 1 - (A - 1) * c - sq; } else { return 0; } const _a0 = 1 / a0; - const b0n = b0 * _a0, b1n = b1 * _a0, b2n = b2 * _a0; - const a1n = a1 * _a0, a2n = a2 * _a0; - const cp = Math.cos(p), c2p = Math.cos(2 * p); + const b0n = b0 * _a0, + b1n = b1 * _a0, + b2n = b2 * _a0; + const a1n = a1 * _a0, + a2n = a2 * _a0; + const cp = Math.cos(p), + c2p = Math.cos(2 * p); const n = b0n * b0n + b1n * b1n + b2n * b2n + 2 * (b0n * b1n + b1n * b2n) * cp + 2 * b0n * b2n * c2p; const d = 1 + a1n * a1n + a2n * a2n + 2 * (a1n + a1n * a2n) * cp + 2 * a2n * c2p; return 10 * Math.log10(n / d); @@ -74,7 +87,10 @@ function interpolate(freq, data) { if (freq >= data[data.length - 1].freq) return data[data.length - 1].gain; for (let i = 0; i < data.length - 1; i++) { if (freq >= data[i].freq && freq <= data[i + 1].freq) { - return data[i].gain + (freq - data[i].freq) / (data[i + 1].freq - data[i].freq) * (data[i + 1].gain - data[i].gain); + return ( + data[i].gain + + ((freq - data[i].freq) / (data[i + 1].freq - data[i].freq)) * (data[i + 1].gain - data[i].gain) + ); } } return 0; @@ -86,7 +102,8 @@ function interpolate(freq, data) { * @returns {number} Average gain in midrange */ function getNormalizationOffset(data) { - let sum = 0, count = 0; + let sum = 0, + count = 0; for (const p of data) { if (p.freq >= 250 && p.freq <= 2500) { sum += p.gain; @@ -108,18 +125,29 @@ function getNormalizationOffset(data) { * @param {number} maxQ - Maximum Q factor * @returns {Array<{id: number, type: string, freq: number, gain: number, q: number, enabled: boolean}>} */ -function runAutoEqAlgorithm(measurement, target, bandCount, maxFreq = 16000, minFreq = 20, maxQ = 5.0, sampleRate = DEFAULT_SR) { +function runAutoEqAlgorithm( + measurement, + target, + bandCount, + maxFreq = 16000, + minFreq = 20, + maxQ = 5.0, + sampleRate = DEFAULT_SR +) { if (minFreq > maxFreq) return []; const off = getNormalizationOffset(target) - getNormalizationOffset(measurement); - let err = measurement.map(p => ({ freq: p.freq, gain: (p.gain + off) - interpolate(p.freq, target) })); + let err = measurement.map((p) => ({ freq: p.freq, gain: p.gain + off - interpolate(p.freq, target) })); - const hasInRangePoints = err.some(p => p.freq >= minFreq && p.freq <= maxFreq); + const hasInRangePoints = err.some((p) => p.freq >= minFreq && p.freq <= maxFreq); if (!hasInRangePoints) return []; const out = []; for (let i = 0; i < bandCount; i++) { - let maxDev = 0, maxWeightedDev = 0, peakFreq = 1000, peakIdx = 0; + let maxDev = 0, + maxWeightedDev = 0, + peakFreq = 1000, + peakIdx = 0; // Scan for maximum weighted error for (let j = 0; j < err.length; j++) { @@ -158,8 +186,10 @@ function runAutoEqAlgorithm(measurement, target, bandCount, maxFreq = 16000, min if (Math.abs(gain) < 0.2) break; // Q factor calculation from error bandwidth (half-gain points) - let upperFreq = peakFreq, lowerFreq = peakFreq; - let foundLower = false, foundUpper = false; + let upperFreq = peakFreq, + lowerFreq = peakFreq; + let foundLower = false, + foundUpper = false; const thresholdError = maxDev / 2; for (let k = peakIdx; k >= 0; k--) { if (Math.abs(err[k].gain) < Math.abs(thresholdError)) { @@ -179,9 +209,9 @@ function runAutoEqAlgorithm(measurement, target, bandCount, maxFreq = 16000, min // If half-gain boundary not found on one side, mirror the other side // to avoid degenerate bandwidth = 0 producing extremely narrow filters if (!foundLower && foundUpper) { - lowerFreq = peakFreq * peakFreq / upperFreq; + lowerFreq = (peakFreq * peakFreq) / upperFreq; } else if (!foundUpper && foundLower) { - upperFreq = peakFreq * peakFreq / lowerFreq; + upperFreq = (peakFreq * peakFreq) / lowerFreq; } else if (!foundLower && !foundUpper) { // Neither boundary found — use 1 octave default lowerFreq = peakFreq / Math.SQRT2; @@ -212,7 +242,7 @@ function runAutoEqAlgorithm(measurement, target, bandCount, maxFreq = 16000, min out.push(newBand); // Update error curve by applying the new band's response - err = err.map(p => ({ ...p, gain: p.gain + calculateBiquadResponse(p.freq, newBand, sampleRate) })); + err = err.map((p) => ({ ...p, gain: p.gain + calculateBiquadResponse(p.freq, newBand, sampleRate) })); } return out.sort((a, b) => a.freq - b.freq).map((b, i) => ({ ...b, id: i })); diff --git a/js/autoeq-importer.js b/js/autoeq-importer.js index 0cd1cf0..5a5d798 100644 --- a/js/autoeq-importer.js +++ b/js/autoeq-importer.js @@ -12,23 +12,83 @@ const CACHE_EXPIRY = 24 * 60 * 60 * 1000; // 24 hours // 5 most popular headphones — pre-loaded as defaults and shown in the headphone select // All measured on Rtings B&K 5128 rig for consistency const POPULAR_HEADPHONES = [ - { name: 'Sony WH-1000XM5 (Rtings)', type: 'over-ear', path: 'Rtings/Bruel & Kjaer 5128 over-ear/Sony WH-1000XM5', fileName: 'Sony WH-1000XM5.csv' }, - { name: 'Apple AirPods Pro2 (Rtings)', type: 'in-ear', path: 'Rtings/Bruel & Kjaer 5128 in-ear/Apple AirPods Pro2', fileName: 'Apple AirPods Pro2.csv' }, - { name: 'Sony WF-1000XM5 (Rtings)', type: 'in-ear', path: 'Rtings/Bruel & Kjaer 5128 in-ear/Sony WF-1000XM5', fileName: 'Sony WF-1000XM5.csv' }, - { name: 'Samsung Galaxy Buds3 Pro (Rtings)', type: 'in-ear', path: 'Rtings/Bruel & Kjaer 5128 in-ear/Samsung Galaxy Buds3 Pro', fileName: 'Samsung Galaxy Buds3 Pro.csv' }, - { name: 'Sennheiser HD 600 (Rtings)', type: 'over-ear', path: 'Rtings/Bruel & Kjaer 5128 over-ear/Sennheiser HD 600', fileName: 'Sennheiser HD 600.csv' }, + { + name: 'Sony WH-1000XM5 (Rtings)', + type: 'over-ear', + path: 'Rtings/Bruel & Kjaer 5128 over-ear/Sony WH-1000XM5', + fileName: 'Sony WH-1000XM5.csv', + }, + { + name: 'Apple AirPods Pro2 (Rtings)', + type: 'in-ear', + path: 'Rtings/Bruel & Kjaer 5128 in-ear/Apple AirPods Pro2', + fileName: 'Apple AirPods Pro2.csv', + }, + { + name: 'Sony WF-1000XM5 (Rtings)', + type: 'in-ear', + path: 'Rtings/Bruel & Kjaer 5128 in-ear/Sony WF-1000XM5', + fileName: 'Sony WF-1000XM5.csv', + }, + { + name: 'Samsung Galaxy Buds3 Pro (Rtings)', + type: 'in-ear', + path: 'Rtings/Bruel & Kjaer 5128 in-ear/Samsung Galaxy Buds3 Pro', + fileName: 'Samsung Galaxy Buds3 Pro.csv', + }, + { + name: 'Sennheiser HD 600 (Rtings)', + type: 'over-ear', + path: 'Rtings/Bruel & Kjaer 5128 over-ear/Sennheiser HD 600', + fileName: 'Sennheiser HD 600.csv', + }, ]; // Static fallback list in case GitHub API fails — popular picks + additional well-known models const FALLBACK_INDEX = [ ...POPULAR_HEADPHONES, - { name: 'Sennheiser HD 600 (Filk)', type: 'over-ear', path: 'Filk/over-ear/Sennheiser HD 600', fileName: 'Sennheiser HD 600.csv' }, - { name: 'Sennheiser HD 600 (Innerfidelity)', type: 'over-ear', path: 'Innerfidelity/over-ear/Sennheiser HD 600', fileName: 'Sennheiser HD 600.csv' }, - { name: 'Samsung Galaxy Buds2 Pro (Rtings)', type: 'in-ear', path: 'Rtings/Bruel & Kjaer 5128 in-ear/Samsung Galaxy Buds2 Pro', fileName: 'Samsung Galaxy Buds2 Pro.csv' }, - { name: 'Sony WF-1000XM5 (Kazi)', type: 'in-ear', path: 'Kazi/in-ear/Sony WF-1000XM5', fileName: 'Sony WF-1000XM5.csv' }, - { name: 'Samsung Galaxy Buds3 Pro (DHRME)', type: 'in-ear', path: 'DHRME/in-ear/Samsung Galaxy Buds3 Pro', fileName: 'Samsung Galaxy Buds3 Pro.csv' }, - { name: 'Apple AirPods Pro (Super Review)', type: 'in-ear', path: 'Super Review/in-ear/Apple AirPods Pro', fileName: 'Apple AirPods Pro.csv' }, - { name: 'Sennheiser HD 600 (2020) (Kuulokenurkka)', type: 'over-ear', path: 'Kuulokenurkka/over-ear/Sennheiser HD 600 (2020)', fileName: 'Sennheiser HD 600 (2020).csv' }, + { + name: 'Sennheiser HD 600 (Filk)', + type: 'over-ear', + path: 'Filk/over-ear/Sennheiser HD 600', + fileName: 'Sennheiser HD 600.csv', + }, + { + name: 'Sennheiser HD 600 (Innerfidelity)', + type: 'over-ear', + path: 'Innerfidelity/over-ear/Sennheiser HD 600', + fileName: 'Sennheiser HD 600.csv', + }, + { + name: 'Samsung Galaxy Buds2 Pro (Rtings)', + type: 'in-ear', + path: 'Rtings/Bruel & Kjaer 5128 in-ear/Samsung Galaxy Buds2 Pro', + fileName: 'Samsung Galaxy Buds2 Pro.csv', + }, + { + name: 'Sony WF-1000XM5 (Kazi)', + type: 'in-ear', + path: 'Kazi/in-ear/Sony WF-1000XM5', + fileName: 'Sony WF-1000XM5.csv', + }, + { + name: 'Samsung Galaxy Buds3 Pro (DHRME)', + type: 'in-ear', + path: 'DHRME/in-ear/Samsung Galaxy Buds3 Pro', + fileName: 'Samsung Galaxy Buds3 Pro.csv', + }, + { + name: 'Apple AirPods Pro (Super Review)', + type: 'in-ear', + path: 'Super Review/in-ear/Apple AirPods Pro', + fileName: 'Apple AirPods Pro.csv', + }, + { + name: 'Sennheiser HD 600 (2020) (Kuulokenurkka)', + type: 'over-ear', + path: 'Kuulokenurkka/over-ear/Sennheiser HD 600 (2020)', + fileName: 'Sennheiser HD 600 (2020).csv', + }, ]; /** @@ -39,7 +99,11 @@ const FALLBACK_INDEX = [ */ async function fetchAutoEqIndex() { // Migrate: remove old localStorage cache to free quota - try { localStorage.removeItem(OLD_LS_CACHE_KEY); } catch { /* ignore */ } + try { + localStorage.removeItem(OLD_LS_CACHE_KEY); + } catch { + /* ignore */ + } // 1. Try loading from IndexedDB cache try { @@ -61,7 +125,9 @@ async function fetchAutoEqIndex() { const timeoutId = setTimeout(() => controller.abort(), 8000); let response; try { - response = await fetch('https://api.github.com/repos/jaakkopasanen/AutoEq/git/trees/master?recursive=1', { signal: controller.signal }); + response = await fetch('https://api.github.com/repos/jaakkopasanen/AutoEq/git/trees/master?recursive=1', { + signal: controller.signal, + }); } finally { clearTimeout(timeoutId); } @@ -73,7 +139,9 @@ async function fetchAutoEqIndex() { console.warn('[AutoEQ] GitHub API limit reached. Using stale cache.'); return cached.data; } - } catch { /* ignore */ } + } catch { + /* ignore */ + } console.warn('[AutoEQ] GitHub API error. Using fallback.'); return FALLBACK_INDEX; } @@ -92,13 +160,15 @@ async function fetchAutoEqIndex() { const fileNameLower = fileName.toLowerCase(); // Skip non-measurement files (EQ presets, not raw frequency response) - if (fileNameLower.includes('parametriceq') || + if ( + fileNameLower.includes('parametriceq') || fileNameLower.includes('fixedbandeq') || fileNameLower.includes('graphiceq') || fileNameLower.includes('convolution') || fileNameLower.includes('fixed band eq') || fileNameLower.includes('parametric eq') || - fileNameLower.includes('graphic eq')) { + fileNameLower.includes('graphic eq') + ) { continue; } @@ -144,7 +214,9 @@ async function fetchAutoEqIndex() { try { const cached = await db.getSetting(CACHE_KEY); if (cached?.data) return cached.data; - } catch { /* ignore */ } + } catch { + /* ignore */ + } } else { console.error('[AutoEQ] Failed to fetch index:', err); } @@ -205,12 +277,12 @@ function searchHeadphones(query, entries, typeFilter = 'all', limit = 100) { let filtered = entries; if (typeFilter !== 'all') { - filtered = filtered.filter(e => e.type === typeFilter); + filtered = filtered.filter((e) => e.type === typeFilter); } if (query && query.trim()) { const lower = query.toLowerCase().trim(); - filtered = filtered.filter(e => e.name.toLowerCase().includes(lower)); + filtered = filtered.filter((e) => e.name.toLowerCase().includes(lower)); } return filtered.slice(0, limit); diff --git a/styles.css b/styles.css index 2150620..81caf61 100644 --- a/styles.css +++ b/styles.css @@ -7910,6 +7910,7 @@ body:has(#side-panel.active) #close-fullscreen-cover-btn { background: rgb(255 255 255 / 0.5); border: 1px dashed rgb(255 255 255 / 0.3); } + .legend-corrected .legend-dot { background: #f472b6; } @@ -8109,6 +8110,7 @@ body:has(#side-panel.active) #close-fullscreen-cover-btn { .autoeq-status.error { color: var(--destructive); } + .autoeq-status.success { color: var(--primary); } @@ -8941,12 +8943,15 @@ body:has(#side-panel.active) #close-fullscreen-cover-btn { .speaker-eq-slider-value.bass { color: #22d3ee; } + .speaker-eq-slider-value.room { color: #f59e0b; } + .speaker-eq-section .autoeq-control-label.bass { color: #22d3ee; } + .speaker-eq-section .autoeq-control-label.room { color: #f59e0b; } From 782e98061bd2318bc92cff5c374df063070baf49 Mon Sep 17 00:00:00 2001 From: tryptz Date: Wed, 1 Apr 2026 21:34:14 -0400 Subject: [PATCH 07/51] fix: address all CodeRabbit review findings from PR #477 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Engine & algorithm: - Use default shelf Q (1/√2) in calculateBiquadResponse for shelf filters - Compute normalization offset on measurement frequency grid to avoid bias - Try stale cache for all fetch errors in autoeq-importer, not just timeouts Audio pipeline: - Pass postProcessingQuality (preserves Dolby Atmos override) in api.js - Persist custom band frequencies in equalizerSettings storage - Restore custom frequencies on _loadSettings instead of regenerating defaults - Export clamped preamp value in applyAutoEQBands text output - Propagate filter type and Q values through equalizer import chain - Update freqRange after importing custom filter frequencies - Remove return in finally block that hid LOSSLESS fallback failures Data consistency: - Normalize artist IDs with String() in blockArtist/unblockArtist Lint & code quality: - Annotate empty catch blocks (Atmos codec probes) - Remove unused catch parameters Accessibility: - Add aria-label and for attributes to all AutoEQ form controls - Add role="status" aria-live="polite" to feedback spans - Update filter type documentation to reflect shelf support - Hide parametric-only sections by default to match active tab UI: - Move AutoEq button directly under graph - Hide shared button in Parametric/Speaker modes - Replace hardcoded white legend dot with theme-adaptive color-mix - Add pointer-events:none and focus-within to profile delete button --- index.html | 34 +++++++++++++++++++--------------- js/api.js | 6 ++++-- js/audio-context.js | 8 +++++--- js/autoeq-engine.js | 41 ++++++++++++++++++++++++++++++----------- js/autoeq-importer.js | 12 ++++++------ js/equalizer.js | 30 +++++++++++++++++++++++++++++- js/player.js | 14 +++++++++----- js/settings.js | 6 ++++-- js/storage.js | 39 +++++++++++++++++++++++++++++++++++++-- styles.css | 9 ++++++--- 10 files changed, 149 insertions(+), 50 deletions(-) diff --git a/index.html b/index.html index 4b8e51a..2ee1c42 100644 --- a/index.html +++ b/index.html @@ -4046,8 +4046,8 @@

Parametric EQ — Manual Control

  1. - Each band is a peaking filter with frequency, gain, and Q - (width). + Each band supports peaking, low-shelf, and high-shelf filter + types with frequency, gain, and Q (width).
  2. Drag nodes on the graph to adjust frequency and gain @@ -4162,6 +4162,7 @@
+
@@ -4180,6 +4181,7 @@ id="autoeq-headphone-search" placeholder="Search model (e.g. HD 600)..." autocomplete="off" + aria-label="Search headphone model" />
@@ -4194,11 +4196,10 @@
-
- +
-
- +
@@ -4313,6 +4314,7 @@ id="autoeq-profile-name" class="autoeq-profile-name-input" placeholder="Profile name..." + aria-label="AutoEQ profile name" maxlength="50" /> - +
@@ -4542,6 +4544,7 @@ id="speaker-profile-name" class="autoeq-profile-name-input" placeholder="Profile name..." + aria-label="Speaker EQ profile name" maxlength="50" /> - +
Date: Thu, 2 Apr 2026 18:28:46 +0000 Subject: [PATCH 21/51] fix: l opens lyrics --- js/api.js | 2 +- js/app.js | 2 +- js/autoeq-engine.js | 2 +- js/autoeq-importer.js | 4 ++-- js/events.js | 2 +- js/player.js | 2 +- js/settings.js | 10 +++++----- 7 files changed, 12 insertions(+), 12 deletions(-) diff --git a/js/api.js b/js/api.js index 7099f59..eafcb65 100644 --- a/js/api.js +++ b/js/api.js @@ -1501,7 +1501,7 @@ export class LosslessAPI { ); } } catch { - // Atmos codec probe — intentionally swallowed; canPlayAtmos stays false + // Atmos codec probe - intentionally swallowed; canPlayAtmos stays false } const paramsArray = []; diff --git a/js/app.js b/js/app.js index 94abf54..074929e 100644 --- a/js/app.js +++ b/js/app.js @@ -256,7 +256,7 @@ function initializeKeyboardShortcuts(player, _audioPlayer) { }, lyrics: () => { trackKeyboardShortcut('L'); - document.querySelector('.now-playing-bar .cover')?.click(); + document.getElementById('toggle-lyrics-btn')?.click(); }, search: () => { trackKeyboardShortcut('/'); diff --git a/js/autoeq-engine.js b/js/autoeq-engine.js index ea28007..2989313 100644 --- a/js/autoeq-engine.js +++ b/js/autoeq-engine.js @@ -232,7 +232,7 @@ function runAutoEqAlgorithm( } else if (!foundUpper && foundLower) { upperFreq = (peakFreq * peakFreq) / lowerFreq; } else if (!foundLower && !foundUpper) { - // Neither boundary found — use 1 octave default + // Neither boundary found - use 1 octave default lowerFreq = peakFreq / Math.SQRT2; upperFreq = peakFreq * Math.SQRT2; } diff --git a/js/autoeq-importer.js b/js/autoeq-importer.js index 9b4e325..df26bf2 100644 --- a/js/autoeq-importer.js +++ b/js/autoeq-importer.js @@ -9,7 +9,7 @@ const CACHE_KEY = 'autoeq_index_v4'; const OLD_LS_CACHE_KEY = 'monochrome_autoeq_index_v4'; const CACHE_EXPIRY = 24 * 60 * 60 * 1000; // 24 hours -// 5 most popular headphones — pre-loaded as defaults and shown in the headphone select +// 5 most popular headphones - pre-loaded as defaults and shown in the headphone select // All measured on Rtings B&K 5128 rig for consistency const POPULAR_HEADPHONES = [ { @@ -44,7 +44,7 @@ const POPULAR_HEADPHONES = [ }, ]; -// Static fallback list in case GitHub API fails — popular picks + additional well-known models +// Static fallback list in case GitHub API fails - popular picks + additional well-known models const FALLBACK_INDEX = [ ...POPULAR_HEADPHONES, { diff --git a/js/events.js b/js/events.js index 7a98571..46a7a9f 100644 --- a/js/events.js +++ b/js/events.js @@ -655,7 +655,7 @@ export function initializePlayerEvents(player, audioPlayer, scrobbler, ui) { progressBar.style.maskImage = ''; try { - const streamUrl = await player.api.getStreamUrl(player.currentTrack.id, 'LOW'); + const { url: streamUrl } = await player.api.getStreamUrl(player.currentTrack.id, 'LOW'); const waveformData = await waveformGenerator.getWaveform(streamUrl, player.currentTrack.id); if (waveformData && currentTrackIdForWaveform === player.currentTrack.id) { diff --git a/js/player.js b/js/player.js index 28272c5..02cab2a 100644 --- a/js/player.js +++ b/js/player.js @@ -1035,7 +1035,7 @@ export class Player { await this.playTrackFromQueue(startTime, recursiveCount, true); return; } catch { - // LOSSLESS fallback also failed — fall through to error handling below + // LOSSLESS fallback also failed - fall through to error handling below } finally { this.quality = originalQuality; this.isFallbackRetry = false; diff --git a/js/settings.js b/js/settings.js index d6c6bc3..faa1df0 100644 --- a/js/settings.js +++ b/js/settings.js @@ -1232,7 +1232,7 @@ export async function initializeSettings(scrobbler, player, api, ui) { } // ======================================== - // Precision AutoEQ — Redesigned Equalizer + // Precision AutoEQ - Redesigned Equalizer // ======================================== const eqToggle = document.getElementById('equalizer-enabled-toggle'); const eqContainer = document.getElementById('equalizer-container'); @@ -1943,7 +1943,7 @@ export async function initializeSettings(scrobbler, player, api, ui) { const coords = getCanvasCoords(e); const isParam = currentMode === 'parametric'; - // getActiveBands() returns null in autoeq mode before first run — init to empty array + // getActiveBands() returns null in autoeq mode before first run - init to empty array let bands = getActiveBands(); if (!bands) { if (currentMode === 'autoeq') { @@ -2288,7 +2288,7 @@ export async function initializeSettings(scrobbler, player, api, ui) { const dpr = window.devicePixelRatio || 1; const rect = canvas.getBoundingClientRect(); if (rect.width === 0) { - // Canvas not laid out yet — retry when it becomes visible + // Canvas not laid out yet - retry when it becomes visible const obs = new IntersectionObserver((entries, observer) => { if (entries[0].isIntersecting) { observer.disconnect(); @@ -3738,7 +3738,7 @@ export async function initializeSettings(scrobbler, player, api, ui) { }); } - // Measure All — plays pink noise once, assigns averaged measurement to all active channels + // Measure All - plays pink noise once, assigns averaged measurement to all active channels const speakerMeasureAllBtn = document.getElementById('speaker-measure-all-btn'); if (speakerMeasureAllBtn) { speakerMeasureAllBtn.addEventListener('click', async () => { @@ -3882,7 +3882,7 @@ export async function initializeSettings(scrobbler, player, api, ui) { }); } - // AutoEQ All — runs AutoEQ on every active channel that has a measurement + // AutoEQ All - runs AutoEQ on every active channel that has a measurement const speakerAutoEqAllBtn = document.getElementById('speaker-autoeq-all-btn'); if (speakerAutoEqAllBtn) { speakerAutoEqAllBtn.addEventListener('click', () => { From a6082a5288c02e1f43765883cddca8f48248646c Mon Sep 17 00:00:00 2001 From: edideaur Date: Thu, 2 Apr 2026 18:32:48 +0000 Subject: [PATCH 22/51] fix:blocking --- js/events.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/events.js b/js/events.js index 46a7a9f..348d5b5 100644 --- a/js/events.js +++ b/js/events.js @@ -1336,7 +1336,7 @@ export async function handleTrackAction( // Individual Track Actions // Check if track/artist is blocked const { contentBlockingSettings } = await import('./storage.js'); - if (type === 'track' && contentBlockingSettings.shouldHideTrack(item)) { + if (type === 'track' && action !== 'block-track' && contentBlockingSettings.shouldHideTrack(item)) { showNotification('This track is blocked'); return; } From 2183610df15d7f07ff6a7aabb111d83d6ea478d4 Mon Sep 17 00:00:00 2001 From: edideaur Date: Thu, 2 Apr 2026 18:44:13 +0000 Subject: [PATCH 23/51] further:allow right clicking blocked content for easier unblocking --- index.html | 16 ++++++++-------- js/events.js | 14 ++++++-------- js/storage.js | 12 ++++++------ styles.css | 12 ------------ 4 files changed, 20 insertions(+), 34 deletions(-) diff --git a/index.html b/index.html index 0f895a8..8367213 100644 --- a/index.html +++ b/index.html @@ -4060,14 +4060,14 @@
-

AutoEQ — Headphone Correction

+

AutoEQ - Headphone Correction

  1. Select your headphone from the dropdown or search the database below.
  2. - Pick a target curve — Harman is the most popular. You + Pick a target curve - Harman is the most popular. You can also import a custom target.
  3. @@ -4075,7 +4075,7 @@ default).
  4. - Click AutoEQ — the algorithm generates parametric + Click AutoEQ - the algorithm generates parametric filters that shape your headphone's response toward the target.
  5. @@ -4103,7 +4103,7 @@ id="eq-howto-parametric" style="display: none" > -

    Parametric EQ — Manual Control

    +

    Parametric EQ - Manual Control

    1. Each band supports peaking, low-shelf, and high-shelf filter @@ -4141,7 +4141,7 @@
+

{ + if (!player.currentTrack) return; + const track = player.currentTrack; + if (track.isLocal) return; + + const target = e.target.closest('.cover, .title, .album, .artist'); + if (!target) return; + + e.preventDefault(); + e.stopPropagation(); + + if (contextMenu._originalHTML) { + contextMenu.innerHTML = contextMenu._originalHTML; + contextMenu._originalHTML = null; + } + + contextTrack = track; + contextMenu._contextTrack = track; + contextMenu._contextType = track.type || 'track'; + contextMenu._selectedTracks = []; + + const unavailableActions = ['play-next', 'add-to-queue', 'download', 'track-mix']; + contextMenu.querySelectorAll('[data-action]').forEach((btn) => { + if (unavailableActions.includes(btn.dataset.action)) { + btn.style.display = track.isUnavailable ? 'none' : 'block'; + } + }); + + await updateContextMenuLikeState(contextMenu, track); + positionMenu(contextMenu, e.clientX, e.clientY); + }); + document.addEventListener('click', (e) => { if (contextMenu.style.display === 'block') { if (contextMenu._originalHTML) { From 8f30983296b0cd9d56a29f731b915865ef8d449b Mon Sep 17 00:00:00 2001 From: edideaur <182119792+edideaur@users.noreply.github.com> Date: Thu, 2 Apr 2026 18:53:06 +0000 Subject: [PATCH 26/51] style: auto-fix linting issues --- index.html | 45 +++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 41 insertions(+), 4 deletions(-) diff --git a/index.html b/index.html index 697a1f0..f08edb7 100644 --- a/index.html +++ b/index.html @@ -5208,17 +5208,54 @@

From 51e5e1973fa5cf1d24dc46ed2feb00f8b1f5fc54 Mon Sep 17 00:00:00 2001 From: Daniel <790119+DanTheMan827@users.noreply.github.com> Date: Thu, 2 Apr 2026 15:56:18 -0500 Subject: [PATCH 27/51] fix: update taglib-ts --- bun.lock | 4 ++-- js/api.test.ts | 6 +++--- package.json | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/bun.lock b/bun.lock index a66978e..c0e23a9 100644 --- a/bun.lock +++ b/bun.lock @@ -10,7 +10,6 @@ "@capacitor/core": "^8.2.0", "@capacitor/haptics": "^8.0.1", "@capacitor/ios": "^8.2.0", - "@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,6 +26,7 @@ "events": "^3.3.0", "fuse.js": "^7.1.0", "hls.js": "^1.6.15", + "@dantheman827/taglib-ts": "https://github.com/DanTheMan827/taglib-ts/archive/ebd0e369b706c127a280d4ad631977f8d12ff88f.tar.gz", "jose": "^6.2.0", "lucide-static": "^0.577.0", "mime": "^4.1.0", @@ -292,7 +292,7 @@ "@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=="], + "@dantheman827/taglib-ts": ["@dantheman827/taglib-ts@https://github.com/DanTheMan827/taglib-ts/archive/ebd0e369b706c127a280d4ad631977f8d12ff88f.tar.gz", {}, "sha512-QPl5eWhFqP716VKIX5t7x39eRswRHG+5RpQ9wW6cNbDh1QSa/gWpPyvwP55O+/qCV7VjLepZI1/XFoxMO8jK7w=="], "@dual-bundle/import-meta-resolve": ["@dual-bundle/import-meta-resolve@4.2.1", "", {}, "sha512-id+7YRUgoUX6CgV0DtuhirQWodeeA7Lf4i2x71JS/vtA5pRb/hIGWlw+G6MeXvsM+MXrz0VAydTGElX1rAfgPg=="], diff --git a/js/api.test.ts b/js/api.test.ts index 2320b5f..6bb12ad 100644 --- a/js/api.test.ts +++ b/js/api.test.ts @@ -430,7 +430,7 @@ suite('Track Downloads', async () => { expect(file.file()).toBeInstanceOf(OggFile); const ogg = file.file() as OggFile; expect(ogg.audioProperties().sampleRate).toBe(44100); - //expect(ogg.audioProperties().bitrate).toBe(320); + expect(ogg.audioProperties().bitrate).toBe(314); break; } @@ -438,7 +438,7 @@ suite('Track Downloads', async () => { expect(file.file()).toBeInstanceOf(OggFile); const ogg = file.file() as OggFile; expect(ogg.audioProperties().sampleRate).toBe(44100); - //expect(ogg.audioProperties().bitrate).toBe(256); + expect(ogg.audioProperties().bitrate).toBe(253); break; } @@ -446,7 +446,7 @@ suite('Track Downloads', async () => { expect(file.file()).toBeInstanceOf(OggFile); const ogg = file.file() as OggFile; expect(ogg.audioProperties().sampleRate).toBe(44100); - //expect(ogg.audioProperties().bitrate).toBe(128); + expect(ogg.audioProperties().bitrate).toBe(130); break; } diff --git a/package.json b/package.json index be101bb..859a6c2 100644 --- a/package.json +++ b/package.json @@ -62,7 +62,7 @@ "@capacitor/core": "^8.2.0", "@capacitor/haptics": "^8.0.1", "@capacitor/ios": "^8.2.0", - "@dantheman827/taglib-ts": "https://github.com/DanTheMan827/taglib-ts/archive/b4238b2627aceb97f58813258046f1259f68cab7.tar.gz", + "@dantheman827/taglib-ts": "https://github.com/DanTheMan827/taglib-ts/archive/ebd0e369b706c127a280d4ad631977f8d12ff88f.tar.gz", "@ffmpeg/core": "^0.12.10", "@ffmpeg/ffmpeg": "^0.12.15", "@ffmpeg/util": "^0.12.2", From 5b727a103ecc810e09921c6f2b12dea4044b14a3 Mon Sep 17 00:00:00 2001 From: Daniel <790119+DanTheMan827@users.noreply.github.com> Date: Thu, 2 Apr 2026 16:23:49 -0500 Subject: [PATCH 28/51] feat(downloads): allow writing multiple artists to metadata This will write each artist separately to the metadata rather than as a single concatenated string. This allows for better library searching if the player supports it. If multiple artists are written to an m4a file, iTunes will only show the first artist. --- index.html | 14 ++++++++++++++ js/ModernSettings.ts | 4 ++++ js/metadata.js | 18 ++++++++++++++---- js/settings.js | 9 +++++++++ js/taglib.types.ts | 3 ++- js/taglib.worker.ts | 14 +++++++++++--- js/utils.js | 14 ++++++++++++-- 7 files changed, 66 insertions(+), 10 deletions(-) diff --git a/index.html b/index.html index f08edb7..776d6f4 100644 --- a/index.html +++ b/index.html @@ -4885,6 +4885,20 @@ +
+
+
+ Write Artists Separately + Write artists separately to metadata. Requires player support. +
+ +
+
diff --git a/js/ModernSettings.ts b/js/ModernSettings.ts index de90cdc..c114181 100644 --- a/js/ModernSettings.ts +++ b/js/ModernSettings.ts @@ -261,6 +261,7 @@ export const modernSettings = new ModernSettings() transformer: String, }, }) + .addProperty('writeArtistsSeparately', false) .finalize() as ModernSettings & { /** The last used directory handle for bulk downloads */ bulkDownloadFolder: FileSystemDirectoryHandle | null; @@ -286,4 +287,7 @@ export const modernSettings = new ModernSettings() /** Filename template for downloads */ filenameTemplate: string; + + /** Whether to write multiple artists to downloaded files */ + writeArtistsSeparately: boolean; }; diff --git a/js/metadata.js b/js/metadata.js index 57fe1c8..b826ffd 100644 --- a/js/metadata.js +++ b/js/metadata.js @@ -1,7 +1,15 @@ -import { getCoverBlob, getTrackTitle, getFullArtistString, getMimeType, getTrackCoverId } from './utils.js'; +import { + getCoverBlob, + getTrackTitle, + getFullArtistString, + getMimeType, + getTrackCoverId, + getFullArtistArray, +} from './utils.js'; import { addMetadataWithTagLib, getMetadataWithTagLib } from './taglib.ts'; import { LyricsManager } from './lyrics.js'; import { Mp4Stik } from './taglib.types.ts'; +import { modernSettings } from './ModernSettings.js'; /** * @typedef {import('./container-classes.ts').Track} Track @@ -35,13 +43,15 @@ export async function addMetadataToAudio(audioBlob, track, api, _quality, prefet /** * @type {TagLibMetadata} */ - const data = {}; + const data = { + writeArtistsSeparately: modernSettings.writeArtistsSeparately, + }; try { data.title = getTrackTitle(track); - data.artist = getFullArtistString(track); + data.artist = getFullArtistArray(track); data.albumTitle = track.album?.title; - data.albumArtist = track.album?.artist?.name || track.artist?.name; + data.albumArtist = track.album?.artist?.name || getFullArtistString(track) || ''; data.trackNumber = track.trackNumber; data.discNumber = track.volumeNumber ?? track.discNumber; data.totalTracks = track.album?.numberOfTracksOnDisc ?? track.album?.numberOfTracks; diff --git a/js/settings.js b/js/settings.js index faa1df0..19951e6 100644 --- a/js/settings.js +++ b/js/settings.js @@ -4568,6 +4568,15 @@ export async function initializeSettings(scrobbler, player, api, ui) { }); } + // Write multiple artists toggle + const writeArtistsSeparatelyToggle = document.getElementById('write-artists-separately-toggle'); + if (writeArtistsSeparatelyToggle) { + writeArtistsSeparatelyToggle.checked = modernSettings.writeArtistsSeparately; + writeArtistsSeparatelyToggle.addEventListener('change', (e) => { + modernSettings.writeArtistsSeparately = e.target.checked; + }); + } + // Download Lyrics Toggle const downloadLyricsToggle = document.getElementById('download-lyrics-toggle'); if (downloadLyricsToggle) { diff --git a/js/taglib.types.ts b/js/taglib.types.ts index 187d29b..45b3b72 100644 --- a/js/taglib.types.ts +++ b/js/taglib.types.ts @@ -16,7 +16,8 @@ export interface TagLibWorkerResponse { export interface TagLibMetadata { title?: string; - artist?: string; + artist?: string | string[]; + writeArtistsSeparately?: boolean; albumTitle?: string; albumArtist?: string; trackNumber?: number; diff --git a/js/taglib.worker.ts b/js/taglib.worker.ts index fb91b10..c06c495 100644 --- a/js/taglib.worker.ts +++ b/js/taglib.worker.ts @@ -29,8 +29,8 @@ import { FileSystemFileHandleStream } from '!/@dantheman827/taglib-ts/src/toolki 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'; +import { WavFile } from '!/@dantheman827/taglib-ts/src/riff/wav/wavFile'; export const isWorker = typeof WorkerGlobalScope !== 'undefined' && self instanceof WorkerGlobalScope; @@ -41,6 +41,7 @@ export async function addMetadataToAudio(message: _AddMetadataMessage): Promise< filename, title, artist, + writeArtistsSeparately = false, albumTitle, albumArtist, trackNumber, @@ -74,17 +75,24 @@ export async function addMetadataToAudio(message: _AddMetadataMessage): Promise< } const underlying = ref.file(); + const isFlac = underlying instanceof FlacFile; const isMp4 = underlying instanceof Mp4File; const isMpeg = underlying instanceof MpegFile; + const isOgg = underlying instanceof OggVorbisFile; + const isWav = underlying instanceof WavFile; + const needsCombinedTrackDisc = isMp4 || isMpeg; + const artistArray = Array.isArray(artist) ? artist : artist ? [artist] : []; + const supportsMultiValuedArtist = writeArtistsSeparately && (isFlac || isOgg || isMp4); + doTimed('Tagging file', () => { const props = ref.properties(); if (title) props.replace('TITLE', [title]); - if (artist) props.replace('ARTIST', [artist]); + if (artistArray.length) props.replace('ARTIST', supportsMultiValuedArtist ? artistArray : [artistArray.join('; ')]); if (albumTitle) props.replace('ALBUM', [albumTitle]); - if (albumArtist || artist) props.replace('ALBUMARTIST', [albumArtist || artist!]); + if (albumArtist || artistArray.length) props.replace('ALBUMARTIST', albumArtist ? [albumArtist] : [artistArray.join('; ')]); if (trackNumber) { const trackStr = diff --git a/js/utils.js b/js/utils.js index 6c78b17..bd4be2e 100644 --- a/js/utils.js +++ b/js/utils.js @@ -616,10 +616,10 @@ export const getShareUrl = (path) => { }; /** - * Builds a full artist string by combining the track's listed artists + * Builds a full artist array by combining the track's listed artists * with any featured artists parsed from the title (feat./with). */ -export function getFullArtistString(track) { +export function getFullArtistArray(track) { const knownArtists = Array.isArray(track.artists) && track.artists.length > 0 ? track.artists.map((a) => (typeof a === 'string' ? a : a.name) || '').filter(Boolean) @@ -646,6 +646,16 @@ export function getFullArtistString(track) { } } + return knownArtists; +} + +/** + * Builds a full artist string by combining the track's listed artists + * with any featured artists parsed from the title (feat./with). + */ +export function getFullArtistString(track) { + const knownArtists = getFullArtistArray(track); + return knownArtists.join('; ') || null; } From a0f60a2dbd3e4b7b86df9be2a5effc2c5399c16b Mon Sep 17 00:00:00 2001 From: edideaur <182119792+edideaur@users.noreply.github.com> Date: Fri, 3 Apr 2026 11:31:45 +0000 Subject: [PATCH 29/51] style: auto-fix linting issues --- js/taglib.worker.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/js/taglib.worker.ts b/js/taglib.worker.ts index c06c495..b50b5f0 100644 --- a/js/taglib.worker.ts +++ b/js/taglib.worker.ts @@ -90,9 +90,11 @@ export async function addMetadataToAudio(message: _AddMetadataMessage): Promise< const props = ref.properties(); if (title) props.replace('TITLE', [title]); - if (artistArray.length) props.replace('ARTIST', supportsMultiValuedArtist ? artistArray : [artistArray.join('; ')]); + if (artistArray.length) + props.replace('ARTIST', supportsMultiValuedArtist ? artistArray : [artistArray.join('; ')]); if (albumTitle) props.replace('ALBUM', [albumTitle]); - if (albumArtist || artistArray.length) props.replace('ALBUMARTIST', albumArtist ? [albumArtist] : [artistArray.join('; ')]); + if (albumArtist || artistArray.length) + props.replace('ALBUMARTIST', albumArtist ? [albumArtist] : [artistArray.join('; ')]); if (trackNumber) { const trackStr = From 98f603c596060101d523cae1a76b4ca146229645 Mon Sep 17 00:00:00 2001 From: Daniel <790119+DanTheMan827@users.noreply.github.com> Date: Fri, 3 Apr 2026 10:21:51 -0500 Subject: [PATCH 30/51] remove package-lock.json We use bun, and the package-lock.json wasn't being maintained anyways. --- .gitignore | 3 +- package-lock.json | 15823 -------------------------------------------- 2 files changed, 2 insertions(+), 15824 deletions(-) delete mode 100644 package-lock.json diff --git a/.gitignore b/.gitignore index 3d5c22d..995e435 100644 --- a/.gitignore +++ b/.gitignore @@ -16,4 +16,5 @@ bin/ .storage/ auth_storage/ www -neutralino.js \ No newline at end of file +neutralino.js +package-lock.json \ No newline at end of file diff --git a/package-lock.json b/package-lock.json deleted file mode 100644 index 72dd198..0000000 --- a/package-lock.json +++ /dev/null @@ -1,15823 +0,0 @@ -{ - "name": "monochrome", - "version": "2.5.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "monochrome", - "version": "2.5.0", - "license": "ISC", - "dependencies": { - "@capacitor/android": "^8.2.0", - "@capacitor/app": "^8.0.1", - "@capacitor/core": "^8.2.0", - "@capacitor/haptics": "^8.0.1", - "@capacitor/ios": "^8.2.0", - "@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", - "@kawarp/core": "^1.1.1", - "@svta/common-media-library": "^0.18.1", - "@uimaxbai/am-lyrics": "^1.1.4", - "@vitest/web-worker": "^4.1.2", - "appwrite": "^23.0.0", - "butterchurn": "^2.6.7", - "butterchurn-presets": "^2.4.7", - "client-zip": "^2.5.0", - "cookie-session": "^2.1.1", - "eventemitter3": "^5.0.4", - "events": "^3.3.0", - "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", - "shaka-player": "^5.0.7", - "simple-icons": "^16.12.0", - "svgo": "^4.0.1", - "url-toolkit": "^2.2.5", - "uuid": "^13.0.0", - "vitest": "^4.1.2" - }, - "devDependencies": { - "@capacitor/assets": "^3.0.5", - "@capacitor/cli": "^8.2.0", - "@testing-library/dom": "^10.4.1", - "@types/node": "^25.3.5", - "@vitest/browser-playwright": "^4.1.2", - "eslint": "^9.39.3", - "eslint-config-prettier": "^10.1.8", - "formidable": "^3.5.4", - "globals": "^17.4.0", - "htmlhint": "^1.9.2", - "miniflare": "^4.20260301.1", - "playwright": "^1.58.2", - "prettier": "^3.8.1", - "stylelint": "^16.26.1", - "stylelint-config-standard": "^39.0.1", - "stylelint-config-standard-scss": "^16.0.0", - "typescript": "^5.9.3", - "vite": "^7.3.1", - "vite-plugin-pwa": "^1.2.0" - } - }, - "node_modules/@apideck/better-ajv-errors": { - "version": "0.3.6", - "dev": true, - "license": "MIT", - "dependencies": { - "json-schema": "^0.4.0", - "jsonpointer": "^5.0.0", - "leven": "^3.1.0" - }, - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "ajv": ">=8" - } - }, - "node_modules/@babel/code-frame": { - "version": "7.29.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-validator-identifier": "^7.28.5", - "js-tokens": "^4.0.0", - "picocolors": "^1.1.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/compat-data": { - "version": "7.29.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/core": { - "version": "7.29.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.29.0", - "@babel/generator": "^7.29.0", - "@babel/helper-compilation-targets": "^7.28.6", - "@babel/helper-module-transforms": "^7.28.6", - "@babel/helpers": "^7.28.6", - "@babel/parser": "^7.29.0", - "@babel/template": "^7.28.6", - "@babel/traverse": "^7.29.0", - "@babel/types": "^7.29.0", - "@jridgewell/remapping": "^2.3.5", - "convert-source-map": "^2.0.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.2", - "json5": "^2.2.3", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/babel" - } - }, - "node_modules/@babel/core/node_modules/debug": { - "version": "4.4.3", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/@babel/core/node_modules/debug/node_modules/ms": { - "version": "2.1.3", - "dev": true, - "license": "MIT" - }, - "node_modules/@babel/core/node_modules/semver": { - "version": "6.3.1", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/@babel/generator": { - "version": "7.29.1", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.29.0", - "@babel/types": "^7.29.0", - "@jridgewell/gen-mapping": "^0.3.12", - "@jridgewell/trace-mapping": "^0.3.28", - "jsesc": "^3.0.2" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/generator/node_modules/@jridgewell/trace-mapping": { - "version": "0.3.31", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" - } - }, - "node_modules/@babel/helper-annotate-as-pure": { - "version": "7.27.3", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.27.3" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-compilation-targets": { - "version": "7.28.6", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/compat-data": "^7.28.6", - "@babel/helper-validator-option": "^7.27.1", - "browserslist": "^4.24.0", - "lru-cache": "^5.1.1", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-compilation-targets/node_modules/lru-cache": { - "version": "5.1.1", - "dev": true, - "license": "ISC", - "dependencies": { - "yallist": "^3.0.2" - } - }, - "node_modules/@babel/helper-compilation-targets/node_modules/lru-cache/node_modules/yallist": { - "version": "3.1.1", - "dev": true, - "license": "ISC" - }, - "node_modules/@babel/helper-compilation-targets/node_modules/semver": { - "version": "6.3.1", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/@babel/helper-create-class-features-plugin": { - "version": "7.28.6", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.27.3", - "@babel/helper-member-expression-to-functions": "^7.28.5", - "@babel/helper-optimise-call-expression": "^7.27.1", - "@babel/helper-replace-supers": "^7.28.6", - "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", - "@babel/traverse": "^7.28.6", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-create-class-features-plugin/node_modules/semver": { - "version": "6.3.1", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/@babel/helper-create-regexp-features-plugin": { - "version": "7.28.5", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.27.3", - "regexpu-core": "^6.3.1", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-create-regexp-features-plugin/node_modules/semver": { - "version": "6.3.1", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/@babel/helper-define-polyfill-provider": { - "version": "0.6.8", - "dev": true, - "license": "MIT", - "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" - } - }, - "node_modules/@babel/helper-define-polyfill-provider/node_modules/debug": { - "version": "4.4.3", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/@babel/helper-define-polyfill-provider/node_modules/debug/node_modules/ms": { - "version": "2.1.3", - "dev": true, - "license": "MIT" - }, - "node_modules/@babel/helper-globals": { - "version": "7.28.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-member-expression-to-functions": { - "version": "7.28.5", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/traverse": "^7.28.5", - "@babel/types": "^7.28.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-module-imports": { - "version": "7.28.6", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/traverse": "^7.28.6", - "@babel/types": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-module-transforms": { - "version": "7.28.6", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-module-imports": "^7.28.6", - "@babel/helper-validator-identifier": "^7.28.5", - "@babel/traverse": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-optimise-call-expression": { - "version": "7.27.1", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-plugin-utils": { - "version": "7.28.6", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-remap-async-to-generator": { - "version": "7.27.1", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.27.1", - "@babel/helper-wrap-function": "^7.27.1", - "@babel/traverse": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-replace-supers": { - "version": "7.28.6", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-member-expression-to-functions": "^7.28.5", - "@babel/helper-optimise-call-expression": "^7.27.1", - "@babel/traverse": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-skip-transparent-expression-wrappers": { - "version": "7.27.1", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/traverse": "^7.27.1", - "@babel/types": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-string-parser": { - "version": "7.27.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-identifier": { - "version": "7.28.5", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-option": { - "version": "7.27.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-wrap-function": { - "version": "7.28.6", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/template": "^7.28.6", - "@babel/traverse": "^7.28.6", - "@babel/types": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helpers": { - "version": "7.29.2", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/template": "^7.28.6", - "@babel/types": "^7.29.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/parser": { - "version": "7.29.2", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.29.0" - }, - "bin": { - "parser": "bin/babel-parser.js" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@babel/plugin-bugfix-firefox-class-in-computed-class-key": { - "version": "7.28.5", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/traverse": "^7.28.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/plugin-bugfix-safari-class-field-initializer-scope": { - "version": "7.27.1", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { - "version": "7.27.1", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { - "version": "7.27.1", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", - "@babel/plugin-transform-optional-chaining": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.13.0" - } - }, - "node_modules/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": { - "version": "7.28.6", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.28.6", - "@babel/traverse": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/plugin-proposal-private-property-in-object": { - "version": "7.21.0-placeholder-for-preset-env.2", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-import-assertions": { - "version": "7.28.6", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-import-attributes": { - "version": "7.28.6", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-unicode-sets-regex": { - "version": "7.18.6", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/plugin-transform-arrow-functions": { - "version": "7.27.1", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-async-generator-functions": { - "version": "7.29.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.28.6", - "@babel/helper-remap-async-to-generator": "^7.27.1", - "@babel/traverse": "^7.29.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-async-to-generator": { - "version": "7.28.6", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-module-imports": "^7.28.6", - "@babel/helper-plugin-utils": "^7.28.6", - "@babel/helper-remap-async-to-generator": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-block-scoped-functions": { - "version": "7.27.1", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-block-scoping": { - "version": "7.28.6", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-class-properties": { - "version": "7.28.6", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.28.6", - "@babel/helper-plugin-utils": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-class-static-block": { - "version": "7.28.6", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.28.6", - "@babel/helper-plugin-utils": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.12.0" - } - }, - "node_modules/@babel/plugin-transform-classes": { - "version": "7.28.6", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.27.3", - "@babel/helper-compilation-targets": "^7.28.6", - "@babel/helper-globals": "^7.28.0", - "@babel/helper-plugin-utils": "^7.28.6", - "@babel/helper-replace-supers": "^7.28.6", - "@babel/traverse": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-computed-properties": { - "version": "7.28.6", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.28.6", - "@babel/template": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-destructuring": { - "version": "7.28.5", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/traverse": "^7.28.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-dotall-regex": { - "version": "7.28.6", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.28.5", - "@babel/helper-plugin-utils": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-duplicate-keys": { - "version": "7.27.1", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-duplicate-named-capturing-groups-regex": { - "version": "7.29.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.28.5", - "@babel/helper-plugin-utils": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/plugin-transform-dynamic-import": { - "version": "7.27.1", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-explicit-resource-management": { - "version": "7.28.6", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.28.6", - "@babel/plugin-transform-destructuring": "^7.28.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-exponentiation-operator": { - "version": "7.28.6", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-export-namespace-from": { - "version": "7.27.1", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-for-of": { - "version": "7.27.1", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-function-name": { - "version": "7.27.1", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-compilation-targets": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/traverse": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-json-strings": { - "version": "7.28.6", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-literals": { - "version": "7.27.1", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-logical-assignment-operators": { - "version": "7.28.6", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-member-expression-literals": { - "version": "7.27.1", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-modules-amd": { - "version": "7.27.1", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-module-transforms": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-modules-commonjs": { - "version": "7.28.6", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-module-transforms": "^7.28.6", - "@babel/helper-plugin-utils": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-modules-systemjs": { - "version": "7.29.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-module-transforms": "^7.28.6", - "@babel/helper-plugin-utils": "^7.28.6", - "@babel/helper-validator-identifier": "^7.28.5", - "@babel/traverse": "^7.29.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-modules-umd": { - "version": "7.27.1", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-module-transforms": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-named-capturing-groups-regex": { - "version": "7.29.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.28.5", - "@babel/helper-plugin-utils": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/plugin-transform-new-target": { - "version": "7.27.1", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-nullish-coalescing-operator": { - "version": "7.28.6", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-numeric-separator": { - "version": "7.28.6", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-object-rest-spread": { - "version": "7.28.6", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-compilation-targets": "^7.28.6", - "@babel/helper-plugin-utils": "^7.28.6", - "@babel/plugin-transform-destructuring": "^7.28.5", - "@babel/plugin-transform-parameters": "^7.27.7", - "@babel/traverse": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-object-super": { - "version": "7.27.1", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/helper-replace-supers": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-optional-catch-binding": { - "version": "7.28.6", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-optional-chaining": { - "version": "7.28.6", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.28.6", - "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-parameters": { - "version": "7.27.7", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-private-methods": { - "version": "7.28.6", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.28.6", - "@babel/helper-plugin-utils": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-private-property-in-object": { - "version": "7.28.6", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.27.3", - "@babel/helper-create-class-features-plugin": "^7.28.6", - "@babel/helper-plugin-utils": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-property-literals": { - "version": "7.27.1", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-regenerator": { - "version": "7.29.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-regexp-modifiers": { - "version": "7.28.6", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.28.5", - "@babel/helper-plugin-utils": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/plugin-transform-reserved-words": { - "version": "7.27.1", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-shorthand-properties": { - "version": "7.27.1", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-spread": { - "version": "7.28.6", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.28.6", - "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-sticky-regex": { - "version": "7.27.1", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-template-literals": { - "version": "7.27.1", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-typeof-symbol": { - "version": "7.27.1", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-unicode-escapes": { - "version": "7.27.1", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-unicode-property-regex": { - "version": "7.28.6", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.28.5", - "@babel/helper-plugin-utils": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-unicode-regex": { - "version": "7.27.1", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-unicode-sets-regex": { - "version": "7.28.6", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.28.5", - "@babel/helper-plugin-utils": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/preset-env": { - "version": "7.29.2", - "dev": true, - "license": "MIT", - "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" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/preset-env/node_modules/semver": { - "version": "6.3.1", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/@babel/preset-modules": { - "version": "0.1.6-no-external-plugins", - "dev": true, - "license": "MIT", - "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" - } - }, - "node_modules/@babel/runtime": { - "version": "7.29.2", - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/template": { - "version": "7.28.6", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.28.6", - "@babel/parser": "^7.28.6", - "@babel/types": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/traverse": { - "version": "7.29.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.29.0", - "@babel/generator": "^7.29.0", - "@babel/helper-globals": "^7.28.0", - "@babel/parser": "^7.29.0", - "@babel/template": "^7.28.6", - "@babel/types": "^7.29.0", - "debug": "^4.3.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/traverse/node_modules/debug": { - "version": "4.4.3", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/@babel/traverse/node_modules/debug/node_modules/ms": { - "version": "2.1.3", - "dev": true, - "license": "MIT" - }, - "node_modules/@babel/types": { - "version": "7.29.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-string-parser": "^7.27.1", - "@babel/helper-validator-identifier": "^7.28.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@blazediff/core": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/@blazediff/core/-/core-1.9.1.tgz", - "integrity": "sha512-ehg3jIkYKulZh+8om/O25vkvSsXXwC+skXmyA87FFx6A/45eqOkZsBltMw/TVteb0mloiGT8oGRTcjRAz66zaA==", - "devOptional": true, - "license": "MIT" - }, - "node_modules/@cacheable/memory": { - "version": "2.0.8", - "dev": true, - "license": "MIT", - "dependencies": { - "@cacheable/utils": "^2.4.0", - "@keyv/bigmap": "^1.3.1", - "hookified": "^1.15.1", - "keyv": "^5.6.0" - } - }, - "node_modules/@cacheable/utils": { - "version": "2.4.1", - "dev": true, - "license": "MIT", - "dependencies": { - "hashery": "^1.5.1", - "keyv": "^5.6.0" - } - }, - "node_modules/@capacitor/android": { - "version": "8.3.0", - "license": "MIT", - "peerDependencies": { - "@capacitor/core": "^8.3.0" - } - }, - "node_modules/@capacitor/app": { - "version": "8.1.0", - "license": "MIT", - "peerDependencies": { - "@capacitor/core": ">=8.0.0" - } - }, - "node_modules/@capacitor/assets": { - "version": "3.0.5", - "dev": true, - "license": "MIT", - "dependencies": { - "@capacitor/cli": "^5.3.0", - "@ionic/utils-array": "2.1.6", - "@ionic/utils-fs": "3.1.7", - "@trapezedev/project": "^7.0.10", - "commander": "8.3.0", - "debug": "4.3.4", - "fs-extra": "10.1.0", - "node-fetch": "2.7.0", - "node-html-parser": "5.4.2", - "sharp": "0.32.6", - "tslib": "2.6.2", - "yargs": "17.7.2" - }, - "bin": { - "capacitor-assets": "bin/capacitor-assets" - }, - "engines": { - "node": ">=10.3.0" - } - }, - "node_modules/@capacitor/assets/node_modules/@capacitor/cli": { - "version": "5.7.8", - "dev": true, - "license": "MIT", - "dependencies": { - "@ionic/cli-framework-output": "^2.2.5", - "@ionic/utils-fs": "^3.1.6", - "@ionic/utils-subprocess": "^2.1.11", - "@ionic/utils-terminal": "^2.3.3", - "commander": "^9.3.0", - "debug": "^4.3.4", - "env-paths": "^2.2.0", - "kleur": "^4.1.4", - "native-run": "^2.0.0", - "open": "^8.4.0", - "plist": "^3.0.5", - "prompts": "^2.4.2", - "rimraf": "^4.4.1", - "semver": "^7.3.7", - "tar": "^6.1.11", - "tslib": "^2.4.0", - "xml2js": "^0.5.0" - }, - "bin": { - "cap": "bin/capacitor", - "capacitor": "bin/capacitor" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@capacitor/assets/node_modules/@capacitor/cli/node_modules/@ionic/utils-subprocess": { - "version": "2.1.14", - "dev": true, - "license": "MIT", - "dependencies": { - "@ionic/utils-array": "2.1.6", - "@ionic/utils-fs": "3.1.7", - "@ionic/utils-process": "2.1.11", - "@ionic/utils-stream": "3.1.6", - "@ionic/utils-terminal": "2.3.4", - "cross-spawn": "^7.0.3", - "debug": "^4.0.0", - "tslib": "^2.0.1" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@capacitor/assets/node_modules/@capacitor/cli/node_modules/@ionic/utils-subprocess/node_modules/@ionic/utils-process": { - "version": "2.1.11", - "dev": true, - "license": "MIT", - "dependencies": { - "@ionic/utils-object": "2.1.6", - "@ionic/utils-terminal": "2.3.4", - "debug": "^4.0.0", - "signal-exit": "^3.0.3", - "tree-kill": "^1.2.2", - "tslib": "^2.0.1" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@capacitor/assets/node_modules/@capacitor/cli/node_modules/@ionic/utils-subprocess/node_modules/@ionic/utils-stream": { - "version": "3.1.6", - "dev": true, - "license": "MIT", - "dependencies": { - "debug": "^4.0.0", - "tslib": "^2.0.1" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@capacitor/assets/node_modules/@capacitor/cli/node_modules/@ionic/utils-subprocess/node_modules/@ionic/utils-terminal": { - "version": "2.3.4", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/slice-ansi": "^4.0.0", - "debug": "^4.0.0", - "signal-exit": "^3.0.3", - "slice-ansi": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0", - "tslib": "^2.0.1", - "untildify": "^4.0.0", - "wrap-ansi": "^7.0.0" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@capacitor/assets/node_modules/@capacitor/cli/node_modules/commander": { - "version": "9.5.0", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.20.0 || >=14" - } - }, - "node_modules/@capacitor/assets/node_modules/@capacitor/cli/node_modules/debug": { - "version": "4.4.3", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/@capacitor/assets/node_modules/@capacitor/cli/node_modules/debug/node_modules/ms": { - "version": "2.1.3", - "dev": true, - "license": "MIT" - }, - "node_modules/@capacitor/assets/node_modules/@capacitor/cli/node_modules/rimraf": { - "version": "4.4.1", - "dev": true, - "license": "ISC", - "dependencies": { - "glob": "^9.2.0" - }, - "bin": { - "rimraf": "dist/cjs/src/bin.js" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/@capacitor/assets/node_modules/@capacitor/cli/node_modules/rimraf/node_modules/glob": { - "version": "9.3.5", - "dev": true, - "license": "ISC", - "dependencies": { - "fs.realpath": "^1.0.0", - "minimatch": "^8.0.2", - "minipass": "^4.2.4", - "path-scurry": "^1.6.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/@capacitor/assets/node_modules/@capacitor/cli/node_modules/rimraf/node_modules/glob/node_modules/minimatch": { - "version": "8.0.7", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/@capacitor/assets/node_modules/@capacitor/cli/node_modules/rimraf/node_modules/glob/node_modules/minimatch/node_modules/brace-expansion": { - "version": "2.0.3", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/@capacitor/assets/node_modules/@capacitor/cli/node_modules/rimraf/node_modules/glob/node_modules/minimatch/node_modules/brace-expansion/node_modules/balanced-match": { - "version": "1.0.2", - "dev": true, - "license": "MIT" - }, - "node_modules/@capacitor/assets/node_modules/@capacitor/cli/node_modules/rimraf/node_modules/glob/node_modules/minipass": { - "version": "4.2.8", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=8" - } - }, - "node_modules/@capacitor/assets/node_modules/@capacitor/cli/node_modules/rimraf/node_modules/glob/node_modules/path-scurry": { - "version": "1.11.1", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "lru-cache": "^10.2.0", - "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" - }, - "engines": { - "node": ">=16 || 14 >=14.18" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/@capacitor/assets/node_modules/@capacitor/cli/node_modules/rimraf/node_modules/glob/node_modules/path-scurry/node_modules/lru-cache": { - "version": "10.4.3", - "dev": true, - "license": "ISC" - }, - "node_modules/@capacitor/assets/node_modules/@capacitor/cli/node_modules/rimraf/node_modules/glob/node_modules/path-scurry/node_modules/minipass": { - "version": "7.1.3", - "dev": true, - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=16 || 14 >=14.17" - } - }, - "node_modules/@capacitor/assets/node_modules/@capacitor/cli/node_modules/tar": { - "version": "6.2.1", - "dev": true, - "license": "ISC", - "dependencies": { - "chownr": "^2.0.0", - "fs-minipass": "^2.0.0", - "minipass": "^5.0.0", - "minizlib": "^2.1.1", - "mkdirp": "^1.0.3", - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@capacitor/assets/node_modules/@capacitor/cli/node_modules/tar/node_modules/chownr": { - "version": "2.0.0", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=10" - } - }, - "node_modules/@capacitor/assets/node_modules/@capacitor/cli/node_modules/tar/node_modules/minipass": { - "version": "5.0.0", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=8" - } - }, - "node_modules/@capacitor/assets/node_modules/@capacitor/cli/node_modules/tar/node_modules/minizlib": { - "version": "2.1.2", - "dev": true, - "license": "MIT", - "dependencies": { - "minipass": "^3.0.0", - "yallist": "^4.0.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@capacitor/assets/node_modules/@capacitor/cli/node_modules/tar/node_modules/minizlib/node_modules/minipass": { - "version": "3.3.6", - "dev": true, - "license": "ISC", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@capacitor/assets/node_modules/@capacitor/cli/node_modules/tar/node_modules/yallist": { - "version": "4.0.0", - "dev": true, - "license": "ISC" - }, - "node_modules/@capacitor/assets/node_modules/@capacitor/cli/node_modules/tslib": { - "version": "2.8.1", - "dev": true, - "license": "0BSD" - }, - "node_modules/@capacitor/assets/node_modules/@capacitor/cli/node_modules/xml2js": { - "version": "0.5.0", - "dev": true, - "license": "MIT", - "dependencies": { - "sax": ">=0.6.0", - "xmlbuilder": "~11.0.0" - }, - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/@capacitor/assets/node_modules/@capacitor/cli/node_modules/xml2js/node_modules/xmlbuilder": { - "version": "11.0.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4.0" - } - }, - "node_modules/@capacitor/cli": { - "version": "8.3.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@ionic/cli-framework-output": "^2.2.8", - "@ionic/utils-subprocess": "^3.0.1", - "@ionic/utils-terminal": "^2.3.5", - "commander": "^12.1.0", - "debug": "^4.4.0", - "env-paths": "^2.2.0", - "fs-extra": "^11.2.0", - "kleur": "^4.1.5", - "native-run": "^2.0.3", - "open": "^8.4.0", - "plist": "^3.1.0", - "prompts": "^2.4.2", - "rimraf": "^6.0.1", - "semver": "^7.6.3", - "tar": "^7.5.3", - "tslib": "^2.8.1", - "xml2js": "^0.6.2" - }, - "bin": { - "cap": "bin/capacitor", - "capacitor": "bin/capacitor" - }, - "engines": { - "node": ">=22.0.0" - } - }, - "node_modules/@capacitor/cli/node_modules/commander": { - "version": "12.1.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - } - }, - "node_modules/@capacitor/cli/node_modules/debug": { - "version": "4.4.3", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/@capacitor/cli/node_modules/debug/node_modules/ms": { - "version": "2.1.3", - "dev": true, - "license": "MIT" - }, - "node_modules/@capacitor/cli/node_modules/fs-extra": { - "version": "11.3.4", - "dev": true, - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "engines": { - "node": ">=14.14" - } - }, - "node_modules/@capacitor/cli/node_modules/tslib": { - "version": "2.8.1", - "dev": true, - "license": "0BSD" - }, - "node_modules/@capacitor/core": { - "version": "8.3.0", - "license": "MIT", - "dependencies": { - "tslib": "^2.1.0" - } - }, - "node_modules/@capacitor/haptics": { - "version": "8.0.2", - "license": "MIT", - "peerDependencies": { - "@capacitor/core": ">=8.0.0" - } - }, - "node_modules/@capacitor/ios": { - "version": "8.3.0", - "license": "MIT", - "peerDependencies": { - "@capacitor/core": "^8.3.0" - } - }, - "node_modules/@cloudflare/workerd-darwin-64": { - "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" - ], - "dev": true, - "license": "Apache-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=16" - } - }, - "node_modules/@cloudflare/workerd-darwin-arm64": { - "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" - ], - "dev": true, - "license": "Apache-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=16" - } - }, - "node_modules/@cloudflare/workerd-linux-64": { - "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" - ], - "dev": true, - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=16" - } - }, - "node_modules/@cloudflare/workerd-linux-arm64": { - "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" - ], - "dev": true, - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=16" - } - }, - "node_modules/@cloudflare/workerd-windows-64": { - "version": "1.20260317.1", - "cpu": [ - "x64" - ], - "dev": true, - "license": "Apache-2.0", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=16" - } - }, - "node_modules/@cspotcode/source-map-support": { - "version": "0.8.1", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/trace-mapping": "0.3.9" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@csstools/css-parser-algorithms": { - "version": "3.0.5", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT", - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@csstools/css-tokenizer": "^3.0.4" - } - }, - "node_modules/@csstools/css-syntax-patches-for-csstree": { - "version": "1.1.2", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "peerDependencies": { - "css-tree": "^3.2.1" - }, - "peerDependenciesMeta": { - "css-tree": { - "optional": true - } - } - }, - "node_modules/@csstools/css-tokenizer": { - "version": "3.0.4", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT", - "engines": { - "node": ">=18" - } - }, - "node_modules/@csstools/media-query-list-parser": { - "version": "4.0.3", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT", - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@csstools/css-parser-algorithms": "^3.0.5", - "@csstools/css-tokenizer": "^3.0.4" - } - }, - "node_modules/@csstools/selector-specificity": { - "version": "5.0.0", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "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, - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/JounQin" - } - }, - "node_modules/@emnapi/runtime": { - "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, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@esbuild/aix-ppc64": { - "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" - ], - "license": "MIT", - "optional": true, - "os": [ - "aix" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.4.tgz", - "integrity": "sha512-X9bUgvxiC8CHAGKYufLIHGXPJWnr0OCdR0anD2e21vdvgCI8lIfqFbnoeOz7lBjdrAGUhqLZLcQo6MLhTO2DKQ==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm64": { - "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" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-x64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.4.tgz", - "integrity": "sha512-PzPFnBNVF292sfpfhiyiXCGSn9HZg5BcAz+ivBuSsl6Rk4ga1oEXAamhOXRFyMcjwr2DVtm40G65N3GLeH1Lvw==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-arm64": { - "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" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-x64": { - "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" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-arm64": { - "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" - ], - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-x64": { - "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" - ], - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm": { - "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" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.4.tgz", - "integrity": "sha512-7nQOttdzVGth1iz57kxg9uCz57dxQLHWxopL6mYuYthohPKEK0vU0C3O21CcBK6KDlkYVcnDXY099HcCDXd9dA==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ia32": { - "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" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-loong64": { - "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" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-mips64el": { - "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" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.4.tgz", - "integrity": "sha512-tLCwNG47l3sd9lpfyx9LAGEGItCUeRCWeAx6x2Jmbav65nAwoPXfewtAdtbtit/pJFLUWOhpv0FpS6GQAmPrHA==", - "cpu": [ - "ppc64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-riscv64": { - "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" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-s390x": { - "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" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-x64": { - "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" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-arm64": { - "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" - ], - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.4.tgz", - "integrity": "sha512-RugOvOdXfdyi5Tyv40kgQnI0byv66BFgAqjdgtAKqHoZTbTF2QqfQrFwa7cHEORJf6X2ht+l9ABLMP0dnKYsgg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-arm64": { - "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" - ], - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-x64": { - "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" - ], - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openharmony-arm64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.4.tgz", - "integrity": "sha512-JkTZrl6VbyO8lDQO3yv26nNr2RM2yZzNrNHEsj9bm6dOwwu9OYN28CjzZkH57bh4w0I2F7IodpQvUAEd1mbWXg==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.4.tgz", - "integrity": "sha512-/gOzgaewZJfeJTlsWhvUEmUG4tWEY2Spp5M20INYRg2ZKl9QPO3QEEgPeRtLjEWSW8FilRNacPOg8R1uaYkA6g==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-arm64": { - "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" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-ia32": { - "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" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-x64": { - "version": "0.27.4", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@eslint-community/eslint-utils": { - "version": "4.9.1", - "dev": true, - "license": "MIT", - "dependencies": { - "eslint-visitor-keys": "^3.4.3" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - }, - "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" - } - }, - "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { - "version": "3.4.3", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/@eslint-community/regexpp": { - "version": "4.12.2", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.0.0 || ^14.0.0 || >=16.0.0" - } - }, - "node_modules/@eslint/config-array": { - "version": "0.21.2", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@eslint/object-schema": "^2.1.7", - "debug": "^4.3.1", - "minimatch": "^3.1.5" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@eslint/config-array/node_modules/debug": { - "version": "4.4.3", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/@eslint/config-array/node_modules/debug/node_modules/ms": { - "version": "2.1.3", - "dev": true, - "license": "MIT" - }, - "node_modules/@eslint/config-helpers": { - "version": "0.4.2", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@eslint/core": "^0.17.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@eslint/core": { - "version": "0.17.0", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@types/json-schema": "^7.0.15" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@eslint/eslintrc": { - "version": "3.3.5", - "dev": true, - "license": "MIT", - "dependencies": { - "ajv": "^6.14.0", - "debug": "^4.3.2", - "espree": "^10.0.1", - "globals": "^14.0.0", - "ignore": "^5.2.0", - "import-fresh": "^3.2.1", - "js-yaml": "^4.1.1", - "minimatch": "^3.1.5", - "strip-json-comments": "^3.1.1" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "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": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/@eslint/eslintrc/node_modules/debug": { - "version": "4.4.3", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/@eslint/eslintrc/node_modules/debug/node_modules/ms": { - "version": "2.1.3", - "dev": true, - "license": "MIT" - }, - "node_modules/@eslint/eslintrc/node_modules/globals": { - "version": "14.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "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" - }, - "node_modules/@eslint/js": { - "version": "9.39.4", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://eslint.org/donate" - } - }, - "node_modules/@eslint/object-schema": { - "version": "2.1.7", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@eslint/plugin-kit": { - "version": "0.4.1", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@eslint/core": "^0.17.0", - "levn": "^0.4.1" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@ffmpeg/core": { - "version": "0.12.10", - "license": "GPL-2.0-or-later", - "engines": { - "node": ">=16.x" - } - }, - "node_modules/@ffmpeg/ffmpeg": { - "version": "0.12.15", - "license": "MIT", - "dependencies": { - "@ffmpeg/types": "^0.12.4" - }, - "engines": { - "node": ">=18.x" - } - }, - "node_modules/@ffmpeg/types": { - "version": "0.12.4", - "license": "MIT", - "engines": { - "node": ">=16.x" - } - }, - "node_modules/@ffmpeg/util": { - "version": "0.12.2", - "license": "MIT", - "engines": { - "node": ">=18.x" - } - }, - "node_modules/@humanfs/core": { - "version": "0.19.1", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=18.18.0" - } - }, - "node_modules/@humanfs/node": { - "version": "0.16.7", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@humanfs/core": "^0.19.1", - "@humanwhocodes/retry": "^0.4.0" - }, - "engines": { - "node": ">=18.18.0" - } - }, - "node_modules/@humanwhocodes/module-importer": { - "version": "1.0.1", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=12.22" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" - } - }, - "node_modules/@humanwhocodes/retry": { - "version": "0.4.3", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=18.18" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" - } - }, - "node_modules/@hutson/parse-repository-url": { - "version": "3.0.2", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@img/colour": { - "version": "1.1.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - } - }, - "node_modules/@img/sharp-darwin-arm64": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.5.tgz", - "integrity": "sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "Apache-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-darwin-arm64": "1.2.4" - } - }, - "node_modules/@img/sharp-darwin-x64": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.34.5.tgz", - "integrity": "sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "Apache-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-darwin-x64": "1.2.4" - } - }, - "node_modules/@img/sharp-libvips-darwin-arm64": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.2.4.tgz", - "integrity": "sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "darwin" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-darwin-x64": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.2.4.tgz", - "integrity": "sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "darwin" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-linux-arm": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.2.4.tgz", - "integrity": "sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "linux" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-linux-arm64": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.2.4.tgz", - "integrity": "sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "linux" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-linux-ppc64": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.2.4.tgz", - "integrity": "sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "linux" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-linux-riscv64": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.2.4.tgz", - "integrity": "sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "linux" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-linux-s390x": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.2.4.tgz", - "integrity": "sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "linux" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "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" - ], - "dev": true, - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "linux" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-linuxmusl-arm64": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.2.4.tgz", - "integrity": "sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "linux" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "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" - ], - "dev": true, - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "linux" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-linux-arm": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.34.5.tgz", - "integrity": "sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linux-arm": "1.2.4" - } - }, - "node_modules/@img/sharp-linux-arm64": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.34.5.tgz", - "integrity": "sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linux-arm64": "1.2.4" - } - }, - "node_modules/@img/sharp-linux-ppc64": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.34.5.tgz", - "integrity": "sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linux-ppc64": "1.2.4" - } - }, - "node_modules/@img/sharp-linux-riscv64": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.34.5.tgz", - "integrity": "sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linux-riscv64": "1.2.4" - } - }, - "node_modules/@img/sharp-linux-s390x": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.34.5.tgz", - "integrity": "sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linux-s390x": "1.2.4" - } - }, - "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" - ], - "dev": true, - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linux-x64": "1.2.4" - } - }, - "node_modules/@img/sharp-linuxmusl-arm64": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.5.tgz", - "integrity": "sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linuxmusl-arm64": "1.2.4" - } - }, - "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" - ], - "dev": true, - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linuxmusl-x64": "1.2.4" - } - }, - "node_modules/@img/sharp-wasm32": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.34.5.tgz", - "integrity": "sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==", - "cpu": [ - "wasm32" - ], - "dev": true, - "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", - "optional": true, - "dependencies": { - "@emnapi/runtime": "^1.7.0" - }, - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-win32-arm64": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.34.5.tgz", - "integrity": "sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "Apache-2.0 AND LGPL-3.0-or-later", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-win32-ia32": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.34.5.tgz", - "integrity": "sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "Apache-2.0 AND LGPL-3.0-or-later", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-win32-x64": { - "version": "0.34.5", - "cpu": [ - "x64" - ], - "dev": true, - "license": "Apache-2.0 AND LGPL-3.0-or-later", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@ionic/cli-framework-output": { - "version": "2.2.8", - "dev": true, - "license": "MIT", - "dependencies": { - "@ionic/utils-terminal": "2.3.5", - "debug": "^4.0.0", - "tslib": "^2.0.1" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@ionic/cli-framework-output/node_modules/debug": { - "version": "4.4.3", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/@ionic/cli-framework-output/node_modules/debug/node_modules/ms": { - "version": "2.1.3", - "dev": true, - "license": "MIT" - }, - "node_modules/@ionic/cli-framework-output/node_modules/tslib": { - "version": "2.8.1", - "dev": true, - "license": "0BSD" - }, - "node_modules/@ionic/utils-array": { - "version": "2.1.6", - "dev": true, - "license": "MIT", - "dependencies": { - "debug": "^4.0.0", - "tslib": "^2.0.1" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@ionic/utils-array/node_modules/debug": { - "version": "4.4.3", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/@ionic/utils-array/node_modules/debug/node_modules/ms": { - "version": "2.1.3", - "dev": true, - "license": "MIT" - }, - "node_modules/@ionic/utils-array/node_modules/tslib": { - "version": "2.8.1", - "dev": true, - "license": "0BSD" - }, - "node_modules/@ionic/utils-fs": { - "version": "3.1.7", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/fs-extra": "^8.0.0", - "debug": "^4.0.0", - "fs-extra": "^9.0.0", - "tslib": "^2.0.1" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@ionic/utils-fs/node_modules/debug": { - "version": "4.4.3", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/@ionic/utils-fs/node_modules/debug/node_modules/ms": { - "version": "2.1.3", - "dev": true, - "license": "MIT" - }, - "node_modules/@ionic/utils-fs/node_modules/fs-extra": { - "version": "9.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "at-least-node": "^1.0.0", - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@ionic/utils-fs/node_modules/tslib": { - "version": "2.8.1", - "dev": true, - "license": "0BSD" - }, - "node_modules/@ionic/utils-object": { - "version": "2.1.6", - "dev": true, - "license": "MIT", - "dependencies": { - "debug": "^4.0.0", - "tslib": "^2.0.1" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@ionic/utils-object/node_modules/debug": { - "version": "4.4.3", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/@ionic/utils-object/node_modules/debug/node_modules/ms": { - "version": "2.1.3", - "dev": true, - "license": "MIT" - }, - "node_modules/@ionic/utils-object/node_modules/tslib": { - "version": "2.8.1", - "dev": true, - "license": "0BSD" - }, - "node_modules/@ionic/utils-process": { - "version": "2.1.12", - "dev": true, - "license": "MIT", - "dependencies": { - "@ionic/utils-object": "2.1.6", - "@ionic/utils-terminal": "2.3.5", - "debug": "^4.0.0", - "signal-exit": "^3.0.3", - "tree-kill": "^1.2.2", - "tslib": "^2.0.1" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@ionic/utils-process/node_modules/debug": { - "version": "4.4.3", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/@ionic/utils-process/node_modules/debug/node_modules/ms": { - "version": "2.1.3", - "dev": true, - "license": "MIT" - }, - "node_modules/@ionic/utils-process/node_modules/tslib": { - "version": "2.8.1", - "dev": true, - "license": "0BSD" - }, - "node_modules/@ionic/utils-stream": { - "version": "3.1.7", - "dev": true, - "license": "MIT", - "dependencies": { - "debug": "^4.0.0", - "tslib": "^2.0.1" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@ionic/utils-stream/node_modules/debug": { - "version": "4.4.3", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/@ionic/utils-stream/node_modules/debug/node_modules/ms": { - "version": "2.1.3", - "dev": true, - "license": "MIT" - }, - "node_modules/@ionic/utils-stream/node_modules/tslib": { - "version": "2.8.1", - "dev": true, - "license": "0BSD" - }, - "node_modules/@ionic/utils-subprocess": { - "version": "3.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "@ionic/utils-array": "2.1.6", - "@ionic/utils-fs": "3.1.7", - "@ionic/utils-process": "2.1.12", - "@ionic/utils-stream": "3.1.7", - "@ionic/utils-terminal": "2.3.5", - "cross-spawn": "^7.0.3", - "debug": "^4.0.0", - "tslib": "^2.0.1" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@ionic/utils-subprocess/node_modules/debug": { - "version": "4.4.3", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/@ionic/utils-subprocess/node_modules/debug/node_modules/ms": { - "version": "2.1.3", - "dev": true, - "license": "MIT" - }, - "node_modules/@ionic/utils-subprocess/node_modules/tslib": { - "version": "2.8.1", - "dev": true, - "license": "0BSD" - }, - "node_modules/@ionic/utils-terminal": { - "version": "2.3.5", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/slice-ansi": "^4.0.0", - "debug": "^4.0.0", - "signal-exit": "^3.0.3", - "slice-ansi": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0", - "tslib": "^2.0.1", - "untildify": "^4.0.0", - "wrap-ansi": "^7.0.0" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@ionic/utils-terminal/node_modules/debug": { - "version": "4.4.3", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/@ionic/utils-terminal/node_modules/debug/node_modules/ms": { - "version": "2.1.3", - "dev": true, - "license": "MIT" - }, - "node_modules/@ionic/utils-terminal/node_modules/tslib": { - "version": "2.8.1", - "dev": true, - "license": "0BSD" - }, - "node_modules/@isaacs/cliui": { - "version": "9.0.0", - "dev": true, - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=18" - } - }, - "node_modules/@isaacs/fs-minipass": { - "version": "4.0.1", - "dev": true, - "license": "ISC", - "dependencies": { - "minipass": "^7.0.4" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.13", - "devOptional": true, - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.0", - "@jridgewell/trace-mapping": "^0.3.24" - } - }, - "node_modules/@jridgewell/gen-mapping/node_modules/@jridgewell/trace-mapping": { - "version": "0.3.31", - "devOptional": true, - "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" - } - }, - "node_modules/@jridgewell/remapping": { - "version": "2.3.5", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.24" - } - }, - "node_modules/@jridgewell/remapping/node_modules/@jridgewell/trace-mapping": { - "version": "0.3.31", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" - } - }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "devOptional": true, - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/source-map": { - "version": "0.3.11", - "devOptional": true, - "license": "MIT", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.25" - } - }, - "node_modules/@jridgewell/source-map/node_modules/@jridgewell/trace-mapping": { - "version": "0.3.31", - "devOptional": true, - "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.5", - "license": "MIT" - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.9", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.0.3", - "@jridgewell/sourcemap-codec": "^1.4.10" - } - }, - "node_modules/@kawarp/core": { - "version": "1.1.1", - "license": "AGPL-3.0" - }, - "node_modules/@keyv/bigmap": { - "version": "1.3.1", - "dev": true, - "license": "MIT", - "dependencies": { - "hashery": "^1.4.0", - "hookified": "^1.15.0" - }, - "engines": { - "node": ">= 18" - }, - "peerDependencies": { - "keyv": "^5.6.0" - } - }, - "node_modules/@keyv/serialize": { - "version": "1.1.1", - "dev": true, - "license": "MIT" - }, - "node_modules/@lit-labs/ssr-dom-shim": { - "version": "1.5.1", - "license": "BSD-3-Clause" - }, - "node_modules/@lit/reactive-element": { - "version": "2.1.2", - "license": "BSD-3-Clause", - "dependencies": { - "@lit-labs/ssr-dom-shim": "^1.5.0" - } - }, - "node_modules/@noble/hashes": { - "version": "1.8.0", - "dev": true, - "license": "MIT", - "engines": { - "node": "^14.21.3 || >=16" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/@nodelib/fs.scandir": { - "version": "2.1.5", - "dev": true, - "license": "MIT", - "dependencies": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.stat": { - "version": "2.0.5", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.walk": { - "version": "1.2.8", - "dev": true, - "license": "MIT", - "dependencies": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@paralleldrive/cuid2": { - "version": "2.3.1", - "dev": true, - "license": "MIT", - "dependencies": { - "@noble/hashes": "^1.1.5" - } - }, - "node_modules/@polka/url": { - "version": "1.0.0-next.29", - "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.29.tgz", - "integrity": "sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==", - "devOptional": true, - "license": "MIT" - }, - "node_modules/@poppinss/colors": { - "version": "4.1.6", - "dev": true, - "license": "MIT", - "dependencies": { - "kleur": "^4.1.5" - } - }, - "node_modules/@poppinss/dumper": { - "version": "0.6.5", - "dev": true, - "license": "MIT", - "dependencies": { - "@poppinss/colors": "^4.1.5", - "@sindresorhus/is": "^7.0.2", - "supports-color": "^10.0.0" - } - }, - "node_modules/@poppinss/dumper/node_modules/supports-color": { - "version": "10.2.2", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" - } - }, - "node_modules/@poppinss/exception": { - "version": "1.2.3", - "dev": true, - "license": "MIT" - }, - "node_modules/@prettier/plugin-xml": { - "version": "2.2.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@xml-tools/parser": "^1.0.11", - "prettier": ">=2.4.0" - } - }, - "node_modules/@rollup/plugin-babel": { - "version": "5.3.1", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-module-imports": "^7.10.4", - "@rollup/pluginutils": "^3.1.0" - }, - "engines": { - "node": ">= 10.0.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0", - "@types/babel__core": "^7.1.9", - "rollup": "^1.20.0||^2.0.0" - }, - "peerDependenciesMeta": { - "@types/babel__core": { - "optional": true - } - } - }, - "node_modules/@rollup/plugin-node-resolve": { - "version": "15.3.1", - "dev": true, - "license": "MIT", - "dependencies": { - "@rollup/pluginutils": "^5.0.1", - "@types/resolve": "1.20.2", - "deepmerge": "^4.2.2", - "is-module": "^1.0.0", - "resolve": "^1.22.1" - }, - "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "rollup": "^2.78.0||^3.0.0||^4.0.0" - }, - "peerDependenciesMeta": { - "rollup": { - "optional": true - } - } - }, - "node_modules/@rollup/plugin-node-resolve/node_modules/@rollup/pluginutils": { - "version": "5.3.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0", - "estree-walker": "^2.0.2", - "picomatch": "^4.0.2" - }, - "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" - }, - "peerDependenciesMeta": { - "rollup": { - "optional": true - } - } - }, - "node_modules/@rollup/plugin-node-resolve/node_modules/@rollup/pluginutils/node_modules/estree-walker": { - "version": "2.0.2", - "dev": true, - "license": "MIT" - }, - "node_modules/@rollup/plugin-replace": { - "version": "2.4.2", - "dev": true, - "license": "MIT", - "dependencies": { - "@rollup/pluginutils": "^3.1.0", - "magic-string": "^0.25.7" - }, - "peerDependencies": { - "rollup": "^1.20.0 || ^2.0.0" - } - }, - "node_modules/@rollup/plugin-terser": { - "version": "0.4.4", - "dev": true, - "license": "MIT", - "dependencies": { - "serialize-javascript": "^6.0.1", - "smob": "^1.0.0", - "terser": "^5.17.4" - }, - "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "rollup": "^2.0.0||^3.0.0||^4.0.0" - }, - "peerDependenciesMeta": { - "rollup": { - "optional": true - } - } - }, - "node_modules/@rollup/pluginutils": { - "version": "3.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/estree": "0.0.39", - "estree-walker": "^1.0.1", - "picomatch": "^2.2.2" - }, - "engines": { - "node": ">= 8.0.0" - }, - "peerDependencies": { - "rollup": "^1.20.0||^2.0.0" - } - }, - "node_modules/@rollup/pluginutils/node_modules/@types/estree": { - "version": "0.0.39", - "dev": true, - "license": "MIT" - }, - "node_modules/@rollup/pluginutils/node_modules/picomatch": { - "version": "2.3.2", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.60.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.60.0.tgz", - "integrity": "sha512-WOhNW9K8bR3kf4zLxbfg6Pxu2ybOUbB2AjMDHSQx86LIF4rH4Ft7vmMwNt0loO0eonglSNy4cpD3MKXXKQu0/A==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@rollup/rollup-android-arm64": { - "version": "4.60.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.60.0.tgz", - "integrity": "sha512-u6JHLll5QKRvjciE78bQXDmqRqNs5M/3GVqZeMwvmjaNODJih/WIrJlFVEihvV0MiYFmd+ZyPr9wxOVbPAG2Iw==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.60.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.60.0.tgz", - "integrity": "sha512-qEF7CsKKzSRc20Ciu2Zw1wRrBz4g56F7r/vRwY430UPp/nt1x21Q/fpJ9N5l47WWvJlkNCPJz3QRVw008fi7yA==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.60.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.60.0.tgz", - "integrity": "sha512-WADYozJ4QCnXCH4wPB+3FuGmDPoFseVCUrANmA5LWwGmC6FL14BWC7pcq+FstOZv3baGX65tZ378uT6WG8ynTw==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.60.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.60.0.tgz", - "integrity": "sha512-6b8wGHJlDrGeSE3aH5mGNHBjA0TTkxdoNHik5EkvPHCt351XnigA4pS7Wsj/Eo9Y8RBU6f35cjN9SYmCFBtzxw==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] - }, - "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.60.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.60.0.tgz", - "integrity": "sha512-h25Ga0t4jaylMB8M/JKAyrvvfxGRjnPQIR8lnCayyzEjEOx2EJIlIiMbhpWxDRKGKF8jbNH01NnN663dH638mA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] - }, - "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.60.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.60.0.tgz", - "integrity": "sha512-RzeBwv0B3qtVBWtcuABtSuCzToo2IEAIQrcyB/b2zMvBWVbjo8bZDjACUpnaafaxhTw2W+imQbP2BD1usasK4g==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.60.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.60.0.tgz", - "integrity": "sha512-Sf7zusNI2CIU1HLzuu9Tc5YGAHEZs5Lu7N1ssJG4Tkw6e0MEsN7NdjUDDfGNHy2IU+ENyWT+L2obgWiguWibWQ==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.60.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.60.0.tgz", - "integrity": "sha512-DX2x7CMcrJzsE91q7/O02IJQ5/aLkVtYFryqCjduJhUfGKG6yJV8hxaw8pZa93lLEpPTP/ohdN4wFz7yp/ry9A==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.60.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.60.0.tgz", - "integrity": "sha512-09EL+yFVbJZlhcQfShpswwRZ0Rg+z/CsSELFCnPt3iK+iqwGsI4zht3secj5vLEs957QvFFXnzAT0FFPIxSrkQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-loong64-gnu": { - "version": "4.60.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.60.0.tgz", - "integrity": "sha512-i9IcCMPr3EXm8EQg5jnja0Zyc1iFxJjZWlb4wr7U2Wx/GrddOuEafxRdMPRYVaXjgbhvqalp6np07hN1w9kAKw==", - "cpu": [ - "loong64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-loong64-musl": { - "version": "4.60.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.60.0.tgz", - "integrity": "sha512-DGzdJK9kyJ+B78MCkWeGnpXJ91tK/iKA6HwHxF4TAlPIY7GXEvMe8hBFRgdrR9Ly4qebR/7gfUs9y2IoaVEyog==", - "cpu": [ - "loong64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-ppc64-gnu": { - "version": "4.60.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.60.0.tgz", - "integrity": "sha512-RwpnLsqC8qbS8z1H1AxBA1H6qknR4YpPR9w2XX0vo2Sz10miu57PkNcnHVaZkbqyw/kUWfKMI73jhmfi9BRMUQ==", - "cpu": [ - "ppc64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-ppc64-musl": { - "version": "4.60.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.60.0.tgz", - "integrity": "sha512-Z8pPf54Ly3aqtdWC3G4rFigZgNvd+qJlOE52fmko3KST9SoGfAdSRCwyoyG05q1HrrAblLbk1/PSIV+80/pxLg==", - "cpu": [ - "ppc64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.60.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.60.0.tgz", - "integrity": "sha512-3a3qQustp3COCGvnP4SvrMHnPQ9d1vzCakQVRTliaz8cIp/wULGjiGpbcqrkv0WrHTEp8bQD/B3HBjzujVWLOA==", - "cpu": [ - "riscv64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.60.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.60.0.tgz", - "integrity": "sha512-pjZDsVH/1VsghMJ2/kAaxt6dL0psT6ZexQVrijczOf+PeP2BUqTHYejk3l6TlPRydggINOeNRhvpLa0AYpCWSQ==", - "cpu": [ - "riscv64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.60.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.60.0.tgz", - "integrity": "sha512-3ObQs0BhvPgiUVZrN7gqCSvmFuMWvWvsjG5ayJ3Lraqv+2KhOsp+pUbigqbeWqueGIsnn+09HBw27rJ+gYK4VQ==", - "cpu": [ - "s390x" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.60.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.60.0.tgz", - "integrity": "sha512-EtylprDtQPdS5rXvAayrNDYoJhIz1/vzN2fEubo3yLE7tfAw+948dO0g4M0vkTVFhKojnF+n6C8bDNe+gDRdTg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.60.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.60.0.tgz", - "integrity": "sha512-k09oiRCi/bHU9UVFqD17r3eJR9bn03TyKraCrlz5ULFJGdJGi7VOmm9jl44vOJvRJ6P7WuBi/s2A97LxxHGIdw==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-openbsd-x64": { - "version": "4.60.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.60.0.tgz", - "integrity": "sha512-1o/0/pIhozoSaDJoDcec+IVLbnRtQmHwPV730+AOD29lHEEo4F5BEUB24H0OBdhbBBDwIOSuf7vgg0Ywxdfiiw==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ] - }, - "node_modules/@rollup/rollup-openharmony-arm64": { - "version": "4.60.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.60.0.tgz", - "integrity": "sha512-pESDkos/PDzYwtyzB5p/UoNU/8fJo68vcXM9ZW2V0kjYayj1KaaUfi1NmTUTUpMn4UhU4gTuK8gIaFO4UGuMbA==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ] - }, - "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.60.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.60.0.tgz", - "integrity": "sha512-hj1wFStD7B1YBeYmvY+lWXZ7ey73YGPcViMShYikqKT1GtstIKQAtfUI6yrzPjAy/O7pO0VLXGmUVWXQMaYgTQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.60.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.60.0.tgz", - "integrity": "sha512-SyaIPFoxmUPlNDq5EHkTbiKzmSEmq/gOYFI/3HHJ8iS/v1mbugVa7dXUzcJGQfoytp9DJFLhHH4U3/eTy2Bq4w==", - "cpu": [ - "ia32" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-x64-gnu": { - "version": "4.60.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.60.0.tgz", - "integrity": "sha512-RdcryEfzZr+lAr5kRm2ucN9aVlCCa2QNq4hXelZxb8GG0NJSazq44Z3PCCc8wISRuCVnGs0lQJVX5Vp6fKA+IA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.60.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.60.0.tgz", - "integrity": "sha512-PrsWNQ8BuE00O3Xsx3ALh2Df8fAj9+cvvX9AIA6o4KpATR98c9mud4XtDWVvsEuyia5U4tVSTKygawyJkjm60w==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@sindresorhus/is": { - "version": "7.2.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sindresorhus/is?sponsor=1" - } - }, - "node_modules/@speed-highlight/core": { - "version": "1.2.15", - "dev": true, - "license": "CC0-1.0" - }, - "node_modules/@standard-schema/spec": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", - "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", - "license": "MIT" - }, - "node_modules/@surma/rollup-plugin-off-main-thread": { - "version": "2.2.3", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "ejs": "^3.1.6", - "json5": "^2.2.0", - "magic-string": "^0.25.0", - "string.prototype.matchall": "^4.0.6" - } - }, - "node_modules/@svta/common-media-library": { - "version": "0.18.1", - "license": "Apache-2.0", - "engines": { - "node": ">=20" - } - }, - "node_modules/@testing-library/dom": { - "version": "10.4.1", - "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.1.tgz", - "integrity": "sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.10.4", - "@babel/runtime": "^7.12.5", - "@types/aria-query": "^5.0.1", - "aria-query": "5.3.0", - "dom-accessibility-api": "^0.5.9", - "lz-string": "^1.5.0", - "picocolors": "1.1.1", - "pretty-format": "^27.0.2" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@trapezedev/gradle-parse": { - "version": "7.1.3", - "dev": true, - "license": "SEE LICENSE" - }, - "node_modules/@trapezedev/project": { - "version": "7.1.3", - "dev": true, - "license": "SEE LICENSE", - "dependencies": { - "@ionic/utils-fs": "^3.1.5", - "@ionic/utils-subprocess": "^2.1.8", - "@prettier/plugin-xml": "^2.2.0", - "@trapezedev/gradle-parse": "7.1.3", - "@xmldom/xmldom": "^0.7.5", - "conventional-changelog": "^3.1.4", - "cross-spawn": "^7.0.3", - "diff": "^5.1.0", - "env-paths": "^3.0.0", - "gradle-to-js": "^2.0.0", - "ini": "^2.0.0", - "kleur": "^4.1.5", - "lodash": "^4.17.21", - "mergexml": "^1.2.3", - "plist": "^3.0.4", - "prettier": "^2.7.1", - "prompts": "^2.4.2", - "replace": "^1.1.0", - "tempy": "^1.0.1", - "tmp": "^0.2.1", - "ts-node": "^10.2.1", - "xcode": "^3.0.1", - "xml-js": "^1.6.11", - "xpath": "^0.0.32", - "yargs": "^17.2.1" - } - }, - "node_modules/@trapezedev/project/node_modules/@ionic/utils-subprocess": { - "version": "2.1.14", - "dev": true, - "license": "MIT", - "dependencies": { - "@ionic/utils-array": "2.1.6", - "@ionic/utils-fs": "3.1.7", - "@ionic/utils-process": "2.1.11", - "@ionic/utils-stream": "3.1.6", - "@ionic/utils-terminal": "2.3.4", - "cross-spawn": "^7.0.3", - "debug": "^4.0.0", - "tslib": "^2.0.1" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@trapezedev/project/node_modules/@ionic/utils-subprocess/node_modules/@ionic/utils-process": { - "version": "2.1.11", - "dev": true, - "license": "MIT", - "dependencies": { - "@ionic/utils-object": "2.1.6", - "@ionic/utils-terminal": "2.3.4", - "debug": "^4.0.0", - "signal-exit": "^3.0.3", - "tree-kill": "^1.2.2", - "tslib": "^2.0.1" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@trapezedev/project/node_modules/@ionic/utils-subprocess/node_modules/@ionic/utils-stream": { - "version": "3.1.6", - "dev": true, - "license": "MIT", - "dependencies": { - "debug": "^4.0.0", - "tslib": "^2.0.1" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@trapezedev/project/node_modules/@ionic/utils-subprocess/node_modules/@ionic/utils-terminal": { - "version": "2.3.4", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/slice-ansi": "^4.0.0", - "debug": "^4.0.0", - "signal-exit": "^3.0.3", - "slice-ansi": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0", - "tslib": "^2.0.1", - "untildify": "^4.0.0", - "wrap-ansi": "^7.0.0" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@trapezedev/project/node_modules/@ionic/utils-subprocess/node_modules/debug": { - "version": "4.4.3", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/@trapezedev/project/node_modules/@ionic/utils-subprocess/node_modules/debug/node_modules/ms": { - "version": "2.1.3", - "dev": true, - "license": "MIT" - }, - "node_modules/@trapezedev/project/node_modules/@ionic/utils-subprocess/node_modules/tslib": { - "version": "2.8.1", - "dev": true, - "license": "0BSD" - }, - "node_modules/@trapezedev/project/node_modules/env-paths": { - "version": "3.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@trapezedev/project/node_modules/prettier": { - "version": "2.8.8", - "dev": true, - "license": "MIT", - "bin": { - "prettier": "bin-prettier.js" - }, - "engines": { - "node": ">=10.13.0" - }, - "funding": { - "url": "https://github.com/prettier/prettier?sponsor=1" - } - }, - "node_modules/@tsconfig/node10": { - "version": "1.0.12", - "dev": true, - "license": "MIT" - }, - "node_modules/@tsconfig/node12": { - "version": "1.0.11", - "dev": true, - "license": "MIT" - }, - "node_modules/@tsconfig/node14": { - "version": "1.0.3", - "dev": true, - "license": "MIT" - }, - "node_modules/@tsconfig/node16": { - "version": "1.0.4", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/aria-query": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz", - "integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/chai": { - "version": "5.2.3", - "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", - "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", - "license": "MIT", - "dependencies": { - "@types/deep-eql": "*", - "assertion-error": "^2.0.1" - } - }, - "node_modules/@types/deep-eql": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", - "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", - "license": "MIT" - }, - "node_modules/@types/estree": { - "version": "1.0.8", - "license": "MIT" - }, - "node_modules/@types/fs-extra": { - "version": "8.1.5", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/json-schema": { - "version": "7.0.15", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/minimist": { - "version": "1.2.5", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/node": { - "version": "25.5.0", - "devOptional": true, - "license": "MIT", - "dependencies": { - "undici-types": "~7.18.0" - } - }, - "node_modules/@types/normalize-package-data": { - "version": "2.4.4", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/resolve": { - "version": "1.20.2", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/sarif": { - "version": "2.1.7", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/slice-ansi": { - "version": "4.0.0", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/trusted-types": { - "version": "2.0.7", - "license": "MIT" - }, - "node_modules/@uimaxbai/am-lyrics": { - "version": "1.1.4", - "license": "MPL-2.0", - "dependencies": { - "@babel/runtime": "^7.27.6", - "lit": "^3.1.4" - }, - "peerDependencies": { - "@lit/react": "^1.0.0", - "react": ">=17.0.0" - }, - "peerDependenciesMeta": { - "@lit/react": { - "optional": true - }, - "react": { - "optional": true - } - } - }, - "node_modules/@vitest/browser": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/@vitest/browser/-/browser-4.1.2.tgz", - "integrity": "sha512-CwdIf90LNf1Zitgqy63ciMAzmyb4oIGs8WZ40VGYrWkssQKeEKr32EzO8MKUrDPPcPVHFI9oQ5ni2Hp24NaNRQ==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "@blazediff/core": "1.9.1", - "@vitest/mocker": "4.1.2", - "@vitest/utils": "4.1.2", - "magic-string": "^0.30.21", - "pngjs": "^7.0.0", - "sirv": "^3.0.2", - "tinyrainbow": "^3.1.0", - "ws": "^8.19.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - }, - "peerDependencies": { - "vitest": "4.1.2" - } - }, - "node_modules/@vitest/browser-playwright": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/@vitest/browser-playwright/-/browser-playwright-4.1.2.tgz", - "integrity": "sha512-N0Z2HzMLvMR6k/tWPTS6Q/DaRscrkax/f2f9DIbNQr+Cd1l4W4wTf/I6S983PAMr0tNqqoTL+xNkLh9M5vbkLg==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "@vitest/browser": "4.1.2", - "@vitest/mocker": "4.1.2", - "tinyrainbow": "^3.1.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - }, - "peerDependencies": { - "playwright": "*", - "vitest": "4.1.2" - }, - "peerDependenciesMeta": { - "playwright": { - "optional": false - } - } - }, - "node_modules/@vitest/browser/node_modules/magic-string": { - "version": "0.30.21", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", - "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.5" - } - }, - "node_modules/@vitest/browser/node_modules/ws": { - "version": "8.20.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.20.0.tgz", - "integrity": "sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA==", - "devOptional": true, - "license": "MIT", - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": ">=5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, - "node_modules/@vitest/expect": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.2.tgz", - "integrity": "sha512-gbu+7B0YgUJ2nkdsRJrFFW6X7NTP44WlhiclHniUhxADQJH5Szt9mZ9hWnJPJ8YwOK5zUOSSlSvyzRf0u1DSBQ==", - "license": "MIT", - "dependencies": { - "@standard-schema/spec": "^1.1.0", - "@types/chai": "^5.2.2", - "@vitest/spy": "4.1.2", - "@vitest/utils": "4.1.2", - "chai": "^6.2.2", - "tinyrainbow": "^3.1.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/mocker": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.2.tgz", - "integrity": "sha512-Ize4iQtEALHDttPRCmN+FKqOl2vxTiNUhzobQFFt/BM1lRUTG7zRCLOykG/6Vo4E4hnUdfVLo5/eqKPukcWW7Q==", - "license": "MIT", - "dependencies": { - "@vitest/spy": "4.1.2", - "estree-walker": "^3.0.3", - "magic-string": "^0.30.21" - }, - "funding": { - "url": "https://opencollective.com/vitest" - }, - "peerDependencies": { - "msw": "^2.4.9", - "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" - }, - "peerDependenciesMeta": { - "msw": { - "optional": true - }, - "vite": { - "optional": true - } - } - }, - "node_modules/@vitest/mocker/node_modules/estree-walker": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", - "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0" - } - }, - "node_modules/@vitest/mocker/node_modules/magic-string": { - "version": "0.30.21", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", - "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.5" - } - }, - "node_modules/@vitest/pretty-format": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.2.tgz", - "integrity": "sha512-dwQga8aejqeuB+TvXCMzSQemvV9hNEtDDpgUKDzOmNQayl2OG241PSWeJwKRH3CiC+sESrmoFd49rfnq7T4RnA==", - "license": "MIT", - "dependencies": { - "tinyrainbow": "^3.1.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/runner": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.2.tgz", - "integrity": "sha512-Gr+FQan34CdiYAwpGJmQG8PgkyFVmARK8/xSijia3eTFgVfpcpztWLuP6FttGNfPLJhaZVP/euvujeNYar36OQ==", - "license": "MIT", - "dependencies": { - "@vitest/utils": "4.1.2", - "pathe": "^2.0.3" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/snapshot": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.2.tgz", - "integrity": "sha512-g7yfUmxYS4mNxk31qbOYsSt2F4m1E02LFqO53Xpzg3zKMhLAPZAjjfyl9e6z7HrW6LvUdTwAQR3HHfLjpko16A==", - "license": "MIT", - "dependencies": { - "@vitest/pretty-format": "4.1.2", - "@vitest/utils": "4.1.2", - "magic-string": "^0.30.21", - "pathe": "^2.0.3" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/snapshot/node_modules/magic-string": { - "version": "0.30.21", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", - "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.5" - } - }, - "node_modules/@vitest/spy": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.2.tgz", - "integrity": "sha512-DU4fBnbVCJGNBwVA6xSToNXrkZNSiw59H8tcuUspVMsBDBST4nfvsPsEHDHGtWRRnqBERBQu7TrTKskmjqTXKA==", - "license": "MIT", - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/utils": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.2.tgz", - "integrity": "sha512-xw2/TiX82lQHA06cgbqRKFb5lCAy3axQ4H4SoUFhUsg+wztiet+co86IAMDtF6Vm1hc7J6j09oh/rgDn+JdKIQ==", - "license": "MIT", - "dependencies": { - "@vitest/pretty-format": "4.1.2", - "convert-source-map": "^2.0.0", - "tinyrainbow": "^3.1.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/web-worker": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/@vitest/web-worker/-/web-worker-4.1.2.tgz", - "integrity": "sha512-P5XxkZuiVcN8NGePi53VS3gJUHu3J0luyPPuWBirZB3d6Tjfqy4+AW+3vCQTMr5KnEfZXBmEXWh3o9K58bSoAw==", - "license": "MIT", - "dependencies": { - "obug": "^2.1.1" - }, - "funding": { - "url": "https://opencollective.com/vitest" - }, - "peerDependencies": { - "vitest": "4.1.2" - } - }, - "node_modules/@xml-tools/parser": { - "version": "1.0.11", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "chevrotain": "7.1.1" - } - }, - "node_modules/@xmldom/xmldom": { - "version": "0.7.13", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/acorn": { - "version": "8.16.0", - "devOptional": true, - "license": "MIT", - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/acorn-jsx": { - "version": "5.3.2", - "dev": true, - "license": "MIT", - "peerDependencies": { - "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" - } - }, - "node_modules/acorn-walk": { - "version": "8.3.5", - "dev": true, - "license": "MIT", - "dependencies": { - "acorn": "^8.11.0" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/add-stream": { - "version": "1.0.0", - "dev": true, - "license": "MIT" - }, - "node_modules/aggregate-error": { - "version": "3.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "clean-stack": "^2.0.0", - "indent-string": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/ajv": { - "version": "8.18.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", - "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.3", - "fast-uri": "^3.0.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/ansi-regex": { - "version": "5.0.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/ansi-styles": { - "version": "4.3.0", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/appwrite": { - "version": "23.0.0", - "license": "BSD-3-Clause", - "dependencies": { - "json-bigint": "1.0.0" - } - }, - "node_modules/arg": { - "version": "4.1.3", - "dev": true, - "license": "MIT" - }, - "node_modules/argparse": { - "version": "2.0.1", - "dev": true, - "license": "Python-2.0" - }, - "node_modules/aria-query": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz", - "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "dequal": "^2.0.3" - } - }, - "node_modules/array-buffer-byte-length": { - "version": "1.0.2", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "is-array-buffer": "^3.0.5" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/array-ify": { - "version": "1.0.0", - "dev": true, - "license": "MIT" - }, - "node_modules/array-union": { - "version": "2.1.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/arraybuffer.prototype.slice": { - "version": "1.0.4", - "dev": true, - "license": "MIT", - "dependencies": { - "array-buffer-byte-length": "^1.0.1", - "call-bind": "^1.0.8", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.5", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.6", - "is-array-buffer": "^3.0.4" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/arrify": { - "version": "1.0.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/asap": { - "version": "2.0.6", - "dev": true, - "license": "MIT" - }, - "node_modules/assertion-error": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", - "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", - "license": "MIT", - "engines": { - "node": ">=12" - } - }, - "node_modules/astral-regex": { - "version": "2.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/async": { - "version": "3.2.6", - "dev": true, - "license": "MIT" - }, - "node_modules/async-function": { - "version": "1.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/at-least-node": { - "version": "1.0.0", - "dev": true, - "license": "ISC", - "engines": { - "node": ">= 4.0.0" - } - }, - "node_modules/available-typed-arrays": { - "version": "1.0.7", - "dev": true, - "license": "MIT", - "dependencies": { - "possible-typed-array-names": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/b4a": { - "version": "1.8.0", - "dev": true, - "license": "Apache-2.0", - "peerDependencies": { - "react-native-b4a": "*" - }, - "peerDependenciesMeta": { - "react-native-b4a": { - "optional": true - } - } - }, - "node_modules/babel-plugin-polyfill-corejs2": { - "version": "0.4.17", - "dev": true, - "license": "MIT", - "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" - } - }, - "node_modules/babel-plugin-polyfill-corejs2/node_modules/semver": { - "version": "6.3.1", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/babel-plugin-polyfill-corejs3": { - "version": "0.14.2", - "dev": true, - "license": "MIT", - "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" - } - }, - "node_modules/babel-plugin-polyfill-regenerator": { - "version": "0.6.8", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.6.8" - }, - "peerDependencies": { - "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" - } - }, - "node_modules/babel-runtime": { - "version": "6.26.0", - "license": "MIT", - "dependencies": { - "core-js": "^2.4.0", - "regenerator-runtime": "^0.11.0" - } - }, - "node_modules/balanced-match": { - "version": "2.0.0", - "dev": true, - "license": "MIT" - }, - "node_modules/bare-events": { - "version": "2.8.2", - "dev": true, - "license": "Apache-2.0", - "peerDependencies": { - "bare-abort-controller": "*" - }, - "peerDependenciesMeta": { - "bare-abort-controller": { - "optional": true - } - } - }, - "node_modules/bare-fs": { - "version": "4.5.6", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "bare-events": "^2.5.4", - "bare-path": "^3.0.0", - "bare-stream": "^2.6.4", - "bare-url": "^2.2.2", - "fast-fifo": "^1.3.2" - }, - "engines": { - "bare": ">=1.16.0" - }, - "peerDependencies": { - "bare-buffer": "*" - }, - "peerDependenciesMeta": { - "bare-buffer": { - "optional": true - } - } - }, - "node_modules/bare-os": { - "version": "3.8.0", - "dev": true, - "license": "Apache-2.0", - "engines": { - "bare": ">=1.14.0" - } - }, - "node_modules/bare-path": { - "version": "3.0.0", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "bare-os": "^3.0.1" - } - }, - "node_modules/bare-stream": { - "version": "2.11.0", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "streamx": "^2.25.0", - "teex": "^1.0.1" - }, - "peerDependencies": { - "bare-abort-controller": "*", - "bare-buffer": "*", - "bare-events": "*" - }, - "peerDependenciesMeta": { - "bare-abort-controller": { - "optional": true - }, - "bare-buffer": { - "optional": true - }, - "bare-events": { - "optional": true - } - } - }, - "node_modules/bare-url": { - "version": "2.4.0", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "bare-path": "^3.0.0" - } - }, - "node_modules/base64-js": { - "version": "1.5.1", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, - "node_modules/baseline-browser-mapping": { - "version": "2.10.11", - "dev": true, - "license": "Apache-2.0", - "bin": { - "baseline-browser-mapping": "dist/cli.cjs" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/big-integer": { - "version": "1.6.52", - "dev": true, - "license": "Unlicense", - "engines": { - "node": ">=0.6" - } - }, - "node_modules/bignumber.js": { - "version": "9.3.1", - "license": "MIT", - "engines": { - "node": "*" - } - }, - "node_modules/bl": { - "version": "4.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "buffer": "^5.5.0", - "inherits": "^2.0.4", - "readable-stream": "^3.4.0" - } - }, - "node_modules/boolbase": { - "version": "1.0.0", - "license": "ISC" - }, - "node_modules/bplist-creator": { - "version": "0.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "stream-buffers": "2.2.x" - } - }, - "node_modules/bplist-parser": { - "version": "0.3.2", - "dev": true, - "license": "MIT", - "dependencies": { - "big-integer": "1.6.x" - }, - "engines": { - "node": ">= 5.10.0" - } - }, - "node_modules/brace-expansion": { - "version": "1.1.13", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/brace-expansion/node_modules/balanced-match": { - "version": "1.0.2", - "dev": true, - "license": "MIT" - }, - "node_modules/braces": { - "version": "3.0.3", - "dev": true, - "license": "MIT", - "dependencies": { - "fill-range": "^7.1.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/browserslist": { - "version": "4.28.1", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "baseline-browser-mapping": "^2.9.0", - "caniuse-lite": "^1.0.30001759", - "electron-to-chromium": "^1.5.263", - "node-releases": "^2.0.27", - "update-browserslist-db": "^1.2.0" - }, - "bin": { - "browserslist": "cli.js" - }, - "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" - } - }, - "node_modules/buffer": { - "version": "5.7.1", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.1.13" - } - }, - "node_modules/buffer-crc32": { - "version": "0.2.13", - "dev": true, - "license": "MIT", - "engines": { - "node": "*" - } - }, - "node_modules/buffer-from": { - "version": "1.1.2", - "devOptional": true, - "license": "MIT" - }, - "node_modules/butterchurn": { - "version": "2.6.7", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.0.0", - "ecma-proposal-math-extensions": "0.0.2" - } - }, - "node_modules/butterchurn-presets": { - "version": "2.4.7", - "license": "MIT", - "dependencies": { - "babel-runtime": "^6.26.0", - "ecma-proposal-math-extensions": "0.0.2", - "lodash": "^4.17.4" - } - }, - "node_modules/cacheable": { - "version": "2.3.4", - "dev": true, - "license": "MIT", - "dependencies": { - "@cacheable/memory": "^2.0.8", - "@cacheable/utils": "^2.4.0", - "hookified": "^1.15.0", - "keyv": "^5.6.0", - "qified": "^0.9.0" - } - }, - "node_modules/call-bind": { - "version": "1.0.8", - "dev": true, - "license": "MIT", - "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" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/call-bind-apply-helpers": { - "version": "1.0.2", - "dev": true, - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/call-bound": { - "version": "1.0.4", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "get-intrinsic": "^1.3.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/callsites": { - "version": "3.1.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/camelcase": { - "version": "5.3.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/camelcase-keys": { - "version": "6.2.2", - "dev": true, - "license": "MIT", - "dependencies": { - "camelcase": "^5.3.1", - "map-obj": "^4.0.0", - "quick-lru": "^4.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/caniuse-lite": { - "version": "1.0.30001781", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/caniuse-lite" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "CC-BY-4.0" - }, - "node_modules/chai": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz", - "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==", - "license": "MIT", - "engines": { - "node": ">=18" - } - }, - "node_modules/chalk": { - "version": "4.1.2", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/chevrotain": { - "version": "7.1.1", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "regexp-to-ast": "0.5.0" - } - }, - "node_modules/chownr": { - "version": "3.0.0", - "dev": true, - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=18" - } - }, - "node_modules/clean-stack": { - "version": "2.2.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/client-zip": { - "version": "2.5.0", - "license": "MIT" - }, - "node_modules/cliui": { - "version": "8.0.1", - "dev": true, - "license": "ISC", - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/color": { - "version": "4.2.3", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1", - "color-string": "^1.9.0" - }, - "engines": { - "node": ">=12.5.0" - } - }, - "node_modules/color-convert": { - "version": "2.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/color-name": { - "version": "1.1.4", - "dev": true, - "license": "MIT" - }, - "node_modules/color-string": { - "version": "1.9.1", - "dev": true, - "license": "MIT", - "dependencies": { - "color-name": "^1.0.0", - "simple-swizzle": "^0.2.2" - } - }, - "node_modules/colord": { - "version": "2.9.3", - "dev": true, - "license": "MIT" - }, - "node_modules/commander": { - "version": "8.3.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 12" - } - }, - "node_modules/common-tags": { - "version": "1.8.2", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/compare-func": { - "version": "2.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "array-ify": "^1.0.0", - "dot-prop": "^5.1.0" - } - }, - "node_modules/concat-map": { - "version": "0.0.1", - "dev": true, - "license": "MIT" - }, - "node_modules/conventional-changelog": { - "version": "3.1.25", - "dev": true, - "license": "MIT", - "dependencies": { - "conventional-changelog-angular": "^5.0.12", - "conventional-changelog-atom": "^2.0.8", - "conventional-changelog-codemirror": "^2.0.8", - "conventional-changelog-conventionalcommits": "^4.5.0", - "conventional-changelog-core": "^4.2.1", - "conventional-changelog-ember": "^2.0.9", - "conventional-changelog-eslint": "^3.0.9", - "conventional-changelog-express": "^2.0.6", - "conventional-changelog-jquery": "^3.0.11", - "conventional-changelog-jshint": "^2.0.9", - "conventional-changelog-preset-loader": "^2.3.4" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/conventional-changelog-angular": { - "version": "5.0.13", - "dev": true, - "license": "ISC", - "dependencies": { - "compare-func": "^2.0.0", - "q": "^1.5.1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/conventional-changelog-atom": { - "version": "2.0.8", - "dev": true, - "license": "ISC", - "dependencies": { - "q": "^1.5.1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/conventional-changelog-codemirror": { - "version": "2.0.8", - "dev": true, - "license": "ISC", - "dependencies": { - "q": "^1.5.1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/conventional-changelog-conventionalcommits": { - "version": "4.6.3", - "dev": true, - "license": "ISC", - "dependencies": { - "compare-func": "^2.0.0", - "lodash": "^4.17.15", - "q": "^1.5.1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/conventional-changelog-core": { - "version": "4.2.4", - "dev": true, - "license": "MIT", - "dependencies": { - "add-stream": "^1.0.0", - "conventional-changelog-writer": "^5.0.0", - "conventional-commits-parser": "^3.2.0", - "dateformat": "^3.0.0", - "get-pkg-repo": "^4.0.0", - "git-raw-commits": "^2.0.8", - "git-remote-origin-url": "^2.0.0", - "git-semver-tags": "^4.1.1", - "lodash": "^4.17.15", - "normalize-package-data": "^3.0.0", - "q": "^1.5.1", - "read-pkg": "^3.0.0", - "read-pkg-up": "^3.0.0", - "through2": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/conventional-changelog-ember": { - "version": "2.0.9", - "dev": true, - "license": "ISC", - "dependencies": { - "q": "^1.5.1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/conventional-changelog-eslint": { - "version": "3.0.9", - "dev": true, - "license": "ISC", - "dependencies": { - "q": "^1.5.1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/conventional-changelog-express": { - "version": "2.0.6", - "dev": true, - "license": "ISC", - "dependencies": { - "q": "^1.5.1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/conventional-changelog-jquery": { - "version": "3.0.11", - "dev": true, - "license": "ISC", - "dependencies": { - "q": "^1.5.1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/conventional-changelog-jshint": { - "version": "2.0.9", - "dev": true, - "license": "ISC", - "dependencies": { - "compare-func": "^2.0.0", - "q": "^1.5.1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/conventional-changelog-preset-loader": { - "version": "2.3.4", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - } - }, - "node_modules/conventional-changelog-writer": { - "version": "5.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "conventional-commits-filter": "^2.0.7", - "dateformat": "^3.0.0", - "handlebars": "^4.7.7", - "json-stringify-safe": "^5.0.1", - "lodash": "^4.17.15", - "meow": "^8.0.0", - "semver": "^6.0.0", - "split": "^1.0.0", - "through2": "^4.0.0" - }, - "bin": { - "conventional-changelog-writer": "cli.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/conventional-changelog-writer/node_modules/meow": { - "version": "8.1.2", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/minimist": "^1.2.0", - "camelcase-keys": "^6.2.2", - "decamelize-keys": "^1.1.0", - "hard-rejection": "^2.1.0", - "minimist-options": "4.1.0", - "normalize-package-data": "^3.0.0", - "read-pkg-up": "^7.0.1", - "redent": "^3.0.0", - "trim-newlines": "^3.0.0", - "type-fest": "^0.18.0", - "yargs-parser": "^20.2.3" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/conventional-changelog-writer/node_modules/meow/node_modules/read-pkg-up": { - "version": "7.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "find-up": "^4.1.0", - "read-pkg": "^5.2.0", - "type-fest": "^0.8.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/conventional-changelog-writer/node_modules/meow/node_modules/read-pkg-up/node_modules/find-up": { - "version": "4.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/conventional-changelog-writer/node_modules/meow/node_modules/read-pkg-up/node_modules/find-up/node_modules/locate-path": { - "version": "5.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "p-locate": "^4.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/conventional-changelog-writer/node_modules/meow/node_modules/read-pkg-up/node_modules/find-up/node_modules/locate-path/node_modules/p-locate": { - "version": "4.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "p-limit": "^2.2.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/conventional-changelog-writer/node_modules/meow/node_modules/read-pkg-up/node_modules/find-up/node_modules/locate-path/node_modules/p-locate/node_modules/p-limit": { - "version": "2.3.0", - "dev": true, - "license": "MIT", - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/conventional-changelog-writer/node_modules/meow/node_modules/read-pkg-up/node_modules/read-pkg": { - "version": "5.2.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/normalize-package-data": "^2.4.0", - "normalize-package-data": "^2.5.0", - "parse-json": "^5.0.0", - "type-fest": "^0.6.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/conventional-changelog-writer/node_modules/meow/node_modules/read-pkg-up/node_modules/read-pkg/node_modules/normalize-package-data": { - "version": "2.5.0", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "hosted-git-info": "^2.1.4", - "resolve": "^1.10.0", - "semver": "2 || 3 || 4 || 5", - "validate-npm-package-license": "^3.0.1" - } - }, - "node_modules/conventional-changelog-writer/node_modules/meow/node_modules/read-pkg-up/node_modules/read-pkg/node_modules/normalize-package-data/node_modules/hosted-git-info": { - "version": "2.8.9", - "dev": true, - "license": "ISC" - }, - "node_modules/conventional-changelog-writer/node_modules/meow/node_modules/read-pkg-up/node_modules/read-pkg/node_modules/normalize-package-data/node_modules/semver": { - "version": "5.7.2", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver" - } - }, - "node_modules/conventional-changelog-writer/node_modules/meow/node_modules/read-pkg-up/node_modules/read-pkg/node_modules/type-fest": { - "version": "0.6.0", - "dev": true, - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=8" - } - }, - "node_modules/conventional-changelog-writer/node_modules/meow/node_modules/read-pkg-up/node_modules/type-fest": { - "version": "0.8.1", - "dev": true, - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=8" - } - }, - "node_modules/conventional-changelog-writer/node_modules/meow/node_modules/type-fest": { - "version": "0.18.1", - "dev": true, - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/conventional-changelog-writer/node_modules/meow/node_modules/yargs-parser": { - "version": "20.2.9", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=10" - } - }, - "node_modules/conventional-changelog-writer/node_modules/semver": { - "version": "6.3.1", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/conventional-commits-filter": { - "version": "2.0.7", - "dev": true, - "license": "MIT", - "dependencies": { - "lodash.ismatch": "^4.4.0", - "modify-values": "^1.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/conventional-commits-parser": { - "version": "3.2.4", - "dev": true, - "license": "MIT", - "dependencies": { - "is-text-path": "^1.0.1", - "JSONStream": "^1.0.4", - "lodash": "^4.17.15", - "meow": "^8.0.0", - "split2": "^3.0.0", - "through2": "^4.0.0" - }, - "bin": { - "conventional-commits-parser": "cli.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/conventional-commits-parser/node_modules/meow": { - "version": "8.1.2", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/minimist": "^1.2.0", - "camelcase-keys": "^6.2.2", - "decamelize-keys": "^1.1.0", - "hard-rejection": "^2.1.0", - "minimist-options": "4.1.0", - "normalize-package-data": "^3.0.0", - "read-pkg-up": "^7.0.1", - "redent": "^3.0.0", - "trim-newlines": "^3.0.0", - "type-fest": "^0.18.0", - "yargs-parser": "^20.2.3" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/conventional-commits-parser/node_modules/meow/node_modules/read-pkg-up": { - "version": "7.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "find-up": "^4.1.0", - "read-pkg": "^5.2.0", - "type-fest": "^0.8.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/conventional-commits-parser/node_modules/meow/node_modules/read-pkg-up/node_modules/find-up": { - "version": "4.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/conventional-commits-parser/node_modules/meow/node_modules/read-pkg-up/node_modules/find-up/node_modules/locate-path": { - "version": "5.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "p-locate": "^4.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/conventional-commits-parser/node_modules/meow/node_modules/read-pkg-up/node_modules/find-up/node_modules/locate-path/node_modules/p-locate": { - "version": "4.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "p-limit": "^2.2.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/conventional-commits-parser/node_modules/meow/node_modules/read-pkg-up/node_modules/find-up/node_modules/locate-path/node_modules/p-locate/node_modules/p-limit": { - "version": "2.3.0", - "dev": true, - "license": "MIT", - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/conventional-commits-parser/node_modules/meow/node_modules/read-pkg-up/node_modules/read-pkg": { - "version": "5.2.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/normalize-package-data": "^2.4.0", - "normalize-package-data": "^2.5.0", - "parse-json": "^5.0.0", - "type-fest": "^0.6.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/conventional-commits-parser/node_modules/meow/node_modules/read-pkg-up/node_modules/read-pkg/node_modules/normalize-package-data": { - "version": "2.5.0", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "hosted-git-info": "^2.1.4", - "resolve": "^1.10.0", - "semver": "2 || 3 || 4 || 5", - "validate-npm-package-license": "^3.0.1" - } - }, - "node_modules/conventional-commits-parser/node_modules/meow/node_modules/read-pkg-up/node_modules/read-pkg/node_modules/normalize-package-data/node_modules/hosted-git-info": { - "version": "2.8.9", - "dev": true, - "license": "ISC" - }, - "node_modules/conventional-commits-parser/node_modules/meow/node_modules/read-pkg-up/node_modules/read-pkg/node_modules/normalize-package-data/node_modules/semver": { - "version": "5.7.2", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver" - } - }, - "node_modules/conventional-commits-parser/node_modules/meow/node_modules/read-pkg-up/node_modules/read-pkg/node_modules/type-fest": { - "version": "0.6.0", - "dev": true, - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=8" - } - }, - "node_modules/conventional-commits-parser/node_modules/meow/node_modules/read-pkg-up/node_modules/type-fest": { - "version": "0.8.1", - "dev": true, - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=8" - } - }, - "node_modules/conventional-commits-parser/node_modules/meow/node_modules/type-fest": { - "version": "0.18.1", - "dev": true, - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/conventional-commits-parser/node_modules/meow/node_modules/yargs-parser": { - "version": "20.2.9", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=10" - } - }, - "node_modules/conventional-commits-parser/node_modules/split2": { - "version": "3.2.2", - "dev": true, - "license": "ISC", - "dependencies": { - "readable-stream": "^3.0.0" - } - }, - "node_modules/convert-source-map": { - "version": "2.0.0", - "license": "MIT" - }, - "node_modules/cookie": { - "version": "1.1.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/cookie-session": { - "version": "2.1.1", - "license": "MIT", - "dependencies": { - "cookies": "0.9.1", - "debug": "3.2.7", - "on-headers": "~1.1.0", - "safe-buffer": "5.2.1" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/cookie-session/node_modules/debug": { - "version": "3.2.7", - "license": "MIT", - "dependencies": { - "ms": "^2.1.1" - } - }, - "node_modules/cookie-session/node_modules/debug/node_modules/ms": { - "version": "2.1.3", - "license": "MIT" - }, - "node_modules/cookies": { - "version": "0.9.1", - "license": "MIT", - "dependencies": { - "depd": "~2.0.0", - "keygrip": "~1.1.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/core-js": { - "version": "2.6.12", - "hasInstallScript": true, - "license": "MIT" - }, - "node_modules/core-js-compat": { - "version": "3.49.0", - "dev": true, - "license": "MIT", - "dependencies": { - "browserslist": "^4.28.1" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/core-js" - } - }, - "node_modules/core-util-is": { - "version": "1.0.3", - "dev": true, - "license": "MIT" - }, - "node_modules/cosmiconfig": { - "version": "9.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "env-paths": "^2.2.1", - "import-fresh": "^3.3.0", - "js-yaml": "^4.1.0", - "parse-json": "^5.2.0" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/d-fischer" - }, - "peerDependencies": { - "typescript": ">=4.9.5" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/create-require": { - "version": "1.1.1", - "dev": true, - "license": "MIT" - }, - "node_modules/cross-spawn": { - "version": "7.0.6", - "dev": true, - "license": "MIT", - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/crypto-random-string": { - "version": "2.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/css-functions-list": { - "version": "3.3.3", - "dev": true, - "license": "MIT", - "engines": { - "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", - "license": "MIT", - "dependencies": { - "mdn-data": "2.27.1", - "source-map-js": "^1.2.1" - }, - "engines": { - "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, - "license": "MIT", - "bin": { - "cssesc": "bin/cssesc" - }, - "engines": { - "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/dargs": { - "version": "7.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/data-view-buffer": { - "version": "1.0.2", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "es-errors": "^1.3.0", - "is-data-view": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/data-view-byte-length": { - "version": "1.0.2", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "es-errors": "^1.3.0", - "is-data-view": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/inspect-js" - } - }, - "node_modules/data-view-byte-offset": { - "version": "1.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "is-data-view": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/dateformat": { - "version": "3.0.3", - "dev": true, - "license": "MIT", - "engines": { - "node": "*" - } - }, - "node_modules/debug": { - "version": "4.3.4", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/decamelize": { - "version": "1.2.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/decamelize-keys": { - "version": "1.1.1", - "dev": true, - "license": "MIT", - "dependencies": { - "decamelize": "^1.1.0", - "map-obj": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/decamelize-keys/node_modules/map-obj": { - "version": "1.0.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/decompress-response": { - "version": "6.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "mimic-response": "^3.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/deep-extend": { - "version": "0.6.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/deep-is": { - "version": "0.1.4", - "dev": true, - "license": "MIT" - }, - "node_modules/deepmerge": { - "version": "4.3.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/define-data-property": { - "version": "1.1.4", - "dev": true, - "license": "MIT", - "dependencies": { - "es-define-property": "^1.0.0", - "es-errors": "^1.3.0", - "gopd": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/define-lazy-prop": { - "version": "2.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/define-properties": { - "version": "1.2.1", - "dev": true, - "license": "MIT", - "dependencies": { - "define-data-property": "^1.0.1", - "has-property-descriptors": "^1.0.0", - "object-keys": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/del": { - "version": "6.1.1", - "dev": true, - "license": "MIT", - "dependencies": { - "globby": "^11.0.1", - "graceful-fs": "^4.2.4", - "is-glob": "^4.0.1", - "is-path-cwd": "^2.2.0", - "is-path-inside": "^3.0.2", - "p-map": "^4.0.0", - "rimraf": "^3.0.2", - "slash": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/del/node_modules/rimraf": { - "version": "3.0.2", - "dev": true, - "license": "ISC", - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/del/node_modules/rimraf/node_modules/glob": { - "version": "7.2.3", - "dev": true, - "license": "ISC", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/depd": { - "version": "2.0.0", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/dequal": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", - "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/detect-libc": { - "version": "2.1.2", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=8" - } - }, - "node_modules/dezalgo": { - "version": "1.0.4", - "dev": true, - "license": "ISC", - "dependencies": { - "asap": "^2.0.0", - "wrappy": "1" - } - }, - "node_modules/diff": { - "version": "5.2.2", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.3.1" - } - }, - "node_modules/dir-glob": { - "version": "3.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "path-type": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/dom-accessibility-api": { - "version": "0.5.16", - "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz", - "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==", - "dev": true, - "license": "MIT" - }, - "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, - "license": "MIT", - "dependencies": { - "is-obj": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/dot-prop/node_modules/is-obj": { - "version": "2.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/dunder-proto": { - "version": "1.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.1", - "es-errors": "^1.3.0", - "gopd": "^1.2.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/ecma-proposal-math-extensions": { - "version": "0.0.2", - "license": "MIT" - }, - "node_modules/ejs": { - "version": "3.1.10", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "jake": "^10.8.5" - }, - "bin": { - "ejs": "bin/cli.js" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/electron-to-chromium": { - "version": "1.5.328", - "dev": true, - "license": "ISC" - }, - "node_modules/elementtree": { - "version": "0.1.7", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "sax": "1.1.4" - }, - "engines": { - "node": ">= 0.4.0" - } - }, - "node_modules/elementtree/node_modules/sax": { - "version": "1.1.4", - "dev": true, - "license": "ISC" - }, - "node_modules/emoji-regex": { - "version": "8.0.0", - "dev": true, - "license": "MIT" - }, - "node_modules/end-of-stream": { - "version": "1.4.5", - "dev": true, - "license": "MIT", - "dependencies": { - "once": "^1.4.0" - } - }, - "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, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/error-ex": { - "version": "1.3.4", - "dev": true, - "license": "MIT", - "dependencies": { - "is-arrayish": "^0.2.1" - } - }, - "node_modules/error-stack-parser-es": { - "version": "1.0.5", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/antfu" - } - }, - "node_modules/es-abstract": { - "version": "1.24.1", - "dev": true, - "license": "MIT", - "dependencies": { - "array-buffer-byte-length": "^1.0.2", - "arraybuffer.prototype.slice": "^1.0.4", - "available-typed-arrays": "^1.0.7", - "call-bind": "^1.0.8", - "call-bound": "^1.0.4", - "data-view-buffer": "^1.0.2", - "data-view-byte-length": "^1.0.2", - "data-view-byte-offset": "^1.0.1", - "es-define-property": "^1.0.1", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.1.1", - "es-set-tostringtag": "^2.1.0", - "es-to-primitive": "^1.3.0", - "function.prototype.name": "^1.1.8", - "get-intrinsic": "^1.3.0", - "get-proto": "^1.0.1", - "get-symbol-description": "^1.1.0", - "globalthis": "^1.0.4", - "gopd": "^1.2.0", - "has-property-descriptors": "^1.0.2", - "has-proto": "^1.2.0", - "has-symbols": "^1.1.0", - "hasown": "^2.0.2", - "internal-slot": "^1.1.0", - "is-array-buffer": "^3.0.5", - "is-callable": "^1.2.7", - "is-data-view": "^1.0.2", - "is-negative-zero": "^2.0.3", - "is-regex": "^1.2.1", - "is-set": "^2.0.3", - "is-shared-array-buffer": "^1.0.4", - "is-string": "^1.1.1", - "is-typed-array": "^1.1.15", - "is-weakref": "^1.1.1", - "math-intrinsics": "^1.1.0", - "object-inspect": "^1.13.4", - "object-keys": "^1.1.1", - "object.assign": "^4.1.7", - "own-keys": "^1.0.1", - "regexp.prototype.flags": "^1.5.4", - "safe-array-concat": "^1.1.3", - "safe-push-apply": "^1.0.0", - "safe-regex-test": "^1.1.0", - "set-proto": "^1.0.0", - "stop-iteration-iterator": "^1.1.0", - "string.prototype.trim": "^1.2.10", - "string.prototype.trimend": "^1.0.9", - "string.prototype.trimstart": "^1.0.8", - "typed-array-buffer": "^1.0.3", - "typed-array-byte-length": "^1.0.3", - "typed-array-byte-offset": "^1.0.4", - "typed-array-length": "^1.0.7", - "unbox-primitive": "^1.1.0", - "which-typed-array": "^1.1.19" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/es-define-property": { - "version": "1.0.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-errors": { - "version": "1.3.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-module-lexer": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.0.0.tgz", - "integrity": "sha512-5POEcUuZybH7IdmGsD8wlf0AI55wMecM9rVBTI/qEAy2c1kTOm3DjFYjrBdI2K3BaJjJYfYFeRtM0t9ssnRuxw==", - "license": "MIT" - }, - "node_modules/es-object-atoms": { - "version": "1.1.1", - "dev": true, - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-set-tostringtag": { - "version": "2.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.6", - "has-tostringtag": "^1.0.2", - "hasown": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-to-primitive": { - "version": "1.3.0", - "dev": true, - "license": "MIT", - "dependencies": { - "is-callable": "^1.2.7", - "is-date-object": "^1.0.5", - "is-symbol": "^1.0.4" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/esbuild": { - "version": "0.27.4", - "hasInstallScript": true, - "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=18" - }, - "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" - } - }, - "node_modules/escalade": { - "version": "3.2.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/escape-string-regexp": { - "version": "4.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint": { - "version": "9.39.4", - "dev": true, - "license": "MIT", - "dependencies": { - "@eslint-community/eslint-utils": "^4.8.0", - "@eslint-community/regexpp": "^4.12.1", - "@eslint/config-array": "^0.21.2", - "@eslint/config-helpers": "^0.4.2", - "@eslint/core": "^0.17.0", - "@eslint/eslintrc": "^3.3.5", - "@eslint/js": "9.39.4", - "@eslint/plugin-kit": "^0.4.1", - "@humanfs/node": "^0.16.6", - "@humanwhocodes/module-importer": "^1.0.1", - "@humanwhocodes/retry": "^0.4.2", - "@types/estree": "^1.0.6", - "ajv": "^6.14.0", - "chalk": "^4.0.0", - "cross-spawn": "^7.0.6", - "debug": "^4.3.2", - "escape-string-regexp": "^4.0.0", - "eslint-scope": "^8.4.0", - "eslint-visitor-keys": "^4.2.1", - "espree": "^10.4.0", - "esquery": "^1.5.0", - "esutils": "^2.0.2", - "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^8.0.0", - "find-up": "^5.0.0", - "glob-parent": "^6.0.2", - "ignore": "^5.2.0", - "imurmurhash": "^0.1.4", - "is-glob": "^4.0.0", - "json-stable-stringify-without-jsonify": "^1.0.1", - "lodash.merge": "^4.6.2", - "minimatch": "^3.1.5", - "natural-compare": "^1.4.0", - "optionator": "^0.9.3" - }, - "bin": { - "eslint": "bin/eslint.js" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://eslint.org/donate" - }, - "peerDependencies": { - "jiti": "*" - }, - "peerDependenciesMeta": { - "jiti": { - "optional": true - } - } - }, - "node_modules/eslint-config-prettier": { - "version": "10.1.8", - "dev": true, - "license": "MIT", - "bin": { - "eslint-config-prettier": "bin/cli.js" - }, - "funding": { - "url": "https://opencollective.com/eslint-config-prettier" - }, - "peerDependencies": { - "eslint": ">=7.0.0" - } - }, - "node_modules/eslint-scope": { - "version": "8.4.0", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^5.2.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint-visitor-keys": { - "version": "4.2.1", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "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": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/eslint/node_modules/debug": { - "version": "4.4.3", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/eslint/node_modules/debug/node_modules/ms": { - "version": "2.1.3", - "dev": true, - "license": "MIT" - }, - "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" - }, - "node_modules/espree": { - "version": "10.4.0", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "acorn": "^8.15.0", - "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^4.2.1" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/esquery": { - "version": "1.7.0", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "estraverse": "^5.1.0" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/esrecurse": { - "version": "4.3.0", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "estraverse": "^5.2.0" - }, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/estraverse": { - "version": "5.3.0", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=4.0" - } - }, - "node_modules/estree-walker": { - "version": "1.0.1", - "dev": true, - "license": "MIT" - }, - "node_modules/esutils": { - "version": "2.0.3", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/eventemitter3": { - "version": "5.0.4", - "license": "MIT" - }, - "node_modules/events": { - "version": "3.3.0", - "license": "MIT", - "engines": { - "node": ">=0.8.x" - } - }, - "node_modules/events-universal": { - "version": "1.0.1", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "bare-events": "^2.7.0" - } - }, - "node_modules/expand-template": { - "version": "2.0.3", - "dev": true, - "license": "(MIT OR WTFPL)", - "engines": { - "node": ">=6" - } - }, - "node_modules/expect-type": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz", - "integrity": "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==", - "license": "Apache-2.0", - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/fast-deep-equal": { - "version": "3.1.3", - "dev": true, - "license": "MIT" - }, - "node_modules/fast-fifo": { - "version": "1.3.2", - "dev": true, - "license": "MIT" - }, - "node_modules/fast-glob": { - "version": "3.3.3", - "dev": true, - "license": "MIT", - "dependencies": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.8" - }, - "engines": { - "node": ">=8.6.0" - } - }, - "node_modules/fast-glob/node_modules/glob-parent": { - "version": "5.1.2", - "dev": true, - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/fast-json-stable-stringify": { - "version": "2.1.0", - "dev": true, - "license": "MIT" - }, - "node_modules/fast-levenshtein": { - "version": "2.0.6", - "dev": true, - "license": "MIT" - }, - "node_modules/fast-uri": { - "version": "3.1.0", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fastify" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fastify" - } - ], - "license": "BSD-3-Clause" - }, - "node_modules/fastest-levenshtein": { - "version": "1.0.16", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 4.9.1" - } - }, - "node_modules/fastq": { - "version": "1.20.1", - "dev": true, - "license": "ISC", - "dependencies": { - "reusify": "^1.0.4" - } - }, - "node_modules/fd-slicer": { - "version": "1.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "pend": "~1.2.0" - } - }, - "node_modules/fdir": { - "version": "6.5.0", - "license": "MIT", - "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "picomatch": "^3 || ^4" - }, - "peerDependenciesMeta": { - "picomatch": { - "optional": true - } - } - }, - "node_modules/file-entry-cache": { - "version": "8.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "flat-cache": "^4.0.0" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/filelist": { - "version": "1.0.6", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "minimatch": "^5.0.1" - } - }, - "node_modules/filelist/node_modules/minimatch": { - "version": "5.1.9", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/filelist/node_modules/minimatch/node_modules/brace-expansion": { - "version": "2.0.3", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/filelist/node_modules/minimatch/node_modules/brace-expansion/node_modules/balanced-match": { - "version": "1.0.2", - "dev": true, - "license": "MIT" - }, - "node_modules/fill-range": { - "version": "7.1.1", - "dev": true, - "license": "MIT", - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/find-up": { - "version": "5.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/flat-cache": { - "version": "4.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "flatted": "^3.2.9", - "keyv": "^4.5.4" - }, - "engines": { - "node": ">=16" - } - }, - "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": { - "json-buffer": "3.0.1" - } - }, - "node_modules/flatted": { - "version": "3.4.2", - "dev": true, - "license": "ISC" - }, - "node_modules/for-each": { - "version": "0.3.5", - "dev": true, - "license": "MIT", - "dependencies": { - "is-callable": "^1.2.7" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/foreground-child": { - "version": "3.3.1", - "dev": true, - "license": "ISC", - "dependencies": { - "cross-spawn": "^7.0.6", - "signal-exit": "^4.0.1" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/foreground-child/node_modules/signal-exit": { - "version": "4.1.0", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/formidable": { - "version": "3.5.4", - "dev": true, - "license": "MIT", - "dependencies": { - "@paralleldrive/cuid2": "^2.2.2", - "dezalgo": "^1.0.4", - "once": "^1.4.0" - }, - "engines": { - "node": ">=14.0.0" - }, - "funding": { - "url": "https://ko-fi.com/tunnckoCore/commissions" - } - }, - "node_modules/fs-constants": { - "version": "1.0.0", - "dev": true, - "license": "MIT" - }, - "node_modules/fs-extra": { - "version": "10.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/fs-minipass": { - "version": "2.1.0", - "dev": true, - "license": "ISC", - "dependencies": { - "minipass": "^3.0.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/fs-minipass/node_modules/minipass": { - "version": "3.3.6", - "dev": true, - "license": "ISC", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/fs-minipass/node_modules/minipass/node_modules/yallist": { - "version": "4.0.0", - "dev": true, - "license": "ISC" - }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "dev": true, - "license": "ISC" - }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/function-bind": { - "version": "1.1.2", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/function.prototype.name": { - "version": "1.1.8", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.3", - "define-properties": "^1.2.1", - "functions-have-names": "^1.2.3", - "hasown": "^2.0.2", - "is-callable": "^1.2.7" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/functions-have-names": { - "version": "1.2.3", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/fuse.js": { - "version": "7.1.0", - "license": "Apache-2.0", - "engines": { - "node": ">=10" - } - }, - "node_modules/generator-function": { - "version": "2.0.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/gensync": { - "version": "1.0.0-beta.2", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/get-caller-file": { - "version": "2.0.5", - "dev": true, - "license": "ISC", - "engines": { - "node": "6.* || 8.* || >= 10.*" - } - }, - "node_modules/get-intrinsic": { - "version": "1.3.0", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "es-define-property": "^1.0.1", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.1.1", - "function-bind": "^1.1.2", - "get-proto": "^1.0.1", - "gopd": "^1.2.0", - "has-symbols": "^1.1.0", - "hasown": "^2.0.2", - "math-intrinsics": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-own-enumerable-property-symbols": { - "version": "3.0.2", - "dev": true, - "license": "ISC" - }, - "node_modules/get-pkg-repo": { - "version": "4.2.1", - "dev": true, - "license": "MIT", - "dependencies": { - "@hutson/parse-repository-url": "^3.0.0", - "hosted-git-info": "^4.0.0", - "through2": "^2.0.0", - "yargs": "^16.2.0" - }, - "bin": { - "get-pkg-repo": "src/cli.js" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/get-pkg-repo/node_modules/through2": { - "version": "2.0.5", - "dev": true, - "license": "MIT", - "dependencies": { - "readable-stream": "~2.3.6", - "xtend": "~4.0.1" - } - }, - "node_modules/get-pkg-repo/node_modules/through2/node_modules/readable-stream": { - "version": "2.3.8", - "dev": true, - "license": "MIT", - "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "node_modules/get-pkg-repo/node_modules/through2/node_modules/readable-stream/node_modules/isarray": { - "version": "1.0.0", - "dev": true, - "license": "MIT" - }, - "node_modules/get-pkg-repo/node_modules/through2/node_modules/readable-stream/node_modules/safe-buffer": { - "version": "5.1.2", - "dev": true, - "license": "MIT" - }, - "node_modules/get-pkg-repo/node_modules/through2/node_modules/readable-stream/node_modules/string_decoder": { - "version": "1.1.1", - "dev": true, - "license": "MIT", - "dependencies": { - "safe-buffer": "~5.1.0" - } - }, - "node_modules/get-pkg-repo/node_modules/yargs": { - "version": "16.2.0", - "dev": true, - "license": "MIT", - "dependencies": { - "cliui": "^7.0.2", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.0", - "y18n": "^5.0.5", - "yargs-parser": "^20.2.2" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/get-pkg-repo/node_modules/yargs/node_modules/cliui": { - "version": "7.0.4", - "dev": true, - "license": "ISC", - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^7.0.0" - } - }, - "node_modules/get-pkg-repo/node_modules/yargs/node_modules/yargs-parser": { - "version": "20.2.9", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=10" - } - }, - "node_modules/get-proto": { - "version": "1.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "dunder-proto": "^1.0.1", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/get-symbol-description": { - "version": "1.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.6" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/git-raw-commits": { - "version": "2.0.11", - "dev": true, - "license": "MIT", - "dependencies": { - "dargs": "^7.0.0", - "lodash": "^4.17.15", - "meow": "^8.0.0", - "split2": "^3.0.0", - "through2": "^4.0.0" - }, - "bin": { - "git-raw-commits": "cli.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/git-raw-commits/node_modules/meow": { - "version": "8.1.2", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/minimist": "^1.2.0", - "camelcase-keys": "^6.2.2", - "decamelize-keys": "^1.1.0", - "hard-rejection": "^2.1.0", - "minimist-options": "4.1.0", - "normalize-package-data": "^3.0.0", - "read-pkg-up": "^7.0.1", - "redent": "^3.0.0", - "trim-newlines": "^3.0.0", - "type-fest": "^0.18.0", - "yargs-parser": "^20.2.3" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/git-raw-commits/node_modules/meow/node_modules/read-pkg-up": { - "version": "7.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "find-up": "^4.1.0", - "read-pkg": "^5.2.0", - "type-fest": "^0.8.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/git-raw-commits/node_modules/meow/node_modules/read-pkg-up/node_modules/find-up": { - "version": "4.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/git-raw-commits/node_modules/meow/node_modules/read-pkg-up/node_modules/find-up/node_modules/locate-path": { - "version": "5.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "p-locate": "^4.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/git-raw-commits/node_modules/meow/node_modules/read-pkg-up/node_modules/find-up/node_modules/locate-path/node_modules/p-locate": { - "version": "4.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "p-limit": "^2.2.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/git-raw-commits/node_modules/meow/node_modules/read-pkg-up/node_modules/find-up/node_modules/locate-path/node_modules/p-locate/node_modules/p-limit": { - "version": "2.3.0", - "dev": true, - "license": "MIT", - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/git-raw-commits/node_modules/meow/node_modules/read-pkg-up/node_modules/read-pkg": { - "version": "5.2.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/normalize-package-data": "^2.4.0", - "normalize-package-data": "^2.5.0", - "parse-json": "^5.0.0", - "type-fest": "^0.6.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/git-raw-commits/node_modules/meow/node_modules/read-pkg-up/node_modules/read-pkg/node_modules/normalize-package-data": { - "version": "2.5.0", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "hosted-git-info": "^2.1.4", - "resolve": "^1.10.0", - "semver": "2 || 3 || 4 || 5", - "validate-npm-package-license": "^3.0.1" - } - }, - "node_modules/git-raw-commits/node_modules/meow/node_modules/read-pkg-up/node_modules/read-pkg/node_modules/normalize-package-data/node_modules/hosted-git-info": { - "version": "2.8.9", - "dev": true, - "license": "ISC" - }, - "node_modules/git-raw-commits/node_modules/meow/node_modules/read-pkg-up/node_modules/read-pkg/node_modules/normalize-package-data/node_modules/semver": { - "version": "5.7.2", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver" - } - }, - "node_modules/git-raw-commits/node_modules/meow/node_modules/read-pkg-up/node_modules/read-pkg/node_modules/type-fest": { - "version": "0.6.0", - "dev": true, - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=8" - } - }, - "node_modules/git-raw-commits/node_modules/meow/node_modules/read-pkg-up/node_modules/type-fest": { - "version": "0.8.1", - "dev": true, - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=8" - } - }, - "node_modules/git-raw-commits/node_modules/meow/node_modules/type-fest": { - "version": "0.18.1", - "dev": true, - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/git-raw-commits/node_modules/meow/node_modules/yargs-parser": { - "version": "20.2.9", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=10" - } - }, - "node_modules/git-raw-commits/node_modules/split2": { - "version": "3.2.2", - "dev": true, - "license": "ISC", - "dependencies": { - "readable-stream": "^3.0.0" - } - }, - "node_modules/git-remote-origin-url": { - "version": "2.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "gitconfiglocal": "^1.0.0", - "pify": "^2.3.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/git-semver-tags": { - "version": "4.1.1", - "dev": true, - "license": "MIT", - "dependencies": { - "meow": "^8.0.0", - "semver": "^6.0.0" - }, - "bin": { - "git-semver-tags": "cli.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/git-semver-tags/node_modules/meow": { - "version": "8.1.2", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/minimist": "^1.2.0", - "camelcase-keys": "^6.2.2", - "decamelize-keys": "^1.1.0", - "hard-rejection": "^2.1.0", - "minimist-options": "4.1.0", - "normalize-package-data": "^3.0.0", - "read-pkg-up": "^7.0.1", - "redent": "^3.0.0", - "trim-newlines": "^3.0.0", - "type-fest": "^0.18.0", - "yargs-parser": "^20.2.3" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/git-semver-tags/node_modules/meow/node_modules/read-pkg-up": { - "version": "7.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "find-up": "^4.1.0", - "read-pkg": "^5.2.0", - "type-fest": "^0.8.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/git-semver-tags/node_modules/meow/node_modules/read-pkg-up/node_modules/find-up": { - "version": "4.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/git-semver-tags/node_modules/meow/node_modules/read-pkg-up/node_modules/find-up/node_modules/locate-path": { - "version": "5.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "p-locate": "^4.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/git-semver-tags/node_modules/meow/node_modules/read-pkg-up/node_modules/find-up/node_modules/locate-path/node_modules/p-locate": { - "version": "4.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "p-limit": "^2.2.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/git-semver-tags/node_modules/meow/node_modules/read-pkg-up/node_modules/find-up/node_modules/locate-path/node_modules/p-locate/node_modules/p-limit": { - "version": "2.3.0", - "dev": true, - "license": "MIT", - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/git-semver-tags/node_modules/meow/node_modules/read-pkg-up/node_modules/read-pkg": { - "version": "5.2.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/normalize-package-data": "^2.4.0", - "normalize-package-data": "^2.5.0", - "parse-json": "^5.0.0", - "type-fest": "^0.6.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/git-semver-tags/node_modules/meow/node_modules/read-pkg-up/node_modules/read-pkg/node_modules/normalize-package-data": { - "version": "2.5.0", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "hosted-git-info": "^2.1.4", - "resolve": "^1.10.0", - "semver": "2 || 3 || 4 || 5", - "validate-npm-package-license": "^3.0.1" - } - }, - "node_modules/git-semver-tags/node_modules/meow/node_modules/read-pkg-up/node_modules/read-pkg/node_modules/normalize-package-data/node_modules/hosted-git-info": { - "version": "2.8.9", - "dev": true, - "license": "ISC" - }, - "node_modules/git-semver-tags/node_modules/meow/node_modules/read-pkg-up/node_modules/read-pkg/node_modules/normalize-package-data/node_modules/semver": { - "version": "5.7.2", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver" - } - }, - "node_modules/git-semver-tags/node_modules/meow/node_modules/read-pkg-up/node_modules/read-pkg/node_modules/type-fest": { - "version": "0.6.0", - "dev": true, - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=8" - } - }, - "node_modules/git-semver-tags/node_modules/meow/node_modules/read-pkg-up/node_modules/type-fest": { - "version": "0.8.1", - "dev": true, - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=8" - } - }, - "node_modules/git-semver-tags/node_modules/meow/node_modules/type-fest": { - "version": "0.18.1", - "dev": true, - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/git-semver-tags/node_modules/meow/node_modules/yargs-parser": { - "version": "20.2.9", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=10" - } - }, - "node_modules/git-semver-tags/node_modules/semver": { - "version": "6.3.1", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/gitconfiglocal": { - "version": "1.0.0", - "dev": true, - "license": "BSD", - "dependencies": { - "ini": "^1.3.2" - } - }, - "node_modules/gitconfiglocal/node_modules/ini": { - "version": "1.3.8", - "dev": true, - "license": "ISC" - }, - "node_modules/github-from-package": { - "version": "0.0.0", - "dev": true, - "license": "MIT" - }, - "node_modules/glob": { - "version": "13.0.6", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "minimatch": "^10.2.2", - "minipass": "^7.1.3", - "path-scurry": "^2.0.2" - }, - "engines": { - "node": "18 || 20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/glob-parent": { - "version": "6.0.2", - "dev": true, - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.3" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/glob/node_modules/minimatch": { - "version": "10.2.4", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "brace-expansion": "^5.0.2" - }, - "engines": { - "node": "18 || 20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/glob/node_modules/minimatch/node_modules/brace-expansion": { - "version": "5.0.5", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^4.0.2" - }, - "engines": { - "node": "18 || 20 || >=22" - } - }, - "node_modules/glob/node_modules/minimatch/node_modules/brace-expansion/node_modules/balanced-match": { - "version": "4.0.4", - "dev": true, - "license": "MIT", - "engines": { - "node": "18 || 20 || >=22" - } - }, - "node_modules/global-modules": { - "version": "2.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "global-prefix": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/global-prefix": { - "version": "3.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "ini": "^1.3.5", - "kind-of": "^6.0.2", - "which": "^1.3.1" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/global-prefix/node_modules/ini": { - "version": "1.3.8", - "dev": true, - "license": "ISC" - }, - "node_modules/global-prefix/node_modules/which": { - "version": "1.3.1", - "dev": true, - "license": "ISC", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "which": "bin/which" - } - }, - "node_modules/globals": { - "version": "17.4.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/globalthis": { - "version": "1.0.4", - "dev": true, - "license": "MIT", - "dependencies": { - "define-properties": "^1.2.1", - "gopd": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/globby": { - "version": "11.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "array-union": "^2.1.0", - "dir-glob": "^3.0.1", - "fast-glob": "^3.2.9", - "ignore": "^5.2.0", - "merge2": "^1.4.1", - "slash": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/globjoin": { - "version": "0.1.4", - "dev": true, - "license": "MIT" - }, - "node_modules/gopd": { - "version": "1.2.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/graceful-fs": { - "version": "4.2.11", - "dev": true, - "license": "ISC" - }, - "node_modules/gradle-to-js": { - "version": "2.0.1", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "lodash.merge": "^4.6.2" - }, - "bin": { - "gradle-to-js": "cli.js" - } - }, - "node_modules/handlebars": { - "version": "4.7.9", - "dev": true, - "license": "MIT", - "dependencies": { - "minimist": "^1.2.5", - "neo-async": "^2.6.2", - "source-map": "^0.6.1", - "wordwrap": "^1.0.0" - }, - "bin": { - "handlebars": "bin/handlebars" - }, - "engines": { - "node": ">=0.4.7" - }, - "optionalDependencies": { - "uglify-js": "^3.1.4" - } - }, - "node_modules/hard-rejection": { - "version": "2.1.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/has-bigints": { - "version": "1.1.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-flag": { - "version": "4.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/has-property-descriptors": { - "version": "1.0.2", - "dev": true, - "license": "MIT", - "dependencies": { - "es-define-property": "^1.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-proto": { - "version": "1.2.0", - "dev": true, - "license": "MIT", - "dependencies": { - "dunder-proto": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-symbols": { - "version": "1.1.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-tostringtag": { - "version": "1.0.2", - "dev": true, - "license": "MIT", - "dependencies": { - "has-symbols": "^1.0.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/hashery": { - "version": "1.5.1", - "dev": true, - "license": "MIT", - "dependencies": { - "hookified": "^1.15.0" - }, - "engines": { - "node": ">=20" - } - }, - "node_modules/hasown": { - "version": "2.0.2", - "dev": true, - "license": "MIT", - "dependencies": { - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/he": { - "version": "1.2.0", - "dev": true, - "license": "MIT", - "bin": { - "he": "bin/he" - } - }, - "node_modules/hls.js": { - "version": "1.6.15", - "license": "Apache-2.0" - }, - "node_modules/hookified": { - "version": "1.15.1", - "dev": true, - "license": "MIT" - }, - "node_modules/hosted-git-info": { - "version": "4.1.0", - "dev": true, - "license": "ISC", - "dependencies": { - "lru-cache": "^6.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/hosted-git-info/node_modules/lru-cache": { - "version": "6.0.0", - "dev": true, - "license": "ISC", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/hosted-git-info/node_modules/lru-cache/node_modules/yallist": { - "version": "4.0.0", - "dev": true, - "license": "ISC" - }, - "node_modules/html-tags": { - "version": "3.3.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/htmlhint": { - "version": "1.9.2", - "dev": true, - "license": "MIT", - "dependencies": { - "async": "3.2.6", - "chalk": "4.1.2", - "commander": "11.1.0", - "glob": "^13.0.6", - "is-glob": "^4.0.3", - "node-sarif-builder": "3.2.0", - "strip-json-comments": "3.1.1", - "xml": "1.0.1" - }, - "bin": { - "htmlhint": "bin/htmlhint" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "type": "Open Collective", - "url": "https://opencollective.com/htmlhint" - } - }, - "node_modules/htmlhint/node_modules/commander": { - "version": "11.1.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=16" - } - }, - "node_modules/idb": { - "version": "7.1.1", - "dev": true, - "license": "ISC" - }, - "node_modules/ieee754": { - "version": "1.2.1", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "BSD-3-Clause" - }, - "node_modules/ignore": { - "version": "5.3.2", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 4" - } - }, - "node_modules/import-fresh": { - "version": "3.3.1", - "dev": true, - "license": "MIT", - "dependencies": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/import-fresh/node_modules/resolve-from": { - "version": "4.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/imurmurhash": { - "version": "0.1.4", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.8.19" - } - }, - "node_modules/indent-string": { - "version": "4.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/inflight": { - "version": "1.0.6", - "dev": true, - "license": "ISC", - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "dev": true, - "license": "ISC" - }, - "node_modules/ini": { - "version": "2.0.0", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=10" - } - }, - "node_modules/internal-slot": { - "version": "1.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "hasown": "^2.0.2", - "side-channel": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/is-array-buffer": { - "version": "3.0.5", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.3", - "get-intrinsic": "^1.2.6" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-arrayish": { - "version": "0.2.1", - "dev": true, - "license": "MIT" - }, - "node_modules/is-async-function": { - "version": "2.1.1", - "dev": true, - "license": "MIT", - "dependencies": { - "async-function": "^1.0.0", - "call-bound": "^1.0.3", - "get-proto": "^1.0.1", - "has-tostringtag": "^1.0.2", - "safe-regex-test": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-bigint": { - "version": "1.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "has-bigints": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-boolean-object": { - "version": "1.2.2", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "has-tostringtag": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-callable": { - "version": "1.2.7", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-core-module": { - "version": "2.16.1", - "dev": true, - "license": "MIT", - "dependencies": { - "hasown": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-data-view": { - "version": "1.0.2", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "get-intrinsic": "^1.2.6", - "is-typed-array": "^1.1.13" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-date-object": { - "version": "1.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "has-tostringtag": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-docker": { - "version": "2.2.1", - "dev": true, - "license": "MIT", - "bin": { - "is-docker": "cli.js" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-extglob": { - "version": "2.1.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-finalizationregistry": { - "version": "1.1.1", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/is-generator-function": { - "version": "1.1.2", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.4", - "generator-function": "^2.0.0", - "get-proto": "^1.0.1", - "has-tostringtag": "^1.0.2", - "safe-regex-test": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-glob": { - "version": "4.0.3", - "dev": true, - "license": "MIT", - "dependencies": { - "is-extglob": "^2.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-map": { - "version": "2.0.3", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-module": { - "version": "1.0.0", - "dev": true, - "license": "MIT" - }, - "node_modules/is-negative-zero": { - "version": "2.0.3", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-number": { - "version": "7.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/is-number-object": { - "version": "1.1.1", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "has-tostringtag": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-path-cwd": { - "version": "2.2.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/is-path-inside": { - "version": "3.0.3", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/is-plain-obj": { - "version": "1.1.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-plain-object": { - "version": "5.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-regex": { - "version": "1.2.1", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "gopd": "^1.2.0", - "has-tostringtag": "^1.0.2", - "hasown": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-regexp": { - "version": "1.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-set": { - "version": "2.0.3", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-shared-array-buffer": { - "version": "1.0.4", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-stream": { - "version": "2.0.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-string": { - "version": "1.1.1", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "has-tostringtag": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-symbol": { - "version": "1.1.1", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "has-symbols": "^1.1.0", - "safe-regex-test": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-text-path": { - "version": "1.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "text-extensions": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-typed-array": { - "version": "1.1.15", - "dev": true, - "license": "MIT", - "dependencies": { - "which-typed-array": "^1.1.16" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-weakmap": { - "version": "2.0.2", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-weakref": { - "version": "1.1.1", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-weakset": { - "version": "2.0.4", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "get-intrinsic": "^1.2.6" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-wsl": { - "version": "2.2.0", - "dev": true, - "license": "MIT", - "dependencies": { - "is-docker": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/isarray": { - "version": "2.0.5", - "dev": true, - "license": "MIT" - }, - "node_modules/isexe": { - "version": "2.0.0", - "dev": true, - "license": "ISC" - }, - "node_modules/jackspeak": { - "version": "4.2.3", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "@isaacs/cliui": "^9.0.0" - }, - "engines": { - "node": "20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/jake": { - "version": "10.9.4", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "async": "^3.2.6", - "filelist": "^1.0.4", - "picocolors": "^1.1.1" - }, - "bin": { - "jake": "bin/cli.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/jose": { - "version": "6.2.2", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/panva" - } - }, - "node_modules/js-tokens": { - "version": "4.0.0", - "dev": true, - "license": "MIT" - }, - "node_modules/js-yaml": { - "version": "4.1.1", - "dev": true, - "license": "MIT", - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/jsesc": { - "version": "3.1.0", - "dev": true, - "license": "MIT", - "bin": { - "jsesc": "bin/jsesc" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/json-bigint": { - "version": "1.0.0", - "license": "MIT", - "dependencies": { - "bignumber.js": "^9.0.0" - } - }, - "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" - }, - "node_modules/json-parse-better-errors": { - "version": "1.0.2", - "dev": true, - "license": "MIT" - }, - "node_modules/json-parse-even-better-errors": { - "version": "2.3.1", - "dev": true, - "license": "MIT" - }, - "node_modules/json-schema": { - "version": "0.4.0", - "dev": true, - "license": "(AFL-2.1 OR BSD-3-Clause)" - }, - "node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true, - "license": "MIT" - }, - "node_modules/json-stable-stringify-without-jsonify": { - "version": "1.0.1", - "dev": true, - "license": "MIT" - }, - "node_modules/json-stringify-safe": { - "version": "5.0.1", - "dev": true, - "license": "ISC" - }, - "node_modules/json5": { - "version": "2.2.3", - "dev": true, - "license": "MIT", - "bin": { - "json5": "lib/cli.js" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/jsonfile": { - "version": "6.2.0", - "dev": true, - "license": "MIT", - "dependencies": { - "universalify": "^2.0.0" - }, - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, - "node_modules/jsonparse": { - "version": "1.3.1", - "dev": true, - "engines": [ - "node >= 0.2.0" - ], - "license": "MIT" - }, - "node_modules/jsonpointer": { - "version": "5.0.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/JSONStream": { - "version": "1.3.5", - "dev": true, - "license": "(MIT OR Apache-2.0)", - "dependencies": { - "jsonparse": "^1.2.0", - "through": ">=2.2.7 <3" - }, - "bin": { - "JSONStream": "bin.js" - }, - "engines": { - "node": "*" - } - }, - "node_modules/keygrip": { - "version": "1.1.0", - "license": "MIT", - "dependencies": { - "tsscmp": "1.0.6" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/keyv": { - "version": "5.6.0", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-5.6.0.tgz", - "integrity": "sha512-CYDD3SOtsHtyXeEORYRx2qBtpDJFjRTGXUtmNEMGyzYOKj1TE3tycdlho7kA1Ufx9OYWZzg52QFBGALTirzDSw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@keyv/serialize": "^1.1.1" - } - }, - "node_modules/kind-of": { - "version": "6.0.3", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/kleur": { - "version": "4.1.5", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/known-css-properties": { - "version": "0.37.0", - "dev": true, - "license": "MIT" - }, - "node_modules/leven": { - "version": "3.1.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/levn": { - "version": "0.4.1", - "dev": true, - "license": "MIT", - "dependencies": { - "prelude-ls": "^1.2.1", - "type-check": "~0.4.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/lines-and-columns": { - "version": "1.2.4", - "dev": true, - "license": "MIT" - }, - "node_modules/lit": { - "version": "3.3.2", - "license": "BSD-3-Clause", - "dependencies": { - "@lit/reactive-element": "^2.1.0", - "lit-element": "^4.2.0", - "lit-html": "^3.3.0" - } - }, - "node_modules/lit-element": { - "version": "4.2.2", - "license": "BSD-3-Clause", - "dependencies": { - "@lit-labs/ssr-dom-shim": "^1.5.0", - "@lit/reactive-element": "^2.1.0", - "lit-html": "^3.3.0" - } - }, - "node_modules/lit-html": { - "version": "3.3.2", - "license": "BSD-3-Clause", - "dependencies": { - "@types/trusted-types": "^2.0.2" - } - }, - "node_modules/load-json-file": { - "version": "4.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.1.2", - "parse-json": "^4.0.0", - "pify": "^3.0.0", - "strip-bom": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/load-json-file/node_modules/parse-json": { - "version": "4.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "error-ex": "^1.3.1", - "json-parse-better-errors": "^1.0.1" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/load-json-file/node_modules/pify": { - "version": "3.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/locate-path": { - "version": "6.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "p-locate": "^5.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/lodash": { - "version": "4.18.1", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", - "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==", - "license": "MIT" - }, - "node_modules/lodash.debounce": { - "version": "4.0.8", - "dev": true, - "license": "MIT" - }, - "node_modules/lodash.ismatch": { - "version": "4.4.0", - "dev": true, - "license": "MIT" - }, - "node_modules/lodash.merge": { - "version": "4.6.2", - "dev": true, - "license": "MIT" - }, - "node_modules/lodash.truncate": { - "version": "4.4.2", - "dev": true, - "license": "MIT" - }, - "node_modules/lru-cache": { - "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/lz-string": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz", - "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==", - "dev": true, - "license": "MIT", - "bin": { - "lz-string": "bin/bin.js" - } - }, - "node_modules/magic-string": { - "version": "0.25.9", - "dev": true, - "license": "MIT", - "dependencies": { - "sourcemap-codec": "^1.4.8" - } - }, - "node_modules/make-error": { - "version": "1.3.6", - "dev": true, - "license": "ISC" - }, - "node_modules/map-obj": { - "version": "4.3.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/math-intrinsics": { - "version": "1.1.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/mathml-tag-names": { - "version": "2.1.3", - "dev": true, - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/mdn-data": { - "version": "2.27.1", - "license": "CC0-1.0" - }, - "node_modules/meow": { - "version": "13.2.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/merge2": { - "version": "1.4.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 8" - } - }, - "node_modules/mergexml": { - "version": "1.2.4", - "dev": true, - "license": "ISC", - "dependencies": { - "@xmldom/xmldom": "^0.7.0", - "formidable": "^3.5.1", - "xpath": "0.0.27" - } - }, - "node_modules/mergexml/node_modules/xpath": { - "version": "0.0.27", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.6.0" - } - }, - "node_modules/micromatch": { - "version": "4.0.8", - "dev": true, - "license": "MIT", - "dependencies": { - "braces": "^3.0.3", - "picomatch": "^2.3.1" - }, - "engines": { - "node": ">=8.6" - } - }, - "node_modules/micromatch/node_modules/picomatch": { - "version": "2.3.2", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/mime": { - "version": "4.1.0", - "funding": [ - "https://github.com/sponsors/broofa" - ], - "license": "MIT", - "bin": { - "mime": "bin/cli.js" - }, - "engines": { - "node": ">=16" - } - }, - "node_modules/mimic-response": { - "version": "3.1.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/min-indent": { - "version": "1.0.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/miniflare": { - "version": "4.20260317.2", - "dev": true, - "license": "MIT", - "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" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/miniflare/node_modules/sharp": { - "version": "0.34.5", - "dev": true, - "hasInstallScript": true, - "license": "Apache-2.0", - "dependencies": { - "@img/colour": "^1.0.0", - "detect-libc": "^2.1.2", - "semver": "^7.7.3" - }, - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-darwin-arm64": "0.34.5", - "@img/sharp-darwin-x64": "0.34.5", - "@img/sharp-libvips-darwin-arm64": "1.2.4", - "@img/sharp-libvips-darwin-x64": "1.2.4", - "@img/sharp-libvips-linux-arm": "1.2.4", - "@img/sharp-libvips-linux-arm64": "1.2.4", - "@img/sharp-libvips-linux-ppc64": "1.2.4", - "@img/sharp-libvips-linux-riscv64": "1.2.4", - "@img/sharp-libvips-linux-s390x": "1.2.4", - "@img/sharp-libvips-linux-x64": "1.2.4", - "@img/sharp-libvips-linuxmusl-arm64": "1.2.4", - "@img/sharp-libvips-linuxmusl-x64": "1.2.4", - "@img/sharp-linux-arm": "0.34.5", - "@img/sharp-linux-arm64": "0.34.5", - "@img/sharp-linux-ppc64": "0.34.5", - "@img/sharp-linux-riscv64": "0.34.5", - "@img/sharp-linux-s390x": "0.34.5", - "@img/sharp-linux-x64": "0.34.5", - "@img/sharp-linuxmusl-arm64": "0.34.5", - "@img/sharp-linuxmusl-x64": "0.34.5", - "@img/sharp-wasm32": "0.34.5", - "@img/sharp-win32-arm64": "0.34.5", - "@img/sharp-win32-ia32": "0.34.5", - "@img/sharp-win32-x64": "0.34.5" - } - }, - "node_modules/minimatch": { - "version": "3.1.5", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/minimist": { - "version": "1.2.8", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/minimist-options": { - "version": "4.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "arrify": "^1.0.1", - "is-plain-obj": "^1.1.0", - "kind-of": "^6.0.3" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/minipass": { - "version": "7.1.3", - "dev": true, - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=16 || 14 >=14.17" - } - }, - "node_modules/minizlib": { - "version": "3.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "minipass": "^7.1.2" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/mkdirp": { - "version": "1.0.4", - "dev": true, - "license": "MIT", - "bin": { - "mkdirp": "bin/cmd.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/mkdirp-classic": { - "version": "0.5.3", - "dev": true, - "license": "MIT" - }, - "node_modules/modify-values": { - "version": "1.0.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/mrmime": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.1.tgz", - "integrity": "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==", - "devOptional": true, - "license": "MIT", - "engines": { - "node": ">=10" - } - }, - "node_modules/ms": { - "version": "2.1.2", - "dev": true, - "license": "MIT" - }, - "node_modules/nanoid": { - "version": "3.3.11", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, - "node_modules/napi-build-utils": { - "version": "2.0.0", - "dev": true, - "license": "MIT" - }, - "node_modules/native-run": { - "version": "2.0.3", - "dev": true, - "license": "MIT", - "dependencies": { - "@ionic/utils-fs": "^3.1.7", - "@ionic/utils-terminal": "^2.3.4", - "bplist-parser": "^0.3.2", - "debug": "^4.3.4", - "elementtree": "^0.1.7", - "ini": "^4.1.1", - "plist": "^3.1.0", - "split2": "^4.2.0", - "through2": "^4.0.2", - "tslib": "^2.6.2", - "yauzl": "^2.10.0" - }, - "bin": { - "native-run": "bin/native-run" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/native-run/node_modules/debug": { - "version": "4.4.3", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/native-run/node_modules/debug/node_modules/ms": { - "version": "2.1.3", - "dev": true, - "license": "MIT" - }, - "node_modules/native-run/node_modules/ini": { - "version": "4.1.3", - "dev": true, - "license": "ISC", - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/native-run/node_modules/tslib": { - "version": "2.8.1", - "dev": true, - "license": "0BSD" - }, - "node_modules/natural-compare": { - "version": "1.4.0", - "dev": true, - "license": "MIT" - }, - "node_modules/neo-async": { - "version": "2.6.2", - "dev": true, - "license": "MIT" - }, - "node_modules/node-abi": { - "version": "3.89.0", - "dev": true, - "license": "MIT", - "dependencies": { - "semver": "^7.3.5" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/node-addon-api": { - "version": "6.1.0", - "dev": true, - "license": "MIT" - }, - "node_modules/node-fetch": { - "version": "2.7.0", - "dev": true, - "license": "MIT", - "dependencies": { - "whatwg-url": "^5.0.0" - }, - "engines": { - "node": "4.x || >=6.0.0" - }, - "peerDependencies": { - "encoding": "^0.1.0" - }, - "peerDependenciesMeta": { - "encoding": { - "optional": true - } - } - }, - "node_modules/node-html-parser": { - "version": "5.4.2", - "dev": true, - "license": "MIT", - "dependencies": { - "css-select": "^4.2.1", - "he": "1.2.0" - } - }, - "node_modules/node-html-parser/node_modules/css-select": { - "version": "4.3.0", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "boolbase": "^1.0.0", - "css-what": "^6.0.1", - "domhandler": "^4.3.1", - "domutils": "^2.8.0", - "nth-check": "^2.0.1" - }, - "funding": { - "url": "https://github.com/sponsors/fb55" - } - }, - "node_modules/node-html-parser/node_modules/css-select/node_modules/domhandler": { - "version": "4.3.1", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "domelementtype": "^2.2.0" - }, - "engines": { - "node": ">= 4" - }, - "funding": { - "url": "https://github.com/fb55/domhandler?sponsor=1" - } - }, - "node_modules/node-html-parser/node_modules/css-select/node_modules/domutils": { - "version": "2.8.0", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "dom-serializer": "^1.0.1", - "domelementtype": "^2.2.0", - "domhandler": "^4.2.0" - }, - "funding": { - "url": "https://github.com/fb55/domutils?sponsor=1" - } - }, - "node_modules/node-html-parser/node_modules/css-select/node_modules/domutils/node_modules/dom-serializer": { - "version": "1.4.1", - "dev": true, - "license": "MIT", - "dependencies": { - "domelementtype": "^2.0.1", - "domhandler": "^4.2.0", - "entities": "^2.0.0" - }, - "funding": { - "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" - } - }, - "node_modules/node-html-parser/node_modules/css-select/node_modules/domutils/node_modules/dom-serializer/node_modules/entities": { - "version": "2.2.0", - "dev": true, - "license": "BSD-2-Clause", - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, - "node_modules/node-releases": { - "version": "2.0.36", - "dev": true, - "license": "MIT" - }, - "node_modules/node-sarif-builder": { - "version": "3.2.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/sarif": "^2.1.7", - "fs-extra": "^11.1.1" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/node-sarif-builder/node_modules/fs-extra": { - "version": "11.3.4", - "dev": true, - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "engines": { - "node": ">=14.14" - } - }, - "node_modules/normalize-package-data": { - "version": "3.0.3", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "hosted-git-info": "^4.0.1", - "is-core-module": "^2.5.0", - "semver": "^7.3.4", - "validate-npm-package-license": "^3.0.1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/normalize-path": { - "version": "3.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/npm": { - "version": "11.12.1", - "bundleDependencies": [ - "@isaacs/string-locale-compare", - "@npmcli/arborist", - "@npmcli/config", - "@npmcli/fs", - "@npmcli/map-workspaces", - "@npmcli/metavuln-calculator", - "@npmcli/package-json", - "@npmcli/promise-spawn", - "@npmcli/redact", - "@npmcli/run-script", - "@sigstore/tuf", - "abbrev", - "archy", - "cacache", - "chalk", - "ci-info", - "fastest-levenshtein", - "fs-minipass", - "glob", - "graceful-fs", - "hosted-git-info", - "ini", - "init-package-json", - "is-cidr", - "json-parse-even-better-errors", - "libnpmaccess", - "libnpmdiff", - "libnpmexec", - "libnpmfund", - "libnpmorg", - "libnpmpack", - "libnpmpublish", - "libnpmsearch", - "libnpmteam", - "libnpmversion", - "make-fetch-happen", - "minimatch", - "minipass", - "minipass-pipeline", - "ms", - "node-gyp", - "nopt", - "npm-audit-report", - "npm-install-checks", - "npm-package-arg", - "npm-pick-manifest", - "npm-profile", - "npm-registry-fetch", - "npm-user-validate", - "p-map", - "pacote", - "parse-conflict-json", - "proc-log", - "qrcode-terminal", - "read", - "semver", - "spdx-expression-parse", - "ssri", - "supports-color", - "tar", - "text-table", - "tiny-relative-date", - "treeverse", - "validate-npm-package-name", - "which" - ], - "license": "Artistic-2.0", - "workspaces": [ - "docs", - "smoke-tests", - "mock-globals", - "mock-registry", - "workspaces/*" - ], - "dependencies": { - "@isaacs/string-locale-compare": "^1.1.0", - "@npmcli/arborist": "^9.4.2", - "@npmcli/config": "^10.8.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.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" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/npm/node_modules/@gar/promise-retry": { - "version": "1.0.3", - "inBundle": true, - "license": "MIT", - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/npm/node_modules/@isaacs/fs-minipass": { - "version": "4.0.1", - "inBundle": true, - "license": "ISC", - "dependencies": { - "minipass": "^7.0.4" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/npm/node_modules/@isaacs/string-locale-compare": { - "version": "1.1.0", - "inBundle": true, - "license": "ISC" - }, - "node_modules/npm/node_modules/@npmcli/agent": { - "version": "4.0.0", - "inBundle": true, - "license": "ISC", - "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" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/npm/node_modules/@npmcli/arborist": { - "version": "9.4.2", - "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", - "@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" - }, - "bin": { - "arborist": "bin/index.js" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/npm/node_modules/@npmcli/config": { - "version": "10.8.1", - "inBundle": true, - "license": "ISC", - "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" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/npm/node_modules/@npmcli/fs": { - "version": "5.0.0", - "inBundle": true, - "license": "ISC", - "dependencies": { - "semver": "^7.3.5" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/npm/node_modules/@npmcli/git": { - "version": "7.0.2", - "inBundle": true, - "license": "ISC", - "dependencies": { - "@gar/promise-retry": "^1.0.0", - "@npmcli/promise-spawn": "^9.0.0", - "ini": "^6.0.0", - "lru-cache": "^11.2.1", - "npm-pick-manifest": "^11.0.1", - "proc-log": "^6.0.0", - "semver": "^7.3.5", - "which": "^6.0.0" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/npm/node_modules/@npmcli/installed-package-contents": { - "version": "4.0.0", - "inBundle": true, - "license": "ISC", - "dependencies": { - "npm-bundled": "^5.0.0", - "npm-normalize-package-bin": "^5.0.0" - }, - "bin": { - "installed-package-contents": "bin/index.js" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/npm/node_modules/@npmcli/map-workspaces": { - "version": "5.0.3", - "inBundle": true, - "license": "ISC", - "dependencies": { - "@npmcli/name-from-folder": "^4.0.0", - "@npmcli/package-json": "^7.0.0", - "glob": "^13.0.0", - "minimatch": "^10.0.3" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/npm/node_modules/@npmcli/metavuln-calculator": { - "version": "9.0.3", - "inBundle": true, - "license": "ISC", - "dependencies": { - "cacache": "^20.0.0", - "json-parse-even-better-errors": "^5.0.0", - "pacote": "^21.0.0", - "proc-log": "^6.0.0", - "semver": "^7.3.5" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/npm/node_modules/@npmcli/name-from-folder": { - "version": "4.0.0", - "inBundle": true, - "license": "ISC", - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/npm/node_modules/@npmcli/node-gyp": { - "version": "5.0.0", - "inBundle": true, - "license": "ISC", - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/npm/node_modules/@npmcli/package-json": { - "version": "7.0.5", - "inBundle": true, - "license": "ISC", - "dependencies": { - "@npmcli/git": "^7.0.0", - "glob": "^13.0.0", - "hosted-git-info": "^9.0.0", - "json-parse-even-better-errors": "^5.0.0", - "proc-log": "^6.0.0", - "semver": "^7.5.3", - "spdx-expression-parse": "^4.0.0" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/npm/node_modules/@npmcli/promise-spawn": { - "version": "9.0.1", - "inBundle": true, - "license": "ISC", - "dependencies": { - "which": "^6.0.0" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/npm/node_modules/@npmcli/query": { - "version": "5.0.0", - "inBundle": true, - "license": "ISC", - "dependencies": { - "postcss-selector-parser": "^7.0.0" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/npm/node_modules/@npmcli/redact": { - "version": "4.0.0", - "inBundle": true, - "license": "ISC", - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/npm/node_modules/@npmcli/run-script": { - "version": "10.0.4", - "inBundle": true, - "license": "ISC", - "dependencies": { - "@npmcli/node-gyp": "^5.0.0", - "@npmcli/package-json": "^7.0.0", - "@npmcli/promise-spawn": "^9.0.0", - "node-gyp": "^12.1.0", - "proc-log": "^6.0.0" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/npm/node_modules/@sigstore/bundle": { - "version": "4.0.0", - "inBundle": true, - "license": "Apache-2.0", - "dependencies": { - "@sigstore/protobuf-specs": "^0.5.0" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/npm/node_modules/@sigstore/core": { - "version": "3.2.0", - "inBundle": true, - "license": "Apache-2.0", - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/npm/node_modules/@sigstore/protobuf-specs": { - "version": "0.5.0", - "inBundle": true, - "license": "Apache-2.0", - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/npm/node_modules/@sigstore/sign": { - "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.2.0", - "@sigstore/protobuf-specs": "^0.5.0", - "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.2", - "inBundle": true, - "license": "Apache-2.0", - "dependencies": { - "@sigstore/protobuf-specs": "^0.5.0", - "tuf-js": "^4.1.0" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/npm/node_modules/@sigstore/verify": { - "version": "3.1.0", - "inBundle": true, - "license": "Apache-2.0", - "dependencies": { - "@sigstore/bundle": "^4.0.0", - "@sigstore/core": "^3.1.0", - "@sigstore/protobuf-specs": "^0.5.0" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/npm/node_modules/@tufjs/canonical-json": { - "version": "2.0.0", - "inBundle": true, - "license": "MIT", - "engines": { - "node": "^16.14.0 || >=18.0.0" - } - }, - "node_modules/npm/node_modules/@tufjs/models": { - "version": "4.1.0", - "inBundle": true, - "license": "MIT", - "dependencies": { - "@tufjs/canonical-json": "2.0.0", - "minimatch": "^10.1.1" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/npm/node_modules/abbrev": { - "version": "4.0.0", - "inBundle": true, - "license": "ISC", - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/npm/node_modules/agent-base": { - "version": "7.1.4", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">= 14" - } - }, - "node_modules/npm/node_modules/aproba": { - "version": "2.1.0", - "inBundle": true, - "license": "ISC" - }, - "node_modules/npm/node_modules/archy": { - "version": "1.0.0", - "inBundle": true, - "license": "MIT" - }, - "node_modules/npm/node_modules/balanced-match": { - "version": "4.0.4", - "inBundle": true, - "license": "MIT", - "engines": { - "node": "18 || 20 || >=22" - } - }, - "node_modules/npm/node_modules/bin-links": { - "version": "6.0.0", - "inBundle": true, - "license": "ISC", - "dependencies": { - "cmd-shim": "^8.0.0", - "npm-normalize-package-bin": "^5.0.0", - "proc-log": "^6.0.0", - "read-cmd-shim": "^6.0.0", - "write-file-atomic": "^7.0.0" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/npm/node_modules/binary-extensions": { - "version": "3.1.0", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=18.20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/npm/node_modules/brace-expansion": { - "version": "5.0.4", - "inBundle": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^4.0.2" - }, - "engines": { - "node": "18 || 20 || >=22" - } - }, - "node_modules/npm/node_modules/cacache": { - "version": "20.0.4", - "inBundle": true, - "license": "ISC", - "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" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/npm/node_modules/chalk": { - "version": "5.6.2", - "inBundle": true, - "license": "MIT", - "engines": { - "node": "^12.17.0 || ^14.13 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/npm/node_modules/chownr": { - "version": "3.0.0", - "inBundle": true, - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=18" - } - }, - "node_modules/npm/node_modules/ci-info": { - "version": "4.4.0", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/sibiraj-s" - } - ], - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/npm/node_modules/cidr-regex": { - "version": "5.0.3", - "inBundle": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=20" - } - }, - "node_modules/npm/node_modules/cmd-shim": { - "version": "8.0.0", - "inBundle": true, - "license": "ISC", - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/npm/node_modules/common-ancestor-path": { - "version": "2.0.0", - "inBundle": true, - "license": "BlueOak-1.0.0", - "engines": { - "node": ">= 18" - } - }, - "node_modules/npm/node_modules/cssesc": { - "version": "3.0.0", - "inBundle": true, - "license": "MIT", - "bin": { - "cssesc": "bin/cssesc" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/npm/node_modules/debug": { - "version": "4.4.3", - "inBundle": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/npm/node_modules/diff": { - "version": "8.0.3", - "inBundle": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.3.1" - } - }, - "node_modules/npm/node_modules/env-paths": { - "version": "2.2.1", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/npm/node_modules/exponential-backoff": { - "version": "3.1.3", - "inBundle": true, - "license": "Apache-2.0" - }, - "node_modules/npm/node_modules/fastest-levenshtein": { - "version": "1.0.16", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">= 4.9.1" - } - }, - "node_modules/npm/node_modules/fs-minipass": { - "version": "3.0.3", - "inBundle": true, - "license": "ISC", - "dependencies": { - "minipass": "^7.0.3" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/npm/node_modules/glob": { - "version": "13.0.6", - "inBundle": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "minimatch": "^10.2.2", - "minipass": "^7.1.3", - "path-scurry": "^2.0.2" - }, - "engines": { - "node": "18 || 20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/npm/node_modules/graceful-fs": { - "version": "4.2.11", - "inBundle": true, - "license": "ISC" - }, - "node_modules/npm/node_modules/hosted-git-info": { - "version": "9.0.2", - "inBundle": true, - "license": "ISC", - "dependencies": { - "lru-cache": "^11.1.0" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/npm/node_modules/http-cache-semantics": { - "version": "4.2.0", - "inBundle": true, - "license": "BSD-2-Clause" - }, - "node_modules/npm/node_modules/http-proxy-agent": { - "version": "7.0.2", - "inBundle": true, - "license": "MIT", - "dependencies": { - "agent-base": "^7.1.0", - "debug": "^4.3.4" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/npm/node_modules/https-proxy-agent": { - "version": "7.0.6", - "inBundle": true, - "license": "MIT", - "dependencies": { - "agent-base": "^7.1.2", - "debug": "4" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/npm/node_modules/iconv-lite": { - "version": "0.7.2", - "inBundle": true, - "license": "MIT", - "optional": true, - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, - "engines": { - "node": ">=0.10.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/npm/node_modules/ignore-walk": { - "version": "8.0.0", - "inBundle": true, - "license": "ISC", - "dependencies": { - "minimatch": "^10.0.3" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/npm/node_modules/ini": { - "version": "6.0.0", - "inBundle": true, - "license": "ISC", - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/npm/node_modules/init-package-json": { - "version": "8.2.5", - "inBundle": true, - "license": "ISC", - "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" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/npm/node_modules/ip-address": { - "version": "10.1.0", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">= 12" - } - }, - "node_modules/npm/node_modules/is-cidr": { - "version": "6.0.3", - "inBundle": true, - "license": "BSD-2-Clause", - "dependencies": { - "cidr-regex": "^5.0.1" - }, - "engines": { - "node": ">=20" - } - }, - "node_modules/npm/node_modules/isexe": { - "version": "4.0.0", - "inBundle": true, - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=20" - } - }, - "node_modules/npm/node_modules/json-parse-even-better-errors": { - "version": "5.0.0", - "inBundle": true, - "license": "MIT", - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/npm/node_modules/json-stringify-nice": { - "version": "1.1.4", - "inBundle": true, - "license": "ISC", - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/npm/node_modules/jsonparse": { - "version": "1.3.1", - "engines": [ - "node >= 0.2.0" - ], - "inBundle": true, - "license": "MIT" - }, - "node_modules/npm/node_modules/just-diff": { - "version": "6.0.2", - "inBundle": true, - "license": "MIT" - }, - "node_modules/npm/node_modules/just-diff-apply": { - "version": "5.5.0", - "inBundle": true, - "license": "MIT" - }, - "node_modules/npm/node_modules/libnpmaccess": { - "version": "10.0.3", - "inBundle": true, - "license": "ISC", - "dependencies": { - "npm-package-arg": "^13.0.0", - "npm-registry-fetch": "^19.0.0" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/npm/node_modules/libnpmdiff": { - "version": "8.1.5", - "inBundle": true, - "license": "ISC", - "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" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/npm/node_modules/libnpmexec": { - "version": "10.2.5", - "inBundle": true, - "license": "ISC", - "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" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/npm/node_modules/libnpmfund": { - "version": "7.0.19", - "inBundle": true, - "license": "ISC", - "dependencies": { - "@npmcli/arborist": "^9.4.2" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/npm/node_modules/libnpmorg": { - "version": "8.0.1", - "inBundle": true, - "license": "ISC", - "dependencies": { - "aproba": "^2.0.0", - "npm-registry-fetch": "^19.0.0" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/npm/node_modules/libnpmpack": { - "version": "9.1.5", - "inBundle": true, - "license": "ISC", - "dependencies": { - "@npmcli/arborist": "^9.4.2", - "@npmcli/run-script": "^10.0.0", - "npm-package-arg": "^13.0.0", - "pacote": "^21.0.2" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/npm/node_modules/libnpmpublish": { - "version": "11.1.3", - "inBundle": true, - "license": "ISC", - "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" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/npm/node_modules/libnpmsearch": { - "version": "9.0.1", - "inBundle": true, - "license": "ISC", - "dependencies": { - "npm-registry-fetch": "^19.0.0" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/npm/node_modules/libnpmteam": { - "version": "8.0.2", - "inBundle": true, - "license": "ISC", - "dependencies": { - "aproba": "^2.0.0", - "npm-registry-fetch": "^19.0.0" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/npm/node_modules/libnpmversion": { - "version": "8.0.3", - "inBundle": true, - "license": "ISC", - "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" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/npm/node_modules/lru-cache": { - "version": "11.2.7", - "inBundle": true, - "license": "BlueOak-1.0.0", - "engines": { - "node": "20 || >=22" - } - }, - "node_modules/npm/node_modules/make-fetch-happen": { - "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", - "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" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/npm/node_modules/minimatch": { - "version": "10.2.4", - "inBundle": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "brace-expansion": "^5.0.2" - }, - "engines": { - "node": "18 || 20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/npm/node_modules/minipass": { - "version": "7.1.3", - "inBundle": true, - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=16 || 14 >=14.17" - } - }, - "node_modules/npm/node_modules/minipass-collect": { - "version": "2.0.1", - "inBundle": true, - "license": "ISC", - "dependencies": { - "minipass": "^7.0.3" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - } - }, - "node_modules/npm/node_modules/minipass-fetch": { - "version": "5.0.2", - "inBundle": true, - "license": "MIT", - "dependencies": { - "minipass": "^7.0.3", - "minipass-sized": "^2.0.0", - "minizlib": "^3.0.1" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - }, - "optionalDependencies": { - "iconv-lite": "^0.7.2" - } - }, - "node_modules/npm/node_modules/minipass-flush": { - "version": "1.0.5", - "inBundle": true, - "license": "ISC", - "dependencies": { - "minipass": "^3.0.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/npm/node_modules/minipass-flush/node_modules/minipass": { - "version": "3.3.6", - "inBundle": true, - "license": "ISC", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/npm/node_modules/minipass-flush/node_modules/yallist": { - "version": "4.0.0", - "inBundle": true, - "license": "ISC" - }, - "node_modules/npm/node_modules/minipass-pipeline": { - "version": "1.2.4", - "inBundle": true, - "license": "ISC", - "dependencies": { - "minipass": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/npm/node_modules/minipass-pipeline/node_modules/minipass": { - "version": "3.3.6", - "inBundle": true, - "license": "ISC", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/npm/node_modules/minipass-pipeline/node_modules/yallist": { - "version": "4.0.0", - "inBundle": true, - "license": "ISC" - }, - "node_modules/npm/node_modules/minipass-sized": { - "version": "2.0.0", - "inBundle": true, - "license": "ISC", - "dependencies": { - "minipass": "^7.1.2" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/npm/node_modules/minizlib": { - "version": "3.1.0", - "inBundle": true, - "license": "MIT", - "dependencies": { - "minipass": "^7.1.2" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/npm/node_modules/ms": { - "version": "2.1.3", - "inBundle": true, - "license": "MIT" - }, - "node_modules/npm/node_modules/mute-stream": { - "version": "3.0.0", - "inBundle": true, - "license": "ISC", - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/npm/node_modules/negotiator": { - "version": "1.0.0", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/npm/node_modules/node-gyp": { - "version": "12.2.0", - "inBundle": true, - "license": "MIT", - "dependencies": { - "env-paths": "^2.2.0", - "exponential-backoff": "^3.1.1", - "graceful-fs": "^4.2.6", - "make-fetch-happen": "^15.0.0", - "nopt": "^9.0.0", - "proc-log": "^6.0.0", - "semver": "^7.3.5", - "tar": "^7.5.4", - "tinyglobby": "^0.2.12", - "which": "^6.0.0" - }, - "bin": { - "node-gyp": "bin/node-gyp.js" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/npm/node_modules/nopt": { - "version": "9.0.0", - "inBundle": true, - "license": "ISC", - "dependencies": { - "abbrev": "^4.0.0" - }, - "bin": { - "nopt": "bin/nopt.js" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/npm/node_modules/npm-audit-report": { - "version": "7.0.0", - "inBundle": true, - "license": "ISC", - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/npm/node_modules/npm-bundled": { - "version": "5.0.0", - "inBundle": true, - "license": "ISC", - "dependencies": { - "npm-normalize-package-bin": "^5.0.0" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/npm/node_modules/npm-install-checks": { - "version": "8.0.0", - "inBundle": true, - "license": "BSD-2-Clause", - "dependencies": { - "semver": "^7.1.1" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/npm/node_modules/npm-normalize-package-bin": { - "version": "5.0.0", - "inBundle": true, - "license": "ISC", - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/npm/node_modules/npm-package-arg": { - "version": "13.0.2", - "inBundle": true, - "license": "ISC", - "dependencies": { - "hosted-git-info": "^9.0.0", - "proc-log": "^6.0.0", - "semver": "^7.3.5", - "validate-npm-package-name": "^7.0.0" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/npm/node_modules/npm-packlist": { - "version": "10.0.4", - "inBundle": true, - "license": "ISC", - "dependencies": { - "ignore-walk": "^8.0.0", - "proc-log": "^6.0.0" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/npm/node_modules/npm-pick-manifest": { - "version": "11.0.3", - "inBundle": true, - "license": "ISC", - "dependencies": { - "npm-install-checks": "^8.0.0", - "npm-normalize-package-bin": "^5.0.0", - "npm-package-arg": "^13.0.0", - "semver": "^7.3.5" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/npm/node_modules/npm-profile": { - "version": "12.0.1", - "inBundle": true, - "license": "ISC", - "dependencies": { - "npm-registry-fetch": "^19.0.0", - "proc-log": "^6.0.0" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/npm/node_modules/npm-registry-fetch": { - "version": "19.1.1", - "inBundle": true, - "license": "ISC", - "dependencies": { - "@npmcli/redact": "^4.0.0", - "jsonparse": "^1.3.1", - "make-fetch-happen": "^15.0.0", - "minipass": "^7.0.2", - "minipass-fetch": "^5.0.0", - "minizlib": "^3.0.1", - "npm-package-arg": "^13.0.0", - "proc-log": "^6.0.0" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/npm/node_modules/npm-user-validate": { - "version": "4.0.0", - "inBundle": true, - "license": "BSD-2-Clause", - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/npm/node_modules/p-map": { - "version": "7.0.4", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/npm/node_modules/pacote": { - "version": "21.5.0", - "inBundle": true, - "license": "ISC", - "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" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/npm/node_modules/parse-conflict-json": { - "version": "5.0.1", - "inBundle": true, - "license": "ISC", - "dependencies": { - "json-parse-even-better-errors": "^5.0.0", - "just-diff": "^6.0.0", - "just-diff-apply": "^5.2.0" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/npm/node_modules/path-scurry": { - "version": "2.0.2", - "inBundle": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "lru-cache": "^11.0.0", - "minipass": "^7.1.2" - }, - "engines": { - "node": "18 || 20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/npm/node_modules/postcss-selector-parser": { - "version": "7.1.1", - "inBundle": true, - "license": "MIT", - "dependencies": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/npm/node_modules/proc-log": { - "version": "6.1.0", - "inBundle": true, - "license": "ISC", - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/npm/node_modules/proggy": { - "version": "4.0.0", - "inBundle": true, - "license": "ISC", - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/npm/node_modules/promise-all-reject-late": { - "version": "1.0.1", - "inBundle": true, - "license": "ISC", - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/npm/node_modules/promise-call-limit": { - "version": "3.0.2", - "inBundle": true, - "license": "ISC", - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/npm/node_modules/promzard": { - "version": "3.0.1", - "inBundle": true, - "license": "ISC", - "dependencies": { - "read": "^5.0.0" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/npm/node_modules/qrcode-terminal": { - "version": "0.12.0", - "inBundle": true, - "bin": { - "qrcode-terminal": "bin/qrcode-terminal.js" - } - }, - "node_modules/npm/node_modules/read": { - "version": "5.0.1", - "inBundle": true, - "license": "ISC", - "dependencies": { - "mute-stream": "^3.0.0" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/npm/node_modules/read-cmd-shim": { - "version": "6.0.0", - "inBundle": true, - "license": "ISC", - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/npm/node_modules/safer-buffer": { - "version": "2.1.2", - "inBundle": true, - "license": "MIT", - "optional": true - }, - "node_modules/npm/node_modules/semver": { - "version": "7.7.4", - "inBundle": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/npm/node_modules/signal-exit": { - "version": "4.1.0", - "inBundle": true, - "license": "ISC", - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/npm/node_modules/sigstore": { - "version": "4.1.0", - "inBundle": true, - "license": "Apache-2.0", - "dependencies": { - "@sigstore/bundle": "^4.0.0", - "@sigstore/core": "^3.1.0", - "@sigstore/protobuf-specs": "^0.5.0", - "@sigstore/sign": "^4.1.0", - "@sigstore/tuf": "^4.0.1", - "@sigstore/verify": "^3.1.0" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/npm/node_modules/smart-buffer": { - "version": "4.2.0", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">= 6.0.0", - "npm": ">= 3.0.0" - } - }, - "node_modules/npm/node_modules/socks": { - "version": "2.8.7", - "inBundle": true, - "license": "MIT", - "dependencies": { - "ip-address": "^10.0.1", - "smart-buffer": "^4.2.0" - }, - "engines": { - "node": ">= 10.0.0", - "npm": ">= 3.0.0" - } - }, - "node_modules/npm/node_modules/socks-proxy-agent": { - "version": "8.0.5", - "inBundle": true, - "license": "MIT", - "dependencies": { - "agent-base": "^7.1.2", - "debug": "^4.3.4", - "socks": "^2.8.3" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/npm/node_modules/spdx-exceptions": { - "version": "2.5.0", - "inBundle": true, - "license": "CC-BY-3.0" - }, - "node_modules/npm/node_modules/spdx-expression-parse": { - "version": "4.0.0", - "inBundle": true, - "license": "MIT", - "dependencies": { - "spdx-exceptions": "^2.1.0", - "spdx-license-ids": "^3.0.0" - } - }, - "node_modules/npm/node_modules/spdx-license-ids": { - "version": "3.0.23", - "inBundle": true, - "license": "CC0-1.0" - }, - "node_modules/npm/node_modules/ssri": { - "version": "13.0.1", - "inBundle": true, - "license": "ISC", - "dependencies": { - "minipass": "^7.0.3" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/npm/node_modules/supports-color": { - "version": "10.2.2", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" - } - }, - "node_modules/npm/node_modules/tar": { - "version": "7.5.11", - "inBundle": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "@isaacs/fs-minipass": "^4.0.0", - "chownr": "^3.0.0", - "minipass": "^7.1.2", - "minizlib": "^3.1.0", - "yallist": "^5.0.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/npm/node_modules/text-table": { - "version": "0.2.0", - "inBundle": true, - "license": "MIT" - }, - "node_modules/npm/node_modules/tiny-relative-date": { - "version": "2.0.2", - "inBundle": true, - "license": "MIT" - }, - "node_modules/npm/node_modules/tinyglobby": { - "version": "0.2.15", - "inBundle": true, - "license": "MIT", - "dependencies": { - "fdir": "^6.5.0", - "picomatch": "^4.0.3" - }, - "engines": { - "node": ">=12.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/SuperchupuDev" - } - }, - "node_modules/npm/node_modules/tinyglobby/node_modules/fdir": { - "version": "6.5.0", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "picomatch": "^3 || ^4" - }, - "peerDependenciesMeta": { - "picomatch": { - "optional": true - } - } - }, - "node_modules/npm/node_modules/tinyglobby/node_modules/picomatch": { - "version": "4.0.3", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/npm/node_modules/treeverse": { - "version": "3.0.0", - "inBundle": true, - "license": "ISC", - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/npm/node_modules/tuf-js": { - "version": "4.1.0", - "inBundle": true, - "license": "MIT", - "dependencies": { - "@tufjs/models": "4.1.0", - "debug": "^4.4.3", - "make-fetch-happen": "^15.0.1" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/npm/node_modules/util-deprecate": { - "version": "1.0.2", - "inBundle": true, - "license": "MIT" - }, - "node_modules/npm/node_modules/validate-npm-package-name": { - "version": "7.0.2", - "inBundle": true, - "license": "ISC", - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/npm/node_modules/walk-up-path": { - "version": "4.0.0", - "inBundle": true, - "license": "ISC", - "engines": { - "node": "20 || >=22" - } - }, - "node_modules/npm/node_modules/which": { - "version": "6.0.1", - "inBundle": true, - "license": "ISC", - "dependencies": { - "isexe": "^4.0.0" - }, - "bin": { - "node-which": "bin/which.js" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/npm/node_modules/write-file-atomic": { - "version": "7.0.1", - "inBundle": true, - "license": "ISC", - "dependencies": { - "signal-exit": "^4.0.1" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/npm/node_modules/yallist": { - "version": "5.0.0", - "inBundle": true, - "license": "BlueOak-1.0.0", - "engines": { - "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, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/object-keys": { - "version": "1.1.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/object.assign": { - "version": "4.1.7", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.3", - "define-properties": "^1.2.1", - "es-object-atoms": "^1.0.0", - "has-symbols": "^1.1.0", - "object-keys": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/obug": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.1.tgz", - "integrity": "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==", - "funding": [ - "https://github.com/sponsors/sxzz", - "https://opencollective.com/debug" - ], - "license": "MIT" - }, - "node_modules/on-headers": { - "version": "1.1.0", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/once": { - "version": "1.4.0", - "dev": true, - "license": "ISC", - "dependencies": { - "wrappy": "1" - } - }, - "node_modules/open": { - "version": "8.4.2", - "dev": true, - "license": "MIT", - "dependencies": { - "define-lazy-prop": "^2.0.0", - "is-docker": "^2.1.1", - "is-wsl": "^2.2.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/optionator": { - "version": "0.9.4", - "dev": true, - "license": "MIT", - "dependencies": { - "deep-is": "^0.1.3", - "fast-levenshtein": "^2.0.6", - "levn": "^0.4.1", - "prelude-ls": "^1.2.1", - "type-check": "^0.4.0", - "word-wrap": "^1.2.5" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/own-keys": { - "version": "1.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "get-intrinsic": "^1.2.6", - "object-keys": "^1.1.1", - "safe-push-apply": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/p-limit": { - "version": "3.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "yocto-queue": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-locate": { - "version": "5.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "p-limit": "^3.0.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-map": { - "version": "4.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "aggregate-error": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-try": { - "version": "2.2.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/package-json-from-dist": { - "version": "1.0.1", - "dev": true, - "license": "BlueOak-1.0.0" - }, - "node_modules/parent-module": { - "version": "1.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "callsites": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/parse-json": { - "version": "5.2.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.0.0", - "error-ex": "^1.3.1", - "json-parse-even-better-errors": "^2.3.0", - "lines-and-columns": "^1.1.6" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/path-exists": { - "version": "4.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/path-key": { - "version": "3.1.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/path-parse": { - "version": "1.0.7", - "dev": true, - "license": "MIT" - }, - "node_modules/path-scurry": { - "version": "2.0.2", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "lru-cache": "^11.0.0", - "minipass": "^7.1.2" - }, - "engines": { - "node": "18 || 20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/path-type": { - "version": "4.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/pathe": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", - "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", - "license": "MIT" - }, - "node_modules/pend": { - "version": "1.2.0", - "dev": true, - "license": "MIT" - }, - "node_modules/picocolors": { - "version": "1.1.1", - "license": "ISC" - }, - "node_modules/picomatch": { - "version": "4.0.4", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/pify": { - "version": "2.3.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/playwright": { - "version": "1.59.1", - "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.59.1.tgz", - "integrity": "sha512-C8oWjPR3F81yljW9o5OxcWzfh6avkVwDD2VYdwIGqTkl+OGFISgypqzfu7dOe4QNLL2aqcWBmI3PMtLIK233lw==", - "devOptional": true, - "license": "Apache-2.0", - "dependencies": { - "playwright-core": "1.59.1" - }, - "bin": { - "playwright": "cli.js" - }, - "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "fsevents": "2.3.2" - } - }, - "node_modules/playwright-core": { - "version": "1.59.1", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.59.1.tgz", - "integrity": "sha512-HBV/RJg81z5BiiZ9yPzIiClYV/QMsDCKUyogwH9p3MCP6IYjUFu/MActgYAvK0oWyV9NlwM3GLBjADyWgydVyg==", - "devOptional": true, - "license": "Apache-2.0", - "bin": { - "playwright-core": "cli.js" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/playwright/node_modules/fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/plist": { - "version": "3.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@xmldom/xmldom": "^0.8.8", - "base64-js": "^1.5.1", - "xmlbuilder": "^15.1.1" - }, - "engines": { - "node": ">=10.4.0" - } - }, - "node_modules/plist/node_modules/@xmldom/xmldom": { - "version": "0.8.11", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/pngjs": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-7.0.0.tgz", - "integrity": "sha512-LKWqWJRhstyYo9pGvgor/ivk2w94eSjE3RGVuzLGlr3NmD8bf7RcYGze1mNdEHRP6TRP6rMuDHk5t44hnTRyow==", - "devOptional": true, - "license": "MIT", - "engines": { - "node": ">=14.19.0" - } - }, - "node_modules/pocketbase": { - "version": "0.26.8", - "license": "MIT" - }, - "node_modules/possible-typed-array-names": { - "version": "1.1.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/postcss": { - "version": "8.5.8", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "nanoid": "^3.3.11", - "picocolors": "^1.1.1", - "source-map-js": "^1.2.1" - }, - "engines": { - "node": "^10 || ^12 || >=14" - } - }, - "node_modules/postcss-media-query-parser": { - "version": "0.2.3", - "dev": true, - "license": "MIT" - }, - "node_modules/postcss-resolve-nested-selector": { - "version": "0.1.6", - "dev": true, - "license": "MIT" - }, - "node_modules/postcss-safe-parser": { - "version": "7.0.1", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss-safe-parser" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "engines": { - "node": ">=18.0" - }, - "peerDependencies": { - "postcss": "^8.4.31" - } - }, - "node_modules/postcss-scss": { - "version": "4.0.9", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss-scss" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "engines": { - "node": ">=12.0" - }, - "peerDependencies": { - "postcss": "^8.4.29" - } - }, - "node_modules/postcss-selector-parser": { - "version": "7.1.1", - "dev": true, - "license": "MIT", - "dependencies": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/postcss-value-parser": { - "version": "4.2.0", - "dev": true, - "license": "MIT" - }, - "node_modules/prebuild-install": { - "version": "7.1.3", - "dev": true, - "license": "MIT", - "dependencies": { - "detect-libc": "^2.0.0", - "expand-template": "^2.0.3", - "github-from-package": "0.0.0", - "minimist": "^1.2.3", - "mkdirp-classic": "^0.5.3", - "napi-build-utils": "^2.0.0", - "node-abi": "^3.3.0", - "pump": "^3.0.0", - "rc": "^1.2.7", - "simple-get": "^4.0.0", - "tar-fs": "^2.0.0", - "tunnel-agent": "^0.6.0" - }, - "bin": { - "prebuild-install": "bin.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/prebuild-install/node_modules/tar-fs": { - "version": "2.1.4", - "dev": true, - "license": "MIT", - "dependencies": { - "chownr": "^1.1.1", - "mkdirp-classic": "^0.5.2", - "pump": "^3.0.0", - "tar-stream": "^2.1.4" - } - }, - "node_modules/prebuild-install/node_modules/tar-fs/node_modules/chownr": { - "version": "1.1.4", - "dev": true, - "license": "ISC" - }, - "node_modules/prebuild-install/node_modules/tar-fs/node_modules/tar-stream": { - "version": "2.2.0", - "dev": true, - "license": "MIT", - "dependencies": { - "bl": "^4.0.3", - "end-of-stream": "^1.4.1", - "fs-constants": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^3.1.1" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/prelude-ls": { - "version": "1.2.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/prettier": { - "version": "3.8.1", - "dev": true, - "license": "MIT", - "bin": { - "prettier": "bin/prettier.cjs" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/prettier/prettier?sponsor=1" - } - }, - "node_modules/pretty-bytes": { - "version": "6.1.1", - "dev": true, - "license": "MIT", - "engines": { - "node": "^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/pretty-format": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", - "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1", - "ansi-styles": "^5.0.0", - "react-is": "^17.0.1" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/pretty-format/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/process-nextick-args": { - "version": "2.0.1", - "dev": true, - "license": "MIT" - }, - "node_modules/prompts": { - "version": "2.4.2", - "dev": true, - "license": "MIT", - "dependencies": { - "kleur": "^3.0.3", - "sisteransi": "^1.0.5" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/prompts/node_modules/kleur": { - "version": "3.0.3", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/pump": { - "version": "3.0.4", - "dev": true, - "license": "MIT", - "dependencies": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, - "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": { - "node": ">=6" - } - }, - "node_modules/q": { - "version": "1.5.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.6.0", - "teleport": ">=0.2.0" - } - }, - "node_modules/qified": { - "version": "0.9.0", - "dev": true, - "license": "MIT", - "dependencies": { - "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, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, - "node_modules/quick-lru": { - "version": "4.0.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/rc": { - "version": "1.2.8", - "dev": true, - "license": "(BSD-2-Clause OR MIT OR Apache-2.0)", - "dependencies": { - "deep-extend": "^0.6.0", - "ini": "~1.3.0", - "minimist": "^1.2.0", - "strip-json-comments": "~2.0.1" - }, - "bin": { - "rc": "cli.js" - } - }, - "node_modules/rc/node_modules/ini": { - "version": "1.3.8", - "dev": true, - "license": "ISC" - }, - "node_modules/rc/node_modules/strip-json-comments": { - "version": "2.0.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/react-is": { - "version": "17.0.2", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", - "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", - "dev": true, - "license": "MIT" - }, - "node_modules/read-pkg": { - "version": "3.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "load-json-file": "^4.0.0", - "normalize-package-data": "^2.3.2", - "path-type": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/read-pkg-up": { - "version": "3.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "find-up": "^2.0.0", - "read-pkg": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/read-pkg-up/node_modules/find-up": { - "version": "2.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "locate-path": "^2.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/read-pkg-up/node_modules/find-up/node_modules/locate-path": { - "version": "2.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "p-locate": "^2.0.0", - "path-exists": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/read-pkg-up/node_modules/find-up/node_modules/locate-path/node_modules/p-locate": { - "version": "2.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "p-limit": "^1.1.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/read-pkg-up/node_modules/find-up/node_modules/locate-path/node_modules/p-locate/node_modules/p-limit": { - "version": "1.3.0", - "dev": true, - "license": "MIT", - "dependencies": { - "p-try": "^1.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/read-pkg-up/node_modules/find-up/node_modules/locate-path/node_modules/p-locate/node_modules/p-limit/node_modules/p-try": { - "version": "1.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/read-pkg-up/node_modules/find-up/node_modules/locate-path/node_modules/path-exists": { - "version": "3.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/read-pkg/node_modules/normalize-package-data": { - "version": "2.5.0", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "hosted-git-info": "^2.1.4", - "resolve": "^1.10.0", - "semver": "2 || 3 || 4 || 5", - "validate-npm-package-license": "^3.0.1" - } - }, - "node_modules/read-pkg/node_modules/normalize-package-data/node_modules/hosted-git-info": { - "version": "2.8.9", - "dev": true, - "license": "ISC" - }, - "node_modules/read-pkg/node_modules/normalize-package-data/node_modules/semver": { - "version": "5.7.2", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver" - } - }, - "node_modules/read-pkg/node_modules/path-type": { - "version": "3.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "pify": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/read-pkg/node_modules/path-type/node_modules/pify": { - "version": "3.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/readable-stream": { - "version": "3.6.2", - "dev": true, - "license": "MIT", - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/redent": { - "version": "3.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "indent-string": "^4.0.0", - "strip-indent": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/reflect.getprototypeof": { - "version": "1.0.10", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.9", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.0.0", - "get-intrinsic": "^1.2.7", - "get-proto": "^1.0.1", - "which-builtin-type": "^1.2.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/regenerate": { - "version": "1.4.2", - "dev": true, - "license": "MIT" - }, - "node_modules/regenerate-unicode-properties": { - "version": "10.2.2", - "dev": true, - "license": "MIT", - "dependencies": { - "regenerate": "^1.4.2" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/regenerator-runtime": { - "version": "0.11.1", - "license": "MIT" - }, - "node_modules/regexp-to-ast": { - "version": "0.5.0", - "dev": true, - "license": "MIT" - }, - "node_modules/regexp.prototype.flags": { - "version": "1.5.4", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "define-properties": "^1.2.1", - "es-errors": "^1.3.0", - "get-proto": "^1.0.1", - "gopd": "^1.2.0", - "set-function-name": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/regexpu-core": { - "version": "6.4.0", - "dev": true, - "license": "MIT", - "dependencies": { - "regenerate": "^1.4.2", - "regenerate-unicode-properties": "^10.2.2", - "regjsgen": "^0.8.0", - "regjsparser": "^0.13.0", - "unicode-match-property-ecmascript": "^2.0.0", - "unicode-match-property-value-ecmascript": "^2.2.1" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/regjsgen": { - "version": "0.8.0", - "dev": true, - "license": "MIT" - }, - "node_modules/regjsparser": { - "version": "0.13.0", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "jsesc": "~3.1.0" - }, - "bin": { - "regjsparser": "bin/parser" - } - }, - "node_modules/replace": { - "version": "1.2.2", - "dev": true, - "license": "MIT", - "dependencies": { - "chalk": "2.4.2", - "minimatch": "3.0.5", - "yargs": "^15.3.1" - }, - "bin": { - "replace": "bin/replace.js", - "search": "bin/search.js" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/replace/node_modules/chalk": { - "version": "2.4.2", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/replace/node_modules/chalk/node_modules/ansi-styles": { - "version": "3.2.1", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/replace/node_modules/chalk/node_modules/ansi-styles/node_modules/color-convert": { - "version": "1.9.3", - "dev": true, - "license": "MIT", - "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/replace/node_modules/chalk/node_modules/ansi-styles/node_modules/color-convert/node_modules/color-name": { - "version": "1.1.3", - "dev": true, - "license": "MIT" - }, - "node_modules/replace/node_modules/chalk/node_modules/escape-string-regexp": { - "version": "1.0.5", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/replace/node_modules/chalk/node_modules/supports-color": { - "version": "5.5.0", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/replace/node_modules/chalk/node_modules/supports-color/node_modules/has-flag": { - "version": "3.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/replace/node_modules/minimatch": { - "version": "3.0.5", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/replace/node_modules/yargs": { - "version": "15.4.1", - "dev": true, - "license": "MIT", - "dependencies": { - "cliui": "^6.0.0", - "decamelize": "^1.2.0", - "find-up": "^4.1.0", - "get-caller-file": "^2.0.1", - "require-directory": "^2.1.1", - "require-main-filename": "^2.0.0", - "set-blocking": "^2.0.0", - "string-width": "^4.2.0", - "which-module": "^2.0.0", - "y18n": "^4.0.0", - "yargs-parser": "^18.1.2" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/replace/node_modules/yargs/node_modules/cliui": { - "version": "6.0.0", - "dev": true, - "license": "ISC", - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^6.2.0" - } - }, - "node_modules/replace/node_modules/yargs/node_modules/cliui/node_modules/wrap-ansi": { - "version": "6.2.0", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/replace/node_modules/yargs/node_modules/find-up": { - "version": "4.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/replace/node_modules/yargs/node_modules/find-up/node_modules/locate-path": { - "version": "5.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "p-locate": "^4.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/replace/node_modules/yargs/node_modules/find-up/node_modules/locate-path/node_modules/p-locate": { - "version": "4.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "p-limit": "^2.2.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/replace/node_modules/yargs/node_modules/find-up/node_modules/locate-path/node_modules/p-locate/node_modules/p-limit": { - "version": "2.3.0", - "dev": true, - "license": "MIT", - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/replace/node_modules/yargs/node_modules/y18n": { - "version": "4.0.3", - "dev": true, - "license": "ISC" - }, - "node_modules/replace/node_modules/yargs/node_modules/yargs-parser": { - "version": "18.1.3", - "dev": true, - "license": "ISC", - "dependencies": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/require-directory": { - "version": "2.1.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/require-from-string": { - "version": "2.0.2", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/require-main-filename": { - "version": "2.0.0", - "dev": true, - "license": "ISC" - }, - "node_modules/resolve": { - "version": "1.22.11", - "dev": true, - "license": "MIT", - "dependencies": { - "is-core-module": "^2.16.1", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - }, - "bin": { - "resolve": "bin/resolve" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/resolve-from": { - "version": "5.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/reusify": { - "version": "1.1.0", - "dev": true, - "license": "MIT", - "engines": { - "iojs": ">=1.0.0", - "node": ">=0.10.0" - } - }, - "node_modules/rimraf": { - "version": "6.1.3", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "glob": "^13.0.3", - "package-json-from-dist": "^1.0.1" - }, - "bin": { - "rimraf": "dist/esm/bin.mjs" - }, - "engines": { - "node": "20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/rollup": { - "version": "2.80.0", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.80.0.tgz", - "integrity": "sha512-cIFJOD1DESzpjOBl763Kp1AH7UE/0fcdHe6rZXUdQ9c50uvgigvW97u3IcSeBwOkgqL/PXPBktBCh0KEu5L8XQ==", - "dev": true, - "license": "MIT", - "bin": { - "rollup": "dist/bin/rollup" - }, - "engines": { - "node": ">=10.0.0" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - } - }, - "node_modules/run-parallel": { - "version": "1.2.0", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "dependencies": { - "queue-microtask": "^1.2.2" - } - }, - "node_modules/safe-array-concat": { - "version": "1.1.3", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.2", - "get-intrinsic": "^1.2.6", - "has-symbols": "^1.1.0", - "isarray": "^2.0.5" - }, - "engines": { - "node": ">=0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/safe-buffer": { - "version": "5.2.1", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, - "node_modules/safe-push-apply": { - "version": "1.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "isarray": "^2.0.5" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/safe-regex-test": { - "version": "1.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "is-regex": "^1.2.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/sax": { - "version": "1.6.0", - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=11.0.0" - } - }, - "node_modules/semver": { - "version": "7.7.4", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/serialize-javascript": { - "version": "7.0.5", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/set-blocking": { - "version": "2.0.0", - "dev": true, - "license": "ISC" - }, - "node_modules/set-function-length": { - "version": "1.2.2", - "dev": true, - "license": "MIT", - "dependencies": { - "define-data-property": "^1.1.4", - "es-errors": "^1.3.0", - "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.4", - "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/set-function-name": { - "version": "2.0.2", - "dev": true, - "license": "MIT", - "dependencies": { - "define-data-property": "^1.1.4", - "es-errors": "^1.3.0", - "functions-have-names": "^1.2.3", - "has-property-descriptors": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/set-proto": { - "version": "1.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "dunder-proto": "^1.0.1", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/shaka-player": { - "version": "5.0.8", - "license": "Apache-2.0" - }, - "node_modules/sharp": { - "version": "0.32.6", - "dev": true, - "hasInstallScript": true, - "license": "Apache-2.0", - "dependencies": { - "color": "^4.2.3", - "detect-libc": "^2.0.2", - "node-addon-api": "^6.1.0", - "prebuild-install": "^7.1.1", - "semver": "^7.5.4", - "simple-get": "^4.0.1", - "tar-fs": "^3.0.4", - "tunnel-agent": "^0.6.0" - }, - "engines": { - "node": ">=14.15.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/shebang-command": { - "version": "2.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-regex": { - "version": "3.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/side-channel": { - "version": "1.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "object-inspect": "^1.13.3", - "side-channel-list": "^1.0.0", - "side-channel-map": "^1.0.1", - "side-channel-weakmap": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-list": { - "version": "1.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "object-inspect": "^1.13.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-map": { - "version": "1.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.5", - "object-inspect": "^1.13.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-weakmap": { - "version": "1.0.2", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.5", - "object-inspect": "^1.13.3", - "side-channel-map": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/siginfo": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", - "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", - "license": "ISC" - }, - "node_modules/signal-exit": { - "version": "3.0.7", - "dev": true, - "license": "ISC" - }, - "node_modules/simple-concat": { - "version": "1.0.1", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, - "node_modules/simple-get": { - "version": "4.0.1", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "dependencies": { - "decompress-response": "^6.0.0", - "once": "^1.3.1", - "simple-concat": "^1.0.0" - } - }, - "node_modules/simple-icons": { - "version": "16.13.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/simple-plist": { - "version": "1.3.1", - "dev": true, - "license": "MIT", - "dependencies": { - "bplist-creator": "0.1.0", - "bplist-parser": "0.3.1", - "plist": "^3.0.5" - } - }, - "node_modules/simple-plist/node_modules/bplist-parser": { - "version": "0.3.1", - "dev": true, - "license": "MIT", - "dependencies": { - "big-integer": "1.6.x" - }, - "engines": { - "node": ">= 5.10.0" - } - }, - "node_modules/simple-swizzle": { - "version": "0.2.4", - "dev": true, - "license": "MIT", - "dependencies": { - "is-arrayish": "^0.3.1" - } - }, - "node_modules/simple-swizzle/node_modules/is-arrayish": { - "version": "0.3.4", - "dev": true, - "license": "MIT" - }, - "node_modules/sirv": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/sirv/-/sirv-3.0.2.tgz", - "integrity": "sha512-2wcC/oGxHis/BoHkkPwldgiPSYcpZK3JU28WoMVv55yHJgcZ8rlXvuG9iZggz+sU1d4bRgIGASwyWqjxu3FM0g==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "@polka/url": "^1.0.0-next.24", - "mrmime": "^2.0.0", - "totalist": "^3.0.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/sisteransi": { - "version": "1.0.5", - "dev": true, - "license": "MIT" - }, - "node_modules/slash": { - "version": "3.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/slice-ansi": { - "version": "4.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "astral-regex": "^2.0.0", - "is-fullwidth-code-point": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/slice-ansi?sponsor=1" - } - }, - "node_modules/smob": { - "version": "1.6.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/source-map": { - "version": "0.7.6", - "devOptional": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">= 12" - } - }, - "node_modules/source-map-js": { - "version": "1.2.1", - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/source-map-support": { - "version": "0.5.21", - "devOptional": true, - "license": "MIT", - "dependencies": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, - "node_modules/sourcemap-codec": { - "name": "@jridgewell/sourcemap-codec", - "version": "1.5.5", - "dev": true, - "license": "MIT" - }, - "node_modules/spdx-correct": { - "version": "3.2.0", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "spdx-expression-parse": "^3.0.0", - "spdx-license-ids": "^3.0.0" - } - }, - "node_modules/spdx-exceptions": { - "version": "2.5.0", - "dev": true, - "license": "CC-BY-3.0" - }, - "node_modules/spdx-expression-parse": { - "version": "3.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "spdx-exceptions": "^2.1.0", - "spdx-license-ids": "^3.0.0" - } - }, - "node_modules/spdx-license-ids": { - "version": "3.0.23", - "dev": true, - "license": "CC0-1.0" - }, - "node_modules/split": { - "version": "1.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "through": "2" - }, - "engines": { - "node": "*" - } - }, - "node_modules/split2": { - "version": "4.2.0", - "dev": true, - "license": "ISC", - "engines": { - "node": ">= 10.x" - } - }, - "node_modules/stackback": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", - "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", - "license": "MIT" - }, - "node_modules/std-env": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/std-env/-/std-env-4.0.0.tgz", - "integrity": "sha512-zUMPtQ/HBY3/50VbpkupYHbRroTRZJPRLvreamgErJVys0ceuzMkD44J/QjqhHjOzK42GQ3QZIeFG1OYfOtKqQ==", - "license": "MIT" - }, - "node_modules/stop-iteration-iterator": { - "version": "1.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "internal-slot": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/stream-buffers": { - "version": "2.2.0", - "dev": true, - "license": "Unlicense", - "engines": { - "node": ">= 0.10.0" - } - }, - "node_modules/streamx": { - "version": "2.25.0", - "dev": true, - "license": "MIT", - "dependencies": { - "events-universal": "^1.0.0", - "fast-fifo": "^1.3.2", - "text-decoder": "^1.1.0" - } - }, - "node_modules/string_decoder": { - "version": "1.3.0", - "dev": true, - "license": "MIT", - "dependencies": { - "safe-buffer": "~5.2.0" - } - }, - "node_modules/string-width": { - "version": "4.2.3", - "dev": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/string.prototype.matchall": { - "version": "4.0.12", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.3", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.6", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.0.0", - "get-intrinsic": "^1.2.6", - "gopd": "^1.2.0", - "has-symbols": "^1.1.0", - "internal-slot": "^1.1.0", - "regexp.prototype.flags": "^1.5.3", - "set-function-name": "^2.0.2", - "side-channel": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/string.prototype.trim": { - "version": "1.2.10", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.2", - "define-data-property": "^1.1.4", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.5", - "es-object-atoms": "^1.0.0", - "has-property-descriptors": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/string.prototype.trimend": { - "version": "1.0.9", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.2", - "define-properties": "^1.2.1", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/string.prototype.trimstart": { - "version": "1.0.8", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/stringify-object": { - "version": "3.3.0", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "get-own-enumerable-property-symbols": "^3.0.0", - "is-obj": "^1.0.1", - "is-regexp": "^1.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/stringify-object/node_modules/is-obj": { - "version": "1.0.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/strip-ansi": { - "version": "6.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-bom": { - "version": "3.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/strip-comments": { - "version": "2.0.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - } - }, - "node_modules/strip-indent": { - "version": "3.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "min-indent": "^1.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-json-comments": { - "version": "3.1.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/stylelint": { - "version": "16.26.1", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/stylelint" - }, - { - "type": "github", - "url": "https://github.com/sponsors/stylelint" - } - ], - "license": "MIT", - "dependencies": { - "@csstools/css-parser-algorithms": "^3.0.5", - "@csstools/css-syntax-patches-for-csstree": "^1.0.19", - "@csstools/css-tokenizer": "^3.0.4", - "@csstools/media-query-list-parser": "^4.0.3", - "@csstools/selector-specificity": "^5.0.0", - "@dual-bundle/import-meta-resolve": "^4.2.1", - "balanced-match": "^2.0.0", - "colord": "^2.9.3", - "cosmiconfig": "^9.0.0", - "css-functions-list": "^3.2.3", - "css-tree": "^3.1.0", - "debug": "^4.4.3", - "fast-glob": "^3.3.3", - "fastest-levenshtein": "^1.0.16", - "file-entry-cache": "^11.1.1", - "global-modules": "^2.0.0", - "globby": "^11.1.0", - "globjoin": "^0.1.4", - "html-tags": "^3.3.1", - "ignore": "^7.0.5", - "imurmurhash": "^0.1.4", - "is-plain-object": "^5.0.0", - "known-css-properties": "^0.37.0", - "mathml-tag-names": "^2.1.3", - "meow": "^13.2.0", - "micromatch": "^4.0.8", - "normalize-path": "^3.0.0", - "picocolors": "^1.1.1", - "postcss": "^8.5.6", - "postcss-resolve-nested-selector": "^0.1.6", - "postcss-safe-parser": "^7.0.1", - "postcss-selector-parser": "^7.1.0", - "postcss-value-parser": "^4.2.0", - "resolve-from": "^5.0.0", - "string-width": "^4.2.3", - "supports-hyperlinks": "^3.2.0", - "svg-tags": "^1.0.0", - "table": "^6.9.0", - "write-file-atomic": "^5.0.1" - }, - "bin": { - "stylelint": "bin/stylelint.mjs" - }, - "engines": { - "node": ">=18.12.0" - } - }, - "node_modules/stylelint-config-recommended": { - "version": "17.0.0", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/stylelint" - }, - { - "type": "github", - "url": "https://github.com/sponsors/stylelint" - } - ], - "license": "MIT", - "engines": { - "node": ">=18.12.0" - }, - "peerDependencies": { - "stylelint": "^16.23.0" - } - }, - "node_modules/stylelint-config-recommended-scss": { - "version": "16.0.2", - "dev": true, - "license": "MIT", - "dependencies": { - "postcss-scss": "^4.0.9", - "stylelint-config-recommended": "^17.0.0", - "stylelint-scss": "^6.12.1" - }, - "engines": { - "node": ">=20" - }, - "peerDependencies": { - "postcss": "^8.3.3", - "stylelint": "^16.24.0" - }, - "peerDependenciesMeta": { - "postcss": { - "optional": true - } - } - }, - "node_modules/stylelint-config-standard": { - "version": "39.0.1", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/stylelint" - }, - { - "type": "github", - "url": "https://github.com/sponsors/stylelint" - } - ], - "license": "MIT", - "dependencies": { - "stylelint-config-recommended": "^17.0.0" - }, - "engines": { - "node": ">=18.12.0" - }, - "peerDependencies": { - "stylelint": "^16.23.0" - } - }, - "node_modules/stylelint-config-standard-scss": { - "version": "16.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "stylelint-config-recommended-scss": "^16.0.1", - "stylelint-config-standard": "^39.0.0" - }, - "engines": { - "node": ">=20" - }, - "peerDependencies": { - "postcss": "^8.3.3", - "stylelint": "^16.23.1" - }, - "peerDependenciesMeta": { - "postcss": { - "optional": true - } - } - }, - "node_modules/stylelint-scss": { - "version": "6.14.0", - "dev": true, - "license": "MIT", - "dependencies": { - "css-tree": "^3.0.1", - "is-plain-object": "^5.0.0", - "known-css-properties": "^0.37.0", - "mdn-data": "^2.25.0", - "postcss-media-query-parser": "^0.2.3", - "postcss-resolve-nested-selector": "^0.1.6", - "postcss-selector-parser": "^7.1.1", - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": ">=18.12.0" - }, - "peerDependencies": { - "stylelint": "^16.8.2" - } - }, - "node_modules/stylelint/node_modules/debug": { - "version": "4.4.3", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/stylelint/node_modules/debug/node_modules/ms": { - "version": "2.1.3", - "dev": true, - "license": "MIT" - }, - "node_modules/stylelint/node_modules/file-entry-cache": { - "version": "11.1.2", - "dev": true, - "license": "MIT", - "dependencies": { - "flat-cache": "^6.1.20" - } - }, - "node_modules/stylelint/node_modules/file-entry-cache/node_modules/flat-cache": { - "version": "6.1.22", - "dev": true, - "license": "MIT", - "dependencies": { - "cacheable": "^2.3.4", - "flatted": "^3.4.2", - "hookified": "^1.15.0" - } - }, - "node_modules/stylelint/node_modules/ignore": { - "version": "7.0.5", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 4" - } - }, - "node_modules/supports-color": { - "version": "7.2.0", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/supports-hyperlinks": { - "version": "3.2.0", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0", - "supports-color": "^7.0.0" - }, - "engines": { - "node": ">=14.18" - }, - "funding": { - "url": "https://github.com/chalk/supports-hyperlinks?sponsor=1" - } - }, - "node_modules/supports-preserve-symlinks-flag": { - "version": "1.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/svg-tags": { - "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, - "license": "BSD-3-Clause", - "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" - }, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/tar": { - "version": "7.5.13", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "@isaacs/fs-minipass": "^4.0.0", - "chownr": "^3.0.0", - "minipass": "^7.1.2", - "minizlib": "^3.1.0", - "yallist": "^5.0.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/tar-fs": { - "version": "3.1.2", - "dev": true, - "license": "MIT", - "dependencies": { - "pump": "^3.0.0", - "tar-stream": "^3.1.5" - }, - "optionalDependencies": { - "bare-fs": "^4.0.1", - "bare-path": "^3.0.0" - } - }, - "node_modules/tar-stream": { - "version": "3.1.8", - "dev": true, - "license": "MIT", - "dependencies": { - "b4a": "^1.6.4", - "bare-fs": "^4.5.5", - "fast-fifo": "^1.2.0", - "streamx": "^2.15.0" - } - }, - "node_modules/teex": { - "version": "1.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "streamx": "^2.12.5" - } - }, - "node_modules/temp-dir": { - "version": "2.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/tempy": { - "version": "1.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "del": "^6.0.0", - "is-stream": "^2.0.0", - "temp-dir": "^2.0.0", - "type-fest": "^0.16.0", - "unique-string": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/terser": { - "version": "5.46.1", - "devOptional": true, - "license": "BSD-2-Clause", - "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" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/terser/node_modules/commander": { - "version": "2.20.3", - "devOptional": true, - "license": "MIT" - }, - "node_modules/text-decoder": { - "version": "1.2.7", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "b4a": "^1.6.4" - } - }, - "node_modules/text-extensions": { - "version": "1.9.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10" - } - }, - "node_modules/through": { - "version": "2.3.8", - "dev": true, - "license": "MIT" - }, - "node_modules/through2": { - "version": "4.0.2", - "dev": true, - "license": "MIT", - "dependencies": { - "readable-stream": "3" - } - }, - "node_modules/tinybench": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", - "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", - "license": "MIT" - }, - "node_modules/tinyexec": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.4.tgz", - "integrity": "sha512-u9r3uZC0bdpGOXtlxUIdwf9pkmvhqJdrVCH9fapQtgy/OeTTMZ1nqH7agtvEfmGui6e1XxjcdrlxvxJvc3sMqw==", - "license": "MIT", - "engines": { - "node": ">=18" - } - }, - "node_modules/tinyglobby": { - "version": "0.2.15", - "license": "MIT", - "dependencies": { - "fdir": "^6.5.0", - "picomatch": "^4.0.3" - }, - "engines": { - "node": ">=12.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/SuperchupuDev" - } - }, - "node_modules/tinyrainbow": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.1.0.tgz", - "integrity": "sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==", - "license": "MIT", - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/tmp": { - "version": "0.2.5", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=14.14" - } - }, - "node_modules/to-regex-range": { - "version": "5.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/totalist": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/totalist/-/totalist-3.0.1.tgz", - "integrity": "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==", - "devOptional": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/tr46": { - "version": "0.0.3", - "dev": true, - "license": "MIT" - }, - "node_modules/tree-kill": { - "version": "1.2.2", - "dev": true, - "license": "MIT", - "bin": { - "tree-kill": "cli.js" - } - }, - "node_modules/trim-newlines": { - "version": "3.0.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/ts-node": { - "version": "10.9.2", - "dev": true, - "license": "MIT", - "dependencies": { - "@cspotcode/source-map-support": "^0.8.0", - "@tsconfig/node10": "^1.0.7", - "@tsconfig/node12": "^1.0.7", - "@tsconfig/node14": "^1.0.0", - "@tsconfig/node16": "^1.0.2", - "acorn": "^8.4.1", - "acorn-walk": "^8.1.1", - "arg": "^4.1.0", - "create-require": "^1.1.0", - "diff": "^4.0.1", - "make-error": "^1.1.1", - "v8-compile-cache-lib": "^3.0.1", - "yn": "3.1.1" - }, - "bin": { - "ts-node": "dist/bin.js", - "ts-node-cwd": "dist/bin-cwd.js", - "ts-node-esm": "dist/bin-esm.js", - "ts-node-script": "dist/bin-script.js", - "ts-node-transpile-only": "dist/bin-transpile.js", - "ts-script": "dist/bin-script-deprecated.js" - }, - "peerDependencies": { - "@swc/core": ">=1.2.50", - "@swc/wasm": ">=1.2.50", - "@types/node": "*", - "typescript": ">=2.7" - }, - "peerDependenciesMeta": { - "@swc/core": { - "optional": true - }, - "@swc/wasm": { - "optional": true - } - } - }, - "node_modules/ts-node/node_modules/diff": { - "version": "4.0.4", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.3.1" - } - }, - "node_modules/tslib": { - "version": "2.6.2", - "license": "0BSD" - }, - "node_modules/tsscmp": { - "version": "1.0.6", - "license": "MIT", - "engines": { - "node": ">=0.6.x" - } - }, - "node_modules/tunnel-agent": { - "version": "0.6.0", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "safe-buffer": "^5.0.1" - }, - "engines": { - "node": "*" - } - }, - "node_modules/type-check": { - "version": "0.4.0", - "dev": true, - "license": "MIT", - "dependencies": { - "prelude-ls": "^1.2.1" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/type-fest": { - "version": "0.16.0", - "dev": true, - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/typed-array-buffer": { - "version": "1.0.3", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "es-errors": "^1.3.0", - "is-typed-array": "^1.1.14" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/typed-array-byte-length": { - "version": "1.0.3", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "for-each": "^0.3.3", - "gopd": "^1.2.0", - "has-proto": "^1.2.0", - "is-typed-array": "^1.1.14" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/typed-array-byte-offset": { - "version": "1.0.4", - "dev": true, - "license": "MIT", - "dependencies": { - "available-typed-arrays": "^1.0.7", - "call-bind": "^1.0.8", - "for-each": "^0.3.3", - "gopd": "^1.2.0", - "has-proto": "^1.2.0", - "is-typed-array": "^1.1.15", - "reflect.getprototypeof": "^1.0.9" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/typed-array-length": { - "version": "1.0.7", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.7", - "for-each": "^0.3.3", - "gopd": "^1.0.1", - "is-typed-array": "^1.1.13", - "possible-typed-array-names": "^1.0.0", - "reflect.getprototypeof": "^1.0.6" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/typescript": { - "version": "5.9.3", - "dev": true, - "license": "Apache-2.0", - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, - "node_modules/uglify-js": { - "version": "3.19.3", - "dev": true, - "license": "BSD-2-Clause", - "optional": true, - "bin": { - "uglifyjs": "bin/uglifyjs" - }, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/unbox-primitive": { - "version": "1.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "has-bigints": "^1.0.2", - "has-symbols": "^1.1.0", - "which-boxed-primitive": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/undici": { - "version": "7.24.4", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=20.18.1" - } - }, - "node_modules/undici-types": { - "version": "7.18.2", - "devOptional": true, - "license": "MIT" - }, - "node_modules/unicode-canonical-property-names-ecmascript": { - "version": "2.0.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/unicode-match-property-ecmascript": { - "version": "2.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "unicode-canonical-property-names-ecmascript": "^2.0.0", - "unicode-property-aliases-ecmascript": "^2.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/unicode-match-property-value-ecmascript": { - "version": "2.2.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/unicode-property-aliases-ecmascript": { - "version": "2.2.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/unique-string": { - "version": "2.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "crypto-random-string": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/universalify": { - "version": "2.0.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 10.0.0" - } - }, - "node_modules/untildify": { - "version": "4.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/upath": { - "version": "1.2.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4", - "yarn": "*" - } - }, - "node_modules/update-browserslist-db": { - "version": "1.2.3", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "escalade": "^3.2.0", - "picocolors": "^1.1.1" - }, - "bin": { - "update-browserslist-db": "cli.js" - }, - "peerDependencies": { - "browserslist": ">= 4.21.0" - } - }, - "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": { - "punycode": "^2.1.0" - } - }, - "node_modules/url-toolkit": { - "version": "2.2.5", - "license": "Apache-2.0" - }, - "node_modules/util-deprecate": { - "version": "1.0.2", - "dev": true, - "license": "MIT" - }, - "node_modules/uuid": { - "version": "13.0.0", - "funding": [ - "https://github.com/sponsors/broofa", - "https://github.com/sponsors/ctavan" - ], - "license": "MIT", - "bin": { - "uuid": "dist-node/bin/uuid" - } - }, - "node_modules/v8-compile-cache-lib": { - "version": "3.0.1", - "dev": true, - "license": "MIT" - }, - "node_modules/validate-npm-package-license": { - "version": "3.0.4", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "spdx-correct": "^3.0.0", - "spdx-expression-parse": "^3.0.0" - } - }, - "node_modules/vite": { - "version": "7.3.1", - "license": "MIT", - "dependencies": { - "esbuild": "^0.27.0", - "fdir": "^6.5.0", - "picomatch": "^4.0.3", - "postcss": "^8.5.6", - "rollup": "^4.43.0", - "tinyglobby": "^0.2.15" - }, - "bin": { - "vite": "bin/vite.js" - }, - "engines": { - "node": "^20.19.0 || >=22.12.0" - }, - "funding": { - "url": "https://github.com/vitejs/vite?sponsor=1" - }, - "optionalDependencies": { - "fsevents": "~2.3.3" - }, - "peerDependencies": { - "@types/node": "^20.19.0 || >=22.12.0", - "jiti": ">=1.21.0", - "less": "^4.0.0", - "lightningcss": "^1.21.0", - "sass": "^1.70.0", - "sass-embedded": "^1.70.0", - "stylus": ">=0.54.8", - "sugarss": "^5.0.0", - "terser": "^5.16.0", - "tsx": "^4.8.1", - "yaml": "^2.4.2" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "jiti": { - "optional": true - }, - "less": { - "optional": true - }, - "lightningcss": { - "optional": true - }, - "sass": { - "optional": true - }, - "sass-embedded": { - "optional": true - }, - "stylus": { - "optional": true - }, - "sugarss": { - "optional": true - }, - "terser": { - "optional": true - }, - "tsx": { - "optional": true - }, - "yaml": { - "optional": true - } - } - }, - "node_modules/vite-plugin-pwa": { - "version": "1.2.0", - "dev": true, - "license": "MIT", - "dependencies": { - "debug": "^4.3.6", - "pretty-bytes": "^6.1.1", - "tinyglobby": "^0.2.10", - "workbox-build": "^7.4.0", - "workbox-window": "^7.4.0" - }, - "engines": { - "node": ">=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/antfu" - }, - "peerDependencies": { - "@vite-pwa/assets-generator": "^1.0.0", - "vite": "^3.1.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0", - "workbox-build": "^7.4.0", - "workbox-window": "^7.4.0" - }, - "peerDependenciesMeta": { - "@vite-pwa/assets-generator": { - "optional": true - } - } - }, - "node_modules/vite-plugin-pwa/node_modules/debug": { - "version": "4.4.3", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/vite-plugin-pwa/node_modules/debug/node_modules/ms": { - "version": "2.1.3", - "dev": true, - "license": "MIT" - }, - "node_modules/vite/node_modules/rollup": { - "version": "4.60.0", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.60.0.tgz", - "integrity": "sha512-yqjxruMGBQJ2gG4HtjZtAfXArHomazDHoFwFFmZZl0r7Pdo7qCIXKqKHZc8yeoMgzJJ+pO6pEEHa+V7uzWlrAQ==", - "license": "MIT", - "dependencies": { - "@types/estree": "1.0.8" - }, - "bin": { - "rollup": "dist/bin/rollup" - }, - "engines": { - "node": ">=18.0.0", - "npm": ">=8.0.0" - }, - "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.60.0", - "@rollup/rollup-android-arm64": "4.60.0", - "@rollup/rollup-darwin-arm64": "4.60.0", - "@rollup/rollup-darwin-x64": "4.60.0", - "@rollup/rollup-freebsd-arm64": "4.60.0", - "@rollup/rollup-freebsd-x64": "4.60.0", - "@rollup/rollup-linux-arm-gnueabihf": "4.60.0", - "@rollup/rollup-linux-arm-musleabihf": "4.60.0", - "@rollup/rollup-linux-arm64-gnu": "4.60.0", - "@rollup/rollup-linux-arm64-musl": "4.60.0", - "@rollup/rollup-linux-loong64-gnu": "4.60.0", - "@rollup/rollup-linux-loong64-musl": "4.60.0", - "@rollup/rollup-linux-ppc64-gnu": "4.60.0", - "@rollup/rollup-linux-ppc64-musl": "4.60.0", - "@rollup/rollup-linux-riscv64-gnu": "4.60.0", - "@rollup/rollup-linux-riscv64-musl": "4.60.0", - "@rollup/rollup-linux-s390x-gnu": "4.60.0", - "@rollup/rollup-linux-x64-gnu": "4.60.0", - "@rollup/rollup-linux-x64-musl": "4.60.0", - "@rollup/rollup-openbsd-x64": "4.60.0", - "@rollup/rollup-openharmony-arm64": "4.60.0", - "@rollup/rollup-win32-arm64-msvc": "4.60.0", - "@rollup/rollup-win32-ia32-msvc": "4.60.0", - "@rollup/rollup-win32-x64-gnu": "4.60.0", - "@rollup/rollup-win32-x64-msvc": "4.60.0", - "fsevents": "~2.3.2" - } - }, - "node_modules/vitest": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.2.tgz", - "integrity": "sha512-xjR1dMTVHlFLh98JE3i/f/WePqJsah4A0FK9cc8Ehp9Udk0AZk6ccpIZhh1qJ/yxVWRZ+Q54ocnD8TXmkhspGg==", - "license": "MIT", - "dependencies": { - "@vitest/expect": "4.1.2", - "@vitest/mocker": "4.1.2", - "@vitest/pretty-format": "4.1.2", - "@vitest/runner": "4.1.2", - "@vitest/snapshot": "4.1.2", - "@vitest/spy": "4.1.2", - "@vitest/utils": "4.1.2", - "es-module-lexer": "^2.0.0", - "expect-type": "^1.3.0", - "magic-string": "^0.30.21", - "obug": "^2.1.1", - "pathe": "^2.0.3", - "picomatch": "^4.0.3", - "std-env": "^4.0.0-rc.1", - "tinybench": "^2.9.0", - "tinyexec": "^1.0.2", - "tinyglobby": "^0.2.15", - "tinyrainbow": "^3.1.0", - "vite": "^6.0.0 || ^7.0.0 || ^8.0.0", - "why-is-node-running": "^2.3.0" - }, - "bin": { - "vitest": "vitest.mjs" - }, - "engines": { - "node": "^20.0.0 || ^22.0.0 || >=24.0.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - }, - "peerDependencies": { - "@edge-runtime/vm": "*", - "@opentelemetry/api": "^1.9.0", - "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", - "@vitest/browser-playwright": "4.1.2", - "@vitest/browser-preview": "4.1.2", - "@vitest/browser-webdriverio": "4.1.2", - "@vitest/ui": "4.1.2", - "happy-dom": "*", - "jsdom": "*", - "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" - }, - "peerDependenciesMeta": { - "@edge-runtime/vm": { - "optional": true - }, - "@opentelemetry/api": { - "optional": true - }, - "@types/node": { - "optional": true - }, - "@vitest/browser-playwright": { - "optional": true - }, - "@vitest/browser-preview": { - "optional": true - }, - "@vitest/browser-webdriverio": { - "optional": true - }, - "@vitest/ui": { - "optional": true - }, - "happy-dom": { - "optional": true - }, - "jsdom": { - "optional": true - }, - "vite": { - "optional": false - } - } - }, - "node_modules/vitest/node_modules/magic-string": { - "version": "0.30.21", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", - "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.5" - } - }, - "node_modules/webidl-conversions": { - "version": "3.0.1", - "dev": true, - "license": "BSD-2-Clause" - }, - "node_modules/whatwg-url": { - "version": "5.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" - } - }, - "node_modules/which": { - "version": "2.0.2", - "dev": true, - "license": "ISC", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/which-boxed-primitive": { - "version": "1.1.1", - "dev": true, - "license": "MIT", - "dependencies": { - "is-bigint": "^1.1.0", - "is-boolean-object": "^1.2.1", - "is-number-object": "^1.1.1", - "is-string": "^1.1.1", - "is-symbol": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/which-builtin-type": { - "version": "1.2.1", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "function.prototype.name": "^1.1.6", - "has-tostringtag": "^1.0.2", - "is-async-function": "^2.0.0", - "is-date-object": "^1.1.0", - "is-finalizationregistry": "^1.1.0", - "is-generator-function": "^1.0.10", - "is-regex": "^1.2.1", - "is-weakref": "^1.0.2", - "isarray": "^2.0.5", - "which-boxed-primitive": "^1.1.0", - "which-collection": "^1.0.2", - "which-typed-array": "^1.1.16" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/which-collection": { - "version": "1.0.2", - "dev": true, - "license": "MIT", - "dependencies": { - "is-map": "^2.0.3", - "is-set": "^2.0.3", - "is-weakmap": "^2.0.2", - "is-weakset": "^2.0.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/which-module": { - "version": "2.0.1", - "dev": true, - "license": "ISC" - }, - "node_modules/which-typed-array": { - "version": "1.1.20", - "dev": true, - "license": "MIT", - "dependencies": { - "available-typed-arrays": "^1.0.7", - "call-bind": "^1.0.8", - "call-bound": "^1.0.4", - "for-each": "^0.3.5", - "get-proto": "^1.0.1", - "gopd": "^1.2.0", - "has-tostringtag": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/why-is-node-running": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", - "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", - "license": "MIT", - "dependencies": { - "siginfo": "^2.0.0", - "stackback": "0.0.2" - }, - "bin": { - "why-is-node-running": "cli.js" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/word-wrap": { - "version": "1.2.5", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/wordwrap": { - "version": "1.0.0", - "dev": true, - "license": "MIT" - }, - "node_modules/workbox-background-sync": { - "version": "7.4.0", - "dev": true, - "license": "MIT", - "dependencies": { - "idb": "^7.0.1", - "workbox-core": "7.4.0" - } - }, - "node_modules/workbox-broadcast-update": { - "version": "7.4.0", - "dev": true, - "license": "MIT", - "dependencies": { - "workbox-core": "7.4.0" - } - }, - "node_modules/workbox-build": { - "version": "7.4.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@apideck/better-ajv-errors": "^0.3.1", - "@babel/core": "^7.24.4", - "@babel/preset-env": "^7.11.0", - "@babel/runtime": "^7.11.2", - "@rollup/plugin-babel": "^5.2.0", - "@rollup/plugin-node-resolve": "^15.2.3", - "@rollup/plugin-replace": "^2.4.1", - "@rollup/plugin-terser": "^0.4.3", - "@surma/rollup-plugin-off-main-thread": "^2.2.3", - "ajv": "^8.6.0", - "common-tags": "^1.8.0", - "fast-json-stable-stringify": "^2.1.0", - "fs-extra": "^9.0.1", - "glob": "^11.0.1", - "lodash": "^4.17.20", - "pretty-bytes": "^5.3.0", - "rollup": "^2.79.2", - "source-map": "^0.8.0-beta.0", - "stringify-object": "^3.3.0", - "strip-comments": "^2.0.1", - "tempy": "^0.6.0", - "upath": "^1.2.0", - "workbox-background-sync": "7.4.0", - "workbox-broadcast-update": "7.4.0", - "workbox-cacheable-response": "7.4.0", - "workbox-core": "7.4.0", - "workbox-expiration": "7.4.0", - "workbox-google-analytics": "7.4.0", - "workbox-navigation-preload": "7.4.0", - "workbox-precaching": "7.4.0", - "workbox-range-requests": "7.4.0", - "workbox-recipes": "7.4.0", - "workbox-routing": "7.4.0", - "workbox-strategies": "7.4.0", - "workbox-streams": "7.4.0", - "workbox-sw": "7.4.0", - "workbox-window": "7.4.0" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/workbox-build/node_modules/fs-extra": { - "version": "9.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "at-least-node": "^1.0.0", - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/workbox-build/node_modules/glob": { - "version": "11.1.0", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "foreground-child": "^3.3.1", - "jackspeak": "^4.1.1", - "minimatch": "^10.1.1", - "minipass": "^7.1.2", - "package-json-from-dist": "^1.0.0", - "path-scurry": "^2.0.0" - }, - "bin": { - "glob": "dist/esm/bin.mjs" - }, - "engines": { - "node": "20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/workbox-build/node_modules/glob/node_modules/minimatch": { - "version": "10.2.4", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "brace-expansion": "^5.0.2" - }, - "engines": { - "node": "18 || 20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/workbox-build/node_modules/glob/node_modules/minimatch/node_modules/brace-expansion": { - "version": "5.0.5", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^4.0.2" - }, - "engines": { - "node": "18 || 20 || >=22" - } - }, - "node_modules/workbox-build/node_modules/glob/node_modules/minimatch/node_modules/brace-expansion/node_modules/balanced-match": { - "version": "4.0.4", - "dev": true, - "license": "MIT", - "engines": { - "node": "18 || 20 || >=22" - } - }, - "node_modules/workbox-build/node_modules/pretty-bytes": { - "version": "5.6.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/workbox-build/node_modules/tempy": { - "version": "0.6.0", - "dev": true, - "license": "MIT", - "dependencies": { - "is-stream": "^2.0.0", - "temp-dir": "^2.0.0", - "type-fest": "^0.16.0", - "unique-string": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/workbox-cacheable-response": { - "version": "7.4.0", - "dev": true, - "license": "MIT", - "dependencies": { - "workbox-core": "7.4.0" - } - }, - "node_modules/workbox-core": { - "version": "7.4.0", - "dev": true, - "license": "MIT" - }, - "node_modules/workbox-expiration": { - "version": "7.4.0", - "dev": true, - "license": "MIT", - "dependencies": { - "idb": "^7.0.1", - "workbox-core": "7.4.0" - } - }, - "node_modules/workbox-google-analytics": { - "version": "7.4.0", - "dev": true, - "license": "MIT", - "dependencies": { - "workbox-background-sync": "7.4.0", - "workbox-core": "7.4.0", - "workbox-routing": "7.4.0", - "workbox-strategies": "7.4.0" - } - }, - "node_modules/workbox-navigation-preload": { - "version": "7.4.0", - "dev": true, - "license": "MIT", - "dependencies": { - "workbox-core": "7.4.0" - } - }, - "node_modules/workbox-precaching": { - "version": "7.4.0", - "dev": true, - "license": "MIT", - "dependencies": { - "workbox-core": "7.4.0", - "workbox-routing": "7.4.0", - "workbox-strategies": "7.4.0" - } - }, - "node_modules/workbox-range-requests": { - "version": "7.4.0", - "dev": true, - "license": "MIT", - "dependencies": { - "workbox-core": "7.4.0" - } - }, - "node_modules/workbox-recipes": { - "version": "7.4.0", - "dev": true, - "license": "MIT", - "dependencies": { - "workbox-cacheable-response": "7.4.0", - "workbox-core": "7.4.0", - "workbox-expiration": "7.4.0", - "workbox-precaching": "7.4.0", - "workbox-routing": "7.4.0", - "workbox-strategies": "7.4.0" - } - }, - "node_modules/workbox-routing": { - "version": "7.4.0", - "dev": true, - "license": "MIT", - "dependencies": { - "workbox-core": "7.4.0" - } - }, - "node_modules/workbox-strategies": { - "version": "7.4.0", - "dev": true, - "license": "MIT", - "dependencies": { - "workbox-core": "7.4.0" - } - }, - "node_modules/workbox-streams": { - "version": "7.4.0", - "dev": true, - "license": "MIT", - "dependencies": { - "workbox-core": "7.4.0", - "workbox-routing": "7.4.0" - } - }, - "node_modules/workbox-sw": { - "version": "7.4.0", - "dev": true, - "license": "MIT" - }, - "node_modules/workbox-window": { - "version": "7.4.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/trusted-types": "^2.0.2", - "workbox-core": "7.4.0" - } - }, - "node_modules/workerd": { - "version": "1.20260317.1", - "dev": true, - "hasInstallScript": true, - "license": "Apache-2.0", - "bin": { - "workerd": "bin/workerd" - }, - "engines": { - "node": ">=16" - }, - "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" - } - }, - "node_modules/wrap-ansi": { - "version": "7.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/wrappy": { - "version": "1.0.2", - "dev": true, - "license": "ISC" - }, - "node_modules/write-file-atomic": { - "version": "5.0.1", - "dev": true, - "license": "ISC", - "dependencies": { - "imurmurhash": "^0.1.4", - "signal-exit": "^4.0.1" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/write-file-atomic/node_modules/signal-exit": { - "version": "4.1.0", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/ws": { - "version": "8.18.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": ">=5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, - "node_modules/xcode": { - "version": "3.0.1", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "simple-plist": "^1.1.0", - "uuid": "^7.0.3" - }, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/xcode/node_modules/uuid": { - "version": "7.0.3", - "dev": true, - "license": "MIT", - "bin": { - "uuid": "dist/bin/uuid" - } - }, - "node_modules/xml": { - "version": "1.0.1", - "dev": true, - "license": "MIT" - }, - "node_modules/xml-js": { - "version": "1.6.11", - "dev": true, - "license": "MIT", - "dependencies": { - "sax": "^1.2.4" - }, - "bin": { - "xml-js": "bin/cli.js" - } - }, - "node_modules/xml2js": { - "version": "0.6.2", - "dev": true, - "license": "MIT", - "dependencies": { - "sax": ">=0.6.0", - "xmlbuilder": "~11.0.0" - }, - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/xml2js/node_modules/xmlbuilder": { - "version": "11.0.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4.0" - } - }, - "node_modules/xmlbuilder": { - "version": "15.1.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8.0" - } - }, - "node_modules/xpath": { - "version": "0.0.32", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.6.0" - } - }, - "node_modules/xtend": { - "version": "4.0.2", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.4" - } - }, - "node_modules/y18n": { - "version": "5.0.8", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=10" - } - }, - "node_modules/yallist": { - "version": "5.0.0", - "dev": true, - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=18" - } - }, - "node_modules/yargs": { - "version": "17.7.2", - "dev": true, - "license": "MIT", - "dependencies": { - "cliui": "^8.0.1", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.1.1" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/yargs-parser": { - "version": "21.1.1", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=12" - } - }, - "node_modules/yauzl": { - "version": "2.10.0", - "dev": true, - "license": "MIT", - "dependencies": { - "buffer-crc32": "~0.2.3", - "fd-slicer": "~1.1.0" - } - }, - "node_modules/yn": { - "version": "3.1.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/yocto-queue": { - "version": "0.1.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/youch": { - "version": "4.1.0-beta.10", - "dev": true, - "license": "MIT", - "dependencies": { - "@poppinss/colors": "^4.1.5", - "@poppinss/dumper": "^0.6.4", - "@speed-highlight/core": "^1.2.7", - "cookie": "^1.0.2", - "youch-core": "^0.3.3" - } - }, - "node_modules/youch-core": { - "version": "0.3.3", - "dev": true, - "license": "MIT", - "dependencies": { - "@poppinss/exception": "^1.2.2", - "error-stack-parser-es": "^1.0.5" - } - } - } -} From 4a9493fb72d7bbf0681984894d008258db489a5d Mon Sep 17 00:00:00 2001 From: Daniel <790119+DanTheMan827@users.noreply.github.com> Date: Fri, 3 Apr 2026 10:21:57 -0500 Subject: [PATCH 31/51] feat(dev): add vite-bundle-visualizer This will run on build, and automatically generate a bundle-stats.html file in the assets folder containing details about package sizes --- bun.lock | 193 ++++++++++++++++++++++++++------------------------- package.json | 3 +- 2 files changed, 101 insertions(+), 95 deletions(-) diff --git a/bun.lock b/bun.lock index c0e23a9..edf4294 100644 --- a/bun.lock +++ b/bun.lock @@ -10,6 +10,7 @@ "@capacitor/core": "^8.2.0", "@capacitor/haptics": "^8.0.1", "@capacitor/ios": "^8.2.0", + "@dantheman827/taglib-ts": "https://github.com/DanTheMan827/taglib-ts/archive/ebd0e369b706c127a280d4ad631977f8d12ff88f.tar.gz", "@ffmpeg/core": "^0.12.10", "@ffmpeg/ffmpeg": "^0.12.15", "@ffmpeg/util": "^0.12.2", @@ -26,7 +27,6 @@ "events": "^3.3.0", "fuse.js": "^7.1.0", "hls.js": "^1.6.15", - "@dantheman827/taglib-ts": "https://github.com/DanTheMan827/taglib-ts/archive/ebd0e369b706c127a280d4ad631977f8d12ff88f.tar.gz", "jose": "^6.2.0", "lucide-static": "^0.577.0", "mime": "^4.1.0", @@ -58,6 +58,7 @@ "stylelint-config-standard-scss": "^16.0.0", "typescript": "^5.9.3", "vite": "^7.3.1", + "vite-bundle-visualizer": "^1.2.1", "vite-plugin-pwa": "^1.2.0", }, }, @@ -68,7 +69,7 @@ "sourcemap-codec": "npm:@jridgewell/sourcemap-codec@^1.4.14", }, "packages": { - "@apideck/better-ajv-errors": ["@apideck/better-ajv-errors@0.3.6", "", { "dependencies": { "json-schema": "^0.4.0", "jsonpointer": "^5.0.0", "leven": "^3.1.0" }, "peerDependencies": { "ajv": ">=8" } }, "sha512-P+ZygBLZtkp0qqOAJJVX4oX/sFo5JR3eBWwwuqHHhK0GIgQOKWrAfiAaWX0aArHkRWHMuggFEgAZNxVPwPZYaA=="], + "@apideck/better-ajv-errors": ["@apideck/better-ajv-errors@0.3.7", "", { "dependencies": { "jsonpointer": "^5.0.1", "leven": "^3.1.0" }, "peerDependencies": { "ajv": ">=8" } }, "sha512-TajUJwGWbDwkCx/CZi7tRE8PVB7simCvKJfHUsSdvps+aTM/PDPP4gkLmKnc+x3CE//y9i/nj74GqdL/hwk7Iw=="], "@babel/code-frame": ["@babel/code-frame@7.29.0", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.28.5", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw=="], @@ -270,15 +271,15 @@ "@capacitor/ios": ["@capacitor/ios@8.3.0", "", { "peerDependencies": { "@capacitor/core": "^8.3.0" } }, "sha512-5Rtwv8SITKlYTt8lAZG+khnVIdzPtqbocH3eP+JkEmX1vpSMwx4TOKtT8OBz8gpQ+pUJDRp7DBYOv3U6l/obCw=="], - "@cloudflare/workerd-darwin-64": ["@cloudflare/workerd-darwin-64@1.20260317.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-8hjh3sPMwY8M/zedq3/sXoA2Q4BedlGufn3KOOleIG+5a4ReQKLlUah140D7J6zlKmYZAFMJ4tWC7hCuI/s79g=="], + "@cloudflare/workerd-darwin-64": ["@cloudflare/workerd-darwin-64@1.20260401.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-ZSmceM70jH6k+/62VkEcmMNzrpr4kSctkX5Lsgqv38KktfhPY/hsh75y1lRoPWS3H3kgMa4p2pUSlidZR1u2hw=="], - "@cloudflare/workerd-darwin-arm64": ["@cloudflare/workerd-darwin-arm64@1.20260317.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-M/MnNyvO5HMgoIdr3QHjdCj2T1ki9gt0vIUnxYxBu9ISXS/jgtMl6chUVPJ7zHYBn9MyYr8ByeN6frjYxj0MGg=="], + "@cloudflare/workerd-darwin-arm64": ["@cloudflare/workerd-darwin-arm64@1.20260401.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-7UKWF+IUZ3NXMVPsDg8Cjg0r58b+uYlfvs5Yt8bvtU+geCtW4P2MxRHmRSEo8SryckXOJjb/b8tcncgCykFu8g=="], - "@cloudflare/workerd-linux-64": ["@cloudflare/workerd-linux-64@1.20260317.1", "", { "os": "linux", "cpu": "x64" }, "sha512-1ltuEjkRcS3fsVF7CxsKlWiRmzq2ZqMfqDN0qUOgbUwkpXsLVJsXmoblaLf5OP00ELlcgF0QsN0p2xPEua4Uug=="], + "@cloudflare/workerd-linux-64": ["@cloudflare/workerd-linux-64@1.20260401.1", "", { "os": "linux", "cpu": "x64" }, "sha512-MDWUH/0bvL/l9aauN8zEddyYOXId1OueqrUCXXENNJ95R/lSmF6OgGVuXaYhoIhxQkNiEJ/0NOlnVYj9mJq4dw=="], - "@cloudflare/workerd-linux-arm64": ["@cloudflare/workerd-linux-arm64@1.20260317.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-3QrNnPF1xlaNwkHpasvRvAMidOvQs2NhXQmALJrEfpIJ/IDL2la8g499yXp3eqhG3hVMCB07XVY149GTs42Xtw=="], + "@cloudflare/workerd-linux-arm64": ["@cloudflare/workerd-linux-arm64@1.20260401.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-UgkzpMzVWM/bwbo3vjCTg2aoKfGcUhiEoQoDdo6RGWvbHRJyLVZ4VQCG9ZcISiztkiS2ICCoYOtPy6M/lV6Gcw=="], - "@cloudflare/workerd-windows-64": ["@cloudflare/workerd-windows-64@1.20260317.1", "", { "os": "win32", "cpu": "x64" }, "sha512-MfZTz+7LfuIpMGTa3RLXHX8Z/pnycZLItn94WRdHr8LPVet+C5/1Nzei399w/jr3+kzT4pDKk26JF/tlI5elpQ=="], + "@cloudflare/workerd-windows-64": ["@cloudflare/workerd-windows-64@1.20260401.1", "", { "os": "win32", "cpu": "x64" }, "sha512-HBLzcQF5iF4Qv20tQ++pG7xs3OsCnaIbc+GAi6fmhUKZhvmzvml/jwrQzLJ+MPm0cQo41K5OO/U3T4S8tvJetQ=="], "@cspotcode/source-map-support": ["@cspotcode/source-map-support@0.8.1", "", { "dependencies": { "@jridgewell/trace-mapping": "0.3.9" } }, "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw=="], @@ -296,59 +297,59 @@ "@dual-bundle/import-meta-resolve": ["@dual-bundle/import-meta-resolve@4.2.1", "", {}, "sha512-id+7YRUgoUX6CgV0DtuhirQWodeeA7Lf4i2x71JS/vtA5pRb/hIGWlw+G6MeXvsM+MXrz0VAydTGElX1rAfgPg=="], - "@emnapi/runtime": ["@emnapi/runtime@1.9.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-VYi5+ZVLhpgK4hQ0TAjiQiZ6ol0oe4mBx7mVv7IflsiEp0OWoVsp/+f9Vc1hOhE0TtkORVrI1GvzyreqpgWtkA=="], + "@emnapi/runtime": ["@emnapi/runtime@1.9.2", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-3U4+MIWHImeyu1wnmVygh5WlgfYDtyf0k8AbLhMFxOipihf6nrWC4syIm/SwEeec0mNSafiiNnMJwbza/Is6Lw=="], - "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.27.4", "", { "os": "aix", "cpu": "ppc64" }, "sha512-cQPwL2mp2nSmHHJlCyoXgHGhbEPMrEEU5xhkcy3Hs/O7nGZqEpZ2sUtLaL9MORLtDfRvVl2/3PAuEkYZH0Ty8Q=="], + "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.27.7", "", { "os": "aix", "cpu": "ppc64" }, "sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg=="], - "@esbuild/android-arm": ["@esbuild/android-arm@0.27.4", "", { "os": "android", "cpu": "arm" }, "sha512-X9bUgvxiC8CHAGKYufLIHGXPJWnr0OCdR0anD2e21vdvgCI8lIfqFbnoeOz7lBjdrAGUhqLZLcQo6MLhTO2DKQ=="], + "@esbuild/android-arm": ["@esbuild/android-arm@0.27.7", "", { "os": "android", "cpu": "arm" }, "sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ=="], - "@esbuild/android-arm64": ["@esbuild/android-arm64@0.27.4", "", { "os": "android", "cpu": "arm64" }, "sha512-gdLscB7v75wRfu7QSm/zg6Rx29VLdy9eTr2t44sfTW7CxwAtQghZ4ZnqHk3/ogz7xao0QAgrkradbBzcqFPasw=="], + "@esbuild/android-arm64": ["@esbuild/android-arm64@0.27.7", "", { "os": "android", "cpu": "arm64" }, "sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ=="], - "@esbuild/android-x64": ["@esbuild/android-x64@0.27.4", "", { "os": "android", "cpu": "x64" }, "sha512-PzPFnBNVF292sfpfhiyiXCGSn9HZg5BcAz+ivBuSsl6Rk4ga1oEXAamhOXRFyMcjwr2DVtm40G65N3GLeH1Lvw=="], + "@esbuild/android-x64": ["@esbuild/android-x64@0.27.7", "", { "os": "android", "cpu": "x64" }, "sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg=="], - "@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.27.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-b7xaGIwdJlht8ZFCvMkpDN6uiSmnxxK56N2GDTMYPr2/gzvfdQN8rTfBsvVKmIVY/X7EM+/hJKEIbbHs9oA4tQ=="], + "@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.27.7", "", { "os": "darwin", "cpu": "arm64" }, "sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw=="], - "@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.27.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-sR+OiKLwd15nmCdqpXMnuJ9W2kpy0KigzqScqHI3Hqwr7IXxBp3Yva+yJwoqh7rE8V77tdoheRYataNKL4QrPw=="], + "@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.27.7", "", { "os": "darwin", "cpu": "x64" }, "sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ=="], - "@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.27.4", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-jnfpKe+p79tCnm4GVav68A7tUFeKQwQyLgESwEAUzyxk/TJr4QdGog9sqWNcUbr/bZt/O/HXouspuQDd9JxFSw=="], + "@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.27.7", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w=="], - "@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.27.4", "", { "os": "freebsd", "cpu": "x64" }, "sha512-2kb4ceA/CpfUrIcTUl1wrP/9ad9Atrp5J94Lq69w7UwOMolPIGrfLSvAKJp0RTvkPPyn6CIWrNy13kyLikZRZQ=="], + "@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.27.7", "", { "os": "freebsd", "cpu": "x64" }, "sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ=="], - "@esbuild/linux-arm": ["@esbuild/linux-arm@0.27.4", "", { "os": "linux", "cpu": "arm" }, "sha512-aBYgcIxX/wd5n2ys0yESGeYMGF+pv6g0DhZr3G1ZG4jMfruU9Tl1i2Z+Wnj9/KjGz1lTLCcorqE2viePZqj4Eg=="], + "@esbuild/linux-arm": ["@esbuild/linux-arm@0.27.7", "", { "os": "linux", "cpu": "arm" }, "sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA=="], - "@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.27.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-7nQOttdzVGth1iz57kxg9uCz57dxQLHWxopL6mYuYthohPKEK0vU0C3O21CcBK6KDlkYVcnDXY099HcCDXd9dA=="], + "@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.27.7", "", { "os": "linux", "cpu": "arm64" }, "sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A=="], - "@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.27.4", "", { "os": "linux", "cpu": "ia32" }, "sha512-oPtixtAIzgvzYcKBQM/qZ3R+9TEUd1aNJQu0HhGyqtx6oS7qTpvjheIWBbes4+qu1bNlo2V4cbkISr8q6gRBFA=="], + "@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.27.7", "", { "os": "linux", "cpu": "ia32" }, "sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg=="], - "@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.27.4", "", { "os": "linux", "cpu": "none" }, "sha512-8mL/vh8qeCoRcFH2nM8wm5uJP+ZcVYGGayMavi8GmRJjuI3g1v6Z7Ni0JJKAJW+m0EtUuARb6Lmp4hMjzCBWzA=="], + "@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.27.7", "", { "os": "linux", "cpu": "none" }, "sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q=="], - "@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.27.4", "", { "os": "linux", "cpu": "none" }, "sha512-1RdrWFFiiLIW7LQq9Q2NES+HiD4NyT8Itj9AUeCl0IVCA459WnPhREKgwrpaIfTOe+/2rdntisegiPWn/r/aAw=="], + "@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.27.7", "", { "os": "linux", "cpu": "none" }, "sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw=="], - "@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.27.4", "", { "os": "linux", "cpu": "ppc64" }, "sha512-tLCwNG47l3sd9lpfyx9LAGEGItCUeRCWeAx6x2Jmbav65nAwoPXfewtAdtbtit/pJFLUWOhpv0FpS6GQAmPrHA=="], + "@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.27.7", "", { "os": "linux", "cpu": "ppc64" }, "sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ=="], - "@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.27.4", "", { "os": "linux", "cpu": "none" }, "sha512-BnASypppbUWyqjd1KIpU4AUBiIhVr6YlHx/cnPgqEkNoVOhHg+YiSVxM1RLfiy4t9cAulbRGTNCKOcqHrEQLIw=="], + "@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.27.7", "", { "os": "linux", "cpu": "none" }, "sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ=="], - "@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.27.4", "", { "os": "linux", "cpu": "s390x" }, "sha512-+eUqgb/Z7vxVLezG8bVB9SfBie89gMueS+I0xYh2tJdw3vqA/0ImZJ2ROeWwVJN59ihBeZ7Tu92dF/5dy5FttA=="], + "@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.27.7", "", { "os": "linux", "cpu": "s390x" }, "sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw=="], - "@esbuild/linux-x64": ["@esbuild/linux-x64@0.27.4", "", { "os": "linux", "cpu": "x64" }, "sha512-S5qOXrKV8BQEzJPVxAwnryi2+Iq5pB40gTEIT69BQONqR7JH1EPIcQ/Uiv9mCnn05jff9umq/5nqzxlqTOg9NA=="], + "@esbuild/linux-x64": ["@esbuild/linux-x64@0.27.7", "", { "os": "linux", "cpu": "x64" }, "sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA=="], - "@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.27.4", "", { "os": "none", "cpu": "arm64" }, "sha512-xHT8X4sb0GS8qTqiwzHqpY00C95DPAq7nAwX35Ie/s+LO9830hrMd3oX0ZMKLvy7vsonee73x0lmcdOVXFzd6Q=="], + "@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.27.7", "", { "os": "none", "cpu": "arm64" }, "sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w=="], - "@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.27.4", "", { "os": "none", "cpu": "x64" }, "sha512-RugOvOdXfdyi5Tyv40kgQnI0byv66BFgAqjdgtAKqHoZTbTF2QqfQrFwa7cHEORJf6X2ht+l9ABLMP0dnKYsgg=="], + "@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.27.7", "", { "os": "none", "cpu": "x64" }, "sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw=="], - "@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.27.4", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-2MyL3IAaTX+1/qP0O1SwskwcwCoOI4kV2IBX1xYnDDqthmq5ArrW94qSIKCAuRraMgPOmG0RDTA74mzYNQA9ow=="], + "@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.27.7", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A=="], - "@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.27.4", "", { "os": "openbsd", "cpu": "x64" }, "sha512-u8fg/jQ5aQDfsnIV6+KwLOf1CmJnfu1ShpwqdwC0uA7ZPwFws55Ngc12vBdeUdnuWoQYx/SOQLGDcdlfXhYmXQ=="], + "@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.27.7", "", { "os": "openbsd", "cpu": "x64" }, "sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg=="], - "@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.27.4", "", { "os": "none", "cpu": "arm64" }, "sha512-JkTZrl6VbyO8lDQO3yv26nNr2RM2yZzNrNHEsj9bm6dOwwu9OYN28CjzZkH57bh4w0I2F7IodpQvUAEd1mbWXg=="], + "@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.27.7", "", { "os": "none", "cpu": "arm64" }, "sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw=="], - "@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.27.4", "", { "os": "sunos", "cpu": "x64" }, "sha512-/gOzgaewZJfeJTlsWhvUEmUG4tWEY2Spp5M20INYRg2ZKl9QPO3QEEgPeRtLjEWSW8FilRNacPOg8R1uaYkA6g=="], + "@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.27.7", "", { "os": "sunos", "cpu": "x64" }, "sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA=="], - "@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.27.4", "", { "os": "win32", "cpu": "arm64" }, "sha512-Z9SExBg2y32smoDQdf1HRwHRt6vAHLXcxD2uGgO/v2jK7Y718Ix4ndsbNMU/+1Qiem9OiOdaqitioZwxivhXYg=="], + "@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.27.7", "", { "os": "win32", "cpu": "arm64" }, "sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA=="], - "@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.27.4", "", { "os": "win32", "cpu": "ia32" }, "sha512-DAyGLS0Jz5G5iixEbMHi5KdiApqHBWMGzTtMiJ72ZOLhbu/bzxgAe8Ue8CTS3n3HbIUHQz/L51yMdGMeoxXNJw=="], + "@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.27.7", "", { "os": "win32", "cpu": "ia32" }, "sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw=="], - "@esbuild/win32-x64": ["@esbuild/win32-x64@0.27.4", "", { "os": "win32", "cpu": "x64" }, "sha512-+knoa0BDoeXgkNvvV1vvbZX4+hizelrkwmGJBdT17t8FNPwG2lKemmuMZlmaNQ3ws3DKKCxpb4zRZEIp3UxFCg=="], + "@esbuild/win32-x64": ["@esbuild/win32-x64@0.27.7", "", { "os": "win32", "cpu": "x64" }, "sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg=="], "@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=="], @@ -508,55 +509,55 @@ "@rollup/pluginutils": ["@rollup/pluginutils@3.1.0", "", { "dependencies": { "@types/estree": "0.0.39", "estree-walker": "^1.0.1", "picomatch": "^2.2.2" }, "peerDependencies": { "rollup": "^1.20.0||^2.0.0" } }, "sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg=="], - "@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.60.0", "", { "os": "android", "cpu": "arm" }, "sha512-WOhNW9K8bR3kf4zLxbfg6Pxu2ybOUbB2AjMDHSQx86LIF4rH4Ft7vmMwNt0loO0eonglSNy4cpD3MKXXKQu0/A=="], + "@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.60.1", "", { "os": "android", "cpu": "arm" }, "sha512-d6FinEBLdIiK+1uACUttJKfgZREXrF0Qc2SmLII7W2AD8FfiZ9Wjd+rD/iRuf5s5dWrr1GgwXCvPqOuDquOowA=="], - "@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.60.0", "", { "os": "android", "cpu": "arm64" }, "sha512-u6JHLll5QKRvjciE78bQXDmqRqNs5M/3GVqZeMwvmjaNODJih/WIrJlFVEihvV0MiYFmd+ZyPr9wxOVbPAG2Iw=="], + "@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.60.1", "", { "os": "android", "cpu": "arm64" }, "sha512-YjG/EwIDvvYI1YvYbHvDz/BYHtkY4ygUIXHnTdLhG+hKIQFBiosfWiACWortsKPKU/+dUwQQCKQM3qrDe8c9BA=="], - "@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.60.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-qEF7CsKKzSRc20Ciu2Zw1wRrBz4g56F7r/vRwY430UPp/nt1x21Q/fpJ9N5l47WWvJlkNCPJz3QRVw008fi7yA=="], + "@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.60.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-mjCpF7GmkRtSJwon+Rq1N8+pI+8l7w5g9Z3vWj4T7abguC4Czwi3Yu/pFaLvA3TTeMVjnu3ctigusqWUfjZzvw=="], - "@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.60.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-WADYozJ4QCnXCH4wPB+3FuGmDPoFseVCUrANmA5LWwGmC6FL14BWC7pcq+FstOZv3baGX65tZ378uT6WG8ynTw=="], + "@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.60.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-haZ7hJ1JT4e9hqkoT9R/19XW2QKqjfJVv+i5AGg57S+nLk9lQnJ1F/eZloRO3o9Scy9CM3wQ9l+dkXtcBgN5Ew=="], - "@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.60.0", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-6b8wGHJlDrGeSE3aH5mGNHBjA0TTkxdoNHik5EkvPHCt351XnigA4pS7Wsj/Eo9Y8RBU6f35cjN9SYmCFBtzxw=="], + "@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.60.1", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-czw90wpQq3ZsAVBlinZjAYTKduOjTywlG7fEeWKUA7oCmpA8xdTkxZZlwNJKWqILlq0wehoZcJYfBvOyhPTQ6w=="], - "@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.60.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-h25Ga0t4jaylMB8M/JKAyrvvfxGRjnPQIR8lnCayyzEjEOx2EJIlIiMbhpWxDRKGKF8jbNH01NnN663dH638mA=="], + "@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.60.1", "", { "os": "freebsd", "cpu": "x64" }, "sha512-KVB2rqsxTHuBtfOeySEyzEOB7ltlB/ux38iu2rBQzkjbwRVlkhAGIEDiiYnO2kFOkJp+Z7pUXKyrRRFuFUKt+g=="], - "@rollup/rollup-linux-arm-gnueabihf": ["@rollup/rollup-linux-arm-gnueabihf@4.60.0", "", { "os": "linux", "cpu": "arm" }, "sha512-RzeBwv0B3qtVBWtcuABtSuCzToo2IEAIQrcyB/b2zMvBWVbjo8bZDjACUpnaafaxhTw2W+imQbP2BD1usasK4g=="], + "@rollup/rollup-linux-arm-gnueabihf": ["@rollup/rollup-linux-arm-gnueabihf@4.60.1", "", { "os": "linux", "cpu": "arm" }, "sha512-L+34Qqil+v5uC0zEubW7uByo78WOCIrBvci69E7sFASRl0X7b/MB6Cqd1lky/CtcSVTydWa2WZwFuWexjS5o6g=="], - "@rollup/rollup-linux-arm-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.60.0", "", { "os": "linux", "cpu": "arm" }, "sha512-Sf7zusNI2CIU1HLzuu9Tc5YGAHEZs5Lu7N1ssJG4Tkw6e0MEsN7NdjUDDfGNHy2IU+ENyWT+L2obgWiguWibWQ=="], + "@rollup/rollup-linux-arm-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.60.1", "", { "os": "linux", "cpu": "arm" }, "sha512-n83O8rt4v34hgFzlkb1ycniJh7IR5RCIqt6mz1VRJD6pmhRi0CXdmfnLu9dIUS6buzh60IvACM842Ffb3xd6Gg=="], - "@rollup/rollup-linux-arm64-gnu": ["@rollup/rollup-linux-arm64-gnu@4.60.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-DX2x7CMcrJzsE91q7/O02IJQ5/aLkVtYFryqCjduJhUfGKG6yJV8hxaw8pZa93lLEpPTP/ohdN4wFz7yp/ry9A=="], + "@rollup/rollup-linux-arm64-gnu": ["@rollup/rollup-linux-arm64-gnu@4.60.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-Nql7sTeAzhTAja3QXeAI48+/+GjBJ+QmAH13snn0AJSNL50JsDqotyudHyMbO2RbJkskbMbFJfIJKWA6R1LCJQ=="], - "@rollup/rollup-linux-arm64-musl": ["@rollup/rollup-linux-arm64-musl@4.60.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-09EL+yFVbJZlhcQfShpswwRZ0Rg+z/CsSELFCnPt3iK+iqwGsI4zht3secj5vLEs957QvFFXnzAT0FFPIxSrkQ=="], + "@rollup/rollup-linux-arm64-musl": ["@rollup/rollup-linux-arm64-musl@4.60.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-+pUymDhd0ys9GcKZPPWlFiZ67sTWV5UU6zOJat02M1+PiuSGDziyRuI/pPue3hoUwm2uGfxdL+trT6Z9rxnlMA=="], - "@rollup/rollup-linux-loong64-gnu": ["@rollup/rollup-linux-loong64-gnu@4.60.0", "", { "os": "linux", "cpu": "none" }, "sha512-i9IcCMPr3EXm8EQg5jnja0Zyc1iFxJjZWlb4wr7U2Wx/GrddOuEafxRdMPRYVaXjgbhvqalp6np07hN1w9kAKw=="], + "@rollup/rollup-linux-loong64-gnu": ["@rollup/rollup-linux-loong64-gnu@4.60.1", "", { "os": "linux", "cpu": "none" }, "sha512-VSvgvQeIcsEvY4bKDHEDWcpW4Yw7BtlKG1GUT4FzBUlEKQK0rWHYBqQt6Fm2taXS+1bXvJT6kICu5ZwqKCnvlQ=="], - "@rollup/rollup-linux-loong64-musl": ["@rollup/rollup-linux-loong64-musl@4.60.0", "", { "os": "linux", "cpu": "none" }, "sha512-DGzdJK9kyJ+B78MCkWeGnpXJ91tK/iKA6HwHxF4TAlPIY7GXEvMe8hBFRgdrR9Ly4qebR/7gfUs9y2IoaVEyog=="], + "@rollup/rollup-linux-loong64-musl": ["@rollup/rollup-linux-loong64-musl@4.60.1", "", { "os": "linux", "cpu": "none" }, "sha512-4LqhUomJqwe641gsPp6xLfhqWMbQV04KtPp7/dIp0nzPxAkNY1AbwL5W0MQpcalLYk07vaW9Kp1PBhdpZYYcEw=="], - "@rollup/rollup-linux-ppc64-gnu": ["@rollup/rollup-linux-ppc64-gnu@4.60.0", "", { "os": "linux", "cpu": "ppc64" }, "sha512-RwpnLsqC8qbS8z1H1AxBA1H6qknR4YpPR9w2XX0vo2Sz10miu57PkNcnHVaZkbqyw/kUWfKMI73jhmfi9BRMUQ=="], + "@rollup/rollup-linux-ppc64-gnu": ["@rollup/rollup-linux-ppc64-gnu@4.60.1", "", { "os": "linux", "cpu": "ppc64" }, "sha512-tLQQ9aPvkBxOc/EUT6j3pyeMD6Hb8QF2BTBnCQWP/uu1lhc9AIrIjKnLYMEroIz/JvtGYgI9dF3AxHZNaEH0rw=="], - "@rollup/rollup-linux-ppc64-musl": ["@rollup/rollup-linux-ppc64-musl@4.60.0", "", { "os": "linux", "cpu": "ppc64" }, "sha512-Z8pPf54Ly3aqtdWC3G4rFigZgNvd+qJlOE52fmko3KST9SoGfAdSRCwyoyG05q1HrrAblLbk1/PSIV+80/pxLg=="], + "@rollup/rollup-linux-ppc64-musl": ["@rollup/rollup-linux-ppc64-musl@4.60.1", "", { "os": "linux", "cpu": "ppc64" }, "sha512-RMxFhJwc9fSXP6PqmAz4cbv3kAyvD1etJFjTx4ONqFP9DkTkXsAMU4v3Vyc5BgzC+anz7nS/9tp4obsKfqkDHg=="], - "@rollup/rollup-linux-riscv64-gnu": ["@rollup/rollup-linux-riscv64-gnu@4.60.0", "", { "os": "linux", "cpu": "none" }, "sha512-3a3qQustp3COCGvnP4SvrMHnPQ9d1vzCakQVRTliaz8cIp/wULGjiGpbcqrkv0WrHTEp8bQD/B3HBjzujVWLOA=="], + "@rollup/rollup-linux-riscv64-gnu": ["@rollup/rollup-linux-riscv64-gnu@4.60.1", "", { "os": "linux", "cpu": "none" }, "sha512-QKgFl+Yc1eEk6MmOBfRHYF6lTxiiiV3/z/BRrbSiW2I7AFTXoBFvdMEyglohPj//2mZS4hDOqeB0H1ACh3sBbg=="], - "@rollup/rollup-linux-riscv64-musl": ["@rollup/rollup-linux-riscv64-musl@4.60.0", "", { "os": "linux", "cpu": "none" }, "sha512-pjZDsVH/1VsghMJ2/kAaxt6dL0psT6ZexQVrijczOf+PeP2BUqTHYejk3l6TlPRydggINOeNRhvpLa0AYpCWSQ=="], + "@rollup/rollup-linux-riscv64-musl": ["@rollup/rollup-linux-riscv64-musl@4.60.1", "", { "os": "linux", "cpu": "none" }, "sha512-RAjXjP/8c6ZtzatZcA1RaQr6O1TRhzC+adn8YZDnChliZHviqIjmvFwHcxi4JKPSDAt6Uhf/7vqcBzQJy0PDJg=="], - "@rollup/rollup-linux-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.60.0", "", { "os": "linux", "cpu": "s390x" }, "sha512-3ObQs0BhvPgiUVZrN7gqCSvmFuMWvWvsjG5ayJ3Lraqv+2KhOsp+pUbigqbeWqueGIsnn+09HBw27rJ+gYK4VQ=="], + "@rollup/rollup-linux-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.60.1", "", { "os": "linux", "cpu": "s390x" }, "sha512-wcuocpaOlaL1COBYiA89O6yfjlp3RwKDeTIA0hM7OpmhR1Bjo9j31G1uQVpDlTvwxGn2nQs65fBFL5UFd76FcQ=="], - "@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.60.0", "", { "os": "linux", "cpu": "x64" }, "sha512-EtylprDtQPdS5rXvAayrNDYoJhIz1/vzN2fEubo3yLE7tfAw+948dO0g4M0vkTVFhKojnF+n6C8bDNe+gDRdTg=="], + "@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.60.1", "", { "os": "linux", "cpu": "x64" }, "sha512-77PpsFQUCOiZR9+LQEFg9GClyfkNXj1MP6wRnzYs0EeWbPcHs02AXu4xuUbM1zhwn3wqaizle3AEYg5aeoohhg=="], - "@rollup/rollup-linux-x64-musl": ["@rollup/rollup-linux-x64-musl@4.60.0", "", { "os": "linux", "cpu": "x64" }, "sha512-k09oiRCi/bHU9UVFqD17r3eJR9bn03TyKraCrlz5ULFJGdJGi7VOmm9jl44vOJvRJ6P7WuBi/s2A97LxxHGIdw=="], + "@rollup/rollup-linux-x64-musl": ["@rollup/rollup-linux-x64-musl@4.60.1", "", { "os": "linux", "cpu": "x64" }, "sha512-5cIATbk5vynAjqqmyBjlciMJl1+R/CwX9oLk/EyiFXDWd95KpHdrOJT//rnUl4cUcskrd0jCCw3wpZnhIHdD9w=="], - "@rollup/rollup-openbsd-x64": ["@rollup/rollup-openbsd-x64@4.60.0", "", { "os": "openbsd", "cpu": "x64" }, "sha512-1o/0/pIhozoSaDJoDcec+IVLbnRtQmHwPV730+AOD29lHEEo4F5BEUB24H0OBdhbBBDwIOSuf7vgg0Ywxdfiiw=="], + "@rollup/rollup-openbsd-x64": ["@rollup/rollup-openbsd-x64@4.60.1", "", { "os": "openbsd", "cpu": "x64" }, "sha512-cl0w09WsCi17mcmWqqglez9Gk8isgeWvoUZ3WiJFYSR3zjBQc2J5/ihSjpl+VLjPqjQ/1hJRcqBfLjssREQILw=="], - "@rollup/rollup-openharmony-arm64": ["@rollup/rollup-openharmony-arm64@4.60.0", "", { "os": "none", "cpu": "arm64" }, "sha512-pESDkos/PDzYwtyzB5p/UoNU/8fJo68vcXM9ZW2V0kjYayj1KaaUfi1NmTUTUpMn4UhU4gTuK8gIaFO4UGuMbA=="], + "@rollup/rollup-openharmony-arm64": ["@rollup/rollup-openharmony-arm64@4.60.1", "", { "os": "none", "cpu": "arm64" }, "sha512-4Cv23ZrONRbNtbZa37mLSueXUCtN7MXccChtKpUnQNgF010rjrjfHx3QxkS2PI7LqGT5xXyYs1a7LbzAwT0iCA=="], - "@rollup/rollup-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.60.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-hj1wFStD7B1YBeYmvY+lWXZ7ey73YGPcViMShYikqKT1GtstIKQAtfUI6yrzPjAy/O7pO0VLXGmUVWXQMaYgTQ=="], + "@rollup/rollup-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.60.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-i1okWYkA4FJICtr7KpYzFpRTHgy5jdDbZiWfvny21iIKky5YExiDXP+zbXzm3dUcFpkEeYNHgQ5fuG236JPq0g=="], - "@rollup/rollup-win32-ia32-msvc": ["@rollup/rollup-win32-ia32-msvc@4.60.0", "", { "os": "win32", "cpu": "ia32" }, "sha512-SyaIPFoxmUPlNDq5EHkTbiKzmSEmq/gOYFI/3HHJ8iS/v1mbugVa7dXUzcJGQfoytp9DJFLhHH4U3/eTy2Bq4w=="], + "@rollup/rollup-win32-ia32-msvc": ["@rollup/rollup-win32-ia32-msvc@4.60.1", "", { "os": "win32", "cpu": "ia32" }, "sha512-u09m3CuwLzShA0EYKMNiFgcjjzwqtUMLmuCJLeZWjjOYA3IT2Di09KaxGBTP9xVztWyIWjVdsB2E9goMjZvTQg=="], - "@rollup/rollup-win32-x64-gnu": ["@rollup/rollup-win32-x64-gnu@4.60.0", "", { "os": "win32", "cpu": "x64" }, "sha512-RdcryEfzZr+lAr5kRm2ucN9aVlCCa2QNq4hXelZxb8GG0NJSazq44Z3PCCc8wISRuCVnGs0lQJVX5Vp6fKA+IA=="], + "@rollup/rollup-win32-x64-gnu": ["@rollup/rollup-win32-x64-gnu@4.60.1", "", { "os": "win32", "cpu": "x64" }, "sha512-k+600V9Zl1CM7eZxJgMyTUzmrmhB/0XZnF4pRypKAlAgxmedUA+1v9R+XOFv56W4SlHEzfeMtzujLJD22Uz5zg=="], - "@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.60.0", "", { "os": "win32", "cpu": "x64" }, "sha512-PrsWNQ8BuE00O3Xsx3ALh2Df8fAj9+cvvX9AIA6o4KpATR98c9mud4XtDWVvsEuyia5U4tVSTKygawyJkjm60w=="], + "@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.60.1", "", { "os": "win32", "cpu": "x64" }, "sha512-lWMnixq/QzxyhTV6NjQJ4SFo1J6PvOX8vUx5Wb4bBPsEb+8xZ89Bz6kOXpfXj9ak9AHTQVQzlgzBEc1SyM27xQ=="], "@sindresorhus/is": ["@sindresorhus/is@7.2.0", "", {}, "sha512-P1Cz1dWaFfR4IR+U13mqqiGsLFf1KbayybWwdd2vfctdV6hDpUkgCY0nKOLLTMSoRd/jJNjtbqzf13K8DCCXQw=="], @@ -596,7 +597,7 @@ "@types/minimist": ["@types/minimist@1.2.5", "", {}, "sha512-hov8bUuiLiyFPGyFPE1lwWhmzYbirOXQNNo40+y3zow8aFVTeyn3VWL0VFFfdNddA8S4Vf0Tc062rzyNr7Paag=="], - "@types/node": ["@types/node@25.5.0", "", { "dependencies": { "undici-types": "~7.18.0" } }, "sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw=="], + "@types/node": ["@types/node@25.5.2", "", { "dependencies": { "undici-types": "~7.18.0" } }, "sha512-tO4ZIRKNC+MDWV4qKVZe3Ql/woTnmHDr5JD8UI5hn2pwBrHEwOEMZK7WlNb5RKB6EoJ02gwmQS9OrjuFnZYdpg=="], "@types/normalize-package-data": ["@types/normalize-package-data@2.4.4", "", {}, "sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA=="], @@ -698,19 +699,19 @@ "bare-events": ["bare-events@2.8.2", "", { "peerDependencies": { "bare-abort-controller": "*" }, "optionalPeers": ["bare-abort-controller"] }, "sha512-riJjyv1/mHLIPX4RwiK+oW9/4c3TEUeORHKefKAKnZ5kyslbN+HXowtbaVEqt4IMUB7OXlfixcs6gsFeo/jhiQ=="], - "bare-fs": ["bare-fs@4.5.6", "", { "dependencies": { "bare-events": "^2.5.4", "bare-path": "^3.0.0", "bare-stream": "^2.6.4", "bare-url": "^2.2.2", "fast-fifo": "^1.3.2" }, "peerDependencies": { "bare-buffer": "*" }, "optionalPeers": ["bare-buffer"] }, "sha512-1QovqDrR80Pmt5HPAsMsXTCFcDYr+NSUKW6nd6WO5v0JBmnItc/irNRzm2KOQ5oZ69P37y+AMujNyNtG+1Rggw=="], + "bare-fs": ["bare-fs@4.6.0", "", { "dependencies": { "bare-events": "^2.5.4", "bare-path": "^3.0.0", "bare-stream": "^2.6.4", "bare-url": "^2.2.2", "fast-fifo": "^1.3.2" }, "peerDependencies": { "bare-buffer": "*" }, "optionalPeers": ["bare-buffer"] }, "sha512-2YkS7NuiJceSEbyEOdSNLE9tsGd+f4+f7C+Nik/MCk27SYdwIMPT/yRKvg++FZhQXgk0KWJKJyXX9RhVV0RGqA=="], - "bare-os": ["bare-os@3.8.0", "", {}, "sha512-Dc9/SlwfxkXIGYhvMQNUtKaXCaGkZYGcd1vuNUUADVqzu4/vQfvnMkYYOUnt2VwQ2AqKr/8qAVFRtwETljgeFg=="], + "bare-os": ["bare-os@3.8.7", "", {}, "sha512-G4Gr1UsGeEy2qtDTZwL7JFLo2wapUarz7iTMcYcMFdS89AIQuBoyjgXZz0Utv7uHs3xA9LckhVbeBi8lEQrC+w=="], "bare-path": ["bare-path@3.0.0", "", { "dependencies": { "bare-os": "^3.0.1" } }, "sha512-tyfW2cQcB5NN8Saijrhqn0Zh7AnFNsnczRcuWODH0eYAXBsJ5gVxAUuNr7tsHSC6IZ77cA0SitzT+s47kot8Mw=="], - "bare-stream": ["bare-stream@2.11.0", "", { "dependencies": { "streamx": "^2.25.0", "teex": "^1.0.1" }, "peerDependencies": { "bare-abort-controller": "*", "bare-buffer": "*", "bare-events": "*" }, "optionalPeers": ["bare-abort-controller", "bare-buffer", "bare-events"] }, "sha512-Y/+iQ49fL3rIn6w/AVxI/2+BRrpmzJvdWt5Jv8Za6Ngqc6V227c+pYjYYgLdpR3MwQ9ObVXD0ZrqoBztakM0rw=="], + "bare-stream": ["bare-stream@2.12.0", "", { "dependencies": { "streamx": "^2.25.0", "teex": "^1.0.1" }, "peerDependencies": { "bare-abort-controller": "*", "bare-buffer": "*", "bare-events": "*" }, "optionalPeers": ["bare-abort-controller", "bare-buffer", "bare-events"] }, "sha512-w28i8lkBgREV3rPXGbgK+BO66q+ZpKqRWrZLiCdmmUlLPrQ45CzkvRhN+7lnv00Gpi2zy5naRxnUFAxCECDm9g=="], "bare-url": ["bare-url@2.4.0", "", { "dependencies": { "bare-path": "^3.0.0" } }, "sha512-NSTU5WN+fy/L0DDenfE8SXQna4voXuW0FHM7wH8i3/q9khUSchfPbPezO4zSFMnDGIf9YE+mt/RWhZgNRKRIXA=="], "base64-js": ["base64-js@1.5.1", "", {}, "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="], - "baseline-browser-mapping": ["baseline-browser-mapping@2.10.11", "", { "bin": { "baseline-browser-mapping": "dist/cli.cjs" } }, "sha512-DAKrHphkJyiGuau/cFieRYhcTFeK/lBuD++C7cZ6KZHbMhBrisoi+EvhQ5RZrIfV5qwsW8kgQ07JIC+MDJRAhg=="], + "baseline-browser-mapping": ["baseline-browser-mapping@2.10.14", "", { "bin": { "baseline-browser-mapping": "dist/cli.cjs" } }, "sha512-fOVLPAsFTsQfuCkvahZkzq6nf8KvGWanlYoTh0SVA0A/PIUxQGU2AOZAoD95n2gFLVDW/jP6sbGLny95nmEuHA=="], "big-integer": ["big-integer@1.6.52", "", {}, "sha512-QxD8cf2eVqJOOz63z6JIN9BzvVs/dlySa5HGSBH5xtR8dPteIRQnBxxKqkNTiT6jbDTF6jAfrd4oMcND9RGbQg=="], @@ -728,7 +729,7 @@ "braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="], - "browserslist": ["browserslist@4.28.1", "", { "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", "electron-to-chromium": "^1.5.263", "node-releases": "^2.0.27", "update-browserslist-db": "^1.2.0" }, "bin": { "browserslist": "cli.js" } }, "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA=="], + "browserslist": ["browserslist@4.28.2", "", { "dependencies": { "baseline-browser-mapping": "^2.10.12", "caniuse-lite": "^1.0.30001782", "electron-to-chromium": "^1.5.328", "node-releases": "^2.0.36", "update-browserslist-db": "^1.2.3" }, "bin": { "browserslist": "cli.js" } }, "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg=="], "buffer": ["buffer@5.7.1", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.1.13" } }, "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ=="], @@ -736,12 +737,12 @@ "buffer-from": ["buffer-from@1.1.2", "", {}, "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="], - "bufferutil": ["bufferutil@4.1.0", "", { "dependencies": { "node-gyp-build": "^4.3.0" } }, "sha512-ZMANVnAixE6AWWnPzlW2KpUrxhm9woycYvPOo67jWHyFowASTEd9s+QN1EIMsSDtwhIxN4sWE1jotpuDUIgyIw=="], - "butterchurn": ["butterchurn@2.6.7", "", { "dependencies": { "@babel/runtime": "^7.0.0", "ecma-proposal-math-extensions": "0.0.2" } }, "sha512-BJiRA8L0L2+84uoG2SSfkp0kclBuN+vQKf217pK7pMlwEO2ZEg3MtO2/o+l8Qpr8Nbejg8tmL1ZHD1jmhiaaqg=="], "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=="], + "cac": ["cac@6.7.14", "", {}, "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ=="], + "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=="], @@ -756,7 +757,7 @@ "camelcase-keys": ["camelcase-keys@6.2.2", "", { "dependencies": { "camelcase": "^5.3.1", "map-obj": "^4.0.0", "quick-lru": "^4.0.1" } }, "sha512-YrwaA0vEKazPBkn0ipTiMpSajYDSe+KjQfrjhcBMxJt/znbvlHd8Pw/Vamaz5EB4Wfhs3SUR3Z9mwRu/P3s3Yg=="], - "caniuse-lite": ["caniuse-lite@1.0.30001781", "", {}, "sha512-RdwNCyMsNBftLjW6w01z8bKEvT6e/5tpPVEgtn22TiLGlstHOVecsX2KHFkD5e/vRnIE4EGzpuIODb3mtswtkw=="], + "caniuse-lite": ["caniuse-lite@1.0.30001784", "", {}, "sha512-WU346nBTklUV9YfUl60fqRbU5ZqyXlqvo1SgigE1OAXK5bFL8LL9q1K7aap3N739l4BvNqnkm3YrGHiY9sfUQw=="], "chai": ["chai@6.2.2", "", {}, "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg=="], @@ -916,7 +917,7 @@ "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.328", "", {}, "sha512-QNQ5l45DzYytThO21403XN3FvK0hOkWDG8viNf6jqS42msJ8I4tGDSpBCgvDRRPnkffafiwAym2X2eHeGD2V0w=="], + "electron-to-chromium": ["electron-to-chromium@1.5.331", "", {}, "sha512-IbxXrsTlD3hRodkLnbxAPP4OuJYdWCeM3IOdT+CpcMoIwIoDfCmRpEtSPfwBXxVkg9xmBeY7Lz2Eo2TDn/HC3Q=="], "elementtree": ["elementtree@0.1.7", "", { "dependencies": { "sax": "1.1.4" } }, "sha512-wkgGT6kugeQk/P6VZ/f4T+4HB41BVgNBq5CDIZVbQ02nvTVqAiVTbskxxu3eA/X96lMlfYOwnLQpN2v5E1zDEg=="], @@ -946,7 +947,7 @@ "es-to-primitive": ["es-to-primitive@1.3.0", "", { "dependencies": { "is-callable": "^1.2.7", "is-date-object": "^1.0.5", "is-symbol": "^1.0.4" } }, "sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g=="], - "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=="], + "esbuild": ["esbuild@0.27.7", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.27.7", "@esbuild/android-arm": "0.27.7", "@esbuild/android-arm64": "0.27.7", "@esbuild/android-x64": "0.27.7", "@esbuild/darwin-arm64": "0.27.7", "@esbuild/darwin-x64": "0.27.7", "@esbuild/freebsd-arm64": "0.27.7", "@esbuild/freebsd-x64": "0.27.7", "@esbuild/linux-arm": "0.27.7", "@esbuild/linux-arm64": "0.27.7", "@esbuild/linux-ia32": "0.27.7", "@esbuild/linux-loong64": "0.27.7", "@esbuild/linux-mips64el": "0.27.7", "@esbuild/linux-ppc64": "0.27.7", "@esbuild/linux-riscv64": "0.27.7", "@esbuild/linux-s390x": "0.27.7", "@esbuild/linux-x64": "0.27.7", "@esbuild/netbsd-arm64": "0.27.7", "@esbuild/netbsd-x64": "0.27.7", "@esbuild/openbsd-arm64": "0.27.7", "@esbuild/openbsd-x64": "0.27.7", "@esbuild/openharmony-arm64": "0.27.7", "@esbuild/sunos-x64": "0.27.7", "@esbuild/win32-arm64": "0.27.7", "@esbuild/win32-ia32": "0.27.7", "@esbuild/win32-x64": "0.27.7" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w=="], "escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="], @@ -1036,7 +1037,7 @@ "functions-have-names": ["functions-have-names@1.2.3", "", {}, "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ=="], - "fuse.js": ["fuse.js@7.1.0", "", {}, "sha512-trLf4SzuuUxfusZADLINj+dE8clK1frKdmqiJNb1Es75fmI5oY6X2mxLVUciLLjxqw/xr72Dhy+lER6dGd02FQ=="], + "fuse.js": ["fuse.js@7.2.0", "", {}, "sha512-zf4vdcIGpjNKTuXwug33Hm2okqX6a0t2ZEbez+o9oBJQSNhVJ5AqERfeiRD3r8HcLqP66MrjdkmzxrncbAOTUQ=="], "generator-function": ["generator-function@2.0.1", "", {}, "sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g=="], @@ -1126,6 +1127,10 @@ "import-fresh": ["import-fresh@3.3.1", "", { "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" } }, "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ=="], + "import-from-esm": ["import-from-esm@1.3.4", "", { "dependencies": { "debug": "^4.3.4", "import-meta-resolve": "^4.0.0" } }, "sha512-7EyUlPFC0HOlBDpUFGfYstsU7XHxZJKAAMzCT8wZ0hMW7b+hG51LIKTDcsgtz8Pu6YC0HqRVbX+rVUtsGMUKvg=="], + + "import-meta-resolve": ["import-meta-resolve@4.2.0", "", {}, "sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg=="], + "imurmurhash": ["imurmurhash@0.1.4", "", {}, "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA=="], "indent-string": ["indent-string@4.0.0", "", {}, "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg=="], @@ -1238,8 +1243,6 @@ "json-parse-even-better-errors": ["json-parse-even-better-errors@2.3.1", "", {}, "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w=="], - "json-schema": ["json-schema@0.4.0", "", {}, "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA=="], - "json-schema-traverse": ["json-schema-traverse@0.4.1", "", {}, "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="], "json-stable-stringify-without-jsonify": ["json-stable-stringify-without-jsonify@1.0.1", "", {}, "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw=="], @@ -1280,7 +1283,7 @@ "locate-path": ["locate-path@6.0.0", "", { "dependencies": { "p-locate": "^5.0.0" } }, "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw=="], - "lodash": ["lodash@4.17.23", "", {}, "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w=="], + "lodash": ["lodash@4.18.1", "", {}, "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q=="], "lodash.debounce": ["lodash.debounce@4.0.8", "", {}, "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow=="], @@ -1322,7 +1325,7 @@ "min-indent": ["min-indent@1.0.1", "", {}, "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg=="], - "miniflare": ["miniflare@4.20260317.2", "", { "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-qNL+yWAFMX6fr0pWU6Lx1vNpPobpnDSF1V8eunIckWvoIQl8y1oBjL2RJFEGY3un+l3f9gwW9dirDPP26usYJQ=="], + "miniflare": ["miniflare@4.20260401.0", "", { "dependencies": { "@cspotcode/source-map-support": "0.8.1", "sharp": "^0.34.5", "undici": "7.24.4", "workerd": "1.20260401.1", "ws": "8.18.0", "youch": "4.1.0-beta.10" }, "bin": { "miniflare": "bootstrap.js" } }, "sha512-lngHPzZFN9sxYG/mhzvnWiBMNVAN5MsO/7g32ttJ07rymtiK/ZBalODTKb8Od+BQdlU5DOR4CjVt9NydjnUyYg=="], "minimatch": ["minimatch@3.1.5", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w=="], @@ -1360,11 +1363,9 @@ "node-fetch": ["node-fetch@2.7.0", "", { "dependencies": { "whatwg-url": "^5.0.0" }, "peerDependencies": { "encoding": "^0.1.0" }, "optionalPeers": ["encoding"] }, "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A=="], - "node-gyp-build": ["node-gyp-build@4.8.4", "", { "bin": { "node-gyp-build": "bin.js", "node-gyp-build-optional": "optional.js", "node-gyp-build-test": "build-test.js" } }, "sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ=="], - "node-html-parser": ["node-html-parser@5.4.2", "", { "dependencies": { "css-select": "^4.2.1", "he": "1.2.0" } }, "sha512-RaBPP3+51hPne/OolXxcz89iYvQvKOydaqoePpOgXcrOKZhjVIzmpKZz+Hd/RBO2/zN2q6CNJhQzucVz+u3Jyw=="], - "node-releases": ["node-releases@2.0.36", "", {}, "sha512-TdC8FSgHz8Mwtw9g5L4gR/Sh9XhSP/0DEkQxfEFXOpiul5IiHgHan2VhYYb6agDSfp4KuvltmGApc8HMgUrIkA=="], + "node-releases": ["node-releases@2.0.37", "", {}, "sha512-1h5gKZCF+pO/o3Iqt5Jp7wc9rH3eJJ0+nh/CIoiRwjRxde/hAHyLPXYN4V3CqKAbiZPSeJFSWHmJsbkicta0Eg=="], "node-sarif-builder": ["node-sarif-builder@3.2.0", "", { "dependencies": { "@types/sarif": "^2.1.7", "fs-extra": "^11.1.1" } }, "sha512-kVIOdynrF2CRodHZeP/97Rh1syTUHBNiw17hUCIVhlhEsWlfJm19MuO56s4MdKbr22xWx6mzMnNAgXzVlIYM9Q=="], @@ -1430,9 +1431,9 @@ "pify": ["pify@2.3.0", "", {}, "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog=="], - "playwright": ["playwright@1.58.2", "", { "dependencies": { "playwright-core": "1.58.2" }, "optionalDependencies": { "fsevents": "2.3.2" }, "bin": { "playwright": "cli.js" } }, "sha512-vA30H8Nvkq/cPBnNw4Q8TWz1EJyqgpuinBcHET0YVJVFldr8JDNiU9LaWAE1KqSkRYazuaBhTpB5ZzShOezQ6A=="], + "playwright": ["playwright@1.59.1", "", { "dependencies": { "playwright-core": "1.59.1" }, "optionalDependencies": { "fsevents": "2.3.2" }, "bin": { "playwright": "cli.js" } }, "sha512-C8oWjPR3F81yljW9o5OxcWzfh6avkVwDD2VYdwIGqTkl+OGFISgypqzfu7dOe4QNLL2aqcWBmI3PMtLIK233lw=="], - "playwright-core": ["playwright-core@1.58.2", "", { "bin": { "playwright-core": "cli.js" } }, "sha512-yZkEtftgwS8CsfYo7nm0KE8jsvm6i/PTgVtB8DL726wNf6H2IMsDuxCpJj59KDaxCtSnrWan2AeDqM7JBaultg=="], + "playwright-core": ["playwright-core@1.59.1", "", { "bin": { "playwright-core": "cli.js" } }, "sha512-HBV/RJg81z5BiiZ9yPzIiClYV/QMsDCKUyogwH9p3MCP6IYjUFu/MActgYAvK0oWyV9NlwM3GLBjADyWgydVyg=="], "plist": ["plist@3.1.0", "", { "dependencies": { "@xmldom/xmldom": "^0.8.8", "base64-js": "^1.5.1", "xmlbuilder": "^15.1.1" } }, "sha512-uysumyrvkUX0rX/dEVqt8gC3sTBzd4zoWfLeS29nb53imdaXVvLINYXTI2GNqzaMuvacNx4uJQ8+b3zXR0pkgQ=="], @@ -1476,7 +1477,7 @@ "q": ["q@1.5.1", "", {}, "sha512-kV/CThkXo6xyFEZUugw/+pIOywXcDbFYgSct5cT3gqlbkBE1SJdwy6UQoZvodiWF/ckQLZyDE/Bu1M6gVu5lVw=="], - "qified": ["qified@0.9.0", "", { "dependencies": { "hookified": "^2.1.0" } }, "sha512-4q61YgkHbY6gmwkqm0BsxyLDO3UYdrdiJTJ7JiaZb3xpW1duxn135SB7KqUEkCiuu5O4W+TtwEWP2VjmSRanvA=="], + "qified": ["qified@0.9.1", "", { "dependencies": { "hookified": "^2.1.1" } }, "sha512-n7mar4T0xQ+39dE2vGTAlbxUEpndwPANH0kDef1/MYsB8Bba9wshkybIRx74qgcvKQPEWErf9AqAdYjhzY2Ilg=="], "queue-microtask": ["queue-microtask@1.2.3", "", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="], @@ -1528,7 +1529,9 @@ "rimraf": ["rimraf@6.1.3", "", { "dependencies": { "glob": "^13.0.3", "package-json-from-dist": "^1.0.1" }, "bin": { "rimraf": "dist/esm/bin.mjs" } }, "sha512-LKg+Cr2ZF61fkcaK1UdkH2yEBBKnYjTyWzTJT6KNPcSPaiT7HSdhtMXQuN5wkTX0Xu72KQ1l8S42rlmexS2hSA=="], - "rollup": ["rollup@4.60.0", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.60.0", "@rollup/rollup-android-arm64": "4.60.0", "@rollup/rollup-darwin-arm64": "4.60.0", "@rollup/rollup-darwin-x64": "4.60.0", "@rollup/rollup-freebsd-arm64": "4.60.0", "@rollup/rollup-freebsd-x64": "4.60.0", "@rollup/rollup-linux-arm-gnueabihf": "4.60.0", "@rollup/rollup-linux-arm-musleabihf": "4.60.0", "@rollup/rollup-linux-arm64-gnu": "4.60.0", "@rollup/rollup-linux-arm64-musl": "4.60.0", "@rollup/rollup-linux-loong64-gnu": "4.60.0", "@rollup/rollup-linux-loong64-musl": "4.60.0", "@rollup/rollup-linux-ppc64-gnu": "4.60.0", "@rollup/rollup-linux-ppc64-musl": "4.60.0", "@rollup/rollup-linux-riscv64-gnu": "4.60.0", "@rollup/rollup-linux-riscv64-musl": "4.60.0", "@rollup/rollup-linux-s390x-gnu": "4.60.0", "@rollup/rollup-linux-x64-gnu": "4.60.0", "@rollup/rollup-linux-x64-musl": "4.60.0", "@rollup/rollup-openbsd-x64": "4.60.0", "@rollup/rollup-openharmony-arm64": "4.60.0", "@rollup/rollup-win32-arm64-msvc": "4.60.0", "@rollup/rollup-win32-ia32-msvc": "4.60.0", "@rollup/rollup-win32-x64-gnu": "4.60.0", "@rollup/rollup-win32-x64-msvc": "4.60.0", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-yqjxruMGBQJ2gG4HtjZtAfXArHomazDHoFwFFmZZl0r7Pdo7qCIXKqKHZc8yeoMgzJJ+pO6pEEHa+V7uzWlrAQ=="], + "rollup": ["rollup@4.60.1", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.60.1", "@rollup/rollup-android-arm64": "4.60.1", "@rollup/rollup-darwin-arm64": "4.60.1", "@rollup/rollup-darwin-x64": "4.60.1", "@rollup/rollup-freebsd-arm64": "4.60.1", "@rollup/rollup-freebsd-x64": "4.60.1", "@rollup/rollup-linux-arm-gnueabihf": "4.60.1", "@rollup/rollup-linux-arm-musleabihf": "4.60.1", "@rollup/rollup-linux-arm64-gnu": "4.60.1", "@rollup/rollup-linux-arm64-musl": "4.60.1", "@rollup/rollup-linux-loong64-gnu": "4.60.1", "@rollup/rollup-linux-loong64-musl": "4.60.1", "@rollup/rollup-linux-ppc64-gnu": "4.60.1", "@rollup/rollup-linux-ppc64-musl": "4.60.1", "@rollup/rollup-linux-riscv64-gnu": "4.60.1", "@rollup/rollup-linux-riscv64-musl": "4.60.1", "@rollup/rollup-linux-s390x-gnu": "4.60.1", "@rollup/rollup-linux-x64-gnu": "4.60.1", "@rollup/rollup-linux-x64-musl": "4.60.1", "@rollup/rollup-openbsd-x64": "4.60.1", "@rollup/rollup-openharmony-arm64": "4.60.1", "@rollup/rollup-win32-arm64-msvc": "4.60.1", "@rollup/rollup-win32-ia32-msvc": "4.60.1", "@rollup/rollup-win32-x64-gnu": "4.60.1", "@rollup/rollup-win32-x64-msvc": "4.60.1", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-VmtB2rFU/GroZ4oL8+ZqXgSA38O6GR8KSIvWmEFv63pQ0G6KaBH9s07PO8XTXP4vI+3UJUEypOfjkGfmSBBR0w=="], + + "rollup-plugin-visualizer": ["rollup-plugin-visualizer@5.14.0", "", { "dependencies": { "open": "^8.4.0", "picomatch": "^4.0.2", "source-map": "^0.7.4", "yargs": "^17.5.1" }, "peerDependencies": { "rolldown": "1.x", "rollup": "2.x || 3.x || 4.x" }, "optionalPeers": ["rolldown", "rollup"], "bin": { "rollup-plugin-visualizer": "dist/bin/cli.js" } }, "sha512-VlDXneTDaKsHIw8yzJAFWtrzguoJ/LnQ+lMpoVfYJ3jJF4Ihe5oYLAqLklIK/35lgUY+1yEzCkHyZ1j4A5w5fA=="], "run-parallel": ["run-parallel@1.2.0", "", { "dependencies": { "queue-microtask": "^1.2.2" } }, "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA=="], @@ -1554,7 +1557,7 @@ "set-proto": ["set-proto@1.0.0", "", { "dependencies": { "dunder-proto": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0" } }, "sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw=="], - "shaka-player": ["shaka-player@5.0.8", "", {}, "sha512-f886rKRvQ0IKhWGk+rINS++YTjTJyc4DT5YypTsHW6wiNV9fiHi2n35+lg5R+hj9RfhqkmJHMjJb3gprUTNa8w=="], + "shaka-player": ["shaka-player@5.0.9", "", {}, "sha512-Y391+8gZHpLYGkiYmY/jHOfHQKQXHrR1op+awAEOxn3BALskjDuyoteNwfFyFScuQOqKIiq0O0PjPe1NfHi+WQ=="], "sharp": ["sharp@0.32.6", "", { "dependencies": { "color": "^4.2.3", "detect-libc": "^2.0.2", "node-addon-api": "^6.1.0", "prebuild-install": "^7.1.1", "semver": "^7.5.4", "simple-get": "^4.0.1", "tar-fs": "^3.0.4", "tunnel-agent": "^0.6.0" } }, "sha512-KyLTWwgcR9Oe4d9HwCwNM2l7+J0dUQwn/yf7S0EnTtb0eVS4RxO0eUSvxPtzT4F3SY+C4K6fqdv/DO27sJ/v/w=="], @@ -1578,7 +1581,7 @@ "simple-get": ["simple-get@4.0.1", "", { "dependencies": { "decompress-response": "^6.0.0", "once": "^1.3.1", "simple-concat": "^1.0.0" } }, "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA=="], - "simple-icons": ["simple-icons@16.13.0", "", {}, "sha512-N4AMZvFERU5YLEtUudtUesiM2H4O5xQ9qfS3K0oOV5II5KVtxOUAlmZ7KqBgiTSGBgCVkuLD/Z9dJKBtnI3kKQ=="], + "simple-icons": ["simple-icons@16.14.0", "", {}, "sha512-2Nvs3jJpCfMWQerD4zdv91g/MpnWn81a7uhyAC0reuhrjmS2MtSmwIKwewOJR6Xe97ZmfltDntCDqKJIBawQOw=="], "simple-plist": ["simple-plist@1.3.1", "", { "dependencies": { "bplist-creator": "0.1.0", "bplist-parser": "0.3.1", "plist": "^3.0.5" } }, "sha512-iMSw5i0XseMnrhtIzRb7XpQEXepa9xhWxGUojHBL43SIpQuDQkh3Wpy67ZbDzZVr6EKxvwVChnVpdl8hEVLDiw=="], @@ -1766,8 +1769,6 @@ "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=="], "uuid": ["uuid@13.0.0", "", { "bin": { "uuid": "dist-node/bin/uuid" } }, "sha512-XQegIaBTVUjSHliKqcnFqYypAd4S+WCYt5NIeRs6w/UAry7z8Y9j5ZwRRL4kzq9U3sD6v+85er9FvkEaBpji2w=="], @@ -1778,6 +1779,8 @@ "vite": ["vite@7.3.1", "", { "dependencies": { "esbuild": "^0.27.0", "fdir": "^6.5.0", "picomatch": "^4.0.3", "postcss": "^8.5.6", "rollup": "^4.43.0", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "jiti": ">=1.21.0", "less": "^4.0.0", "lightningcss": "^1.21.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA=="], + "vite-bundle-visualizer": ["vite-bundle-visualizer@1.2.1", "", { "dependencies": { "cac": "^6.7.14", "import-from-esm": "^1.3.3", "rollup-plugin-visualizer": "^5.11.0", "tmp": "^0.2.1" }, "bin": { "vite-bundle-visualizer": "bin.js" } }, "sha512-cwz/Pg6+95YbgIDp+RPwEToc4TKxfsFWSG/tsl2DSZd9YZicUag1tQXjJ5xcL7ydvEoaC2FOZeaXOU60t9BRXw=="], + "vite-plugin-pwa": ["vite-plugin-pwa@1.2.0", "", { "dependencies": { "debug": "^4.3.6", "pretty-bytes": "^6.1.1", "tinyglobby": "^0.2.10", "workbox-build": "^7.4.0", "workbox-window": "^7.4.0" }, "peerDependencies": { "@vite-pwa/assets-generator": "^1.0.0", "vite": "^3.1.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" }, "optionalPeers": ["@vite-pwa/assets-generator"] }, "sha512-a2xld+SJshT9Lgcv8Ji4+srFJL4k/1bVbd1x06JIkvecpQkwkvCncD1+gSzcdm3s+owWLpMJerG3aN5jupJEVw=="], "vitest": ["vitest@4.1.2", "", { "dependencies": { "@vitest/expect": "4.1.2", "@vitest/mocker": "4.1.2", "@vitest/pretty-format": "4.1.2", "@vitest/runner": "4.1.2", "@vitest/snapshot": "4.1.2", "@vitest/spy": "4.1.2", "@vitest/utils": "4.1.2", "es-module-lexer": "^2.0.0", "expect-type": "^1.3.0", "magic-string": "^0.30.21", "obug": "^2.1.1", "pathe": "^2.0.3", "picomatch": "^4.0.3", "std-env": "^4.0.0-rc.1", "tinybench": "^2.9.0", "tinyexec": "^1.0.2", "tinyglobby": "^0.2.15", "tinyrainbow": "^3.1.0", "vite": "^6.0.0 || ^7.0.0 || ^8.0.0", "why-is-node-running": "^2.3.0" }, "peerDependencies": { "@edge-runtime/vm": "*", "@opentelemetry/api": "^1.9.0", "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", "@vitest/browser-playwright": "4.1.2", "@vitest/browser-preview": "4.1.2", "@vitest/browser-webdriverio": "4.1.2", "@vitest/ui": "4.1.2", "happy-dom": "*", "jsdom": "*" }, "optionalPeers": ["@edge-runtime/vm", "@opentelemetry/api", "@types/node", "@vitest/browser-playwright", "@vitest/browser-preview", "@vitest/browser-webdriverio", "@vitest/ui", "happy-dom", "jsdom"], "bin": { "vitest": "vitest.mjs" } }, "sha512-xjR1dMTVHlFLh98JE3i/f/WePqJsah4A0FK9cc8Ehp9Udk0AZk6ccpIZhh1qJ/yxVWRZ+Q54ocnD8TXmkhspGg=="], @@ -1836,7 +1839,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.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=="], + "workerd": ["workerd@1.20260401.1", "", { "optionalDependencies": { "@cloudflare/workerd-darwin-64": "1.20260401.1", "@cloudflare/workerd-darwin-arm64": "1.20260401.1", "@cloudflare/workerd-linux-64": "1.20260401.1", "@cloudflare/workerd-linux-arm64": "1.20260401.1", "@cloudflare/workerd-windows-64": "1.20260401.1" }, "bin": { "workerd": "bin/workerd" } }, "sha512-mUYCd+ohaWJWF5nhDzxugWaAD/DM8Dw0ze3B7bu8JaA7S70+XQJXcvcvwE8C4qGcxSdCyqjsrFzqxKubECDwzg=="], "wrap-ansi": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="], @@ -1930,8 +1933,6 @@ "@ionic/utils-array/debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], - "@ionic/utils-array/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], - "@ionic/utils-fs/debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], "@ionic/utils-fs/fs-extra": ["fs-extra@9.1.0", "", { "dependencies": { "at-least-node": "^1.0.0", "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ=="], @@ -2046,7 +2047,7 @@ "gitconfiglocal/ini": ["ini@1.3.8", "", {}, "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew=="], - "glob/minimatch": ["minimatch@10.2.4", "", { "dependencies": { "brace-expansion": "^5.0.2" } }, "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg=="], + "glob/minimatch": ["minimatch@10.2.5", "", { "dependencies": { "brace-expansion": "^5.0.5" } }, "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg=="], "global-prefix/ini": ["ini@1.3.8", "", {}, "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew=="], @@ -2058,6 +2059,8 @@ "import-fresh/resolve-from": ["resolve-from@4.0.0", "", {}, "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="], + "import-from-esm/debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], + "load-json-file/parse-json": ["parse-json@4.0.0", "", { "dependencies": { "error-ex": "^1.3.1", "json-parse-better-errors": "^1.0.1" } }, "sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw=="], "load-json-file/pify": ["pify@3.0.0", "", {}, "sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg=="], @@ -2236,7 +2239,7 @@ "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=="], + "npm/minimatch": ["minimatch@10.2.5", "", { "dependencies": { "brace-expansion": "^5.0.5" }, "bundled": true }, "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg=="], "npm/minipass": ["minipass@7.1.3", "", { "bundled": true }, "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A=="], @@ -2358,13 +2361,13 @@ "npm/yallist": ["yallist@5.0.0", "", {}, "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw=="], - "plist/@xmldom/xmldom": ["@xmldom/xmldom@0.8.11", "", {}, "sha512-cQzWCtO6C8TQiYl1ruKNn2U6Ao4o4WBBcbL61yJl84x+j5sOWWFU9X7DpND8XZG3daDppSsigMdfAIl2upQBRw=="], + "plist/@xmldom/xmldom": ["@xmldom/xmldom@0.8.12", "", {}, "sha512-9k/gHF6n/pAi/9tqr3m3aqkuiNosYTurLLUtc7xQ9sxB/wm7WPygCv8GYa6mS0fLJEHhqMC1ATYhz++U/lRHqg=="], "prebuild-install/tar-fs": ["tar-fs@2.1.4", "", { "dependencies": { "chownr": "^1.1.1", "mkdirp-classic": "^0.5.2", "pump": "^3.0.0", "tar-stream": "^2.1.4" } }, "sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ=="], "prompts/kleur": ["kleur@3.0.3", "", {}, "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w=="], - "qified/hookified": ["hookified@2.1.0", "", {}, "sha512-ootKng4eaxNxa7rx6FJv2YKef3DuhqbEj3l70oGXwddPQEEnISm50TEZQclqiLTAtilT2nu7TErtCO523hHkyg=="], + "qified/hookified": ["hookified@2.1.1", "", {}, "sha512-AHb76R16GB5EsPBE2J7Ko5kiEyXwviB9P5SMrAKcuAu4vJPZttViAbj9+tZeaQE5zjDme+1vcHP78Yj/WoAveA=="], "rc/ini": ["ini@1.3.8", "", {}, "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew=="], @@ -2538,6 +2541,8 @@ "hosted-git-info/lru-cache/yallist": ["yallist@4.0.0", "", {}, "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="], + "import-from-esm/debug/ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], + "native-run/debug/ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], "node-html-parser/css-select/domhandler": ["domhandler@4.3.1", "", { "dependencies": { "domelementtype": "^2.2.0" } }, "sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ=="], @@ -2584,7 +2589,7 @@ "workbox-build/ajv/json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="], - "workbox-build/glob/minimatch": ["minimatch@10.2.4", "", { "dependencies": { "brace-expansion": "^5.0.2" } }, "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg=="], + "workbox-build/glob/minimatch": ["minimatch@10.2.5", "", { "dependencies": { "brace-expansion": "^5.0.5" } }, "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg=="], "workbox-build/rollup/fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], diff --git a/package.json b/package.json index 859a6c2..0b25d0d 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,7 @@ "test:watch": "vitest --config=vite.config.ts", "test:watch:headless": "HEADLESS=true vitest --config=vite.config.ts", "install:playwright": "playwright install chromium", - "build": "vite build", + "build": "vite build && vite-bundle-visualizer -o dist/assets/bundle-stats.html --open false", "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 .", @@ -49,6 +49,7 @@ "stylelint-config-standard-scss": "^16.0.0", "typescript": "^5.9.3", "vite": "^7.3.1", + "vite-bundle-visualizer": "^1.2.1", "vite-plugin-pwa": "^1.2.0" }, "overrides": { From 5d43e1a6eea93a606a475d6ad402ba8111a0d21b Mon Sep 17 00:00:00 2001 From: edideaur Date: Fri, 3 Apr 2026 17:59:47 +0000 Subject: [PATCH 32/51] editors picks gh action --- .github/workflows/editors-picks.yml | 95 +++++++++++++++++++++++++++++ editors-picks-input.txt | 17 ++++++ gen-editors-picks.py | 33 +++++----- 3 files changed, 130 insertions(+), 15 deletions(-) create mode 100644 .github/workflows/editors-picks.yml create mode 100644 editors-picks-input.txt diff --git a/.github/workflows/editors-picks.yml b/.github/workflows/editors-picks.yml new file mode 100644 index 0000000..fa294f0 --- /dev/null +++ b/.github/workflows/editors-picks.yml @@ -0,0 +1,95 @@ +name: Update Editors Picks + +on: + push: + branches: [main] + paths: + - 'editors-picks-input.txt' + +jobs: + update: + runs-on: ubuntu-latest + permissions: + contents: write + + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 1 + + - name: Check for backoff condition + id: backoff + run: | + CHANGED=$(git diff-tree --no-commit-id -r --name-only HEAD) + echo "Files changed in this commit:" + echo "$CHANGED" + if echo "$CHANGED" | grep -qE '^public/editors-picks\.json$|^public/editors-picks-old/'; then + echo "Detected changes to generated files in this commit — backing off to avoid overwriting manual edits." + echo "skip=true" >> "$GITHUB_OUTPUT" + else + echo "skip=false" >> "$GITHUB_OUTPUT" + fi + + - name: Setup Python + if: steps.backoff.outputs.skip == 'false' + uses: actions/setup-python@v5 + with: + python-version: '3.x' + + - name: Archive current editors picks + if: steps.backoff.outputs.skip == 'false' + run: | + python3 - << 'EOF' + import json, re + from datetime import date + + today = date.today() + # Filename uses non-padded month/day to match existing convention + filename = f"{today.year}-{today.month}-{today.day}.json" + # Date field uses ISO format (zero-padded) + iso_date = today.strftime("%Y-%m-%d") + archive_path = f"public/editors-picks-old/{filename}" + + # Read optional label from input file + label = iso_date + with open("editors-picks-input.txt") as f: + for line in f: + m = re.match(r"^#\s*label:\s*(.+)", line.strip()) + if m: + label = m.group(1).strip() + break + + # Copy current picks to archive + with open("public/editors-picks.json") as f: + current = json.load(f) + with open(archive_path, "w") as f: + json.dump(current, f, indent=4) + + # Prepend to index so newest archived version appears first + with open("public/editors-picks-old/index.json") as f: + index = json.load(f) + index.insert(0, { + "file": filename, + "label": label, + "date": iso_date, + }) + with open("public/editors-picks-old/index.json", "w") as f: + json.dump(index, f, indent=4) + + print(f"Archived to {archive_path} with label '{label}'") + EOF + + - name: Generate new editors picks + if: steps.backoff.outputs.skip == 'false' + run: python3 gen-editors-picks.py + + - name: Commit and push + if: steps.backoff.outputs.skip == 'false' + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + git add public/editors-picks.json public/editors-picks-old/ + git diff --staged --quiet && echo "No changes to commit." && exit 0 + git commit -m "chore: update editors picks [skip ci]" + git push diff --git a/editors-picks-input.txt b/editors-picks-input.txt new file mode 100644 index 0000000..bdfb599 --- /dev/null +++ b/editors-picks-input.txt @@ -0,0 +1,17 @@ +# Editors Picks — album IDs (one per line) +# Optional: add a label for this set with "# label: ..." +# Lines starting with # are ignored. +# label: Spring 2026 + +324660713 +15427733 +464178301 +75115890 +410197513 +418729278 +504004321 +510893864 +325723583 +336178142 +106369871 +423471869 diff --git a/gen-editors-picks.py b/gen-editors-picks.py index eb3a8e4..2d05e72 100644 --- a/gen-editors-picks.py +++ b/gen-editors-picks.py @@ -2,22 +2,10 @@ import urllib.request import json +import re import sys -ALBUMS = [ - 324660713, - 15427733, - 464178301, - 75115890, - 410197513, - 418729278, - 504004321, - 510893864, - 325723583, - 336178142, - 106369871, - 423471869, -] +INPUT_FILE = "editors-picks-input.txt" TOKEN = "eyJraWQiOiJ2OU1GbFhqWSIsImFsZyI6IkVTMjU2In0.eyJ0eXBlIjoibzJfYWNjZXNzIiwic2NvcGUiOiIiLCJnVmVyIjowLCJzVmVyIjowLCJjaWQiOjEzNTU3LCJhdCI6IklOVEVSTkFMIiwiZXhwIjoxNzc1MTI4ODUzLCJpc3MiOiJodHRwczovL2F1dGgudGlkYWwuY29tL3YxIn0.qRoN8BRLM3R5WAXM3kS2hkWyaGk5tWF0FaHWJmkrWNvI48hKyS9lhVOTSnP1XkFEfdXv6aTzGUNUewyp-O_d3w" @@ -26,6 +14,19 @@ HEADERS = { "authorization": f"Bearer {TOKEN}", } +def read_album_ids(path): + ids = [] + with open(path) as f: + for line in f: + line = line.strip() + if not line or line.startswith("#"): + continue + try: + ids.append(int(line)) + except ValueError: + print(f"Skipping invalid ID: {line!r}", file=sys.stderr) + return ids + def fetch_album(album_id): url = f"https://api.tidal.com/v1/albums/{album_id}?countryCode=US" req = urllib.request.Request(url, headers=HEADERS) @@ -52,8 +53,10 @@ def transform_album(api_data): "mediaMetadata": api_data.get("mediaMetadata"), } +albums = read_album_ids(INPUT_FILE) + picks = [] -for album_id in ALBUMS: +for album_id in albums: data = fetch_album(album_id) if data: picks.append(transform_album(data)) From 72fdc3aadd6235086004ab7ea627ef392cad5425 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 3 Apr 2026 18:00:02 +0000 Subject: [PATCH 33/51] chore: update editors picks [skip ci] --- public/editors-picks-old/2026-4-3.json | 223 +++++++++++++++++++++++++ public/editors-picks-old/index.json | 7 +- public/editors-picks.json | 195 +-------------------- 3 files changed, 230 insertions(+), 195 deletions(-) create mode 100644 public/editors-picks-old/2026-4-3.json diff --git a/public/editors-picks-old/2026-4-3.json b/public/editors-picks-old/2026-4-3.json new file mode 100644 index 0000000..8ab398b --- /dev/null +++ b/public/editors-picks-old/2026-4-3.json @@ -0,0 +1,223 @@ +[ + { + "type": "album", + "id": 324660713, + "title": "JOECHILLWORLD", + "artist": { + "id": 40978758, + "name": "Devon Hendryx" + }, + "releaseDate": "2010-07-10", + "cover": "25d45544-3e82-4184-b8c2-2c2c6f0f152a", + "explicit": true, + "audioQuality": "LOSSLESS", + "mediaMetadata": { + "tags": [ + "LOSSLESS", + "HIRES_LOSSLESS" + ] + } + }, + { + "type": "album", + "id": 15427733, + "title": "Mysterious Phonk: The Chronicles of SpaceGhostPurrp", + "artist": { + "id": 4611745, + "name": "Spaceghostpurrp" + }, + "releaseDate": "2012-06-12", + "cover": "c78b7543-1cd8-4921-9155-e81d421353a0", + "explicit": true, + "audioQuality": "LOSSLESS", + "mediaMetadata": { + "tags": [ + "LOSSLESS" + ] + } + }, + { + "type": "album", + "id": 464178301, + "title": "Never Forget", + "artist": { + "id": 5516508, + "name": "Chris Travis" + }, + "releaseDate": "2014-05-14", + "cover": "4ab11f0d-0768-4cce-8de5-1894134d5994", + "explicit": true, + "audioQuality": "LOSSLESS", + "mediaMetadata": { + "tags": [ + "LOSSLESS" + ] + } + }, + { + "type": "album", + "id": 75115890, + "title": "Blood Shore Season 2", + "artist": { + "id": 6332342, + "name": "Xavier Wulf" + }, + "releaseDate": "2014-10-30", + "cover": "517303e5-d541-4704-b552-026427e05fcb", + "explicit": true, + "audioQuality": "LOSSLESS", + "mediaMetadata": { + "tags": [ + "LOSSLESS" + ] + } + }, + { + "type": "album", + "id": 410197513, + "title": "THE PEAK", + "artist": { + "id": 33481052, + "name": "smokedope2016" + }, + "releaseDate": "2025-01-17", + "cover": "ea18084d-36ec-4cea-98a7-fe4684246986", + "explicit": true, + "audioQuality": "LOSSLESS", + "mediaMetadata": { + "tags": [ + "LOSSLESS" + ] + } + }, + { + "type": "album", + "id": 418729278, + "title": "I LAY DOWN MY LIFE FOR YOU: DIRECTOR'S CUT", + "artist": { + "id": 7958797, + "name": "JPEGMAFIA" + }, + "releaseDate": "2025-02-03", + "cover": "9c84302b-2584-4c0a-9db7-e648542f459f", + "explicit": true, + "audioQuality": "LOSSLESS", + "mediaMetadata": { + "tags": [ + "LOSSLESS", + "HIRES_LOSSLESS" + ] + } + }, + { + "type": "album", + "id": 504004321, + "title": "Half Blood (BloodLuxe)", + "artist": { + "id": 50799233, + "name": "slayr" + }, + "releaseDate": "2025-11-05", + "cover": "2767cc63-7e92-4a48-aa4b-806a3ea7ec1c", + "explicit": true, + "audioQuality": "LOSSLESS", + "mediaMetadata": { + "tags": [ + "LOSSLESS", + "HIRES_LOSSLESS" + ] + } + }, + { + "type": "album", + "id": 510893864, + "title": "BULLY", + "artist": { + "id": 25022, + "name": "Kanye West" + }, + "releaseDate": "2026-03-28", + "cover": "cf2f2c9c-ff67-44f6-83aa-a7622f8c6b64", + "explicit": true, + "audioQuality": "LOSSLESS", + "mediaMetadata": { + "tags": [ + "LOSSLESS", + "HIRES_LOSSLESS" + ] + } + }, + { + "type": "album", + "id": 325723583, + "title": "Replica", + "artist": { + "id": 3715530, + "name": "Oneohtrix Point Never" + }, + "releaseDate": "2011-11-05", + "cover": "95ceeae9-cac7-42dc-ae37-7c93c223f809", + "explicit": false, + "audioQuality": "LOSSLESS", + "mediaMetadata": { + "tags": [ + "LOSSLESS" + ] + } + }, + { + "type": "album", + "id": 336178142, + "title": "Pirate This Album", + "artist": { + "id": 8622751, + "name": "Shamana" + }, + "releaseDate": "2023-12-25", + "cover": "a8a647be-0331-4779-9a6e-31645a9abdab", + "explicit": true, + "audioQuality": "LOSSLESS", + "mediaMetadata": { + "tags": [ + "LOSSLESS", + "HIRES_LOSSLESS" + ] + } + }, + { + "type": "album", + "id": 106369871, + "title": "Organic Thoughts from the Synthetic Mind", + "artist": { + "id": 6436013, + "name": "Shinjuku Mad" + }, + "releaseDate": "2009-07-01", + "cover": "3acc888e-35da-40a8-a4b7-7ffd00576cc9", + "explicit": true, + "audioQuality": "LOSSLESS", + "mediaMetadata": { + "tags": [ + "LOSSLESS" + ] + } + }, + { + "type": "album", + "id": 423471869, + "title": "pain", + "artist": { + "id": 44257324, + "name": "bleood" + }, + "releaseDate": "2025-03-11", + "cover": "711b23ba-c473-44e6-a2f0-010fefa9c5b8", + "explicit": true, + "audioQuality": "LOSSLESS", + "mediaMetadata": { + "tags": [ + "LOSSLESS" + ] + } + } +] \ No newline at end of file diff --git a/public/editors-picks-old/index.json b/public/editors-picks-old/index.json index 0d5f958..b981ba9 100644 --- a/public/editors-picks-old/index.json +++ b/public/editors-picks-old/index.json @@ -1,4 +1,9 @@ [ + { + "file": "2026-4-3.json", + "label": "Spring 2026", + "date": "2026-04-03" + }, { "file": "2026-3-20-before-april-fools.json", "label": "Before April Fools '26", @@ -9,4 +14,4 @@ "label": "April Fools '26", "date": "2026-04-01" } -] +] \ No newline at end of file diff --git a/public/editors-picks.json b/public/editors-picks.json index 92c9aa8..0637a08 100644 --- a/public/editors-picks.json +++ b/public/editors-picks.json @@ -1,194 +1 @@ -[ - { - "type": "album", - "id": 324660713, - "title": "JOECHILLWORLD", - "artist": { - "id": 40978758, - "name": "Devon Hendryx" - }, - "releaseDate": "2010-07-10", - "cover": "25d45544-3e82-4184-b8c2-2c2c6f0f152a", - "explicit": true, - "audioQuality": "LOSSLESS", - "mediaMetadata": { - "tags": ["LOSSLESS", "HIRES_LOSSLESS"] - } - }, - { - "type": "album", - "id": 15427733, - "title": "Mysterious Phonk: The Chronicles of SpaceGhostPurrp", - "artist": { - "id": 4611745, - "name": "Spaceghostpurrp" - }, - "releaseDate": "2012-06-12", - "cover": "c78b7543-1cd8-4921-9155-e81d421353a0", - "explicit": true, - "audioQuality": "LOSSLESS", - "mediaMetadata": { - "tags": ["LOSSLESS"] - } - }, - { - "type": "album", - "id": 464178301, - "title": "Never Forget", - "artist": { - "id": 5516508, - "name": "Chris Travis" - }, - "releaseDate": "2014-05-14", - "cover": "4ab11f0d-0768-4cce-8de5-1894134d5994", - "explicit": true, - "audioQuality": "LOSSLESS", - "mediaMetadata": { - "tags": ["LOSSLESS"] - } - }, - { - "type": "album", - "id": 75115890, - "title": "Blood Shore Season 2", - "artist": { - "id": 6332342, - "name": "Xavier Wulf" - }, - "releaseDate": "2014-10-30", - "cover": "517303e5-d541-4704-b552-026427e05fcb", - "explicit": true, - "audioQuality": "LOSSLESS", - "mediaMetadata": { - "tags": ["LOSSLESS"] - } - }, - { - "type": "album", - "id": 410197513, - "title": "THE PEAK", - "artist": { - "id": 33481052, - "name": "smokedope2016" - }, - "releaseDate": "2025-01-17", - "cover": "ea18084d-36ec-4cea-98a7-fe4684246986", - "explicit": true, - "audioQuality": "LOSSLESS", - "mediaMetadata": { - "tags": ["LOSSLESS"] - } - }, - { - "type": "album", - "id": 418729278, - "title": "I LAY DOWN MY LIFE FOR YOU: DIRECTOR'S CUT", - "artist": { - "id": 7958797, - "name": "JPEGMAFIA" - }, - "releaseDate": "2025-02-03", - "cover": "9c84302b-2584-4c0a-9db7-e648542f459f", - "explicit": true, - "audioQuality": "LOSSLESS", - "mediaMetadata": { - "tags": ["LOSSLESS", "HIRES_LOSSLESS"] - } - }, - { - "type": "album", - "id": 504004321, - "title": "Half Blood (BloodLuxe)", - "artist": { - "id": 50799233, - "name": "slayr" - }, - "releaseDate": "2025-11-05", - "cover": "2767cc63-7e92-4a48-aa4b-806a3ea7ec1c", - "explicit": true, - "audioQuality": "LOSSLESS", - "mediaMetadata": { - "tags": ["LOSSLESS", "HIRES_LOSSLESS"] - } - }, - { - "type": "album", - "id": 510893864, - "title": "BULLY", - "artist": { - "id": 25022, - "name": "Kanye West" - }, - "releaseDate": "2026-03-28", - "cover": "cf2f2c9c-ff67-44f6-83aa-a7622f8c6b64", - "explicit": true, - "audioQuality": "LOSSLESS", - "mediaMetadata": { - "tags": ["LOSSLESS", "HIRES_LOSSLESS"] - } - }, - { - "type": "album", - "id": 325723583, - "title": "Replica", - "artist": { - "id": 3715530, - "name": "Oneohtrix Point Never" - }, - "releaseDate": "2011-11-05", - "cover": "95ceeae9-cac7-42dc-ae37-7c93c223f809", - "explicit": false, - "audioQuality": "LOSSLESS", - "mediaMetadata": { - "tags": ["LOSSLESS"] - } - }, - { - "type": "album", - "id": 336178142, - "title": "Pirate This Album", - "artist": { - "id": 8622751, - "name": "Shamana" - }, - "releaseDate": "2023-12-25", - "cover": "a8a647be-0331-4779-9a6e-31645a9abdab", - "explicit": true, - "audioQuality": "LOSSLESS", - "mediaMetadata": { - "tags": ["LOSSLESS", "HIRES_LOSSLESS"] - } - }, - { - "type": "album", - "id": 106369871, - "title": "Organic Thoughts from the Synthetic Mind", - "artist": { - "id": 6436013, - "name": "Shinjuku Mad" - }, - "releaseDate": "2009-07-01", - "cover": "3acc888e-35da-40a8-a4b7-7ffd00576cc9", - "explicit": true, - "audioQuality": "LOSSLESS", - "mediaMetadata": { - "tags": ["LOSSLESS"] - } - }, - { - "type": "album", - "id": 423471869, - "title": "pain", - "artist": { - "id": 44257324, - "name": "bleood" - }, - "releaseDate": "2025-03-11", - "cover": "711b23ba-c473-44e6-a2f0-010fefa9c5b8", - "explicit": true, - "audioQuality": "LOSSLESS", - "mediaMetadata": { - "tags": ["LOSSLESS"] - } - } -] +[] \ No newline at end of file From c23ad0b43730649accfa71c5a4fed4d284bd0a7c Mon Sep 17 00:00:00 2001 From: edideaur <182119792+edideaur@users.noreply.github.com> Date: Fri, 3 Apr 2026 18:00:32 +0000 Subject: [PATCH 34/51] style: auto-fix linting issues --- public/editors-picks-old/2026-4-3.json | 55 ++++++-------------------- public/editors-picks-old/index.json | 2 +- public/editors-picks.json | 2 +- 3 files changed, 15 insertions(+), 44 deletions(-) diff --git a/public/editors-picks-old/2026-4-3.json b/public/editors-picks-old/2026-4-3.json index 8ab398b..92c9aa8 100644 --- a/public/editors-picks-old/2026-4-3.json +++ b/public/editors-picks-old/2026-4-3.json @@ -12,10 +12,7 @@ "explicit": true, "audioQuality": "LOSSLESS", "mediaMetadata": { - "tags": [ - "LOSSLESS", - "HIRES_LOSSLESS" - ] + "tags": ["LOSSLESS", "HIRES_LOSSLESS"] } }, { @@ -31,9 +28,7 @@ "explicit": true, "audioQuality": "LOSSLESS", "mediaMetadata": { - "tags": [ - "LOSSLESS" - ] + "tags": ["LOSSLESS"] } }, { @@ -49,9 +44,7 @@ "explicit": true, "audioQuality": "LOSSLESS", "mediaMetadata": { - "tags": [ - "LOSSLESS" - ] + "tags": ["LOSSLESS"] } }, { @@ -67,9 +60,7 @@ "explicit": true, "audioQuality": "LOSSLESS", "mediaMetadata": { - "tags": [ - "LOSSLESS" - ] + "tags": ["LOSSLESS"] } }, { @@ -85,9 +76,7 @@ "explicit": true, "audioQuality": "LOSSLESS", "mediaMetadata": { - "tags": [ - "LOSSLESS" - ] + "tags": ["LOSSLESS"] } }, { @@ -103,10 +92,7 @@ "explicit": true, "audioQuality": "LOSSLESS", "mediaMetadata": { - "tags": [ - "LOSSLESS", - "HIRES_LOSSLESS" - ] + "tags": ["LOSSLESS", "HIRES_LOSSLESS"] } }, { @@ -122,10 +108,7 @@ "explicit": true, "audioQuality": "LOSSLESS", "mediaMetadata": { - "tags": [ - "LOSSLESS", - "HIRES_LOSSLESS" - ] + "tags": ["LOSSLESS", "HIRES_LOSSLESS"] } }, { @@ -141,10 +124,7 @@ "explicit": true, "audioQuality": "LOSSLESS", "mediaMetadata": { - "tags": [ - "LOSSLESS", - "HIRES_LOSSLESS" - ] + "tags": ["LOSSLESS", "HIRES_LOSSLESS"] } }, { @@ -160,9 +140,7 @@ "explicit": false, "audioQuality": "LOSSLESS", "mediaMetadata": { - "tags": [ - "LOSSLESS" - ] + "tags": ["LOSSLESS"] } }, { @@ -178,10 +156,7 @@ "explicit": true, "audioQuality": "LOSSLESS", "mediaMetadata": { - "tags": [ - "LOSSLESS", - "HIRES_LOSSLESS" - ] + "tags": ["LOSSLESS", "HIRES_LOSSLESS"] } }, { @@ -197,9 +172,7 @@ "explicit": true, "audioQuality": "LOSSLESS", "mediaMetadata": { - "tags": [ - "LOSSLESS" - ] + "tags": ["LOSSLESS"] } }, { @@ -215,9 +188,7 @@ "explicit": true, "audioQuality": "LOSSLESS", "mediaMetadata": { - "tags": [ - "LOSSLESS" - ] + "tags": ["LOSSLESS"] } } -] \ No newline at end of file +] diff --git a/public/editors-picks-old/index.json b/public/editors-picks-old/index.json index b981ba9..95a6c0e 100644 --- a/public/editors-picks-old/index.json +++ b/public/editors-picks-old/index.json @@ -14,4 +14,4 @@ "label": "April Fools '26", "date": "2026-04-01" } -] \ No newline at end of file +] diff --git a/public/editors-picks.json b/public/editors-picks.json index 0637a08..fe51488 100644 --- a/public/editors-picks.json +++ b/public/editors-picks.json @@ -1 +1 @@ -[] \ No newline at end of file +[] From 1cd565f0e42d63d33c0ca7fc40d25e3f5010058f Mon Sep 17 00:00:00 2001 From: edideaur Date: Fri, 3 Apr 2026 18:05:14 +0000 Subject: [PATCH 35/51] test --- editors-picks-input.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/editors-picks-input.txt b/editors-picks-input.txt index bdfb599..cddd96b 100644 --- a/editors-picks-input.txt +++ b/editors-picks-input.txt @@ -15,3 +15,5 @@ 336178142 106369871 423471869 +250986538 +509761344 \ No newline at end of file From 0ccf6416997d5fc97200f4b40640ea524ace6e3a Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 3 Apr 2026 18:05:29 +0000 Subject: [PATCH 36/51] chore: update editors picks [skip ci] --- public/editors-picks-old/2026-4-3.json | 195 +------------------------ public/editors-picks-old/index.json | 7 +- public/editors-picks.json | 2 +- 3 files changed, 8 insertions(+), 196 deletions(-) diff --git a/public/editors-picks-old/2026-4-3.json b/public/editors-picks-old/2026-4-3.json index 92c9aa8..0637a08 100644 --- a/public/editors-picks-old/2026-4-3.json +++ b/public/editors-picks-old/2026-4-3.json @@ -1,194 +1 @@ -[ - { - "type": "album", - "id": 324660713, - "title": "JOECHILLWORLD", - "artist": { - "id": 40978758, - "name": "Devon Hendryx" - }, - "releaseDate": "2010-07-10", - "cover": "25d45544-3e82-4184-b8c2-2c2c6f0f152a", - "explicit": true, - "audioQuality": "LOSSLESS", - "mediaMetadata": { - "tags": ["LOSSLESS", "HIRES_LOSSLESS"] - } - }, - { - "type": "album", - "id": 15427733, - "title": "Mysterious Phonk: The Chronicles of SpaceGhostPurrp", - "artist": { - "id": 4611745, - "name": "Spaceghostpurrp" - }, - "releaseDate": "2012-06-12", - "cover": "c78b7543-1cd8-4921-9155-e81d421353a0", - "explicit": true, - "audioQuality": "LOSSLESS", - "mediaMetadata": { - "tags": ["LOSSLESS"] - } - }, - { - "type": "album", - "id": 464178301, - "title": "Never Forget", - "artist": { - "id": 5516508, - "name": "Chris Travis" - }, - "releaseDate": "2014-05-14", - "cover": "4ab11f0d-0768-4cce-8de5-1894134d5994", - "explicit": true, - "audioQuality": "LOSSLESS", - "mediaMetadata": { - "tags": ["LOSSLESS"] - } - }, - { - "type": "album", - "id": 75115890, - "title": "Blood Shore Season 2", - "artist": { - "id": 6332342, - "name": "Xavier Wulf" - }, - "releaseDate": "2014-10-30", - "cover": "517303e5-d541-4704-b552-026427e05fcb", - "explicit": true, - "audioQuality": "LOSSLESS", - "mediaMetadata": { - "tags": ["LOSSLESS"] - } - }, - { - "type": "album", - "id": 410197513, - "title": "THE PEAK", - "artist": { - "id": 33481052, - "name": "smokedope2016" - }, - "releaseDate": "2025-01-17", - "cover": "ea18084d-36ec-4cea-98a7-fe4684246986", - "explicit": true, - "audioQuality": "LOSSLESS", - "mediaMetadata": { - "tags": ["LOSSLESS"] - } - }, - { - "type": "album", - "id": 418729278, - "title": "I LAY DOWN MY LIFE FOR YOU: DIRECTOR'S CUT", - "artist": { - "id": 7958797, - "name": "JPEGMAFIA" - }, - "releaseDate": "2025-02-03", - "cover": "9c84302b-2584-4c0a-9db7-e648542f459f", - "explicit": true, - "audioQuality": "LOSSLESS", - "mediaMetadata": { - "tags": ["LOSSLESS", "HIRES_LOSSLESS"] - } - }, - { - "type": "album", - "id": 504004321, - "title": "Half Blood (BloodLuxe)", - "artist": { - "id": 50799233, - "name": "slayr" - }, - "releaseDate": "2025-11-05", - "cover": "2767cc63-7e92-4a48-aa4b-806a3ea7ec1c", - "explicit": true, - "audioQuality": "LOSSLESS", - "mediaMetadata": { - "tags": ["LOSSLESS", "HIRES_LOSSLESS"] - } - }, - { - "type": "album", - "id": 510893864, - "title": "BULLY", - "artist": { - "id": 25022, - "name": "Kanye West" - }, - "releaseDate": "2026-03-28", - "cover": "cf2f2c9c-ff67-44f6-83aa-a7622f8c6b64", - "explicit": true, - "audioQuality": "LOSSLESS", - "mediaMetadata": { - "tags": ["LOSSLESS", "HIRES_LOSSLESS"] - } - }, - { - "type": "album", - "id": 325723583, - "title": "Replica", - "artist": { - "id": 3715530, - "name": "Oneohtrix Point Never" - }, - "releaseDate": "2011-11-05", - "cover": "95ceeae9-cac7-42dc-ae37-7c93c223f809", - "explicit": false, - "audioQuality": "LOSSLESS", - "mediaMetadata": { - "tags": ["LOSSLESS"] - } - }, - { - "type": "album", - "id": 336178142, - "title": "Pirate This Album", - "artist": { - "id": 8622751, - "name": "Shamana" - }, - "releaseDate": "2023-12-25", - "cover": "a8a647be-0331-4779-9a6e-31645a9abdab", - "explicit": true, - "audioQuality": "LOSSLESS", - "mediaMetadata": { - "tags": ["LOSSLESS", "HIRES_LOSSLESS"] - } - }, - { - "type": "album", - "id": 106369871, - "title": "Organic Thoughts from the Synthetic Mind", - "artist": { - "id": 6436013, - "name": "Shinjuku Mad" - }, - "releaseDate": "2009-07-01", - "cover": "3acc888e-35da-40a8-a4b7-7ffd00576cc9", - "explicit": true, - "audioQuality": "LOSSLESS", - "mediaMetadata": { - "tags": ["LOSSLESS"] - } - }, - { - "type": "album", - "id": 423471869, - "title": "pain", - "artist": { - "id": 44257324, - "name": "bleood" - }, - "releaseDate": "2025-03-11", - "cover": "711b23ba-c473-44e6-a2f0-010fefa9c5b8", - "explicit": true, - "audioQuality": "LOSSLESS", - "mediaMetadata": { - "tags": ["LOSSLESS"] - } - } -] +[] \ No newline at end of file diff --git a/public/editors-picks-old/index.json b/public/editors-picks-old/index.json index 95a6c0e..851de19 100644 --- a/public/editors-picks-old/index.json +++ b/public/editors-picks-old/index.json @@ -1,4 +1,9 @@ [ + { + "file": "2026-4-3.json", + "label": "Spring 2026", + "date": "2026-04-03" + }, { "file": "2026-4-3.json", "label": "Spring 2026", @@ -14,4 +19,4 @@ "label": "April Fools '26", "date": "2026-04-01" } -] +] \ No newline at end of file diff --git a/public/editors-picks.json b/public/editors-picks.json index fe51488..0637a08 100644 --- a/public/editors-picks.json +++ b/public/editors-picks.json @@ -1 +1 @@ -[] +[] \ No newline at end of file From 4409be36748f2e4591f50a52b9a1ce807995ef5d Mon Sep 17 00:00:00 2001 From: edideaur Date: Fri, 3 Apr 2026 18:11:44 +0000 Subject: [PATCH 37/51] remove skip ci --- .github/workflows/editors-picks.yml | 2 +- editors-picks-input.txt | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/editors-picks.yml b/.github/workflows/editors-picks.yml index fa294f0..3745ac8 100644 --- a/.github/workflows/editors-picks.yml +++ b/.github/workflows/editors-picks.yml @@ -91,5 +91,5 @@ jobs: git config user.email "github-actions[bot]@users.noreply.github.com" git add public/editors-picks.json public/editors-picks-old/ git diff --staged --quiet && echo "No changes to commit." && exit 0 - git commit -m "chore: update editors picks [skip ci]" + git commit -m "chore: update editors picks" git push diff --git a/editors-picks-input.txt b/editors-picks-input.txt index cddd96b..0070638 100644 --- a/editors-picks-input.txt +++ b/editors-picks-input.txt @@ -16,4 +16,5 @@ 106369871 423471869 250986538 -509761344 \ No newline at end of file +509761344 +15621057 \ No newline at end of file From 8479ed0ea3b38ce3174d2384f01d649fda4de8c5 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 3 Apr 2026 18:12:04 +0000 Subject: [PATCH 38/51] chore: update editors picks --- public/editors-picks-old/index.json | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/public/editors-picks-old/index.json b/public/editors-picks-old/index.json index 851de19..1d0ccd8 100644 --- a/public/editors-picks-old/index.json +++ b/public/editors-picks-old/index.json @@ -9,6 +9,11 @@ "label": "Spring 2026", "date": "2026-04-03" }, + { + "file": "2026-4-3.json", + "label": "Spring 2026", + "date": "2026-04-03" + }, { "file": "2026-3-20-before-april-fools.json", "label": "Before April Fools '26", From 1bdecc293f31932541d990adb8efa9db373d1160 Mon Sep 17 00:00:00 2001 From: edideaur Date: Fri, 3 Apr 2026 18:21:10 +0000 Subject: [PATCH 39/51] what am i doing bro --- editors-picks-input.txt | 39 ++++--- gen-editors-picks.py | 236 +++++++++++++++++++++++++++++++++------- 2 files changed, 218 insertions(+), 57 deletions(-) diff --git a/editors-picks-input.txt b/editors-picks-input.txt index 0070638..6d8a390 100644 --- a/editors-picks-input.txt +++ b/editors-picks-input.txt @@ -1,20 +1,23 @@ -# Editors Picks — album IDs (one per line) -# Optional: add a label for this set with "# label: ..." -# Lines starting with # are ignored. +# Editors Picks +# One item per line. Format: type:id +# Supported types: album, artist, track, playlist, userplaylist, podcast +# Bare numbers (no prefix) are treated as albums for backwards compatibility. +# Optional: set a label for this archived set with "# label: ..." # label: Spring 2026 -324660713 -15427733 -464178301 -75115890 -410197513 -418729278 -504004321 -510893864 -325723583 -336178142 -106369871 -423471869 -250986538 -509761344 -15621057 \ No newline at end of file +album:324660713 +album:15427733 +album:464178301 +album:75115890 +album:410197513 +album:418729278 +album:504004321 +album:510893864 +album:325723583 +album:336178142 +album:106369871 +album:423471869 +album:250986538 +album:509761344 +album:15621057 +album:103897783 \ No newline at end of file diff --git a/gen-editors-picks.py b/gen-editors-picks.py index 2d05e72..95e102e 100644 --- a/gen-editors-picks.py +++ b/gen-editors-picks.py @@ -1,67 +1,225 @@ #!/usr/bin/env python3 import urllib.request +import urllib.parse import json import re import sys +import hashlib +import time INPUT_FILE = "editors-picks-input.txt" +COUNTRY = "US" -TOKEN = "eyJraWQiOiJ2OU1GbFhqWSIsImFsZyI6IkVTMjU2In0.eyJ0eXBlIjoibzJfYWNjZXNzIiwic2NvcGUiOiIiLCJnVmVyIjowLCJzVmVyIjowLCJjaWQiOjEzNTU3LCJhdCI6IklOVEVSTkFMIiwiZXhwIjoxNzc1MTI4ODUzLCJpc3MiOiJodHRwczovL2F1dGgudGlkYWwuY29tL3YxIn0.qRoN8BRLM3R5WAXM3kS2hkWyaGk5tWF0FaHWJmkrWNvI48hKyS9lhVOTSnP1XkFEfdXv6aTzGUNUewyp-O_d3w" +# Tidal internal token — replace when expired +TIDAL_TOKEN = "eyJraWQiOiJ2OU1GbFhqWSIsImFsZyI6IkVTMjU2In0.eyJ0eXBlIjoibzJfYWNjZXNzIiwic2NvcGUiOiIiLCJnVmVyIjowLCJzVmVyIjowLCJjaWQiOjEzNTU3LCJhdCI6IklOVEVSTkFMIiwiZXhwIjoxNzc1MjQ2NzQ2LCJpc3MiOiJodHRwczovL2F1dGgudGlkYWwuY29tL3YxIn0.ksUE4yhQ39IG7oHWk8DyJ91dwIoDVWGzvTAnpeDJ5p-_Gp0F_yO858xDO11AINBaahQCq0jlbqWqIaTqCTOjqg" -HEADERS = { +TIDAL_HEADERS = { "accept": "*/*", - "authorization": f"Bearer {TOKEN}", + "authorization": f"Bearer {TIDAL_TOKEN}", } -def read_album_ids(path): - ids = [] +# PodcastIndex credentials +PODCAST_API_KEY = "YU5HMSDYBQQVYDF6QN4P" +PODCAST_API_SECRET = "8hCvpjSL7T$S7^5ftnf5MhqQwYUYVjM^fmUL3Ld$" +PODCASTINDEX_BASE = "https://api.podcastindex.org/api/1.0" + + +# ── Tidal helpers ───────────────────────────────────────────────────────────── + +def tidal_get(path, params=None): + if params is None: + params = {} + params.setdefault("countryCode", COUNTRY) + url = f"https://api.tidal.com/v1/{path}?{urllib.parse.urlencode(params)}" + req = urllib.request.Request(url, headers=TIDAL_HEADERS) + try: + with urllib.request.urlopen(req) as resp: + return json.loads(resp.read().decode()) + except Exception as e: + print(f"Error fetching {url}: {e}", file=sys.stderr) + return None + + +def fetch_album(album_id): + return tidal_get(f"albums/{album_id}") + + +def fetch_artist(artist_id): + return tidal_get(f"artists/{artist_id}") + + +def fetch_track(track_id): + return tidal_get(f"tracks/{track_id}") + + +def fetch_playlist(uuid): + return tidal_get(f"playlists/{uuid}") + + +# ── PodcastIndex helper ─────────────────────────────────────────────────────── + +def podcast_get(endpoint): + api_time = str(int(time.time())) + raw = PODCAST_API_KEY + PODCAST_API_SECRET + api_time + auth_hash = hashlib.sha1(raw.encode()).hexdigest() + headers = { + "User-Agent": "MonochromeMusic/1.0", + "X-Auth-Key": PODCAST_API_KEY, + "X-Auth-Date": api_time, + "Authorization": auth_hash, + } + url = f"{PODCASTINDEX_BASE}{endpoint}" + req = urllib.request.Request(url, headers=headers) + try: + with urllib.request.urlopen(req) as resp: + return json.loads(resp.read().decode()) + except Exception as e: + print(f"Error fetching {url}: {e}", file=sys.stderr) + return None + + +def fetch_podcast(feed_id): + return podcast_get(f"/podcasts/byfeedid?id={feed_id}&pretty") + + +# ── Transformers ────────────────────────────────────────────────────────────── + +def transform_album(d): + return { + "type": "album", + "id": d.get("id"), + "title": d.get("title"), + "artist": { + "id": d.get("artist", {}).get("id"), + "name": d.get("artist", {}).get("name"), + }, + "releaseDate": d.get("releaseDate"), + "cover": d.get("cover"), + "explicit": d.get("explicit"), + "audioQuality": d.get("audioQuality"), + "mediaMetadata": d.get("mediaMetadata"), + } + + +def transform_artist(d): + return { + "type": "artist", + "id": d.get("id"), + "name": d.get("name"), + "picture": d.get("picture"), + } + + +def transform_track(d): + album = d.get("album") or {} + return { + "type": "track", + "id": d.get("id"), + "title": d.get("title"), + "artist": { + "id": d.get("artist", {}).get("id"), + "name": d.get("artist", {}).get("name"), + }, + "album": { + "id": album.get("id"), + "title": album.get("title"), + "cover": album.get("cover"), + }, + "duration": d.get("duration"), + "explicit": d.get("explicit"), + "audioQuality": d.get("audioQuality"), + "mediaMetadata": d.get("mediaMetadata"), + } + + +def transform_playlist(d): + # Tidal editorial playlist → rendered as album card with playlist href + cover = d.get("squareImage") or d.get("image") or d.get("cover") + return { + "type": "playlist", + "id": d.get("uuid"), + "title": d.get("title"), + "cover": cover, + "numberOfTracks": d.get("numberOfTracks", 0), + } + + +def transform_userplaylist(d): + # User playlist → rendered with createUserPlaylistCardHTML + cover = d.get("squareImage") or d.get("image") or d.get("cover") + creator = d.get("creator") or {} + return { + "type": "user-playlist", + "id": d.get("uuid"), + "name": d.get("title"), + "cover": cover, + "numberOfTracks": d.get("numberOfTracks", 0), + "username": creator.get("name"), + } + + +def transform_podcast(d): + feed = d.get("feed") or {} + return { + "type": "podcast", + "id": str(feed.get("id", "")), + "title": feed.get("title"), + "author": feed.get("author") or feed.get("ownerName"), + "image": feed.get("image") or feed.get("artwork"), + "episodeCount": feed.get("episodeCount", 0), + } + + +# ── Input parser ────────────────────────────────────────────────────────────── + +def read_items(path): + """ + Parses editors-picks-input.txt. + Each non-comment line is either: + - a bare number → album: (backwards-compatible) + - type:value → e.g. artist:123, track:456, playlist:uuid, podcast:789 + Supported types: album, artist, track, playlist, userplaylist, podcast + """ + items = [] with open(path) as f: for line in f: line = line.strip() if not line or line.startswith("#"): continue - try: - ids.append(int(line)) - except ValueError: - print(f"Skipping invalid ID: {line!r}", file=sys.stderr) - return ids + if ":" in line: + item_type, _, value = line.partition(":") + items.append((item_type.strip().lower(), value.strip())) + else: + # bare number → album + items.append(("album", line)) + return items -def fetch_album(album_id): - url = f"https://api.tidal.com/v1/albums/{album_id}?countryCode=US" - req = urllib.request.Request(url, headers=HEADERS) - try: - with urllib.request.urlopen(req) as resp: - return json.loads(resp.read().decode()) - except Exception as e: - print(f"Error fetching {album_id}: {e}", file=sys.stderr) - return None -def transform_album(api_data): - return { - "type": "album", - "id": api_data.get("id"), - "title": api_data.get("title"), - "artist": { - "id": api_data.get("artist", {}).get("id"), - "name": api_data.get("artist", {}).get("name"), - }, - "releaseDate": api_data.get("releaseDate"), - "cover": api_data.get("cover"), - "explicit": api_data.get("explicit"), - "audioQuality": api_data.get("audioQuality"), - "mediaMetadata": api_data.get("mediaMetadata"), - } +# ── Main ────────────────────────────────────────────────────────────────────── -albums = read_album_ids(INPUT_FILE) +FETCHERS = { + "album": (fetch_album, transform_album), + "artist": (fetch_artist, transform_artist), + "track": (fetch_track, transform_track), + "playlist": (fetch_playlist, transform_playlist), + "userplaylist":(fetch_playlist, transform_userplaylist), + "podcast": (fetch_podcast, transform_podcast), +} +items = read_items(INPUT_FILE) picks = [] -for album_id in albums: - data = fetch_album(album_id) + +for item_type, item_id in items: + if item_type not in FETCHERS: + print(f"Unknown type '{item_type}' for id {item_id!r} — skipping", file=sys.stderr) + continue + fetch_fn, transform_fn = FETCHERS[item_type] + data = fetch_fn(item_id) if data: - picks.append(transform_album(data)) + picks.append(transform_fn(data)) with open("public/editors-picks.json", "w") as f: json.dump(picks, f, indent=4) -print(f"Written {len(picks)} albums to public/editors-picks.json") +print(f"Written {len(picks)} items to public/editors-picks.json") From a1b9c13d60c75a8f9f3b00b736a31c56c825a0f5 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 3 Apr 2026 18:21:36 +0000 Subject: [PATCH 40/51] chore: update editors picks --- public/editors-picks-old/index.json | 5 + public/editors-picks.json | 296 +++++++++++++++++++++++++++- 2 files changed, 300 insertions(+), 1 deletion(-) diff --git a/public/editors-picks-old/index.json b/public/editors-picks-old/index.json index 1d0ccd8..e0ef165 100644 --- a/public/editors-picks-old/index.json +++ b/public/editors-picks-old/index.json @@ -14,6 +14,11 @@ "label": "Spring 2026", "date": "2026-04-03" }, + { + "file": "2026-4-3.json", + "label": "Spring 2026", + "date": "2026-04-03" + }, { "file": "2026-3-20-before-april-fools.json", "label": "Before April Fools '26", diff --git a/public/editors-picks.json b/public/editors-picks.json index 0637a08..8d5ec20 100644 --- a/public/editors-picks.json +++ b/public/editors-picks.json @@ -1 +1,295 @@ -[] \ No newline at end of file +[ + { + "type": "album", + "id": 324660713, + "title": "JOECHILLWORLD", + "artist": { + "id": 40978758, + "name": "Devon Hendryx" + }, + "releaseDate": "2010-07-10", + "cover": "25d45544-3e82-4184-b8c2-2c2c6f0f152a", + "explicit": true, + "audioQuality": "LOSSLESS", + "mediaMetadata": { + "tags": [ + "LOSSLESS", + "HIRES_LOSSLESS" + ] + } + }, + { + "type": "album", + "id": 15427733, + "title": "Mysterious Phonk: The Chronicles of SpaceGhostPurrp", + "artist": { + "id": 4611745, + "name": "Spaceghostpurrp" + }, + "releaseDate": "2012-06-12", + "cover": "c78b7543-1cd8-4921-9155-e81d421353a0", + "explicit": true, + "audioQuality": "LOSSLESS", + "mediaMetadata": { + "tags": [ + "LOSSLESS" + ] + } + }, + { + "type": "album", + "id": 464178301, + "title": "Never Forget", + "artist": { + "id": 5516508, + "name": "Chris Travis" + }, + "releaseDate": "2014-05-14", + "cover": "4ab11f0d-0768-4cce-8de5-1894134d5994", + "explicit": true, + "audioQuality": "LOSSLESS", + "mediaMetadata": { + "tags": [ + "LOSSLESS" + ] + } + }, + { + "type": "album", + "id": 75115890, + "title": "Blood Shore Season 2", + "artist": { + "id": 6332342, + "name": "Xavier Wulf" + }, + "releaseDate": "2014-10-30", + "cover": "517303e5-d541-4704-b552-026427e05fcb", + "explicit": true, + "audioQuality": "LOSSLESS", + "mediaMetadata": { + "tags": [ + "LOSSLESS" + ] + } + }, + { + "type": "album", + "id": 410197513, + "title": "THE PEAK", + "artist": { + "id": 33481052, + "name": "smokedope2016" + }, + "releaseDate": "2025-01-17", + "cover": "ea18084d-36ec-4cea-98a7-fe4684246986", + "explicit": true, + "audioQuality": "LOSSLESS", + "mediaMetadata": { + "tags": [ + "LOSSLESS" + ] + } + }, + { + "type": "album", + "id": 418729278, + "title": "I LAY DOWN MY LIFE FOR YOU: DIRECTOR'S CUT", + "artist": { + "id": 7958797, + "name": "JPEGMAFIA" + }, + "releaseDate": "2025-02-03", + "cover": "9c84302b-2584-4c0a-9db7-e648542f459f", + "explicit": true, + "audioQuality": "LOSSLESS", + "mediaMetadata": { + "tags": [ + "LOSSLESS", + "HIRES_LOSSLESS" + ] + } + }, + { + "type": "album", + "id": 504004321, + "title": "Half Blood (BloodLuxe)", + "artist": { + "id": 50799233, + "name": "slayr" + }, + "releaseDate": "2025-11-05", + "cover": "2767cc63-7e92-4a48-aa4b-806a3ea7ec1c", + "explicit": true, + "audioQuality": "LOSSLESS", + "mediaMetadata": { + "tags": [ + "LOSSLESS", + "HIRES_LOSSLESS" + ] + } + }, + { + "type": "album", + "id": 510893864, + "title": "BULLY", + "artist": { + "id": 25022, + "name": "Kanye West" + }, + "releaseDate": "2026-03-28", + "cover": "cf2f2c9c-ff67-44f6-83aa-a7622f8c6b64", + "explicit": true, + "audioQuality": "LOSSLESS", + "mediaMetadata": { + "tags": [ + "LOSSLESS", + "HIRES_LOSSLESS" + ] + } + }, + { + "type": "album", + "id": 325723583, + "title": "Replica", + "artist": { + "id": 3715530, + "name": "Oneohtrix Point Never" + }, + "releaseDate": "2011-11-05", + "cover": "95ceeae9-cac7-42dc-ae37-7c93c223f809", + "explicit": false, + "audioQuality": "LOSSLESS", + "mediaMetadata": { + "tags": [ + "LOSSLESS" + ] + } + }, + { + "type": "album", + "id": 336178142, + "title": "Pirate This Album", + "artist": { + "id": 8622751, + "name": "Shamana" + }, + "releaseDate": "2023-12-25", + "cover": "a8a647be-0331-4779-9a6e-31645a9abdab", + "explicit": true, + "audioQuality": "LOSSLESS", + "mediaMetadata": { + "tags": [ + "LOSSLESS", + "HIRES_LOSSLESS" + ] + } + }, + { + "type": "album", + "id": 106369871, + "title": "Organic Thoughts from the Synthetic Mind", + "artist": { + "id": 6436013, + "name": "Shinjuku Mad" + }, + "releaseDate": "2009-07-01", + "cover": "3acc888e-35da-40a8-a4b7-7ffd00576cc9", + "explicit": true, + "audioQuality": "LOSSLESS", + "mediaMetadata": { + "tags": [ + "LOSSLESS" + ] + } + }, + { + "type": "album", + "id": 423471869, + "title": "pain", + "artist": { + "id": 44257324, + "name": "bleood" + }, + "releaseDate": "2025-03-11", + "cover": "711b23ba-c473-44e6-a2f0-010fefa9c5b8", + "explicit": true, + "audioQuality": "LOSSLESS", + "mediaMetadata": { + "tags": [ + "LOSSLESS" + ] + } + }, + { + "type": "album", + "id": 250986538, + "title": "Revolutionary, Vol. 1 (Bonus Edition)", + "artist": { + "id": 3604583, + "name": "Immortal Technique" + }, + "releaseDate": "2001-09-14", + "cover": "e510dd6d-dcdf-4272-9c68-f4580f2fbd14", + "explicit": true, + "audioQuality": "LOSSLESS", + "mediaMetadata": { + "tags": [ + "LOSSLESS" + ] + } + }, + { + "type": "album", + "id": 509761344, + "title": "EMOTIONS", + "artist": { + "id": 49124576, + "name": "Nine Vicious" + }, + "releaseDate": "2026-04-03", + "cover": "f29b18d3-b19f-45b1-968a-0ad360647130", + "explicit": true, + "audioQuality": "LOSSLESS", + "mediaMetadata": { + "tags": [ + "LOSSLESS" + ] + } + }, + { + "type": "album", + "id": 15621057, + "title": "Triple F Life: Friends, Fans & Family (Deluxe Version)", + "artist": { + "id": 3654061, + "name": "Waka Flocka Flame" + }, + "releaseDate": "2012-06-12", + "cover": "3199b7de-5e3d-486c-acf1-870ff4c60572", + "explicit": true, + "audioQuality": "LOSSLESS", + "mediaMetadata": { + "tags": [ + "LOSSLESS" + ] + } + }, + { + "type": "album", + "id": 103897783, + "title": "Freewave 3", + "artist": { + "id": 7923685, + "name": "Lucki" + }, + "releaseDate": "2019-02-15", + "cover": "1d481a33-8b20-4ee3-b04b-5ac6e0fc5e78", + "explicit": true, + "audioQuality": "LOSSLESS", + "mediaMetadata": { + "tags": [ + "LOSSLESS" + ] + } + } +] \ No newline at end of file From 117762db7c1bca9d276dc721bb4fdace905bdeba Mon Sep 17 00:00:00 2001 From: edideaur Date: Fri, 3 Apr 2026 21:25:01 +0300 Subject: [PATCH 41/51] Delete public/editors-picks-old/2026-4-3.json --- public/editors-picks-old/2026-4-3.json | 1 - 1 file changed, 1 deletion(-) delete mode 100644 public/editors-picks-old/2026-4-3.json diff --git a/public/editors-picks-old/2026-4-3.json b/public/editors-picks-old/2026-4-3.json deleted file mode 100644 index 0637a08..0000000 --- a/public/editors-picks-old/2026-4-3.json +++ /dev/null @@ -1 +0,0 @@ -[] \ No newline at end of file From c9a29d7e2e6994d57272acf3f498c021d10fb812 Mon Sep 17 00:00:00 2001 From: edideaur <182119792+edideaur@users.noreply.github.com> Date: Fri, 3 Apr 2026 18:25:27 +0000 Subject: [PATCH 42/51] style: auto-fix linting issues --- public/editors-picks-old/index.json | 2 +- public/editors-picks.json | 71 +++++++---------------------- 2 files changed, 18 insertions(+), 55 deletions(-) diff --git a/public/editors-picks-old/index.json b/public/editors-picks-old/index.json index e0ef165..0457d23 100644 --- a/public/editors-picks-old/index.json +++ b/public/editors-picks-old/index.json @@ -29,4 +29,4 @@ "label": "April Fools '26", "date": "2026-04-01" } -] \ No newline at end of file +] diff --git a/public/editors-picks.json b/public/editors-picks.json index 8d5ec20..dfc1405 100644 --- a/public/editors-picks.json +++ b/public/editors-picks.json @@ -12,10 +12,7 @@ "explicit": true, "audioQuality": "LOSSLESS", "mediaMetadata": { - "tags": [ - "LOSSLESS", - "HIRES_LOSSLESS" - ] + "tags": ["LOSSLESS", "HIRES_LOSSLESS"] } }, { @@ -31,9 +28,7 @@ "explicit": true, "audioQuality": "LOSSLESS", "mediaMetadata": { - "tags": [ - "LOSSLESS" - ] + "tags": ["LOSSLESS"] } }, { @@ -49,9 +44,7 @@ "explicit": true, "audioQuality": "LOSSLESS", "mediaMetadata": { - "tags": [ - "LOSSLESS" - ] + "tags": ["LOSSLESS"] } }, { @@ -67,9 +60,7 @@ "explicit": true, "audioQuality": "LOSSLESS", "mediaMetadata": { - "tags": [ - "LOSSLESS" - ] + "tags": ["LOSSLESS"] } }, { @@ -85,9 +76,7 @@ "explicit": true, "audioQuality": "LOSSLESS", "mediaMetadata": { - "tags": [ - "LOSSLESS" - ] + "tags": ["LOSSLESS"] } }, { @@ -103,10 +92,7 @@ "explicit": true, "audioQuality": "LOSSLESS", "mediaMetadata": { - "tags": [ - "LOSSLESS", - "HIRES_LOSSLESS" - ] + "tags": ["LOSSLESS", "HIRES_LOSSLESS"] } }, { @@ -122,10 +108,7 @@ "explicit": true, "audioQuality": "LOSSLESS", "mediaMetadata": { - "tags": [ - "LOSSLESS", - "HIRES_LOSSLESS" - ] + "tags": ["LOSSLESS", "HIRES_LOSSLESS"] } }, { @@ -141,10 +124,7 @@ "explicit": true, "audioQuality": "LOSSLESS", "mediaMetadata": { - "tags": [ - "LOSSLESS", - "HIRES_LOSSLESS" - ] + "tags": ["LOSSLESS", "HIRES_LOSSLESS"] } }, { @@ -160,9 +140,7 @@ "explicit": false, "audioQuality": "LOSSLESS", "mediaMetadata": { - "tags": [ - "LOSSLESS" - ] + "tags": ["LOSSLESS"] } }, { @@ -178,10 +156,7 @@ "explicit": true, "audioQuality": "LOSSLESS", "mediaMetadata": { - "tags": [ - "LOSSLESS", - "HIRES_LOSSLESS" - ] + "tags": ["LOSSLESS", "HIRES_LOSSLESS"] } }, { @@ -197,9 +172,7 @@ "explicit": true, "audioQuality": "LOSSLESS", "mediaMetadata": { - "tags": [ - "LOSSLESS" - ] + "tags": ["LOSSLESS"] } }, { @@ -215,9 +188,7 @@ "explicit": true, "audioQuality": "LOSSLESS", "mediaMetadata": { - "tags": [ - "LOSSLESS" - ] + "tags": ["LOSSLESS"] } }, { @@ -233,9 +204,7 @@ "explicit": true, "audioQuality": "LOSSLESS", "mediaMetadata": { - "tags": [ - "LOSSLESS" - ] + "tags": ["LOSSLESS"] } }, { @@ -251,9 +220,7 @@ "explicit": true, "audioQuality": "LOSSLESS", "mediaMetadata": { - "tags": [ - "LOSSLESS" - ] + "tags": ["LOSSLESS"] } }, { @@ -269,9 +236,7 @@ "explicit": true, "audioQuality": "LOSSLESS", "mediaMetadata": { - "tags": [ - "LOSSLESS" - ] + "tags": ["LOSSLESS"] } }, { @@ -287,9 +252,7 @@ "explicit": true, "audioQuality": "LOSSLESS", "mediaMetadata": { - "tags": [ - "LOSSLESS" - ] + "tags": ["LOSSLESS"] } } -] \ No newline at end of file +] From 3acbcd61f248784efa6f9d443e21a4746ab8f6c1 Mon Sep 17 00:00:00 2001 From: Daniel <790119+DanTheMan827@users.noreply.github.com> Date: Thu, 26 Mar 2026 14:22:17 -0500 Subject: [PATCH 43/51] Adjust linting rules and add vs code extension recommendations --- .gitignore | 1 - .vscode/extensions.json | 3 + .vscode/launch.json | 15 ++++ .vscode/settings.json | 23 ++++++ bun.lock | 163 ++++++++++++++++++++++++++++++---------- eslint.config.js | 46 +++++++++++- package.json | 5 +- tsconfig-eslint.json | 22 ++++++ tsconfig.json | 5 +- 9 files changed, 235 insertions(+), 48 deletions(-) create mode 100644 .vscode/extensions.json create mode 100644 .vscode/launch.json create mode 100644 .vscode/settings.json create mode 100644 tsconfig-eslint.json diff --git a/.gitignore b/.gitignore index 995e435..408564e 100644 --- a/.gitignore +++ b/.gitignore @@ -3,7 +3,6 @@ dist .DS_Store *.local .vite -.vscode .claude # Docker .env diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000..d824444 --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,3 @@ +{ + "recommendations": ["dbaeumer.vscode-eslint", "esbenp.prettier-vscode"] +} diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..4e5596d --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,15 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "Launch Edge", + "request": "launch", + "type": "msedge", + "url": "http://localhost:5173", + "webRoot": "${workspaceFolder}" + } + ] +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..96b9b95 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,23 @@ +{ + "eslint.lintTask.enable": true, + "eslint.validate": ["javascript", "javascriptreact", "typescript", "typescriptreact"], + "eslint.enable": true, + "js/ts.validate.enabled": true, + + "[javascript]": { + "editor.defaultFormatter": "esbenp.prettier-vscode", + "editor.formatOnSave": true + }, + "[javascriptreact]": { + "editor.defaultFormatter": "esbenp.prettier-vscode", + "editor.formatOnSave": true + }, + "[typescriptreact]": { + "editor.defaultFormatter": "esbenp.prettier-vscode", + "editor.formatOnSave": true + }, + "[json]": { + "editor.defaultFormatter": "esbenp.prettier-vscode", + "editor.formatOnSave": true + } +} diff --git a/bun.lock b/bun.lock index edf4294..9db95dd 100644 --- a/bun.lock +++ b/bun.lock @@ -16,6 +16,8 @@ "@ffmpeg/util": "^0.12.2", "@kawarp/core": "^1.1.1", "@svta/common-media-library": "^0.18.1", + "@types/wicg-file-system-access": "^2023.10.7", + "@typescript-eslint/eslint-plugin": "^8.57.2", "@uimaxbai/am-lyrics": "^1.1.4", "@vitest/web-worker": "^4.1.2", "appwrite": "^23.0.0", @@ -35,6 +37,7 @@ "shaka-player": "^5.0.7", "simple-icons": "^16.12.0", "svgo": "^4.0.1", + "typescript-eslint": "^8.57.2", "url-toolkit": "^2.2.5", "uuid": "^13.0.0", "vitest": "^4.1.2", @@ -53,7 +56,7 @@ "miniflare": "^4.20260301.1", "playwright": "^1.58.2", "prettier": "^3.8.1", - "stylelint": "^16.26.1", + "stylelint": "^17.6.0", "stylelint-config-standard": "^39.0.1", "stylelint-config-standard-scss": "^16.0.0", "typescript": "^5.9.3", @@ -283,20 +286,22 @@ "@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-calc": ["@csstools/css-calc@3.1.1", "", { "peerDependencies": { "@csstools/css-parser-algorithms": "^4.0.0", "@csstools/css-tokenizer": "^4.0.0" } }, "sha512-HJ26Z/vmsZQqs/o3a6bgKslXGFAungXGbinULZO3eMsOyNJHeBBZfup5FiZInOghgoM4Hwnmw+OgbJCNg1wwUQ=="], + + "@csstools/css-parser-algorithms": ["@csstools/css-parser-algorithms@4.0.0", "", { "peerDependencies": { "@csstools/css-tokenizer": "^4.0.0" } }, "sha512-+B87qS7fIG3L5h3qwJ/IFbjoVoOe/bpOdh9hAjXbvx0o8ImEmUsGXN0inFOnk2ChCFgqkkGFQ+TpM5rbhkKe4w=="], "@csstools/css-syntax-patches-for-csstree": ["@csstools/css-syntax-patches-for-csstree@1.1.2", "", { "peerDependencies": { "css-tree": "^3.2.1" }, "optionalPeers": ["css-tree"] }, "sha512-5GkLzz4prTIpoyeUiIu3iV6CSG3Plo7xRVOFPKI7FVEJ3mZ0A8SwK0XU3Gl7xAkiQ+mDyam+NNp875/C5y+jSA=="], - "@csstools/css-tokenizer": ["@csstools/css-tokenizer@3.0.4", "", {}, "sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw=="], + "@csstools/css-tokenizer": ["@csstools/css-tokenizer@4.0.0", "", {}, "sha512-QxULHAm7cNu72w97JUNCBFODFaXpbDg+dP8b/oWFAZ2MTRppA3U00Y2L1HqaS4J6yBqxwa/Y3nMBaxVKbB/NsA=="], - "@csstools/media-query-list-parser": ["@csstools/media-query-list-parser@4.0.3", "", { "peerDependencies": { "@csstools/css-parser-algorithms": "^3.0.5", "@csstools/css-tokenizer": "^3.0.4" } }, "sha512-HAYH7d3TLRHDOUQK4mZKf9k9Ph/m8Akstg66ywKR4SFAigjs3yBiUeZtFxywiTm5moZMAp/5W/ZuFnNXXYLuuQ=="], + "@csstools/media-query-list-parser": ["@csstools/media-query-list-parser@5.0.0", "", { "peerDependencies": { "@csstools/css-parser-algorithms": "^4.0.0", "@csstools/css-tokenizer": "^4.0.0" } }, "sha512-T9lXmZOfnam3eMERPsszjY5NK0jX8RmThmmm99FZ8b7z8yMaFZWKwLWGZuTwdO3ddRY5fy13GmmEYZXB4I98Eg=="], - "@csstools/selector-specificity": ["@csstools/selector-specificity@5.0.0", "", { "peerDependencies": { "postcss-selector-parser": "^7.0.0" } }, "sha512-PCqQV3c4CoVm3kdPhyeZ07VmBRdH2EpMFA/pd9OASpOEC3aXNGoqPDAZ80D0cLpMBxnmk0+yNhGsEx31hq7Gtw=="], + "@csstools/selector-resolve-nested": ["@csstools/selector-resolve-nested@4.0.0", "", { "peerDependencies": { "postcss-selector-parser": "^7.1.1" } }, "sha512-9vAPxmp+Dx3wQBIUwc1v7Mdisw1kbbaGqXUM8QLTgWg7SoPGYtXBsMXvsFs/0Bn5yoFhcktzxNZGNaUt0VjgjA=="], + + "@csstools/selector-specificity": ["@csstools/selector-specificity@6.0.0", "", { "peerDependencies": { "postcss-selector-parser": "^7.1.1" } }, "sha512-4sSgl78OtOXEX/2d++8A83zHNTgwCJMaR24FvsYL7Uf/VS8HZk9PTwR51elTbGqMuwH3szLvvOXEaVnqn0Z3zA=="], "@dantheman827/taglib-ts": ["@dantheman827/taglib-ts@https://github.com/DanTheMan827/taglib-ts/archive/ebd0e369b706c127a280d4ad631977f8d12ff88f.tar.gz", {}, "sha512-QPl5eWhFqP716VKIX5t7x39eRswRHG+5RpQ9wW6cNbDh1QSa/gWpPyvwP55O+/qCV7VjLepZI1/XFoxMO8jK7w=="], - "@dual-bundle/import-meta-resolve": ["@dual-bundle/import-meta-resolve@4.2.1", "", {}, "sha512-id+7YRUgoUX6CgV0DtuhirQWodeeA7Lf4i2x71JS/vtA5pRb/hIGWlw+G6MeXvsM+MXrz0VAydTGElX1rAfgPg=="], - "@emnapi/runtime": ["@emnapi/runtime@1.9.2", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-3U4+MIWHImeyu1wnmVygh5WlgfYDtyf0k8AbLhMFxOipihf6nrWC4syIm/SwEeec0mNSafiiNnMJwbza/Is6Lw=="], "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.27.7", "", { "os": "aix", "cpu": "ppc64" }, "sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg=="], @@ -561,6 +566,8 @@ "@sindresorhus/is": ["@sindresorhus/is@7.2.0", "", {}, "sha512-P1Cz1dWaFfR4IR+U13mqqiGsLFf1KbayybWwdd2vfctdV6hDpUkgCY0nKOLLTMSoRd/jJNjtbqzf13K8DCCXQw=="], + "@sindresorhus/merge-streams": ["@sindresorhus/merge-streams@4.0.0", "", {}, "sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ=="], + "@speed-highlight/core": ["@speed-highlight/core@1.2.15", "", {}, "sha512-BMq1K3DsElxDWawkX6eLg9+CKJrTVGCBAWVuHXVUV2u0s2711qiChLSId6ikYPfxhdYocLNt3wWwSvDiTvFabw=="], "@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], @@ -609,6 +616,28 @@ "@types/trusted-types": ["@types/trusted-types@2.0.7", "", {}, "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw=="], + "@types/wicg-file-system-access": ["@types/wicg-file-system-access@2023.10.7", "", {}, "sha512-g49ijasEJvCd7ifmAY2D0wdEtt1xRjBbA33PJTiv8mKBr7DoMsPeISoJ8oQOTopSRi+FBWPpPW5ouDj2QPKtGA=="], + + "@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.58.0", "", { "dependencies": { "@eslint-community/regexpp": "^4.12.2", "@typescript-eslint/scope-manager": "8.58.0", "@typescript-eslint/type-utils": "8.58.0", "@typescript-eslint/utils": "8.58.0", "@typescript-eslint/visitor-keys": "8.58.0", "ignore": "^7.0.5", "natural-compare": "^1.4.0", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "@typescript-eslint/parser": "^8.58.0", "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-RLkVSiNuUP1C2ROIWfqX+YcUfLaSnxGE/8M+Y57lopVwg9VTYYfhuz15Yf1IzCKgZj6/rIbYTmJCUSqr76r0Wg=="], + + "@typescript-eslint/parser": ["@typescript-eslint/parser@8.58.0", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.58.0", "@typescript-eslint/types": "8.58.0", "@typescript-eslint/typescript-estree": "8.58.0", "@typescript-eslint/visitor-keys": "8.58.0", "debug": "^4.4.3" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-rLoGZIf9afaRBYsPUMtvkDWykwXwUPL60HebR4JgTI8mxfFe2cQTu3AGitANp4b9B2QlVru6WzjgB2IzJKiCSA=="], + + "@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.58.0", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.58.0", "@typescript-eslint/types": "^8.58.0", "debug": "^4.4.3" }, "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-8Q/wBPWLQP1j16NxoPNIKpDZFMaxl7yWIoqXWYeWO+Bbd2mjgvoF0dxP2jKZg5+x49rgKdf7Ck473M8PC3V9lg=="], + + "@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.58.0", "", { "dependencies": { "@typescript-eslint/types": "8.58.0", "@typescript-eslint/visitor-keys": "8.58.0" } }, "sha512-W1Lur1oF50FxSnNdGp3Vs6P+yBRSmZiw4IIjEeYxd8UQJwhUF0gDgDD/W/Tgmh73mxgEU3qX0Bzdl/NGuSPEpQ=="], + + "@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.58.0", "", { "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-doNSZEVJsWEu4htiVC+PR6NpM+pa+a4ClH9INRWOWCUzMst/VA9c4gXq92F8GUD1rwhNvRLkgjfYtFXegXQF7A=="], + + "@typescript-eslint/type-utils": ["@typescript-eslint/type-utils@8.58.0", "", { "dependencies": { "@typescript-eslint/types": "8.58.0", "@typescript-eslint/typescript-estree": "8.58.0", "@typescript-eslint/utils": "8.58.0", "debug": "^4.4.3", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-aGsCQImkDIqMyx1u4PrVlbi/krmDsQUs4zAcCV6M7yPcPev+RqVlndsJy9kJ8TLihW9TZ0kbDAzctpLn5o+lOg=="], + + "@typescript-eslint/types": ["@typescript-eslint/types@8.58.0", "", {}, "sha512-O9CjxypDT89fbHxRfETNoAnHj/i6IpRK0CvbVN3qibxlLdo5p5hcLmUuCCrHMpxiWSwKyI8mCP7qRNYuOJ0Uww=="], + + "@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.58.0", "", { "dependencies": { "@typescript-eslint/project-service": "8.58.0", "@typescript-eslint/tsconfig-utils": "8.58.0", "@typescript-eslint/types": "8.58.0", "@typescript-eslint/visitor-keys": "8.58.0", "debug": "^4.4.3", "minimatch": "^10.2.2", "semver": "^7.7.3", "tinyglobby": "^0.2.15", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-7vv5UWbHqew/dvs+D3e1RvLv1v2eeZ9txRHPnEEBUgSNLx5ghdzjHa0sgLWYVKssH+lYmV0JaWdoubo0ncGYLA=="], + + "@typescript-eslint/utils": ["@typescript-eslint/utils@8.58.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", "@typescript-eslint/scope-manager": "8.58.0", "@typescript-eslint/types": "8.58.0", "@typescript-eslint/typescript-estree": "8.58.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-RfeSqcFeHMHlAWzt4TBjWOAtoW9lnsAGiP3GbaX9uVgTYYrMbVnGONEfUCiSss+xMHFl+eHZiipmA8WkQ7FuNA=="], + + "@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.58.0", "", { "dependencies": { "@typescript-eslint/types": "8.58.0", "eslint-visitor-keys": "^5.0.0" } }, "sha512-XJ9UD9+bbDo4a4epraTwG3TsNPeiB9aShrUneAVXy8q4LuwowN+qu89/6ByLMINqvIMeI9H9hOHQtg/ijrYXzQ=="], + "@uimaxbai/am-lyrics": ["@uimaxbai/am-lyrics@1.1.4", "", { "dependencies": { "@babel/runtime": "^7.27.6", "lit": "^3.1.4" }, "peerDependencies": { "@lit/react": "^1.0.0", "react": ">=17.0.0" }, "optionalPeers": ["@lit/react", "react"] }, "sha512-LEwvbfgz6o71kYTq1vMlfou/powr8q4CJQWuyL2H48Dwo1/vH59SKiB3nz/WOEQ1S69uaSmfqf8Prtx6+ZNIrQ=="], "@vitest/browser": ["@vitest/browser@4.1.2", "", { "dependencies": { "@blazediff/core": "1.9.1", "@vitest/mocker": "4.1.2", "@vitest/utils": "4.1.2", "magic-string": "^0.30.21", "pngjs": "^7.0.0", "sirv": "^3.0.2", "tinyrainbow": "^3.1.0", "ws": "^8.19.0" }, "peerDependencies": { "vitest": "4.1.2" } }, "sha512-CwdIf90LNf1Zitgqy63ciMAzmyb4oIGs8WZ40VGYrWkssQKeEKr32EzO8MKUrDPPcPVHFI9oQ5ni2Hp24NaNRQ=="], @@ -695,7 +724,7 @@ "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=="], + "balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], "bare-events": ["bare-events@2.8.2", "", { "peerDependencies": { "bare-abort-controller": "*" }, "optionalPeers": ["bare-abort-controller"] }, "sha512-riJjyv1/mHLIPX4RwiK+oW9/4c3TEUeORHKefKAKnZ5kyslbN+HXowtbaVEqt4IMUB7OXlfixcs6gsFeo/jhiQ=="], @@ -1045,6 +1074,8 @@ "get-caller-file": ["get-caller-file@2.0.5", "", {}, "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg=="], + "get-east-asian-width": ["get-east-asian-width@1.5.0", "", {}, "sha512-CQ+bEO+Tva/qlmw24dCejulK5pMzVnUOFOijVogd3KQs07HnRIgp8TGipvCCRT06xeYEbpbgwaCxglFyiuIcmA=="], + "get-intrinsic": ["get-intrinsic@1.3.0", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ=="], "get-own-enumerable-property-symbols": ["get-own-enumerable-property-symbols@3.0.2", "", {}, "sha512-I0UBV/XOz1XkIJHEUDMZAbzCThU/H8DxmSfmdGcKPnVhu2VfFqr34jr9777IyaTYvxjedWhqVIilEDsCdP5G6g=="], @@ -1077,7 +1108,7 @@ "globalthis": ["globalthis@1.0.4", "", { "dependencies": { "define-properties": "^1.2.1", "gopd": "^1.0.1" } }, "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ=="], - "globby": ["globby@11.1.0", "", { "dependencies": { "array-union": "^2.1.0", "dir-glob": "^3.0.1", "fast-glob": "^3.2.9", "ignore": "^5.2.0", "merge2": "^1.4.1", "slash": "^3.0.0" } }, "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g=="], + "globby": ["globby@16.2.0", "", { "dependencies": { "@sindresorhus/merge-streams": "^4.0.0", "fast-glob": "^3.3.3", "ignore": "^7.0.5", "is-path-inside": "^4.0.0", "slash": "^5.1.0", "unicorn-magic": "^0.4.0" } }, "sha512-QrJia2qDf5BB/V6HYlDTs0I0lBahyjLzpGQg3KT7FnCdTonAyPy2RtY802m2k4ALx6Dp752f82WsOczEVr3l6Q=="], "globjoin": ["globjoin@0.1.4", "", {}, "sha512-xYfnw62CKG8nLkZBfWbhWwDw02CHty86jfPcc2cr3ZfeuK9ysoVPPEUxf21bAD/rWAgk52SuBrLJlefNy8mvFg=="], @@ -1093,7 +1124,7 @@ "has-bigints": ["has-bigints@1.1.0", "", {}, "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg=="], - "has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="], + "has-flag": ["has-flag@5.0.1", "", {}, "sha512-CsNUt5x9LUdx6hnk/E2SZLsDyvfqANZSUq4+D3D8RzDJ2M+HDTIkF60ibS1vHaK55vzgiZw1bEPFG9yH7l33wA=="], "has-property-descriptors": ["has-property-descriptors@1.0.2", "", { "dependencies": { "es-define-property": "^1.0.0" } }, "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg=="], @@ -1115,7 +1146,7 @@ "hosted-git-info": ["hosted-git-info@4.1.0", "", { "dependencies": { "lru-cache": "^6.0.0" } }, "sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA=="], - "html-tags": ["html-tags@3.3.1", "", {}, "sha512-ztqyC3kLto0e9WbNp0aeP+M3kTt+nbaIveGmUxAtZa+8iFgKLUOD4YKM5j+f3QD89bra7UeumolZHKuOXnTmeQ=="], + "html-tags": ["html-tags@5.1.0", "", {}, "sha512-n6l5uca7/y5joxZ3LUePhzmBFUJ+U2YWzhMa8XUTecSeSlQiZdF5XAd/Q3/WUl0VsXgUwWi8I7CNIwdI5WN1SQ=="], "htmlhint": ["htmlhint@1.9.2", "", { "dependencies": { "async": "3.2.6", "chalk": "4.1.2", "commander": "11.1.0", "glob": "^13.0.6", "is-glob": "^4.0.3", "node-sarif-builder": "3.2.0", "strip-json-comments": "3.1.1", "xml": "1.0.1" }, "bin": { "htmlhint": "bin/htmlhint" } }, "sha512-PweWSPA1Pb+AVFIOSpIGu5KhLdmtk/uf/0CpjvrDf6XUWmdTyqUljlylwSxQ0AWLvPGcBxK2n8uISsI4lCOkBQ=="], @@ -1187,7 +1218,7 @@ "is-path-cwd": ["is-path-cwd@2.2.0", "", {}, "sha512-w942bTcih8fdJPJmQHFzkS76NEP8Kzzvmw92cXsazb8intwLqPibPPdXf4ANdKV3rYMuuQYGIWtvz9JilB3NFQ=="], - "is-path-inside": ["is-path-inside@3.0.3", "", {}, "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ=="], + "is-path-inside": ["is-path-inside@4.0.0", "", {}, "sha512-lJJV/5dYS+RcL8uQdBDW9c9uWFLLBNRyFhnAKXw5tVqLlKZ4RMGZKv+YQ/IA3OhD+RpbJa1LLFM1FQPGyIXvOA=="], "is-plain-obj": ["is-plain-obj@1.1.0", "", {}, "sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg=="], @@ -1307,11 +1338,11 @@ "math-intrinsics": ["math-intrinsics@1.1.0", "", {}, "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="], - "mathml-tag-names": ["mathml-tag-names@2.1.3", "", {}, "sha512-APMBEanjybaPzUrfqU0IMU5I0AswKMH7k8OTLs0vvV4KZpExkTkY87nR/zpbuTPj+gARop7aGUbl11pnDfW6xg=="], + "mathml-tag-names": ["mathml-tag-names@4.0.0", "", {}, "sha512-aa6AU2Pcx0VP/XWnh8IGL0SYSgQHDT6Ucror2j2mXeFAlN3ahaNs8EZtG1YiticMkSLj3Gt6VPFfZogt7G5iFQ=="], "mdn-data": ["mdn-data@2.27.1", "", {}, "sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ=="], - "meow": ["meow@13.2.0", "", {}, "sha512-pxQJQzB6djGPXh08dacEloMFopsOqGVRKFPYvPOt9XDZ1HasbgDZA74CJGreSU4G3Ak7EFJGoiH2auq+yXISgA=="], + "meow": ["meow@14.1.0", "", {}, "sha512-EDYo6VlmtnumlcBCbh1gLJ//9jvM/ndXHfVXIFrZVr6fGcwTUyCTFNTLCKuY3ffbK8L/+3Mzqnd58RojiZqHVw=="], "merge2": ["merge2@1.4.1", "", {}, "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg=="], @@ -1419,7 +1450,7 @@ "path-scurry": ["path-scurry@2.0.2", "", { "dependencies": { "lru-cache": "^11.0.0", "minipass": "^7.1.2" } }, "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg=="], - "path-type": ["path-type@4.0.0", "", {}, "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw=="], + "path-type": ["path-type@3.0.0", "", { "dependencies": { "pify": "^3.0.0" } }, "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg=="], "pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="], @@ -1523,7 +1554,7 @@ "resolve": ["resolve@1.22.11", "", { "dependencies": { "is-core-module": "^2.16.1", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" } }, "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ=="], - "resolve-from": ["resolve-from@5.0.0", "", {}, "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw=="], + "resolve-from": ["resolve-from@4.0.0", "", {}, "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="], "reusify": ["reusify@1.1.0", "", {}, "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="], @@ -1591,7 +1622,7 @@ "sisteransi": ["sisteransi@1.0.5", "", {}, "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg=="], - "slash": ["slash@3.0.0", "", {}, "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q=="], + "slash": ["slash@5.1.0", "", {}, "sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg=="], "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=="], @@ -1627,7 +1658,7 @@ "streamx": ["streamx@2.25.0", "", { "dependencies": { "events-universal": "^1.0.0", "fast-fifo": "^1.3.2", "text-decoder": "^1.1.0" } }, "sha512-0nQuG6jf1w+wddNEEXCF4nTg3LtufWINB5eFEN+5TNZW7KWJp6x87+JFL43vaAUPyCfH1wID+mNVyW6OHtFamg=="], - "string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], + "string-width": ["string-width@8.2.0", "", { "dependencies": { "get-east-asian-width": "^1.5.0", "strip-ansi": "^7.1.2" } }, "sha512-6hJPQ8N0V0P3SNmP6h2J99RLuzrWz2gvT7VnK5tKvrNqJoyS9W4/Fb8mo31UiPvy00z7DQXkP2hnKBVav76thw=="], "string.prototype.matchall": ["string.prototype.matchall@4.0.12", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.3", "define-properties": "^1.2.1", "es-abstract": "^1.23.6", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0", "get-intrinsic": "^1.2.6", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "internal-slot": "^1.1.0", "regexp.prototype.flags": "^1.5.3", "set-function-name": "^2.0.2", "side-channel": "^1.1.0" } }, "sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA=="], @@ -1651,7 +1682,7 @@ "strip-json-comments": ["strip-json-comments@3.1.1", "", {}, "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig=="], - "stylelint": ["stylelint@16.26.1", "", { "dependencies": { "@csstools/css-parser-algorithms": "^3.0.5", "@csstools/css-syntax-patches-for-csstree": "^1.0.19", "@csstools/css-tokenizer": "^3.0.4", "@csstools/media-query-list-parser": "^4.0.3", "@csstools/selector-specificity": "^5.0.0", "@dual-bundle/import-meta-resolve": "^4.2.1", "balanced-match": "^2.0.0", "colord": "^2.9.3", "cosmiconfig": "^9.0.0", "css-functions-list": "^3.2.3", "css-tree": "^3.1.0", "debug": "^4.4.3", "fast-glob": "^3.3.3", "fastest-levenshtein": "^1.0.16", "file-entry-cache": "^11.1.1", "global-modules": "^2.0.0", "globby": "^11.1.0", "globjoin": "^0.1.4", "html-tags": "^3.3.1", "ignore": "^7.0.5", "imurmurhash": "^0.1.4", "is-plain-object": "^5.0.0", "known-css-properties": "^0.37.0", "mathml-tag-names": "^2.1.3", "meow": "^13.2.0", "micromatch": "^4.0.8", "normalize-path": "^3.0.0", "picocolors": "^1.1.1", "postcss": "^8.5.6", "postcss-resolve-nested-selector": "^0.1.6", "postcss-safe-parser": "^7.0.1", "postcss-selector-parser": "^7.1.0", "postcss-value-parser": "^4.2.0", "resolve-from": "^5.0.0", "string-width": "^4.2.3", "supports-hyperlinks": "^3.2.0", "svg-tags": "^1.0.0", "table": "^6.9.0", "write-file-atomic": "^5.0.1" }, "bin": { "stylelint": "bin/stylelint.mjs" } }, "sha512-v20V59/crfc8sVTAtge0mdafI3AdnzQ2KsWe6v523L4OA1bJO02S7MO2oyXDCS6iWb9ckIPnqAFVItqSBQr7jw=="], + "stylelint": ["stylelint@17.6.0", "", { "dependencies": { "@csstools/css-calc": "^3.1.1", "@csstools/css-parser-algorithms": "^4.0.0", "@csstools/css-syntax-patches-for-csstree": "^1.1.1", "@csstools/css-tokenizer": "^4.0.0", "@csstools/media-query-list-parser": "^5.0.0", "@csstools/selector-resolve-nested": "^4.0.0", "@csstools/selector-specificity": "^6.0.0", "colord": "^2.9.3", "cosmiconfig": "^9.0.1", "css-functions-list": "^3.3.3", "css-tree": "^3.2.1", "debug": "^4.4.3", "fast-glob": "^3.3.3", "fastest-levenshtein": "^1.0.16", "file-entry-cache": "^11.1.2", "global-modules": "^2.0.0", "globby": "^16.1.1", "globjoin": "^0.1.4", "html-tags": "^5.1.0", "ignore": "^7.0.5", "import-meta-resolve": "^4.2.0", "is-plain-object": "^5.0.0", "mathml-tag-names": "^4.0.0", "meow": "^14.1.0", "micromatch": "^4.0.8", "normalize-path": "^3.0.0", "picocolors": "^1.1.1", "postcss": "^8.5.8", "postcss-safe-parser": "^7.0.1", "postcss-selector-parser": "^7.1.1", "postcss-value-parser": "^4.2.0", "string-width": "^8.2.0", "supports-hyperlinks": "^4.4.0", "svg-tags": "^1.0.0", "table": "^6.9.0", "write-file-atomic": "^7.0.1" }, "bin": { "stylelint": "bin/stylelint.mjs" } }, "sha512-tokrsMIVAR9vAQ/q3UVEr7S0dGXCi7zkCezPRnS2kqPUulvUh5Vgfwngrk4EoAoW7wnrThqTdnTFN5Ra7CaxIg=="], "stylelint-config-recommended": ["stylelint-config-recommended@17.0.0", "", { "peerDependencies": { "stylelint": "^16.23.0" } }, "sha512-WaMSdEiPfZTSFVoYmJbxorJfA610O0tlYuU2aEwY33UQhSPgFbClrVJYWvy3jGJx+XW37O+LyNLiZOEXhKhJmA=="], @@ -1665,7 +1696,7 @@ "supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="], - "supports-hyperlinks": ["supports-hyperlinks@3.2.0", "", { "dependencies": { "has-flag": "^4.0.0", "supports-color": "^7.0.0" } }, "sha512-zFObLMyZeEwzAoKCyu1B91U79K2t7ApXuQfo8OuxwXLDgcKxuwM+YvcbIhm6QWqz7mHUH1TVytR1PwVVjEuMig=="], + "supports-hyperlinks": ["supports-hyperlinks@4.4.0", "", { "dependencies": { "has-flag": "^5.0.1", "supports-color": "^10.2.2" } }, "sha512-UKbpT93hN5Nr9go5UY7bopIB9YQlMz9nm/ct4IXt/irb5YRkn9WaqrOBJGZ5Pwvsd5FQzSVeYlGdXoCAPQZrPg=="], "supports-preserve-symlinks-flag": ["supports-preserve-symlinks-flag@1.0.0", "", {}, "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w=="], @@ -1717,6 +1748,8 @@ "trim-newlines": ["trim-newlines@3.0.1", "", {}, "sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw=="], + "ts-api-utils": ["ts-api-utils@2.5.0", "", { "peerDependencies": { "typescript": ">=4.8.4" } }, "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA=="], + "ts-node": ["ts-node@10.9.2", "", { "dependencies": { "@cspotcode/source-map-support": "^0.8.0", "@tsconfig/node10": "^1.0.7", "@tsconfig/node12": "^1.0.7", "@tsconfig/node14": "^1.0.0", "@tsconfig/node16": "^1.0.2", "acorn": "^8.4.1", "acorn-walk": "^8.1.1", "arg": "^4.1.0", "create-require": "^1.1.0", "diff": "^4.0.1", "make-error": "^1.1.1", "v8-compile-cache-lib": "^3.0.1", "yn": "3.1.1" }, "peerDependencies": { "@swc/core": ">=1.2.50", "@swc/wasm": ">=1.2.50", "@types/node": "*", "typescript": ">=2.7" }, "optionalPeers": ["@swc/core", "@swc/wasm"], "bin": { "ts-node": "dist/bin.js", "ts-script": "dist/bin-script-deprecated.js", "ts-node-cwd": "dist/bin-cwd.js", "ts-node-esm": "dist/bin-esm.js", "ts-node-script": "dist/bin-script.js", "ts-node-transpile-only": "dist/bin-transpile.js" } }, "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ=="], "tslib": ["tslib@2.6.2", "", {}, "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q=="], @@ -1739,6 +1772,8 @@ "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], + "typescript-eslint": ["typescript-eslint@8.58.0", "", { "dependencies": { "@typescript-eslint/eslint-plugin": "8.58.0", "@typescript-eslint/parser": "8.58.0", "@typescript-eslint/typescript-estree": "8.58.0", "@typescript-eslint/utils": "8.58.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-e2TQzKfaI85fO+F3QywtX+tCTsu/D3WW5LVU6nz8hTFKFZ8yBJ6mSYRpXqdR3mFjPWmO0eWsTa5f+UpAOe/FMA=="], + "uglify-js": ["uglify-js@3.19.3", "", { "bin": { "uglifyjs": "bin/uglifyjs" } }, "sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ=="], "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=="], @@ -1755,6 +1790,8 @@ "unicode-property-aliases-ecmascript": ["unicode-property-aliases-ecmascript@2.2.0", "", {}, "sha512-hpbDzxUY9BFwX+UeBnxv3Sh1q7HFxj48DTmXchNgRa46lO8uj3/1iEn3MiNUYTg1g9ctIqXCCERn8gYZhHC5lQ=="], + "unicorn-magic": ["unicorn-magic@0.4.0", "", {}, "sha512-wH590V9VNgYH9g3lH9wWjTrUoKsjLF6sGLjhR4sH1LWpLmCOH0Zf7PukhDA8BiS7KHe4oPNkcTHqYkj7SOGUOw=="], + "unique-string": ["unique-string@2.0.0", "", { "dependencies": { "crypto-random-string": "^2.0.0" } }, "sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg=="], "universalify": ["universalify@2.0.1", "", {}, "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw=="], @@ -1845,7 +1882,7 @@ "wrappy": ["wrappy@1.0.2", "", {}, "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="], - "write-file-atomic": ["write-file-atomic@5.0.1", "", { "dependencies": { "imurmurhash": "^0.1.4", "signal-exit": "^4.0.1" } }, "sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw=="], + "write-file-atomic": ["write-file-atomic@7.0.1", "", { "dependencies": { "signal-exit": "^4.0.1" } }, "sha512-OTIk8iR8/aCRWBqvxrzxR0hgxWpnYBblY1S5hDWBQfk/VFmJwzmJgQFN3WsoUKHISv2eAwe+PpbUzyL1CKTLXg=="], "ws": ["ws@8.18.0", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw=="], @@ -1917,8 +1954,6 @@ "@capacitor/cli/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], - "@emnapi/runtime/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], - "@eslint-community/eslint-utils/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="], "@eslint/config-array/debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], @@ -1937,12 +1972,8 @@ "@ionic/utils-fs/fs-extra": ["fs-extra@9.1.0", "", { "dependencies": { "at-least-node": "^1.0.0", "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ=="], - "@ionic/utils-fs/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], - "@ionic/utils-object/debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], - "@ionic/utils-object/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], - "@ionic/utils-process/debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], "@ionic/utils-process/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], @@ -1957,6 +1988,8 @@ "@ionic/utils-terminal/debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], + "@ionic/utils-terminal/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], + "@ionic/utils-terminal/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], "@jridgewell/gen-mapping/@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="], @@ -1993,16 +2026,30 @@ "@trapezedev/project/prettier": ["prettier@2.8.8", "", { "bin": { "prettier": "bin-prettier.js" } }, "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q=="], + "@typescript-eslint/eslint-plugin/ignore": ["ignore@7.0.5", "", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="], + + "@typescript-eslint/parser/debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], + + "@typescript-eslint/project-service/debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], + + "@typescript-eslint/type-utils/debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], + + "@typescript-eslint/typescript-estree/debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], + + "@typescript-eslint/typescript-estree/minimatch": ["minimatch@10.2.5", "", { "dependencies": { "brace-expansion": "^5.0.5" } }, "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg=="], + + "@typescript-eslint/visitor-keys/eslint-visitor-keys": ["eslint-visitor-keys@5.0.1", "", {}, "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA=="], + "@vitest/browser/ws": ["ws@8.20.0", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA=="], "babel-plugin-polyfill-corejs2/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], - "brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], - "cacheable/keyv": ["keyv@5.6.0", "", { "dependencies": { "@keyv/serialize": "^1.1.1" } }, "sha512-CYDD3SOtsHtyXeEORYRx2qBtpDJFjRTGXUtmNEMGyzYOKj1TE3tycdlho7kA1Ufx9OYWZzg52QFBGALTirzDSw=="], "chalk/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], + "cliui/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], + "conventional-changelog-writer/meow": ["meow@8.1.2", "", { "dependencies": { "@types/minimist": "^1.2.0", "camelcase-keys": "^6.2.2", "decamelize-keys": "^1.1.0", "hard-rejection": "^2.1.0", "minimist-options": "4.1.0", "normalize-package-data": "^3.0.0", "read-pkg-up": "^7.0.1", "redent": "^3.0.0", "trim-newlines": "^3.0.0", "type-fest": "^0.18.0", "yargs-parser": "^20.2.3" } }, "sha512-r85E3NdZ+mpYk1C6RjPFEMSE+s1iZMuHtsHAqY0DT3jZczl0diWUZ8g6oU7h0M9cD2EL+PzaYghhCLzR0ZNn5Q=="], "conventional-changelog-writer/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], @@ -2017,8 +2064,16 @@ "decamelize-keys/map-obj": ["map-obj@1.0.1", "", {}, "sha512-7N/q3lyZ+LVCp7PzuxrJr4KMbBE2hW7BT7YNia330OFxIf4d3r5zVpicP2650l7CPN6RM9zOJRl3NGpqSiw3Eg=="], + "del/globby": ["globby@11.1.0", "", { "dependencies": { "array-union": "^2.1.0", "dir-glob": "^3.0.1", "fast-glob": "^3.2.9", "ignore": "^5.2.0", "merge2": "^1.4.1", "slash": "^3.0.0" } }, "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g=="], + + "del/is-path-inside": ["is-path-inside@3.0.3", "", {}, "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ=="], + "del/rimraf": ["rimraf@3.0.2", "", { "dependencies": { "glob": "^7.1.3" }, "bin": { "rimraf": "bin.js" } }, "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA=="], + "del/slash": ["slash@3.0.0", "", {}, "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q=="], + + "dir-glob/path-type": ["path-type@4.0.0", "", {}, "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw=="], + "dot-prop/is-obj": ["is-obj@2.0.0", "", {}, "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w=="], "elementtree/sax": ["sax@1.1.4", "", {}, "sha512-5f3k2PbGGp+YtKJjOItpg3P99IMD84E4HOvcfleTb5joCHNXYLsR9yWFPOYGgaeMPDubQILTCMdsFb2OMeOjtg=="], @@ -2053,12 +2108,12 @@ "global-prefix/which": ["which@1.3.1", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "which": "./bin/which" } }, "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ=="], + "globby/ignore": ["ignore@7.0.5", "", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="], + "hosted-git-info/lru-cache": ["lru-cache@6.0.0", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA=="], "htmlhint/commander": ["commander@11.1.0", "", {}, "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ=="], - "import-fresh/resolve-from": ["resolve-from@4.0.0", "", {}, "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="], - "import-from-esm/debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], "load-json-file/parse-json": ["parse-json@4.0.0", "", { "dependencies": { "error-ex": "^1.3.1", "json-parse-better-errors": "^1.0.1" } }, "sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw=="], @@ -2361,6 +2416,8 @@ "npm/yallist": ["yallist@5.0.0", "", {}, "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw=="], + "path-type/pify": ["pify@3.0.0", "", {}, "sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg=="], + "plist/@xmldom/xmldom": ["@xmldom/xmldom@0.8.12", "", {}, "sha512-9k/gHF6n/pAi/9tqr3m3aqkuiNosYTurLLUtc7xQ9sxB/wm7WPygCv8GYa6mS0fLJEHhqMC1ATYhz++U/lRHqg=="], "prebuild-install/tar-fs": ["tar-fs@2.1.4", "", { "dependencies": { "chownr": "^1.1.1", "mkdirp-classic": "^0.5.2", "pump": "^3.0.0", "tar-stream": "^2.1.4" } }, "sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ=="], @@ -2375,8 +2432,6 @@ "read-pkg/normalize-package-data": ["normalize-package-data@2.5.0", "", { "dependencies": { "hosted-git-info": "^2.1.4", "resolve": "^1.10.0", "semver": "2 || 3 || 4 || 5", "validate-npm-package-license": "^3.0.1" } }, "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA=="], - "read-pkg/path-type": ["path-type@3.0.0", "", { "dependencies": { "pify": "^3.0.0" } }, "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg=="], - "read-pkg-up/find-up": ["find-up@2.1.0", "", { "dependencies": { "locate-path": "^2.0.0" } }, "sha512-NWzkk0jSJtTt08+FBFMvXoeZnOJD+jTtsRmBYbAIzJdX6l7dLgR7CTubCM5/eDdPUBvLCeVasP1brfVR/9/EZQ=="], "replace/chalk": ["chalk@2.4.2", "", { "dependencies": { "ansi-styles": "^3.2.1", "escape-string-regexp": "^1.0.5", "supports-color": "^5.3.0" } }, "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ=="], @@ -2393,16 +2448,24 @@ "slice-ansi/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], + "string-width/strip-ansi": ["strip-ansi@7.2.0", "", { "dependencies": { "ansi-regex": "^6.2.2" } }, "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w=="], + "stylelint/debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], "stylelint/file-entry-cache": ["file-entry-cache@11.1.2", "", { "dependencies": { "flat-cache": "^6.1.20" } }, "sha512-N2WFfK12gmrK1c1GXOqiAJ1tc5YE+R53zvQ+t5P8S5XhnmKYVB5eZEiLNZKDSmoG8wqqbF9EXYBBW/nef19log=="], "stylelint/ignore": ["ignore@7.0.5", "", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="], + "supports-color/has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="], + + "supports-hyperlinks/supports-color": ["supports-color@10.2.2", "", {}, "sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g=="], + "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=="], + "table/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], + "terser/commander": ["commander@2.20.3", "", {}, "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ=="], "ts-node/diff": ["diff@4.0.4", "", {}, "sha512-X07nttJQkwkfKfvTPG/KSnE2OMdcUCao6+eXF3wmnIQRn2aPAHH3VxDbDOdegkd6JbPsXqShpvEOHfAT+nCNwQ=="], @@ -2425,12 +2488,16 @@ "wrap-ansi/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], + "wrap-ansi/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], + "write-file-atomic/signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="], "xcode/uuid": ["uuid@7.0.3", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-DPSke0pXhTZgoF/d+WSt2QaKMCFSfx7QegxEWT+JOuHF5aWrKEn0G+ztjuJg/gG8/ItK+rbPCD/yNv8yyih6Cg=="], "xml2js/xmlbuilder": ["xmlbuilder@11.0.1", "", {}, "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA=="], + "yargs/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], + "@apideck/better-ajv-errors/ajv/json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="], "@babel/core/debug/ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], @@ -2493,7 +2560,15 @@ "@trapezedev/project/@ionic/utils-subprocess/debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], - "@trapezedev/project/@ionic/utils-subprocess/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], + "@typescript-eslint/parser/debug/ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], + + "@typescript-eslint/project-service/debug/ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], + + "@typescript-eslint/type-utils/debug/ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], + + "@typescript-eslint/typescript-estree/debug/ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], + + "@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@5.0.5", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ=="], "conventional-changelog-writer/meow/read-pkg-up": ["read-pkg-up@7.0.1", "", { "dependencies": { "find-up": "^4.1.0", "read-pkg": "^5.2.0", "type-fest": "^0.8.1" } }, "sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg=="], @@ -2523,6 +2598,8 @@ "get-pkg-repo/yargs/cliui": ["cliui@7.0.4", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.0", "wrap-ansi": "^7.0.0" } }, "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ=="], + "get-pkg-repo/yargs/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], + "get-pkg-repo/yargs/yargs-parser": ["yargs-parser@20.2.9", "", {}, "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w=="], "git-raw-commits/meow/read-pkg-up": ["read-pkg-up@7.0.1", "", { "dependencies": { "find-up": "^4.1.0", "read-pkg": "^5.2.0", "type-fest": "^0.8.1" } }, "sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg=="], @@ -2563,8 +2640,6 @@ "read-pkg/normalize-package-data/semver": ["semver@5.7.2", "", { "bin": { "semver": "bin/semver" } }, "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g=="], - "read-pkg/path-type/pify": ["pify@3.0.0", "", {}, "sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg=="], - "replace/chalk/ansi-styles": ["ansi-styles@3.2.1", "", { "dependencies": { "color-convert": "^1.9.0" } }, "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA=="], "replace/chalk/escape-string-regexp": ["escape-string-regexp@1.0.5", "", {}, "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg=="], @@ -2575,10 +2650,14 @@ "replace/yargs/find-up": ["find-up@4.1.0", "", { "dependencies": { "locate-path": "^5.0.0", "path-exists": "^4.0.0" } }, "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw=="], + "replace/yargs/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], + "replace/yargs/y18n": ["y18n@4.0.3", "", {}, "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ=="], "replace/yargs/yargs-parser": ["yargs-parser@18.1.3", "", { "dependencies": { "camelcase": "^5.0.0", "decamelize": "^1.2.0" } }, "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ=="], + "string-width/strip-ansi/ansi-regex": ["ansi-regex@6.2.2", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="], + "stylelint/debug/ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], "stylelint/file-entry-cache/flat-cache": ["flat-cache@6.1.22", "", { "dependencies": { "cacheable": "^2.3.4", "flatted": "^3.4.2", "hookified": "^1.15.0" } }, "sha512-N2dnzVJIphnNsjHcrxGW7DePckJ6haPrSFqpsBUhHYgwtKGVq4JrBGielEGD2fCVnsGm1zlBVZ8wGhkyuetgug=="], @@ -2599,6 +2678,8 @@ "@capacitor/assets/@capacitor/cli/@ionic/utils-subprocess/@ionic/utils-terminal": ["@ionic/utils-terminal@2.3.4", "", { "dependencies": { "@types/slice-ansi": "^4.0.0", "debug": "^4.0.0", "signal-exit": "^3.0.3", "slice-ansi": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0", "tslib": "^2.0.1", "untildify": "^4.0.0", "wrap-ansi": "^7.0.0" } }, "sha512-cEiMFl3jklE0sW60r8JHH3ijFTwh/jkdEKWbylSyExQwZ8pPuwoXz7gpkWoJRLuoRHHSvg+wzNYyPJazIHfoJA=="], + "@capacitor/assets/@capacitor/cli/@ionic/utils-subprocess/tslib": ["tslib@2.6.2", "", {}, "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q=="], + "@capacitor/assets/@capacitor/cli/debug/ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], "@capacitor/assets/@capacitor/cli/rimraf/glob": ["glob@9.3.5", "", { "dependencies": { "fs.realpath": "^1.0.0", "minimatch": "^8.0.2", "minipass": "^4.2.4", "path-scurry": "^1.6.1" } }, "sha512-e1LleDykUz2Iu+MTYdkSsuWX8lvAjAcs0Xef0lNIu0S2wOAzuTxCJtcd9S3cijlwYF18EsU3rzb8jPVobxDh9Q=="], @@ -2613,8 +2694,12 @@ "@capacitor/assets/@capacitor/cli/xml2js/xmlbuilder": ["xmlbuilder@11.0.1", "", {}, "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA=="], + "@trapezedev/project/@ionic/utils-subprocess/@ionic/utils-terminal/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], + "@trapezedev/project/@ionic/utils-subprocess/debug/ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], + "@typescript-eslint/typescript-estree/minimatch/brace-expansion/balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="], + "conventional-changelog-writer/meow/read-pkg-up/find-up": ["find-up@4.1.0", "", { "dependencies": { "locate-path": "^5.0.0", "path-exists": "^4.0.0" } }, "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw=="], "conventional-changelog-writer/meow/read-pkg-up/read-pkg": ["read-pkg@5.2.0", "", { "dependencies": { "@types/normalize-package-data": "^2.4.0", "normalize-package-data": "^2.5.0", "parse-json": "^5.0.0", "type-fest": "^0.6.0" } }, "sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg=="], @@ -2627,8 +2712,6 @@ "conventional-commits-parser/meow/read-pkg-up/type-fest": ["type-fest@0.8.1", "", {}, "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA=="], - "filelist/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], - "get-pkg-repo/through2/readable-stream/isarray": ["isarray@1.0.0", "", {}, "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ=="], "get-pkg-repo/through2/readable-stream/safe-buffer": ["safe-buffer@5.1.2", "", {}, "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="], @@ -2669,6 +2752,8 @@ "workbox-build/glob/minimatch/brace-expansion": ["brace-expansion@5.0.5", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ=="], + "@capacitor/assets/@capacitor/cli/@ionic/utils-subprocess/@ionic/utils-terminal/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], + "@capacitor/assets/@capacitor/cli/rimraf/glob/minimatch": ["minimatch@8.0.7", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-V+1uQNdzybxa14e/p00HZnQNNcTjnRJjDxg2V8wtkjFctq4M7hXFws4oekyTP0Jebeq7QYtpFyOeBAjc88zvYg=="], "@capacitor/assets/@capacitor/cli/rimraf/glob/minipass": ["minipass@4.2.8", "", {}, "sha512-fNzuVyifolSLFL4NzpF+wEF4qrgqaaKX0haXPQEdQ7NKAN+WecoKMHV09YcuL/DHxrUsYQOK3MiuDf7Ip2OXfQ=="], @@ -2747,8 +2832,6 @@ "replace/yargs/find-up/locate-path/p-locate/p-limit": ["p-limit@2.3.0", "", { "dependencies": { "p-try": "^2.0.0" } }, "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w=="], - "@capacitor/assets/@capacitor/cli/rimraf/glob/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], - "conventional-changelog-writer/meow/read-pkg-up/find-up/locate-path/p-locate/p-limit": ["p-limit@2.3.0", "", { "dependencies": { "p-try": "^2.0.0" } }, "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w=="], "conventional-commits-parser/meow/read-pkg-up/find-up/locate-path/p-locate/p-limit": ["p-limit@2.3.0", "", { "dependencies": { "p-try": "^2.0.0" } }, "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w=="], diff --git a/eslint.config.js b/eslint.config.js index 0e24eab..33502b8 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -1,15 +1,23 @@ import js from '@eslint/js'; import globals from 'globals'; import prettierConfig from 'eslint-config-prettier'; +import tsParser from '@typescript-eslint/parser'; +import tseslint from 'typescript-eslint'; +import { defineConfig } from 'eslint/config'; -export default [ +export default defineConfig( { ignores: ['**/dist/**', '**/node_modules/**', '**/legacy/**', '**/bin/**', '**/www/**', '**/public/lib/**'], }, js.configs.recommended, prettierConfig, + tseslint.configs.recommendedTypeChecked, { languageOptions: { + parser: tsParser, // 👈 REQUIRED + parserOptions: { + project: './tsconfig-eslint.json', // 👈 REQUIRED + }, ecmaVersion: 2022, sourceType: 'module', globals: { @@ -18,8 +26,38 @@ export default [ }, }, rules: { - 'no-unused-vars': ['warn', { argsIgnorePattern: '^_' }], - 'no-console': ['warn', { allow: ['log', 'warn', 'error'] }], + '@typescript-eslint/no-unused-vars': [ + 'warn', + { + args: 'all', + argsIgnorePattern: '^_', + caughtErrors: 'all', + caughtErrorsIgnorePattern: '^_', + destructuredArrayIgnorePattern: '^_', + varsIgnorePattern: '^_', + ignoreRestSiblings: true, + }, + ], + 'no-console': ['warn', { allow: ['log', 'warn', 'error', 'debug', 'trace', 'time', 'timeEnd'] }], + 'no-empty': ['error', { allowEmptyCatch: true }], + 'no-async-promise-executor': 'warn', + '@typescript-eslint/await-thenable': 'error', + '@typescript-eslint/no-floating-promises': 'error', + '@typescript-eslint/no-misused-promises': 'off', + '@typescript-eslint/require-await': 'off', + '@typescript-eslint/no-explicit-any': 'warn', + '@typescript-eslint/no-namespace': 'off', }, }, -]; + { + files: ['**/*.js'], + rules: { + '@typescript-eslint/no-unsafe-assignment': 'off', + '@typescript-eslint/no-unsafe-call': 'off', + '@typescript-eslint/no-unsafe-member-access': 'off', + '@typescript-eslint/no-unsafe-argument': 'off', + '@typescript-eslint/no-unsafe-return': 'off', + '@typescript-eslint/prefer-promise-reject-errors': 'off', + }, + } +); diff --git a/package.json b/package.json index 0b25d0d..c0e6b0c 100644 --- a/package.json +++ b/package.json @@ -44,7 +44,7 @@ "miniflare": "^4.20260301.1", "playwright": "^1.58.2", "prettier": "^3.8.1", - "stylelint": "^16.26.1", + "stylelint": "^17.6.0", "stylelint-config-standard": "^39.0.1", "stylelint-config-standard-scss": "^16.0.0", "typescript": "^5.9.3", @@ -69,6 +69,8 @@ "@ffmpeg/util": "^0.12.2", "@kawarp/core": "^1.1.1", "@svta/common-media-library": "^0.18.1", + "@types/wicg-file-system-access": "^2023.10.7", + "@typescript-eslint/eslint-plugin": "^8.57.2", "@uimaxbai/am-lyrics": "^1.1.4", "@vitest/web-worker": "^4.1.2", "appwrite": "^23.0.0", @@ -88,6 +90,7 @@ "shaka-player": "^5.0.7", "simple-icons": "^16.12.0", "svgo": "^4.0.1", + "typescript-eslint": "^8.57.2", "url-toolkit": "^2.2.5", "uuid": "^13.0.0", "vitest": "^4.1.2" diff --git a/tsconfig-eslint.json b/tsconfig-eslint.json new file mode 100644 index 0000000..5f1e6a2 --- /dev/null +++ b/tsconfig-eslint.json @@ -0,0 +1,22 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "ESNext", + "moduleResolution": "Bundler", + "lib": ["ESNext", "DOM", "DOM.Iterable", "webworker"], + "types": ["vite/client", "node", "@types/wicg-file-system-access"], + "baseUrl": ".", + "paths": { + "!/*": ["node_modules/*"] + }, + "allowJs": true, + "checkJs": true, + "resolveJsonModule": true, + "isolatedModules": true, + "verbatimModuleSyntax": true, + "skipLibCheck": true, + "noEmit": true + }, + "include": ["**/*.ts", "*.ts", "**/*.js", "*.js"], + "exclude": ["**/node_modules/*"] +} diff --git a/tsconfig.json b/tsconfig.json index 8c26866..20d9284 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -4,7 +4,7 @@ "module": "ESNext", "moduleResolution": "Bundler", "lib": ["ESNext", "DOM", "DOM.Iterable", "webworker"], - "types": ["vite/client", "node", "./js/global.d.ts"], + "types": ["vite/client", "node", "@types/wicg-file-system-access"], "baseUrl": ".", "paths": { "!/*": ["node_modules/*"] @@ -17,5 +17,6 @@ "skipLibCheck": true, "noEmit": true }, - "include": ["js/**/*.ts", "js/**/*.d.ts"] + "include": ["**/*.ts", "*.ts", "**/*.js", "*.js"], + "exclude": ["**/node_modules/*"] } From 5504e004cca683b2a7ef16b303aa454f7c1354cc Mon Sep 17 00:00:00 2001 From: Daniel <790119+DanTheMan827@users.noreply.github.com> Date: Fri, 27 Mar 2026 00:29:26 -0500 Subject: [PATCH 44/51] fix(hifi): fix tokenExpiry assignment --- js/HiFi.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/HiFi.ts b/js/HiFi.ts index d87a7ae..cb5d9fe 100644 --- a/js/HiFi.ts +++ b/js/HiFi.ts @@ -149,7 +149,7 @@ class HiFiClient { setToken({ token, tokenExpiry, refreshToken }: HiFiClient.TokenOptions & HiFiClient.RefreshTokenOptions) { this.token = token; - this.appTokenExpiry = this.appTokenExpiry; + this.appTokenExpiry = tokenExpiry; this.refreshToken = refreshToken; } From 3a28ef54d1f37f9c49c340e13b5d0f412b847d67 Mon Sep 17 00:00:00 2001 From: Daniel <790119+DanTheMan827@users.noreply.github.com> Date: Fri, 27 Mar 2026 01:26:47 -0500 Subject: [PATCH 45/51] fix(hifi): remove extra await --- js/HiFi.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/HiFi.ts b/js/HiFi.ts index cb5d9fe..1bddd11 100644 --- a/js/HiFi.ts +++ b/js/HiFi.ts @@ -249,7 +249,7 @@ class HiFiClient { while (true) { const unauthorized = res?.status === 401; const previousResponse = res; - const token = await await this.#fetchAppToken({ + const token = await this.#fetchAppToken({ clientId: this.#clientId, clientSecret: this.#clientSecret, signal, From e154215fc54b6bc620a1cfdef07abd28211b3b84 Mon Sep 17 00:00:00 2001 From: Daniel <790119+DanTheMan827@users.noreply.github.com> Date: Fri, 27 Mar 2026 01:40:41 -0500 Subject: [PATCH 46/51] fix(downloads): update downloadTrackWithMetadata to use MusicAPI - Added default parameter for api in downloadTrackWithMetadata function - Updated api call to enrichTrack to use the correct instance - Imported MusicAPI for proper functionality --- js/downloads.js | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/js/downloads.js b/js/downloads.js index 0c9703c..b8e19aa 100644 --- a/js/downloads.js +++ b/js/downloads.js @@ -20,6 +20,7 @@ import { db } from './db.js'; import { modernSettings } from './ModernSettings.js'; import { SVG_CLOSE } from './icons.ts'; import { LyricsManager } from './lyrics.js'; +import { MusicAPI } from './music-api.js'; const downloadTasks = new Map(); const bulkDownloadTasks = new Map(); @@ -1012,7 +1013,13 @@ function completeBulkDownload(notifEl, success = true, message = null) { } } -export async function downloadTrackWithMetadata(track, quality, api, lyricsManager = null, abortController = null) { +export async function downloadTrackWithMetadata( + track, + quality, + api = MusicAPI.instance.tidalAPI, + lyricsManager = null, + abortController = null +) { if (!track) { alert('No track is currently playing'); return; @@ -1024,7 +1031,7 @@ export async function downloadTrackWithMetadata(track, quality, api, lyricsManag return; } - const { enrichedTrack } = await api.tidalAPI.enrichTrack(track, { downloadQuality: quality }); + const { enrichedTrack } = await api.enrichTrack(track, { downloadQuality: quality }); const filename = buildTrackFilename(enrichedTrack, quality); const controller = abortController || new AbortController(); From a282b37d883d973992b9c467d67b72188d58dab1 Mon Sep 17 00:00:00 2001 From: Daniel <790119+DanTheMan827@users.noreply.github.com> Date: Fri, 27 Mar 2026 10:15:47 -0500 Subject: [PATCH 47/51] refactor(hifi): add getArtistBiography --- js/HiFi.ts | 43 +++++++++++++++++++++++++++++++++++++------ js/api.js | 9 ++------- js/global.d.ts | 4 ++++ 3 files changed, 43 insertions(+), 13 deletions(-) diff --git a/js/HiFi.ts b/js/HiFi.ts index 1bddd11..e903a58 100644 --- a/js/HiFi.ts +++ b/js/HiFi.ts @@ -57,6 +57,7 @@ class HiFiClient { readonly #albumTracksMax = 20; readonly #albumTracksQueue: Array<() => void> = []; readonly #countryCode: string; + readonly #locale: string; readonly #clientId: string; readonly #clientSecret: string; readonly #emitter = new EventEmitter(); @@ -216,16 +217,27 @@ class HiFiClient { } static #getOptions({ + locale = 'en_US', countryCode = 'US', baseUrl = null, clientId = HiFiClient.BROWSER_CLIENT_ID, clientSecret = HiFiClient.BROWSER_CLIENT_SECRET, token, tokenExpiry, - refreshToken: tokenRefresh, + refreshToken, storage = [], - }: HiFiClient.ConstructorOptions = {}) { - return { countryCode, baseUrl, clientId, clientSecret, token, tokenExpiry, tokenRefresh, storage }; + }: HiFiClient.ConstructorOptions = {}): WithRequiredKeys { + return { + locale, + countryCode, + baseUrl, + clientId, + clientSecret, + token, + tokenExpiry, + refreshToken, + storage, + }; } async fetchToken(force: boolean = false, signal: AbortSignal | undefined = undefined) { @@ -289,15 +301,16 @@ class HiFiClient { } constructor(options: HiFiClient.ConstructorOptions = {}) { - const { countryCode, baseUrl, clientId, clientSecret, token, tokenExpiry, tokenRefresh, storage } = + const { locale, countryCode, baseUrl, clientId, clientSecret, token, tokenExpiry, refreshToken, storage } = HiFiClient.#getOptions(options); + this.#locale = locale; this.#countryCode = countryCode; this.#baseUrl = baseUrl; this.#clientId = clientId; this.#clientSecret = clientSecret; this.token = token; this.appTokenExpiry = tokenExpiry; - this.refreshToken = tokenRefresh; + this.refreshToken = refreshToken; for (const store of !Array.isArray(storage) ? [storage] : storage) { this.#useStorage(store); @@ -633,6 +646,17 @@ class HiFiClient { return HiFiClient.#jsonResponse({ version: HiFiClient.API_VERSION, albums: page_data, tracks }); } + async getArtistBiography(artistId: number, signal?: AbortSignal) { + const url = `https://api.tidal.com/v1/artists/${artistId}/bio`; + const params = { + locale: this.#locale, + countryCode: this.#countryCode, + }; + const data = await this.#fetchJson(url, params, signal); + + return HiFiClient.#jsonResponse({ version: API_VERSION, data: data }); + } + #buildCoverEntry(cover_slug: string, name?: string | null, track_id?: number | null) { const slug = cover_slug.replace(/-/g, '/'); return { @@ -922,6 +946,8 @@ class HiFiClient { return new TidalResponse( await this.getSimilarAlbums(Number(qp.id), qp.cursor ?? undefined, signal) ); + case '/artist/bio': + return new TidalResponse(await this.getArtistBiography(Number(qp.id), signal)); case '/artist': return new TidalResponse( await this.getArtist( @@ -1027,8 +1053,13 @@ namespace HiFiClient { clientSecret?: string; } - export interface ConstructorOptions extends ClientOptions, TokenOptions, RefreshTokenOptions { + export interface LocaleOptions { + locale?: string; countryCode?: string; + } + + export interface ConstructorOptions + extends LocaleOptions, RefreshTokenOptions, ClientOptions, TokenOptions, RefreshTokenOptions { baseUrl?: string; storage?: Pick[] | Pick; } diff --git a/js/api.js b/js/api.js index eafcb65..c8ed8a3 100644 --- a/js/api.js +++ b/js/api.js @@ -1240,15 +1240,10 @@ export class LosslessAPI { if (cached) return cached; try { - const url = `https://api.tidal.com/v1/artists/${artistId}/bio?locale=en_US&countryCode=GB`; - const response = await fetch(url, { - headers: { - 'X-Tidal-Token': TIDAL_V2_TOKEN, - }, - }); + const response = await HiFiClient.instance.query(`/artist/bio/?id=${artistId}`); if (response.ok) { - const data = await response.json(); + const { data } = await response.json(); if (data && data.text) { const bio = { text: data.text, diff --git a/js/global.d.ts b/js/global.d.ts index c8dbc54..838a09d 100644 --- a/js/global.d.ts +++ b/js/global.d.ts @@ -27,3 +27,7 @@ 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; } + +type WithRequiredKeys = { + [K in keyof T]-?: T[K] | undefined; +}; From ddc986bc520c678a3b98efd859929f3abfb3d748 Mon Sep 17 00:00:00 2001 From: Daniel <790119+DanTheMan827@users.noreply.github.com> Date: Sat, 28 Mar 2026 15:16:02 -0500 Subject: [PATCH 48/51] fix(downloads): update downloadTrackWithMetadata function - Fix reference to enrichTrack - Added detailed JSDoc comments for better documentation - Updated API reference to use MusicAPI.instance for consistency - Improved error handling and metadata enrichment process --- js/downloads.js | 28 ++++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/js/downloads.js b/js/downloads.js index b8e19aa..1230808 100644 --- a/js/downloads.js +++ b/js/downloads.js @@ -1013,10 +1013,31 @@ function completeBulkDownload(notifEl, success = true, message = null) { } } +/** + * Downloads a track with metadata and optionally lyrics. + * @async + * @param {Object} track - The track object to download + * @param {string} quality - The desired audio quality for download + * @param {MusicAPI | LosslessAPI} [api=MusicAPI.instance] - The API instance to use for downloading + * @param {Object} [lyricsManager=null] - Optional manager for fetching and processing lyrics + * @param {AbortController} [abortController=null] - Optional abort controller for cancelling the download + * @returns {Promise} + * @throws {Error} If the download fails (except for AbortError) + * @description + * This function: + * - Validates that a track is provided + * - Prevents duplicate downloads of the same track + * - Enriches track metadata via the API + * - Downloads the audio blob with progress tracking + * - Organizes the file into subfolders based on the folder template + * - Optionally downloads and saves lyrics in LRC format + * - Updates the local media folder cache if using LocalMedia download method + * - Handles errors gracefully and updates download task status + */ export async function downloadTrackWithMetadata( track, quality, - api = MusicAPI.instance.tidalAPI, + api = MusicAPI.instance, lyricsManager = null, abortController = null ) { @@ -1025,13 +1046,16 @@ export async function downloadTrackWithMetadata( return; } + /** @type {LosslessAPI} */ + const tidalAPI = api.tidalAPI || api; + const downloadKey = `track-${track.id}`; if (ongoingDownloads.has(downloadKey)) { showNotification('This track is already being downloaded'); return; } - const { enrichedTrack } = await api.enrichTrack(track, { downloadQuality: quality }); + const { enrichedTrack } = await tidalAPI.enrichTrack(track, { downloadQuality: quality }); const filename = buildTrackFilename(enrichedTrack, quality); const controller = abortController || new AbortController(); From 648e47e1d89b82af82b59fe6e646d4ea9cbeb8dc Mon Sep 17 00:00:00 2001 From: Daniel <790119+DanTheMan827@users.noreply.github.com> Date: Fri, 3 Apr 2026 16:05:29 -0500 Subject: [PATCH 49/51] fix(linting): fix js linting issues --- functions/about/index.js | 2 +- functions/donate/index.js | 2 +- functions/library/index.js | 2 +- functions/parties/index.js | 2 +- functions/podcasts/[id].js | 2 +- functions/recent/index.js | 2 +- functions/settings/index.js | 2 +- .../unreleased/[sheetId]/[projectName].js | 6 +- functions/unreleased/index.js | 2 +- js/HiFi.test.ts | 45 +- js/HiFi.ts | 11 +- js/ModernSettings.ts | 12 +- js/accounts/auth.js | 2 +- js/accounts/pocketbase.js | 11 +- js/api.js | 12 +- js/api.test.ts | 43 +- js/app.js | 165 +++---- js/bulk-download-writer.ts | 10 +- js/cache.js | 2 +- js/commandPalette.js | 43 +- js/container-classes.ts | 3 +- js/dash-downloader.ts | 4 +- js/db.js | 1 - js/doTimed.ts | 38 +- js/downloads.js | 28 +- js/events.js | 334 +++++++------- js/ffmpeg.js | 2 +- js/ffmpeg.test.ts | 4 +- js/ffmpegFormats.ts | 9 +- js/global.d.ts | 4 + js/indexedIterator.ts | 16 + js/lastfm.js | 8 +- js/librefm.js | 8 +- js/listenbrainz.js | 8 +- js/listening-party.js | 72 +-- js/lyrics.js | 17 +- js/maloja.js | 8 +- js/metadata.js | 2 +- js/metadata.mp4.js | 4 +- js/multi-scrobbler.js | 36 +- js/music-api.js | 57 ++- js/player.js | 236 +++++----- js/playlist-importer.js | 4 +- js/profile.js | 258 ++++++----- js/progressEvents.ts | 4 +- js/settings.js | 57 +-- js/side-panel.js | 10 +- js/storage.js | 8 +- js/taglib.ts | 28 +- js/taglib.types.ts | 1 - js/taglib.worker.ts | 24 +- js/themeStore.js | 21 +- js/tracker.js | 2 +- js/ui-interactions.js | 60 +-- js/ui.js | 421 ++++++++++-------- js/utils.js | 2 +- js/visualizer.js | 4 +- js/visualizers/butterchurn.js | 6 +- js/visualizers/kawarp.js | 2 +- js/visualizers/particles.js | 2 +- test-search.js | 2 +- vite-plugin-auth-gate.js | 1 - vite-plugin-blob.ts | 2 +- vite-plugin-svg-use.ts | 41 +- vite.config.ts | 2 +- 65 files changed, 1202 insertions(+), 1037 deletions(-) create mode 100644 js/indexedIterator.ts diff --git a/functions/about/index.js b/functions/about/index.js index 6ebf029..463b901 100644 --- a/functions/about/index.js +++ b/functions/about/index.js @@ -1,5 +1,5 @@ export async function onRequest(context) { - const { request, env } = context; + const { request } = context; const pageUrl = request.url; const metaHtml = ` diff --git a/functions/donate/index.js b/functions/donate/index.js index 7e11bf4..93e334b 100644 --- a/functions/donate/index.js +++ b/functions/donate/index.js @@ -1,5 +1,5 @@ export async function onRequest(context) { - const { request, env } = context; + const { request } = context; const pageUrl = request.url; const metaHtml = ` diff --git a/functions/library/index.js b/functions/library/index.js index 14a4180..5ed5c78 100644 --- a/functions/library/index.js +++ b/functions/library/index.js @@ -1,5 +1,5 @@ export async function onRequest(context) { - const { request, env } = context; + const { request } = context; const pageUrl = request.url; const metaHtml = ` diff --git a/functions/parties/index.js b/functions/parties/index.js index 8087f1a..db05542 100644 --- a/functions/parties/index.js +++ b/functions/parties/index.js @@ -1,5 +1,5 @@ export async function onRequest(context) { - const { request, env } = context; + const { request } = context; const pageUrl = request.url; const metaHtml = ` diff --git a/functions/podcasts/[id].js b/functions/podcasts/[id].js index 25586fe..40fbe98 100644 --- a/functions/podcasts/[id].js +++ b/functions/podcasts/[id].js @@ -50,7 +50,7 @@ export async function onRequest(context) { const title = feed.title; const author = feed.author || feed.ownerName || ''; const episodeCount = feed.episodeCount || 0; - const rawDescription = feed.description || ''; + const _rawDescription = feed.description || ''; const description = author ? `Podcast by ${author} • ${episodeCount} Episodes\nListen on Monochrome` : `Podcast • ${episodeCount} Episodes\nListen on Monochrome`; diff --git a/functions/recent/index.js b/functions/recent/index.js index 4507e29..48797cc 100644 --- a/functions/recent/index.js +++ b/functions/recent/index.js @@ -1,5 +1,5 @@ export async function onRequest(context) { - const { request, env } = context; + const { request } = context; const pageUrl = request.url; const metaHtml = ` diff --git a/functions/settings/index.js b/functions/settings/index.js index f11f4ba..80a81b3 100644 --- a/functions/settings/index.js +++ b/functions/settings/index.js @@ -1,5 +1,5 @@ export async function onRequest(context) { - const { request, env } = context; + const { request } = context; const pageUrl = request.url; const metaHtml = ` diff --git a/functions/unreleased/[sheetId]/[projectName].js b/functions/unreleased/[sheetId]/[projectName].js index 6213856..d6837c8 100644 --- a/functions/unreleased/[sheetId]/[projectName].js +++ b/functions/unreleased/[sheetId]/[projectName].js @@ -1,7 +1,7 @@ // functions/unreleased/[sheetId]/[projectName].js const ARTISTS_NDJSON_URL = 'https://assets.artistgrid.cx/artists.ndjson'; -const ASSETS_BASE_URL = 'https://assets.artistgrid.cx'; +const _ASSETS_BASE_URL = 'https://assets.artistgrid.cx'; const TRACKER_API_ENDPOINTS = [ 'https://trackerapi-1.artistgrid.cx/get/', 'https://trackerapi-2.artistgrid.cx/get/', @@ -14,7 +14,7 @@ function getSheetId(url) { return match ? match[1] : null; } -function normalizeArtistName(name) { +function _normalizeArtistName(name) { return name.toLowerCase().replace(/[^a-z0-9]/g, ''); } @@ -62,7 +62,7 @@ async function fetchTrackerData(sheetId) { } return data; } catch (e) { - console.warn(`Failed to fetch from ${baseUrl}, trying next...`); + console.warn(`Failed to fetch from ${baseUrl}, trying next...`, e); } } return null; diff --git a/functions/unreleased/index.js b/functions/unreleased/index.js index e9e5f84..cd1513c 100644 --- a/functions/unreleased/index.js +++ b/functions/unreleased/index.js @@ -1,5 +1,5 @@ export async function onRequest(context) { - const { request, env } = context; + const { request } = context; const pageUrl = request.url; const metaHtml = ` diff --git a/js/HiFi.test.ts b/js/HiFi.test.ts index 7cf99f9..6a1fecf 100644 --- a/js/HiFi.test.ts +++ b/js/HiFi.test.ts @@ -1,11 +1,12 @@ -import { expect, suite, test } from 'vitest'; +import { expect, test } from 'vitest'; import { HiFiClient, TidalResponse } from './HiFi'; +import type { Album, PlaybackInfo, Track } from './container-classes'; const ARTIST_ID = 3523908; // deadmau5 const ALBUM_ID = 433360012; // deadmau5 - 4x4=12 -const ALBUM_ATMOS = 463900719; // Taylor Swift - The Life of a Showgirl +const _ALBUM_ATMOS = 463900719; // Taylor Swift - The Life of a Showgirl const TRACK_ATMOS = 463900720; // Taylor Swift - The Fate of Ophelia -const TRACK_NO_LOSSLESS = 31097959; // deadmau5 - while(1<2) +const _TRACK_NO_LOSSLESS = 31097959; // deadmau5 - while(1<2) const TRACK_VIDEO = 466464180; // Taylow Swift - The Fate of Ophelia const TRACK_LOSSLESS = 31097949; // deadmau5 - Avaritia const PLAYLIST_ID = '36ea71a8-445e-41a4-82ab-6628c581535d'; // Pop Hits @@ -19,25 +20,25 @@ function checkVersion({ version }: { version?: string }) { expect(version).equals(HiFiClient.API_VERSION); } -async function getJson(res: Response | Promise) { +async function _getJson(res: Response | Promise) { res = await res; expect(res).toBeInstanceOf(Response); expect(res.ok).toBeTruthy(); - return await res.json(); + return (await res.json()) as object; } async function checkRoute( route: string, - routeResult: () => Promise, - checks: (data: any) => Promise, + routeResult: () => Promise, + checks: (data: object) => Promise, mainKey: string | null = 'data' ) { const routeData = await instance.query(route); - const routeRes = await routeResult(); + const routeRes = (await routeResult()) as unknown; expect(routeData).toBeInstanceOf(TidalResponse); expect(routeData).toEqual(routeRes); - const json = await routeData.json(); + const json = (await routeData.json()) as object; checkVersion(json); if (mainKey != null) { @@ -71,7 +72,7 @@ test('Fetch atmos track info', async () => { await checkRoute( `/info/?id=${TRACK_ATMOS}`, () => instance.getInfo(TRACK_ATMOS), - async (info) => { + async (info: { data: Track }) => { expect(info.data.audioModes).toContain('DOLBY_ATMOS'); } ); @@ -81,8 +82,8 @@ test('Fetch track', async () => { await checkRoute( `/track/?id=${TRACK_LOSSLESS}`, () => instance.getTrack(TRACK_LOSSLESS), - async (track) => { - expect(track.data.trackId).toBe(TRACK_LOSSLESS); + async (track: { data: PlaybackInfo }) => { + expect(track?.data?.trackId).toBe(TRACK_LOSSLESS); expect(track.data.assetPresentation).toBeTypeOf('string'); expect(track.data.audioQuality).toBeTypeOf('string'); expect(track.data.manifestMimeType).toBeTypeOf('string'); @@ -102,7 +103,7 @@ test.skipIf(!instance.refreshToken)('Fetch recommendations', async () => { await checkRoute( `/recommendations/?id=${ARTIST_ID}`, () => instance.getRecommendations(ARTIST_ID), - async (rec) => {} + async (_data) => {} ); }); @@ -110,7 +111,7 @@ test('Fetch similar artists', async () => { await checkRoute( `/artist/similar/?id=${ARTIST_ID}`, () => instance.getSimilarArtists(ARTIST_ID), - async (rec) => {}, + async (_data) => {}, 'artists' ); }); @@ -119,7 +120,7 @@ test('Fetch similar albums', async () => { await checkRoute( `/album/similar/?id=${ALBUM_ID}`, () => instance.getSimilarAlbums(ALBUM_ID), - async (rec) => {}, + async (_data) => {}, 'albums' ); }); @@ -128,7 +129,7 @@ test('Fetch artist info', async () => { await checkRoute( `/artist/?id=${ARTIST_ID}`, () => instance.getArtist(ARTIST_ID), - async (info) => { + async (info: { cover: string }) => { expect(info).toHaveProperty('cover'); expect(info.cover).not.toBeUndefined(); }, @@ -144,7 +145,7 @@ test('Search', async () => { instance.search({ q: query, }), - async (res) => {} + async (_res) => {} ); }); @@ -152,7 +153,7 @@ test('Fetch album info', async () => { await checkRoute( `/album/?id=${ALBUM_ID}`, () => instance.getAlbum(ALBUM_ID), - async (info) => { + async (info: { data: Album }) => { expect(info.data).toHaveProperty('cover'); expect(info.data.cover).not.toBeUndefined(); } @@ -163,7 +164,7 @@ test('Fetch playlist info', async () => { await checkRoute( `/playlist/?id=${PLAYLIST_ID}`, () => instance.getPlaylist(PLAYLIST_ID), - async (info) => { + async (info: { playlist: { image: string } }) => { expect(info.playlist).toHaveProperty('image'); expect(info.playlist.image).not.toBeUndefined(); }, @@ -175,7 +176,7 @@ test.skipIf(!instance.refreshToken)('Fetch lyrics ', async () => { await checkRoute( `/lyrics/?id=${TRACK_ATMOS}`, () => instance.getLyrics(TRACK_ATMOS), - async (info) => {}, + async (_info) => {}, 'lyrics' ); }); @@ -184,7 +185,7 @@ test('Fetch video ', async () => { await checkRoute( `/video/?id=${TRACK_VIDEO}`, () => instance.getVideo(TRACK_VIDEO), - async (info) => {}, + async (_info) => {}, 'video' ); }); @@ -193,7 +194,7 @@ test('Fetch track manifests ', async () => { await checkRoute( `/trackManifests/?id=${TRACK_LOSSLESS}`, () => instance.getTrackManifest(TRACK_LOSSLESS), - async (info) => {}, + async (_info) => {}, 'data' ); }); diff --git a/js/HiFi.ts b/js/HiFi.ts index e903a58..0ea4e8d 100644 --- a/js/HiFi.ts +++ b/js/HiFi.ts @@ -1,3 +1,10 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +/* eslint-disable @typescript-eslint/no-unsafe-argument */ +/* eslint-disable @typescript-eslint/no-unsafe-return */ +/* eslint-disable @typescript-eslint/no-unsafe-call */ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ + import { EventEmitter } from 'events'; type Params = Record; @@ -170,7 +177,7 @@ class HiFiClient { scope?: string; signal?: AbortSignal; force?: boolean; - }) { + }): Promise { if (!force && this.token && (this.appTokenExpiry < 0 || Date.now() < this.appTokenExpiry)) return this.token; return await (this.#tokenPromise ??= (async () => { @@ -654,7 +661,7 @@ class HiFiClient { }; const data = await this.#fetchJson(url, params, signal); - return HiFiClient.#jsonResponse({ version: API_VERSION, data: data }); + return HiFiClient.#jsonResponse({ version: HiFiClient.API_VERSION, data: data }); } #buildCoverEntry(cover_slug: string, name?: string | null, track_id?: number | null) { diff --git a/js/ModernSettings.ts b/js/ModernSettings.ts index c114181..0abc2ce 100644 --- a/js/ModernSettings.ts +++ b/js/ModernSettings.ts @@ -12,9 +12,9 @@ import { db } from './db'; * * @template C The accumulated shape of the settings object. */ -class ModernSettings { +class ModernSettings { /** Internal map of pending async operations keyed by unique symbols. */ - #pending: Record> = {}; + #pending: Record> = {}; /** Whether new properties are prevented from being added. */ #finalized: boolean = false; @@ -51,7 +51,7 @@ class ModernSettings { * @param callback Function producing the promise to track. * @returns The created promise. */ - #addPending>(callback: () => C): C { + #addPending>(callback: () => C): C { const sym = Symbol(); return (this.#pending[sym] = callback().finally(() => { @@ -145,14 +145,14 @@ class ModernSettings { const legacyValue = localStorage.getItem(legacy?.key ?? backingKey ?? key); if (legacyValue !== null) { - db.saveSetting(backingKey ?? key, legacy.transformer!(legacyValue)); + await db.saveSetting(backingKey ?? key, legacy.transformer(legacyValue)); localStorage.removeItem(legacy?.key ?? backingKey ?? key); } } } try { - value = (await db.getSetting(backingKey ?? key)) ?? defaultValue; + value = ((await db.getSetting(backingKey ?? key)) as T) ?? defaultValue; } catch { value = defaultValue; } @@ -162,7 +162,7 @@ class ModernSettings { 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)); + void this.#addPending(() => db.saveSetting(backingKey ?? key, value)); }, enumerable: true, }); diff --git a/js/accounts/auth.js b/js/accounts/auth.js index 8fd23df..7e25269 100644 --- a/js/accounts/auth.js +++ b/js/accounts/auth.js @@ -5,7 +5,7 @@ export class AuthManager { constructor() { this.user = null; this.authListeners = []; - this.init(); + this.init().catch(console.error); } async init() { diff --git a/js/accounts/pocketbase.js b/js/accounts/pocketbase.js index 79c793c..4b697c4 100644 --- a/js/accounts/pocketbase.js +++ b/js/accounts/pocketbase.js @@ -128,7 +128,7 @@ const syncManager = { } }, - safeParseInternal(str, fieldName, fallback) { + safeParseInternal(str, _fieldName, fallback) { if (!str) return fallback; if (typeof str !== 'string') return str; try { @@ -136,7 +136,7 @@ const syncManager = { } catch { try { // Recovery attempt: replace illegal internal quotes in name/title fields - const recovered = str.replace(/(:\s*")(.+?)("(?=\s*[,}\n\r]))/g, (match, p1, p2, p3) => { + const recovered = str.replace(/(:\s*")(.+?)("(?=\s*[,}\n\r]))/g, (_match, p1, p2, p3) => { const escapedContent = p2.replace(/(?|window|document|alert|eval/) ) { + // TODO: maybe this could be parsed as json5? + // eslint-disable-next-line @typescript-eslint/no-implied-eval return new Function('return ' + jsFriendly)(); } } @@ -565,11 +567,6 @@ const syncManager = { if (cloudData) { let database = db; - if (typeof database === 'function') { - database = await database(); - } else { - database = await database; - } const localData = { tracks: (await database.getAll('favorites_tracks')) || [], diff --git a/js/api.js b/js/api.js index c8ed8a3..d5ae23d 100644 --- a/js/api.js +++ b/js/api.js @@ -5,10 +5,7 @@ import { delay, isTrackUnavailable, getExtensionFromBlob, - getTrackTitle, - getFullArtistString, getTrackDiscNumber, - getMimeType, } from './utils.js'; import { preferDolbyAtmosSettings, trackDateSettings } from './storage.js'; import { APICache } from './cache.js'; @@ -36,7 +33,6 @@ import { export const DASH_MANIFEST_UNAVAILABLE_CODE = 'DASH_MANIFEST_UNAVAILABLE'; export { resolveDownloadTotalBytes }; -const TIDAL_V2_TOKEN = 'txNoH4kkV41MfH25'; export class LosslessAPI { constructor(settings) { @@ -48,8 +44,8 @@ export class LosslessAPI { this.streamCache = new Map(); setInterval( - () => { - this.cache.clearExpired(); + async () => { + await this.cache.clearExpired(); this.pruneStreamCache(); }, 1000 * 60 * 5 @@ -492,7 +488,7 @@ export class LosslessAPI { await this.cache.set('search_all', query, results); return results; - } catch (error) { + } catch (_error) { // Fallback to individual searches if the backend proxy doesn't support ?q= or throws const [tracks, videos, artists, albums, playlists] = await Promise.all([ this.searchTracks(query, options).catch(() => ({ items: [] })), @@ -1558,7 +1554,7 @@ export class LosslessAPI { } else { throw new Error('No URI in trackManifests response'); } - } catch (err) { + } catch (_err) { // Fallback to /track endpoint } diff --git a/js/api.test.ts b/js/api.test.ts index 6bb12ad..b2a8b29 100644 --- a/js/api.test.ts +++ b/js/api.test.ts @@ -2,7 +2,6 @@ import { expect, test, suite, vi } from 'vitest'; import { apiSettings, preferDolbyAtmosSettings, losslessContainerSettings } from './storage.js'; import { MusicAPI } from './music-api.js'; import { LyricsManager } from './lyrics.js'; -import type { LosslessAPI } from './api.js'; import { HiFiClient } from './HiFi.js'; import { FileRef } from '!/@dantheman827/taglib-ts/src/fileRef.js'; import { Mp4File } from '!/@dantheman827/taglib-ts/src/mp4/mp4File.js'; @@ -13,6 +12,7 @@ import { ByteVector, StringType } from '!/@dantheman827/taglib-ts/src/byteVector import { Mp4Codec } from '!/@dantheman827/taglib-ts/src/mp4/mp4Properties.js'; import { OggFile } from '!/@dantheman827/taglib-ts/src/ogg/oggFile.js'; import { ffmpeg } from './ffmpeg.js'; +import type { Track } from './container-classes.js'; vi.mock(import('./storage.js'), async (importOriginal) => { const mod = await importOriginal(); @@ -46,26 +46,25 @@ vi.mock(import('./doTimed.js'), async (importOriginal) => { return { ...mod, - doTimed: (label: string, fn: () => any) => { - return fn() as any; + doTimed: function (_label: string, fn: () => T): T { + return fn(); }, doTimedAsync ? Promise : T>( - message: string, + _message: string, callback: () => R, throwError: boolean = false ): R { - return new Promise(async (resolve, reject) => { - try { - const ret = await callback(); - resolve(ret); - } catch (err) { - if (throwError) { - reject(err); - return; - } - - resolve(undefined); - } + return new Promise((resolve, reject) => { + Promise.resolve() + .then(callback) + .then(resolve) + .catch((err) => { + if (throwError) { + reject(err as Error); + } else { + resolve(undefined); + } + }); }) as R; }, } satisfies typeof import('./doTimed.js'); @@ -99,15 +98,14 @@ suite('Track Downloads', async () => { const TRACK_ATMOS = 463900720; // Taylor Swift - The Fate of Ophelia const TRACK_NO_LOSSLESS = 31097959; // deadmau5 - while(1<2) - const { LosslessAPI } = await import('./api.js'); await MusicAPI.initialize(apiSettings); await LyricsManager.initialize(apiSettings); await HiFiClient.initialize(); - const api: LosslessAPI = MusicAPI.instance.tidalAPI; + const api = MusicAPI.instance.tidalAPI; async function downloadTrack(trackId: number, quality: string) { - const track = await (await HiFiClient.instance.getInfo(trackId)).json(); + const track = (await (await HiFiClient.instance.getInfo(trackId)).json()) as { data: Track }; return await api.downloadTrack(trackId.toString(), quality, undefined, { track: track.data, triggerDownload: false, @@ -276,7 +274,9 @@ suite('Track Downloads', async () => { ffmpegCalls: 1, }, ])('$display_quality', async ({ quality, container, preferDolbyAtmos, trackId, detection, ffmpegCalls }) => { + // eslint-disable-next-line @typescript-eslint/unbound-method vi.mocked(preferDolbyAtmosSettings.isEnabled).mockReturnValue(preferDolbyAtmos); + // eslint-disable-next-line @typescript-eslint/unbound-method vi.mocked(losslessContainerSettings.getContainer).mockReturnValue(container); const blob = await downloadTrack(trackId, quality); @@ -286,7 +286,6 @@ suite('Track Downloads', async () => { expect(file.isValid).toBe(true); - let trak: Mp4Atom | null = null; let stsd: Mp4Atom | null = null; let stsdData: ByteVector | null = null; @@ -313,13 +312,13 @@ suite('Track Downloads', async () => { trak = null; } expect(trak).toBeInstanceOf(Mp4Atom); - stsd = trak!.find('mdia', 'minf', 'stbl', 'stsd'); + stsd = trak.find('mdia', 'minf', 'stbl', 'stsd'); expect(stsd).toBeInstanceOf(Mp4Atom); await stream.seek(stsd.offset); stsdData = await stream.readBlock(stsd.length); } - stream.seek(streamPosition); + await stream.seek(streamPosition); switch (detection) { case Detection.DolbyAtmos: { diff --git a/js/app.js b/js/app.js index 074929e..a3272e3 100644 --- a/js/app.js +++ b/js/app.js @@ -33,7 +33,6 @@ import { authManager } from './accounts/auth.js'; import { registerSW } from 'virtual:pwa-register'; import { openEditProfile } from './profile.js'; import { ThemeStore } from './themeStore.js'; -import { partyManager } from './listening-party.js'; import './commandPalette.js'; import { initTracker } from './tracker.js'; import { @@ -406,6 +405,7 @@ document.addEventListener('DOMContentLoaded', async () => { // Populate commit info { const repo = 'https://github.com/monochrome-music/monochrome'; + // eslint-disable-next-line no-undef const hash = typeof __COMMIT_HASH__ !== 'undefined' ? __COMMIT_HASH__ : 'dev'; const commitLink = hash !== 'dev' && hash !== 'unknown' @@ -469,8 +469,7 @@ document.addEventListener('DOMContentLoaded', async () => { await Player.initialize(audioPlayer, MusicAPI.instance, currentQuality); // Initialize tracker - initTracker(); - + initTracker().catch(console.error); const castBtn = document.getElementById('cast-btn'); initializeCasting(audioPlayer, castBtn); @@ -585,7 +584,7 @@ document.addEventListener('DOMContentLoaded', async () => { // 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(); + scanLocalMediaFolder().catch(console.error); const scrobbler = new MultiScrobbler(); window.monochromeScrobbler = scrobbler; @@ -995,9 +994,9 @@ document.addEventListener('DOMContentLoaded', async () => { } }); - document.getElementById('download-current-btn')?.addEventListener('click', () => { + document.getElementById('download-current-btn')?.addEventListener('click', async () => { if (Player.instance.currentTrack) { - handleTrackAction( + await handleTrackAction( 'download', Player.instance.currentTrack, Player.instance, @@ -1420,14 +1419,14 @@ document.addEventListener('DOMContentLoaded', async () => { if (editingId) { // Edit const cover = document.getElementById('playlist-cover-input').value.trim(); - db.getPlaylist(editingId).then(async (playlist) => { + await db.getPlaylist(editingId).then(async (playlist) => { if (playlist) { playlist.name = name; playlist.cover = cover; playlist.description = description; await handlePublicStatus(playlist); await db.performTransaction('user_playlists', 'readwrite', (store) => store.put(playlist)); - syncManager.syncUserPlaylist(playlist, 'update'); + await syncManager.syncUserPlaylist(playlist, 'update'); UIRenderer.instance.renderLibraryPage(); // Also update current page if we are on it if (window.location.pathname === `/userplaylist/${editingId}`) { @@ -1948,7 +1947,7 @@ document.addEventListener('DOMContentLoaded', async () => { console.log(`Added ${tracks.length} tracks (including pending)`); } - db.createPlaylist(name, tracks, cover, description).then(async (playlist) => { + await db.createPlaylist(name, tracks, cover, description).then(async (playlist) => { await handlePublicStatus(playlist); // Update DB again with isPublic flag await db.performTransaction('user_playlists', 'readwrite', (store) => store.put(playlist)); @@ -1971,7 +1970,7 @@ document.addEventListener('DOMContentLoaded', async () => { if (e.target.closest('.edit-playlist-btn')) { const card = e.target.closest('.user-playlist'); const playlistId = card.dataset.userPlaylistId; - db.getPlaylist(playlistId).then(async (playlist) => { + await db.getPlaylist(playlistId).then(async (playlist) => { if (playlist) { const modal = document.getElementById('playlist-modal'); document.getElementById('playlist-modal-title').textContent = 'Edit Playlist'; @@ -1991,7 +1990,10 @@ document.addEventListener('DOMContentLoaded', async () => { shareBtn.style.display = playlist.isPublic ? 'flex' : 'none'; shareBtn.onclick = () => { const url = getShareUrl(`/userplaylist/${playlist.id}`); - navigator.clipboard.writeText(url).then(() => alert('Link copied to clipboard!')); + navigator.clipboard + .writeText(url) + .then(() => alert('Link copied to clipboard!')) + .catch(console.error); }; } @@ -2030,73 +2032,77 @@ document.addEventListener('DOMContentLoaded', async () => { const card = e.target.closest('.user-playlist'); const playlistId = card.dataset.userPlaylistId; if (confirm('Are you sure you want to delete this playlist?')) { - db.deletePlaylist(playlistId).then(() => { - syncManager.syncUserPlaylist({ id: playlistId }, 'delete'); - UIRenderer.instance.renderLibraryPage(); - }); + await db.deletePlaylist(playlistId); + await syncManager.syncUserPlaylist({ id: playlistId }, 'delete'); + UIRenderer.instance.renderLibraryPage(); } } if (e.target.closest('#edit-playlist-btn')) { const playlistId = window.location.pathname.split('/')[2]; - db.getPlaylist(playlistId).then((playlist) => { - if (playlist) { - const modal = document.getElementById('playlist-modal'); - document.getElementById('playlist-modal-title').textContent = 'Edit Playlist'; - document.getElementById('playlist-name-input').value = playlist.name; - document.getElementById('playlist-cover-input').value = playlist.cover || ''; - document.getElementById('playlist-description-input').value = playlist.description || ''; + await db + .getPlaylist(playlistId) + .then((playlist) => { + if (playlist) { + const modal = document.getElementById('playlist-modal'); + document.getElementById('playlist-modal-title').textContent = 'Edit Playlist'; + document.getElementById('playlist-name-input').value = playlist.name; + document.getElementById('playlist-cover-input').value = playlist.cover || ''; + document.getElementById('playlist-description-input').value = playlist.description || ''; - const publicToggle = document.getElementById('playlist-public-toggle'); - const shareBtn = document.getElementById('playlist-share-btn'); + const publicToggle = document.getElementById('playlist-public-toggle'); + const shareBtn = document.getElementById('playlist-share-btn'); - if (publicToggle) publicToggle.checked = !!playlist.isPublic; - if (shareBtn) { - shareBtn.style.display = playlist.isPublic ? 'flex' : 'none'; - shareBtn.onclick = () => { - const url = getShareUrl(`/userplaylist/${playlist.id}`); - navigator.clipboard.writeText(url).then(() => alert('Link copied to clipboard!')); - }; + if (publicToggle) publicToggle.checked = !!playlist.isPublic; + if (shareBtn) { + shareBtn.style.display = playlist.isPublic ? 'flex' : 'none'; + shareBtn.onclick = async () => { + const url = getShareUrl(`/userplaylist/${playlist.id}`); + await navigator.clipboard + .writeText(url) + .then(() => alert('Link copied to clipboard!')) + .catch(console.error); + }; + } + + // Set cover upload state - show URL input if there's an existing cover + const coverUploadBtn = document.getElementById('playlist-cover-upload-btn'); + const coverUrlInput = document.getElementById('playlist-cover-input'); + const coverToggleUrlBtn = document.getElementById('playlist-cover-toggle-url-btn'); + if (playlist.cover) { + if (coverUploadBtn) coverUploadBtn.style.display = 'none'; + if (coverUrlInput) coverUrlInput.style.display = 'block'; + if (coverToggleUrlBtn) { + coverToggleUrlBtn.textContent = 'Upload'; + coverToggleUrlBtn.title = 'Switch to file upload'; + } + } else { + if (coverUploadBtn) { + coverUploadBtn.style.flex = '1'; + coverUploadBtn.style.display = 'flex'; + } + if (coverUrlInput) coverUrlInput.style.display = 'none'; + if (coverToggleUrlBtn) { + coverToggleUrlBtn.textContent = 'or URL'; + coverToggleUrlBtn.title = 'Switch to URL input'; + } + } + + modal.dataset.editingId = playlistId; + document.getElementById('import-section').style.display = 'none'; + modal.classList.add('active'); + document.getElementById('playlist-name-input').focus(); } - - // Set cover upload state - show URL input if there's an existing cover - const coverUploadBtn = document.getElementById('playlist-cover-upload-btn'); - const coverUrlInput = document.getElementById('playlist-cover-input'); - const coverToggleUrlBtn = document.getElementById('playlist-cover-toggle-url-btn'); - if (playlist.cover) { - if (coverUploadBtn) coverUploadBtn.style.display = 'none'; - if (coverUrlInput) coverUrlInput.style.display = 'block'; - if (coverToggleUrlBtn) { - coverToggleUrlBtn.textContent = 'Upload'; - coverToggleUrlBtn.title = 'Switch to file upload'; - } - } else { - if (coverUploadBtn) { - coverUploadBtn.style.flex = '1'; - coverUploadBtn.style.display = 'flex'; - } - if (coverUrlInput) coverUrlInput.style.display = 'none'; - if (coverToggleUrlBtn) { - coverToggleUrlBtn.textContent = 'or URL'; - coverToggleUrlBtn.title = 'Switch to URL input'; - } - } - - modal.dataset.editingId = playlistId; - document.getElementById('import-section').style.display = 'none'; - modal.classList.add('active'); - document.getElementById('playlist-name-input').focus(); - } - }); + }) + .catch(console.error); } if (e.target.closest('#delete-playlist-btn')) { const playlistId = window.location.pathname.split('/')[2]; if (confirm('Are you sure you want to delete this playlist?')) { - db.deletePlaylist(playlistId).then(() => { - syncManager.syncUserPlaylist({ id: playlistId }, 'delete'); - navigate('/library'); - }); + await db.deletePlaylist(playlistId); + await syncManager.syncUserPlaylist({ id: playlistId }, 'delete'); + navigate('/library'); } } @@ -2105,7 +2111,7 @@ document.addEventListener('DOMContentLoaded', async () => { const btn = e.target.closest('.remove-from-playlist-btn'); const playlistId = window.location.pathname.split('/')[2]; - db.getPlaylist(playlistId).then(async (playlist) => { + await db.getPlaylist(playlistId).then(async (playlist) => { let trackId = null; let trackType = null; @@ -2124,7 +2130,7 @@ document.addEventListener('DOMContentLoaded', async () => { if (trackId) { const updatedPlaylist = await db.removeTrackFromPlaylist(playlistId, trackId, trackType); - syncManager.syncUserPlaylist(updatedPlaylist, 'update'); + await syncManager.syncUserPlaylist(updatedPlaylist, 'update'); const scrollTop = document.querySelector('.main-content').scrollTop; await UIRenderer.instance.renderPlaylistPage(playlistId, 'user'); document.querySelector('.main-content').scrollTop = scrollTop; @@ -2645,7 +2651,7 @@ document.addEventListener('DOMContentLoaded', async () => { // PWA Update Logic if (window.__AUTH_GATE__) { - disablePwaForAuthGate(); + await disablePwaForAuthGate().catch(console.error); } else { const updateSW = registerSW({ onNeedRefresh() { @@ -2763,10 +2769,10 @@ document.addEventListener('DOMContentLoaded', async () => { ); }); } else { - headerAccountBtn.addEventListener('click', (e) => { + headerAccountBtn.addEventListener('click', async (e) => { e.stopPropagation(); headerAccountDropdown.classList.toggle('active'); - updateAccountDropdown(); + await updateAccountDropdown(); }); } @@ -2839,8 +2845,8 @@ document.addEventListener('DOMContentLoaded', async () => { `; - document.getElementById('header-create-profile').onclick = () => { - openEditProfile(); + document.getElementById('header-create-profile').onclick = async () => { + openEditProfile().catch(console.error); headerAccountDropdown.classList.remove('active'); }; } @@ -2928,7 +2934,7 @@ function showMissingTracksNotification(missingTracks, playlistName) { const newCopyBtn = copyBtn.cloneNode(true); copyBtn.parentNode.replaceChild(newCopyBtn, copyBtn); - newCopyBtn.addEventListener('click', () => { + newCopyBtn.addEventListener('click', async () => { const header = `Missing songs from ${playlistName} import:\n\n`; const textToCopy = header + @@ -2940,11 +2946,14 @@ function showMissingTracksNotification(missingTracks, playlistName) { }) .join('\n'); - navigator.clipboard.writeText(textToCopy).then(() => { - const originalText = newCopyBtn.textContent; - newCopyBtn.textContent = 'Copied!'; - setTimeout(() => (newCopyBtn.textContent = originalText), 2000); - }); + await navigator.clipboard + .writeText(textToCopy) + .then(async () => { + const originalText = newCopyBtn.textContent; + newCopyBtn.textContent = 'Copied!'; + setTimeout(() => (newCopyBtn.textContent = originalText), 2000); + }) + .catch(console.error); }); } diff --git a/js/bulk-download-writer.ts b/js/bulk-download-writer.ts index 4b6eae4..7efea34 100644 --- a/js/bulk-download-writer.ts +++ b/js/bulk-download-writer.ts @@ -69,9 +69,7 @@ export class ZipStreamWriter implements IBulkDownloadWriter { constructor(private readonly suggestedFilename: string) {} async write(files: AsyncIterable): Promise { - // showSaveFilePicker 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 fileHandle = await (window as any).showSaveFilePicker({ + const fileHandle = await window.showSaveFilePicker({ suggestedName: this.suggestedFilename, types: [{ description: 'ZIP Archive', accept: { 'application/zip': ['.zip'] } }], }); @@ -134,8 +132,7 @@ export class FolderPickerWriter implements IBulkDownloadWriter { // 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' }); + const permission = await savedHandle.requestPermission({ mode: 'readwrite' }); if (permission === 'granted') { return new FolderPickerWriter(savedHandle); } @@ -145,9 +142,8 @@ export class FolderPickerWriter implements IBulkDownloadWriter { } // 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 { - const dirHandle: FileSystemDirectoryHandle = await (window as any).showDirectoryPicker({ + const dirHandle: FileSystemDirectoryHandle = await window.showDirectoryPicker({ mode: 'readwrite', }); return new FolderPickerWriter(dirHandle); diff --git a/js/cache.js b/js/cache.js index 38501ba..7f7dfd5 100644 --- a/js/cache.js +++ b/js/cache.js @@ -7,7 +7,7 @@ export class APICache { this.dbName = 'monochrome-cache'; this.dbVersion = 1; this.db = null; - this.initDB(); + this.initDB().catch(console.error); } async initDB() { diff --git a/js/commandPalette.js b/js/commandPalette.js index 5d45981..ca095db 100644 --- a/js/commandPalette.js +++ b/js/commandPalette.js @@ -338,9 +338,9 @@ class CommandPalette { icon: 'trash', label: 'Clear Queue', keywords: ['wipe', 'clear', 'empty', 'queue'], - action: () => { + action: async () => { Player.instance.wipeQueue(); - this.notify('Queue cleared'); + await this.notify('Queue cleared'); }, }, { @@ -674,7 +674,7 @@ class CommandPalette { keywords: ['edit', 'profile', 'username', 'avatar', 'display name'], action: async () => { const { openEditProfile } = await import('./profile.js'); - openEditProfile(); + await openEditProfile(); }, }, { @@ -780,7 +780,7 @@ class CommandPalette { this.updateSelection(); } else if (e.key === 'Enter') { e.preventDefault(); - this.executeSelected(); + this.executeSelected().catch(console.error); } else if (e.key === 'Escape') { if (this.settingsMode) { this.settingsMode = false; @@ -1036,9 +1036,9 @@ class CommandPalette { el.innerHTML = `${iconHtml}
${escapeHtml(item.label)}${descHtml}
${shortcutHtml}`; - el.addEventListener('click', () => { + el.addEventListener('click', async () => { this.selectedIndex = index; - this.executeSelected(); + await this.executeSelected(); }); el.addEventListener('mouseenter', () => { @@ -1171,14 +1171,14 @@ class CommandPalette { if (opt.dataset.theme === theme) opt.classList.add('active'); else opt.classList.remove('active'); }); - this.notify(`Theme set to ${theme}`); + await this.notify(`Theme set to ${theme}`); } async toggleVisualizer() { const { visualizerSettings } = await import('./storage.js'); const current = visualizerSettings.isEnabled(); visualizerSettings.setEnabled(!current); - this.notify(`Visualizer ${!current ? 'enabled' : 'disabled'}`); + await this.notify(`Visualizer ${!current ? 'enabled' : 'disabled'}`); const overlay = document.getElementById('fullscreen-cover-overlay'); if (overlay && getComputedStyle(overlay).display !== 'none') { @@ -1192,7 +1192,7 @@ class CommandPalette { if (UIRenderer.instance.visualizer) { UIRenderer.instance.visualizer.setPreset(preset); } - this.notify(`Visualizer preset: ${preset}`); + await this.notify(`Visualizer preset: ${preset}`); } async setQuality(quality) { @@ -1225,13 +1225,13 @@ class CommandPalette { const downloadSelect = document.getElementById('download-quality-setting'); if (downloadSelect) downloadSelect.value = dlQuality; - this.notify(`Quality set to ${qualityNames[quality] || quality}`); + await this.notify(`Quality set to ${qualityNames[quality] || quality}`); } - setSleepTimer(minutes) { + async setSleepTimer(minutes) { if (Player.instance) { Player.instance.setSleepTimer(minutes); - this.notify(`Sleep timer: ${minutes} minutes`); + await this.notify(`Sleep timer: ${minutes} minutes`); } } @@ -1242,7 +1242,7 @@ class CommandPalette { const queue = player.getCurrentQueue(); if (queue.length === 0) { - this.notify('Queue is empty'); + await this.notify('Queue is empty'); return; } @@ -1250,7 +1250,7 @@ class CommandPalette { const scrobbler = window.monochromeScrobbler; let likedCount = 0; - this.notify('Liking all tracks in queue...'); + await this.notify('Liking all tracks in queue...'); for (const track of queue) { const isLiked = await db.isFavorite('track', track.id); if (!isLiked) { @@ -1258,7 +1258,7 @@ class CommandPalette { likedCount++; } } - this.notify(`Liked ${likedCount} new track(s)`); + await this.notify(`Liked ${likedCount} new track(s)`); } async downloadQueue() { @@ -1268,40 +1268,39 @@ class CommandPalette { const queue = player.getCurrentQueue(); if (queue.length === 0) { - this.notify('Queue is empty'); + await this.notify('Queue is empty'); return; } const { downloadTracks } = await import('./downloads.js'); const { downloadQualitySettings } = await import('./storage.js'); - downloadTracks(queue, ui.api, downloadQualitySettings.getQuality(), ui.lyricsManager); + await downloadTracks(queue, ui.api, downloadQualitySettings.getQuality(), ui.lyricsManager); } async createPlaylist() { const name = `New Playlist ${new Date().toLocaleDateString()}`; await db.createPlaylist(name); navigate('/library'); - this.notify('Playlist created'); + await this.notify('Playlist created'); } async createFolder() { const name = `New Folder ${new Date().toLocaleDateString()}`; await db.createFolder(name); navigate('/library'); - this.notify('Folder created'); + await this.notify('Folder created'); } async clearCache() { const api = UIRenderer.instance.api; if (api) { await api.clearCache(); - this.notify('Cache cleared'); + await this.notify('Cache cleared'); } } async notify(message) { - const { showNotification } = await import('./downloads.js'); - showNotification(message); + await import('./downloads.js').then((m) => m.showNotification(message)).catch(console.error); } } diff --git a/js/container-classes.ts b/js/container-classes.ts index 57b3df9..bd74872 100644 --- a/js/container-classes.ts +++ b/js/container-classes.ts @@ -85,7 +85,7 @@ export class MediaMetadata extends BaseContainer { } export class Artist extends BaseContainer { - handle: any; + handle: unknown; id: number; name: string; picture: string; @@ -99,6 +99,7 @@ export class Artist extends BaseContainer { export class EnrichedTrack extends Track { declare album: TrackAlbum | EnrichedAlbum; + // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-redundant-type-constituents declare replayGain: any | ReplayGain; constructor(data: object) { diff --git a/js/dash-downloader.ts b/js/dash-downloader.ts index 21391ce..7ac3a93 100644 --- a/js/dash-downloader.ts +++ b/js/dash-downloader.ts @@ -204,13 +204,13 @@ export class DashDownloader { const resolveTemplate = (template: string, number: number, time: number): string => { return template .replace(/\$RepresentationID\$/g, repId ?? '') - .replace(/\$Number(?:%0([0-9]+)d)?\$/g, (_, width) => { + .replace(/\$Number(?:%0([0-9]+)d)?\$/g, (_, width: string) => { if (width) { return number.toString().padStart(parseInt(width), '0'); } return number.toString(); }) - .replace(/\$Time(?:%0([0-9]+)d)?\$/g, (_, width) => { + .replace(/\$Time(?:%0([0-9]+)d)?\$/g, (_, width: string) => { if (width) { return time.toString().padStart(parseInt(width), '0'); } diff --git a/js/db.js b/js/db.js index 9017296..95c306b 100644 --- a/js/db.js +++ b/js/db.js @@ -783,7 +783,6 @@ export class MusicDatabase { } // Return lightweight copy without tracks - // eslint-disable-next-line no-unused-vars const { tracks, ...minified } = playlist; return minified; }); diff --git a/js/doTimed.ts b/js/doTimed.ts index 1e5d123..58b6a7c 100644 --- a/js/doTimed.ts +++ b/js/doTimed.ts @@ -24,24 +24,30 @@ export function doTimedAsync ? Promise : T>( 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); + return new Promise((resolve, reject) => { + (async () => { + 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) { + if (err instanceof Error) { + reject(err); + } else { + reject(new Error(String(err))); + } + } else { + resolve(undefined as R); + } + } finally { + console.timeEnd(message + hiddenId); } - } finally { - console.timeEnd(message + hiddenId); - } + })().catch(reject); }) as R; } else { - return callback() as R; + return callback(); } } diff --git a/js/downloads.js b/js/downloads.js index 1230808..b8ec561 100644 --- a/js/downloads.js +++ b/js/downloads.js @@ -17,10 +17,10 @@ import { ZipStreamWriter, ZipBlobWriter, FolderPickerWriter, SequentialFileWrite import { FfmpegProgress } from './ffmpeg.types.js'; import { DownloadProgress, ProgressMessage, SegmentedDownloadProgress } from './progressEvents.js'; import { db } from './db.js'; -import { modernSettings } from './ModernSettings.js'; +import { BulkDownloadMethod, modernSettings } from './ModernSettings.js'; import { SVG_CLOSE } from './icons.ts'; -import { LyricsManager } from './lyrics.js'; import { MusicAPI } from './music-api.js'; +import { LyricsManager } from './lyrics.js'; const downloadTasks = new Map(); const bulkDownloadTasks = new Map(); @@ -167,7 +167,7 @@ export function showNotification(message) { }, 1500); } -export function addDownloadTask(trackId, track, filename, api, abortController) { +export function addDownloadTask(trackId, track, _filename, api, abortController) { const container = createDownloadNotification(); const taskEl = document.createElement('div'); @@ -508,7 +508,7 @@ async function createSingleTrackFolderWriter() { const method = modernSettings.bulkDownloadMethod; const hasFolderPicker = 'showDirectoryPicker' in window; - if (method === 'local') { + if (method === BulkDownloadMethod.LocalMedia) { const localHandle = await db.getSetting('local_folder_handle'); if (hasFolderPicker && localHandle && typeof localHandle.requestPermission === 'function') { try { @@ -521,7 +521,7 @@ async function createSingleTrackFolderWriter() { return null; } - if (method === 'folder' && hasFolderPicker) { + if (method === BulkDownloadMethod.Folder && hasFolderPicker) { const rememberFolder = modernSettings.rememberBulkDownloadFolder; const savedHandle = rememberFolder ? await db.getSetting('bulk_download_folder_handle') : null; // Try to reuse the saved handle silently first. @@ -564,7 +564,7 @@ async function createBulkWriter(folderName) { const hasFolderPicker = 'showDirectoryPicker' in window; // ── Local Media Folder method ──────────────────────────────────────────── - if (method === 'local') { + if (method === BulkDownloadMethod.LocalMedia) { const localHandle = await db.getSetting('local_folder_handle'); if (hasFolderPicker) { // Browser mode: try to reuse the stored handle with write permission @@ -594,7 +594,7 @@ async function createBulkWriter(folderName) { } // ── Folder Picker method ───────────────────────────────────────────────── - if (method === 'folder' && hasFolderPicker) { + if (method === BulkDownloadMethod.Folder && hasFolderPicker) { const rememberFolder = modernSettings.rememberBulkDownloadFolder; const savedHandle = rememberFolder ? await db.getSetting('bulk_download_folder_handle') : null; try { @@ -614,7 +614,7 @@ async function createBulkWriter(folderName) { } } - if (method === 'individual') { + if (method === BulkDownloadMethod.Individual) { return SequentialFileWriter; } // method === 'zip' (or folder picker unavailable as fallback) @@ -659,7 +659,7 @@ async function startBulkDownload({ completeBulkDownload(notification, true); // If the download went to the local media folder, refresh the local library. - if (modernSettings.bulkDownloadMethod === 'local') { + if (modernSettings.bulkDownloadMethod === BulkDownloadMethod.LocalMedia) { window.refreshLocalMediaFolder?.(); } } catch (error) { @@ -672,7 +672,7 @@ async function startBulkDownload({ } } -export async function downloadTracks(tracks, api, quality, lyricsManager = null) { +export async function downloadTracks(tracks, api, quality, _lyricsManager = null) { const folderName = `Queue - ${new Date().toISOString().slice(0, 10)}`; await startBulkDownload({ tracks, @@ -687,7 +687,7 @@ export async function downloadTracks(tracks, api, quality, lyricsManager = null) }); } -export async function downloadAlbum(album, tracks, api, quality, lyricsManager = null) { +export async function downloadAlbum(album, tracks, api, quality, _lyricsManager = null) { const releaseDateStr = album.releaseDate || (tracks[0]?.streamStartDate ? tracks[0].streamStartDate.split('T')[0] : ''); const releaseDate = releaseDateStr ? new Date(releaseDateStr) : null; @@ -712,7 +712,7 @@ export async function downloadAlbum(album, tracks, api, quality, lyricsManager = }); } -export async function downloadPlaylist(playlist, tracks, api, quality, lyricsManager = null) { +export async function downloadPlaylist(playlist, tracks, api, quality, _lyricsManager = null) { const folderName = formatPathTemplate(modernSettings.folderTemplate, { albumTitle: playlist.title, albumArtist: 'Playlist', @@ -1120,7 +1120,7 @@ export async function downloadTrackWithMetadata( // 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') { + if (modernSettings.bulkDownloadMethod === BulkDownloadMethod.LocalMedia) { window.refreshLocalMediaFolder?.(blob, finalFilename); } @@ -1136,7 +1136,7 @@ export async function downloadTrackWithMetadata( } } -export async function downloadLikedTracks(tracks, api, quality, lyricsManager = null) { +export async function downloadLikedTracks(tracks, api, quality, _lyricsManager = null) { const folderName = `Liked Tracks - ${new Date().toISOString().slice(0, 10)}`; await startBulkDownload({ tracks, diff --git a/js/events.js b/js/events.js index 36e6ca2..54186cf 100644 --- a/js/events.js +++ b/js/events.js @@ -56,6 +56,9 @@ import { } from './analytics.js'; import { SVG_BIN, SVG_MUTE, SVG_PAUSE, SVG_PLAY, SVG_VOLUME, SVG_CHECKBOX, SVG_CHECKBOX_CHECKED } from './icons.js'; import { partyManager } from './listening-party.js'; +import { MusicAPI } from './music-api.js'; +import { LyricsManager } from './lyrics.js'; +import { Player } from './player.js'; let currentTrackIdForWaveform = null; @@ -78,21 +81,21 @@ function handleTrackTouchStart(e) { isLongPress = false; longPressTrackItem = trackItem; - longPressTimer = setTimeout(() => { + longPressTimer = setTimeout(async () => { isLongPress = true; toggleTrackSelection(trackItem, true, false); - hapticLongPress(); + await hapticLongPress(); }, LONG_PRESS_DURATION); } -function handleTrackTouchMove(e) { +function handleTrackTouchMove(_e) { if (longPressTimer) { clearTimeout(longPressTimer); longPressTimer = null; } } -function handleTrackTouchEnd(e) { +function handleTrackTouchEnd(_e) { if (longPressTimer) { clearTimeout(longPressTimer); longPressTimer = null; @@ -204,7 +207,7 @@ function toggleTrackSelection(trackItem, ctrlHeld, shiftHeld) { document.body.classList.toggle('multi-select-mode', trackSelection.isSelecting); } -function showMultiSelectPlaylistModal(tracks) { +async function showMultiSelectPlaylistModal(tracks) { const modal = document.createElement('div'); modal.className = 'modal-overlay'; modal.style.cssText = @@ -237,7 +240,7 @@ function showMultiSelectPlaylistModal(tracks) { document.body.appendChild(modal); document.body.style.overflow = 'hidden'; - db.getPlaylists(true).then((playlists) => { + await db.getPlaylists(true).then((playlists) => { const listEl = modal.querySelector('.playlist-list'); if (playlists.length === 0) { listEl.innerHTML = '
No playlists yet
'; @@ -260,17 +263,17 @@ function showMultiSelectPlaylistModal(tracks) { for (const track of tracks) { await db.addTrackToPlaylist(playlistId, track); } - syncManager.syncUserPlaylist(await db.getPlaylist(playlistId), 'update'); + await syncManager.syncUserPlaylist(await db.getPlaylist(playlistId), 'update'); showNotification(`Added ${tracks.length} tracks to playlist`); closeModal(); }); }); }); - modal.querySelector('.create-new-playlist').addEventListener('click', () => { + modal.querySelector('.create-new-playlist').addEventListener('click', async () => { const name = prompt('Playlist name:'); if (name) { - db.createPlaylist(name, tracks).then((playlist) => { + await db.createPlaylist(name, tracks).then((_playlist) => { showNotification(`Created playlist "${name}" with ${tracks.length} tracks`); closeModal(); }); @@ -278,127 +281,132 @@ function showMultiSelectPlaylistModal(tracks) { }); } -export function initializePlayerEvents(player, audioPlayer, scrobbler, ui) { - const playPauseBtn = document.querySelector('.now-playing-bar .play-pause-btn'); - const nextBtn = document.getElementById('next-btn'); - const prevBtn = document.getElementById('prev-btn'); - const shuffleBtn = document.getElementById('shuffle-btn'); - const repeatBtn = document.getElementById('repeat-btn'); - const homeStartRadioBtn = document.getElementById('home-start-infinite-radio-btn'); - const sleepTimerBtnDesktop = document.getElementById('sleep-timer-btn-desktop'); +const playPauseBtn = document.querySelector('.now-playing-bar .play-pause-btn'); +const nextBtn = document.getElementById('next-btn'); +const prevBtn = document.getElementById('prev-btn'); +const shuffleBtn = document.getElementById('shuffle-btn'); +const repeatBtn = document.getElementById('repeat-btn'); +const homeStartRadioBtn = document.getElementById('home-start-infinite-radio-btn'); +const sleepTimerBtnDesktop = document.getElementById('sleep-timer-btn-desktop'); - const volumeBar = document.getElementById('volume-bar'); - const volumeFill = document.getElementById('volume-fill'); - const volumeBtn = document.getElementById('volume-btn'); +const _volumeBar = document.getElementById('volume-bar'); +const volumeFill = document.getElementById('volume-fill'); +const volumeBtn = document.getElementById('volume-btn'); - const updateVolumeUI = () => { - const activeEl = player.activeElement; - const { muted } = activeEl; - const volume = player.userVolume; - 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}%`; - }; +const updateVolumeUI = () => { + const activeEl = Player.instance.activeElement; + const { muted } = activeEl; + const volume = Player.instance.userVolume; + 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}%`; +}; - 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 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 -
- - - - - -
- +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 +
+ + + + + +
+ `; - document.body.appendChild(bar); + 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); - } + bar.querySelectorAll('button').forEach((btn) => { + btn.addEventListener('click', () => handleSelectionAction(btn.dataset.action)); }); - - 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; - } } + const count = trackSelection.selectedIds.size; + bar.querySelector('.selection-count').textContent = `${count} selected`; + bar.classList.toggle('visible', count > 0); +} + +async 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.instance.setQueue(selectedTracks, 0); + document.getElementById('shuffle-btn').classList.remove('active'); + Player.instance.playTrackFromQueue(); + } + break; + case 'add-to-queue-selected': + if (selectedTracks.length > 0) { + Player.instance.addToQueue(selectedTracks); + if (window.renderQueueFunction) await window.renderQueueFunction(); + showNotification(`Added ${selectedTracks.length} tracks to queue`); + } + break; + case 'add-to-playlist-selected': + if (selectedTracks.length > 0) { + await showMultiSelectPlaylistModal(selectedTracks); + } + break; + case 'download-selected': + if (selectedTracks.length > 0) { + showNotification(`Downloading ${selectedTracks.length} tracks`); + for (const track of selectedTracks) { + await downloadTrackWithMetadata( + track, + downloadQualitySettings.getQuality(), + MusicAPI.instance.tidalAPI, + LyricsManager.instance + ); + } + } + break; + case 'like-selected': + for (const track of selectedTracks) { + const added = await db.toggleFavorite('track', track); + await syncManager.syncLibraryItem('track', track, added); + } + showNotification(`Liked ${selectedTracks.length} tracks`); + break; + case 'clear-selection': + clearSelection(); + break; + } +} + +export function initializePlayerEvents(player, audioPlayer, scrobbler, ui) { if (homeStartRadioBtn) { homeStartRadioBtn.addEventListener('click', async () => { await player.enableRadio(); @@ -417,14 +425,14 @@ export function initializePlayerEvents(player, audioPlayer, scrobbler, ui) { } }); - element.addEventListener('play', () => { + element.addEventListener('play', async () => { if (player.activeElement !== element) return; // Initialize audio context manager for EQ (only once) if (!audioContextManager.isReady()) { audioContextManager.init(element); } - audioContextManager.resume(); + await audioContextManager.resume(); if (player.currentTrack) { // Track play event @@ -435,7 +443,7 @@ export function initializePlayerEvents(player, audioPlayer, scrobbler, ui) { scrobbler.updateNowPlaying(player.currentTrack); } - updateWaveform(); + await updateWaveform(); } playPauseBtn.innerHTML = SVG_PAUSE(20); @@ -479,7 +487,7 @@ export function initializePlayerEvents(player, audioPlayer, scrobbler, ui) { if (currentTime >= 10 && player.currentTrack && player.currentTrack.id !== historyLoggedTrackId) { historyLoggedTrackId = player.currentTrack.id; const historyEntry = await db.addToHistory(player.currentTrack); - syncManager.syncHistoryItem(historyEntry); + await syncManager.syncHistoryItem(historyEntry); if (window.location.hash === '#recent') { ui.renderRecentPage(); @@ -554,31 +562,31 @@ export function initializePlayerEvents(player, audioPlayer, scrobbler, ui) { setupMediaListeners(player.video); } - playPauseBtn.addEventListener('click', () => { - hapticMedium(); + playPauseBtn.addEventListener('click', async () => { + await hapticMedium(); player.handlePlayPause(); }); - nextBtn.addEventListener('click', () => { - hapticMedium(); + nextBtn.addEventListener('click', async () => { + await hapticMedium(); trackSkipTrack(player.currentTrack, 'next'); player.playNext(); }); - prevBtn.addEventListener('click', () => { - hapticMedium(); + prevBtn.addEventListener('click', async () => { + await hapticMedium(); trackSkipTrack(player.currentTrack, 'previous'); player.playPrev(); }); - shuffleBtn.addEventListener('click', () => { - hapticLight(); + shuffleBtn.addEventListener('click', async () => { + await hapticLight(); player.toggleShuffle(); trackToggleShuffle(player.shuffleActive); shuffleBtn.classList.toggle('active', player.shuffleActive); - if (window.renderQueueFunction) window.renderQueueFunction(); + if (window.renderQueueFunction) await window.renderQueueFunction(); }); - repeatBtn.addEventListener('click', () => { - hapticLight(); + repeatBtn.addEventListener('click', async () => { + await hapticLight(); const mode = player.toggleRepeat(); trackToggleRepeat(mode === REPEAT_MODE.OFF ? 'off' : mode === REPEAT_MODE.ALL ? 'all' : 'one'); repeatBtn.classList.toggle('active', mode !== REPEAT_MODE.OFF); @@ -709,7 +717,7 @@ export function initializePlayerEvents(player, audioPlayer, scrobbler, ui) { } }; - window.addEventListener('waveform-toggle', (e) => { + window.addEventListener('waveform-toggle', async (e) => { if (!e.detail.enabled) { const progressBar = document.getElementById('progress-bar'); const playerControls = document.querySelector('.player-controls'); @@ -722,7 +730,7 @@ export function initializePlayerEvents(player, audioPlayer, scrobbler, ui) { playerControls.classList.remove('waveform-loaded'); } } - updateWaveform(); + await updateWaveform(); }); if (volumeBtn) { @@ -1102,7 +1110,7 @@ export async function showAddToPlaylistModal(track) { e.stopPropagation(); await db.removeTrackFromPlaylist(playlistId, track.id); const updatedPlaylist = await db.getPlaylist(playlistId); - syncManager.syncUserPlaylist(updatedPlaylist, 'update'); + await syncManager.syncUserPlaylist(updatedPlaylist, 'update'); showNotification(`Removed from playlist: ${option.querySelector('span').textContent}`); await renderModal(); } else { @@ -1110,7 +1118,7 @@ export async function showAddToPlaylistModal(track) { await db.addTrackToPlaylist(playlistId, track); const updatedPlaylist = await db.getPlaylist(playlistId); - syncManager.syncUserPlaylist(updatedPlaylist, 'update'); + await syncManager.syncUserPlaylist(updatedPlaylist, 'update'); showNotification(`Added to playlist: ${option.querySelector('span').textContent}`); closeModal(); } @@ -1272,14 +1280,14 @@ export async function handleTrackAction( if (action === 'add-to-queue') { player.addToQueue(tracks); - if (window.renderQueueFunction) window.renderQueueFunction(); + if (window.renderQueueFunction) await window.renderQueueFunction(); showNotification(`Added ${tracks.length} tracks to queue`); return; } if (action === 'play-next') { player.addNextToQueue(tracks); - if (window.renderQueueFunction) window.renderQueueFunction(); + if (window.renderQueueFunction) await window.renderQueueFunction(); showNotification(`Playing next: ${tracks.length} tracks`); return; } @@ -1345,12 +1353,12 @@ export async function handleTrackAction( if (action === 'add-to-queue') { trackAddToQueue(item, 'end'); player.addToQueue(item); - if (window.renderQueueFunction) window.renderQueueFunction(); + if (window.renderQueueFunction) await window.renderQueueFunction(); showNotification(`Added to queue: ${item.title}`); } else if (action === 'play-next') { trackPlayNext(item); player.addNextToQueue(item); - if (window.renderQueueFunction) window.renderQueueFunction(); + if (window.renderQueueFunction) await window.renderQueueFunction(); showNotification(`Playing next: ${item.title}`); } else if (action === 'play-card') { player.setQueue([item], 0); @@ -1368,7 +1376,7 @@ export async function handleTrackAction( await downloadTrackWithMetadata(item, downloadQualitySettings.getQuality(), api, lyricsManager); } else if (action === 'toggle-like') { const added = await db.toggleFavorite(type, item); - syncManager.syncLibraryItem(type, item, added); + await syncManager.syncLibraryItem(type, item, added); // Track like/unlike if (added) { @@ -1624,7 +1632,7 @@ export async function handleTrackAction( e.stopPropagation(); await db.removeTrackFromPlaylist(playlistId, item.id); const updatedPlaylist = await db.getPlaylist(playlistId); - syncManager.syncUserPlaylist(updatedPlaylist, 'update'); + await syncManager.syncUserPlaylist(updatedPlaylist, 'update'); showNotification(`Removed from playlist: ${option.querySelector('span').textContent}`); await renderModal(); } else { @@ -1632,7 +1640,7 @@ export async function handleTrackAction( await db.addTrackToPlaylist(playlistId, item); const updatedPlaylist = await db.getPlaylist(playlistId); - syncManager.syncUserPlaylist(updatedPlaylist, 'update'); + await syncManager.syncUserPlaylist(updatedPlaylist, 'update'); showNotification(`Added to playlist: ${option.querySelector('span').textContent}`); closeModal(); } @@ -1670,9 +1678,12 @@ export async function handleTrackAction( const url = getShareUrl(storedHref ? storedHref : `/${typeForUrl}/${item.id || item.uuid}`); trackCopyLink(type, item.id || item.uuid); - navigator.clipboard.writeText(url).then(() => { - showNotification('Link copied to clipboard!'); - }); + await navigator.clipboard + .writeText(url) + .then(() => { + showNotification('Link copied to clipboard!'); + }) + .catch(console.error); } else if (action === 'open-in-new-tab') { // Use stored href from card if available, otherwise construct URL const contextMenu = document.getElementById('context-menu'); @@ -2412,7 +2423,7 @@ export function initializeTrackInteractions(player, api, mainContent, contextMen positionMenu(contextMenu, e.clientX, e.clientY); }); - document.addEventListener('click', (e) => { + document.addEventListener('click', async (e) => { if (contextMenu.style.display === 'block') { if (contextMenu._originalHTML) { contextMenu.innerHTML = contextMenu._originalHTML; @@ -2432,7 +2443,7 @@ export function initializeTrackInteractions(player, api, mainContent, contextMen } }); - document.addEventListener('keydown', (e) => { + document.addEventListener('keydown', async (e) => { if (e.key === 'Escape' && trackSelection.isSelecting) { clearSelection(); } @@ -2488,34 +2499,39 @@ export function initializeTrackInteractions(player, api, mainContent, contextMen trackPlayNext(t); player.addNextToQueue(t); }); - if (window.renderQueueFunction) window.renderQueueFunction(); + if (window.renderQueueFunction) await window.renderQueueFunction(); showNotification(`Playing next: ${selectedTracks.length} tracks`); clearSelection(); break; case 'add-to-queue': player.addToQueue(selectedTracks); - if (window.renderQueueFunction) window.renderQueueFunction(); + if (window.renderQueueFunction) await 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); + await syncManager.syncLibraryItem('track', t, added); }); showNotification(`Liked ${selectedTracks.length} tracks`); clearSelection(); break; case 'add-to-playlist': - showMultiSelectPlaylistModal(selectedTracks); + await showMultiSelectPlaylistModal(selectedTracks); clearSelection(); break; case 'download': - selectedTracks.forEach((t) => { - downloadTrackWithMetadata(t, downloadQualitySettings.getQuality(), api, lyricsManager); - }); showNotification(`Downloading ${selectedTracks.length} tracks`); clearSelection(); + for (const track of selectedTracks) { + await downloadTrackWithMetadata( + track, + downloadQualitySettings.getQuality(), + api, + lyricsManager + ); + } break; default: clearSelection(); diff --git a/js/ffmpeg.js b/js/ffmpeg.js index e01157c..d218aa4 100644 --- a/js/ffmpeg.js +++ b/js/ffmpeg.js @@ -114,7 +114,7 @@ async function ffmpegWorker( reject(new FfmpegError('Worker failed: ' + error.message)); }; - (async () => { + void (async () => { const transferables = []; if (audioData) transferables.push(audioData); for (const f of extraFiles) { diff --git a/js/ffmpeg.test.ts b/js/ffmpeg.test.ts index 547b513..5931d06 100644 --- a/js/ffmpeg.test.ts +++ b/js/ffmpeg.test.ts @@ -1,9 +1,9 @@ -import { expect, test, suite } from 'vitest'; +import { expect, test } from 'vitest'; import { ffmpeg } from './ffmpeg'; test('Run `ffmpeg --help`', async () => { const lines: string[] = []; - const info = await ffmpeg(null, { + await ffmpeg(null, { rawArgs: ['--help'], logConsole: false, outputName: null, diff --git a/js/ffmpegFormats.ts b/js/ffmpegFormats.ts index 2d45089..0e1e22c 100644 --- a/js/ffmpegFormats.ts +++ b/js/ffmpegFormats.ts @@ -183,6 +183,11 @@ export function getContainerFormat(internalName: string): ContainerFormat | unde return containerFormats[internalName]; } +export interface ExtraFile { + name: string; + data: ArrayBuffer | Uint8Array; +} + /** * Transcodes an audio blob using the specified custom format via ffmpeg. * Throws if ffmpeg fails during transcoding. @@ -192,7 +197,7 @@ export async function transcodeWithCustomFormat( format: CustomFormat, onProgress: ((progress: ProgressEvent) => void) | null = null, signal: AbortSignal | null = null, - extraFiles: any[] = [] + extraFiles: ExtraFile[] = [] ): Promise { return ffmpeg(audioBlob, { args: format.ffmpegArgs, @@ -213,7 +218,7 @@ export async function transcodeWithContainerFormat( format: ContainerFormat, onProgress: ((progress: ProgressEvent) => void) | null = null, signal: AbortSignal | null = null, - extraFiles: any[] = [] + extraFiles: ExtraFile[] = [] ): Promise { return ffmpeg(audioBlob, { args: format.ffmpegArgs, diff --git a/js/global.d.ts b/js/global.d.ts index 838a09d..3a8480a 100644 --- a/js/global.d.ts +++ b/js/global.d.ts @@ -31,3 +31,7 @@ declare module 'https://cdn.jsdelivr.net/npm/client-zip@2.4.5/+esm' { type WithRequiredKeys = { [K in keyof T]-?: T[K] | undefined; }; + +declare global { + const __COMMIT_HASH__: string | undefined; +} diff --git a/js/indexedIterator.ts b/js/indexedIterator.ts new file mode 100644 index 0000000..89e3e9f --- /dev/null +++ b/js/indexedIterator.ts @@ -0,0 +1,16 @@ +/** + * A generic iterator that yields the index, total count, and item for any finite iterable. + * + * @template T - The type of items in the iterable. + * @param iterable - The iterable to process. + * @returns A generator that yields an object with index, total, and item. + */ +export default function* indexedIterator( + iterable: Iterable +): Generator<{ index: number; total: number; item: T }> { + const array = Array.from(iterable); // Convert the iterable to an array + const total = array.length; // Get the total count of items + for (let index = 0; index < total; index++) { + yield { index, total, item: array[index] }; // Yield index, total, and item + } +} diff --git a/js/lastfm.js b/js/lastfm.js index e240877..dfd7ea1 100644 --- a/js/lastfm.js +++ b/js/lastfm.js @@ -284,8 +284,8 @@ export class LastFMScrobbler { scheduleScrobble(delay) { this.clearScrobbleTimer(); - this.scrobbleTimer = setTimeout(() => { - this.scrobbleCurrentTrack(); + this.scrobbleTimer = setTimeout(async () => { + await this.scrobbleCurrentTrack(); }, delay); } @@ -350,9 +350,9 @@ export class LastFMScrobbler { } } - onTrackChange(track) { + async onTrackChange(track) { if (!this.isAuthenticated()) return; - this.updateNowPlaying(track); + await this.updateNowPlaying(track); } onPlaybackStop() { diff --git a/js/librefm.js b/js/librefm.js index 83368f8..bfab9e3 100644 --- a/js/librefm.js +++ b/js/librefm.js @@ -216,8 +216,8 @@ export class LibreFmScrobbler { scheduleScrobble(delay) { this.clearScrobbleTimer(); - this.scrobbleTimer = setTimeout(() => { - this.scrobbleCurrentTrack(); + this.scrobbleTimer = setTimeout(async () => { + await this.scrobbleCurrentTrack(); }, delay); } @@ -282,9 +282,9 @@ export class LibreFmScrobbler { } } - onTrackChange(track) { + async onTrackChange(track) { if (!this.isAuthenticated()) return; - this.updateNowPlaying(track); + await this.updateNowPlaying(track); } onPlaybackStop() { diff --git a/js/listenbrainz.js b/js/listenbrainz.js index 0310c16..1f54eaa 100644 --- a/js/listenbrainz.js +++ b/js/listenbrainz.js @@ -209,8 +209,8 @@ export class ListenBrainzScrobbler { scheduleScrobble(delay) { this.clearScrobbleTimer(); - this.scrobbleTimer = setTimeout(() => { - this.scrobbleCurrentTrack(); + this.scrobbleTimer = setTimeout(async () => { + await this.scrobbleCurrentTrack(); }, delay); } @@ -235,8 +235,8 @@ export class ListenBrainzScrobbler { } } - onTrackChange(track) { - this.updateNowPlaying(track); + async onTrackChange(track) { + await this.updateNowPlaying(track); } onPlaybackStop() { diff --git a/js/listening-party.js b/js/listening-party.js index 9cddb6c..b0d3991 100644 --- a/js/listening-party.js +++ b/js/listening-party.js @@ -96,7 +96,7 @@ export class ListeningPartyManager { document.getElementById('copy-party-link-btn')?.addEventListener('click', () => this.copyInviteLink()); document.getElementById('party-chat-send-btn')?.addEventListener('click', () => this.sendChatMessage()); document.getElementById('party-chat-input')?.addEventListener('keypress', (e) => { - if (e.key === 'Enter') this.sendChatMessage(); + if (e.key === 'Enter') this.sendChatMessage().catch(console.error); }); } @@ -104,13 +104,13 @@ export class ListeningPartyManager { const nameInput = document.getElementById('party-name-input'); const user = authManager.user; if (!user) { - Modal.alert('Login Required', 'You must be logged in to host a listening party.'); + await Modal.alert('Login Required', 'You must be logged in to host a listening party.'); return; } const pbUser = await syncManager._getUserRecord(user.$id); if (!pbUser) { - Modal.alert('Sync Error', 'Failed to sync user data. Please try again.'); + await Modal.alert('Sync Error', 'Failed to sync user data. Please try again.'); return; } @@ -171,19 +171,19 @@ export class ListeningPartyManager { this.setupSubscriptions(partyId); this.startHeartbeat(); this.renderPartyUI(); - this.loadInitialData(partyId); + await this.loadInitialData(partyId); if (!this.isHost) { this.lockControls(); this.setupGuestSyncInterception(); if (party.current_track) { await audioContextManager.resume(); - this.syncWithHost(party); + await this.syncWithHost(party); } } } catch (error) { console.error('Join error:', error); - Modal.alert('Error', 'Failed to join the party. It may have ended.'); + await Modal.alert('Error', 'Failed to join the party. It may have ended.'); navigate('/parties'); } finally { this.isJoining = false; @@ -199,7 +199,7 @@ export class ListeningPartyManager { ); return confirmed ? { profile: null } : false; } else { - return new Promise((resolve) => { + return new Promise((resolve, reject) => { const cached = localStorage.getItem('party_guest_profile'); const defaultName = cached ? JSON.parse(cached).name : ''; @@ -225,7 +225,9 @@ export class ListeningPartyManager { }, { label: 'Cancel', type: 'secondary', callback: () => false }, ], - }).then(resolve); + }) + .then(resolve) + .catch(reject); }); } } @@ -262,29 +264,31 @@ export class ListeningPartyManager { pb.collection('parties') .subscribe( partyId, - (e) => { + async (e) => { if (e.action === 'update') { this.currentParty = e.record; - if (!this.isHost) this.syncWithHost(e.record); + if (!this.isHost) await this.syncWithHost(e.record); this.updatePartyHeader(); } else if (e.action === 'delete') { - Modal.alert('Party Ended', 'The host has ended the listening party.'); - this.leaveParty(false); + await Modal.alert('Party Ended', 'The host has ended the listening party.'); + await this.leaveParty(false); } }, { f_id } ) - .then((unsub) => this.unsubscribeFunctions.push(unsub)); + .then((unsub) => this.unsubscribeFunctions.push(unsub)) + .catch(console.error); pb.collection('party_members') .subscribe( '*', - (e) => { - if (e.record.party === partyId) this.loadMembers(); + async (e) => { + if (e.record.party === partyId) await this.loadMembers(); }, { f_id } ) - .then((unsub) => this.unsubscribeFunctions.push(unsub)); + .then((unsub) => this.unsubscribeFunctions.push(unsub)) + .catch(console.error); pb.collection('party_messages') .subscribe( @@ -294,23 +298,25 @@ export class ListeningPartyManager { }, { f_id } ) - .then((unsub) => this.unsubscribeFunctions.push(unsub)); + .then((unsub) => this.unsubscribeFunctions.push(unsub)) + .catch(console.error); pb.collection('party_requests') .subscribe( '*', - (e) => { - if (e.record.party === partyId) this.loadRequests(); + async (e) => { + if (e.record.party === partyId) await this.loadRequests(); }, { f_id } ) - .then((unsub) => this.unsubscribeFunctions.push(unsub)); + .then((unsub) => this.unsubscribeFunctions.push(unsub)) + .catch(console.error); } - async loadInitialData(partyId) { - this.loadMembers(); - this.loadMessages(); - this.loadRequests(); + async loadInitialData(_partyId) { + await this.loadMembers(); + await this.loadMessages(); + await this.loadRequests(); } async loadMembers() { @@ -439,7 +445,7 @@ export class ListeningPartyManager { ${this.isHost ? `` : ''} `; - } catch (e) { + } catch (_e) { return ''; } }) @@ -510,7 +516,7 @@ export class ListeningPartyManager { await pb .collection('party_messages') .create({ party: this.currentParty.id, sender_name: profile.name, content }, { f_id }); - } catch (e) {} + } catch (_e) {} } async requestSong(track) { @@ -560,7 +566,7 @@ export class ListeningPartyManager { if (party.is_playing) { if (el.paused) { - const success = await player.safePlay(el); + const _success = await player.safePlay(el); } const latency = (Date.now() - party.playback_timestamp) / 1000; const targetTime = party.is_playing ? party.playback_time + latency : party.playback_time; @@ -640,7 +646,7 @@ export class ListeningPartyManager { }, { f_id: authManager.user?.$id } ); - } catch (e) {} + } catch (_e) {} }; ['play', 'pause', 'seeked'].forEach((ev) => { player.audio.addEventListener(ev, updateParty); @@ -667,7 +673,7 @@ export class ListeningPartyManager { 'danger' ); if (!leave) return; - this.leaveParty(); + await this.leaveParty(); } return await originalPlayTrackFromQueue(...args); }; @@ -680,7 +686,7 @@ export class ListeningPartyManager { await pb .collection('party_members') .update(this.memberId, { last_seen: Date.now() }, { f_id: authManager.user?.$id || 'guest' }); - } catch (e) {} + } catch (_e) {} }, 30000); } @@ -705,11 +711,11 @@ export class ListeningPartyManager { await cleanup('party_messages'); await cleanup('party_requests'); await pb.collection('parties').delete(this.currentParty.id, { f_id }); - } catch (e) {} + } catch (_e) {} } else if (this.memberId) { try { await pb.collection('party_members').delete(this.memberId, { f_id }); - } catch (e) {} + } catch (_e) {} } this.restorePlayerMethods(); this.unlockControls(); @@ -733,7 +739,7 @@ export class ListeningPartyManager { } copyInviteLink() { - navigator.clipboard.writeText(`${window.location.origin}/party/${this.currentParty.id}`); + navigator.clipboard.writeText(`${window.location.origin}/party/${this.currentParty.id}`).catch(console.error); showNotification('Invite link copied!'); } diff --git a/js/lyrics.js b/js/lyrics.js index 1b8caa2..e59ffae 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').catch(console.error); // Check if text contains Japanese, Chinese, or Korean characters function containsAsianText(text) { @@ -246,6 +246,7 @@ export class LyricsManager { // Monkey-patch XMLHttpRequest to redirect dictionary requests to CDN // Kuromoji uses XHR, not fetch, for loading dictionary files if (!window._originalXHROpen) { + // eslint-disable-next-line @typescript-eslint/unbound-method window._originalXHROpen = XMLHttpRequest.prototype.open; XMLHttpRequest.prototype.open = function (method, url, ...rest) { const urlStr = url.toString(); @@ -264,7 +265,7 @@ export class LyricsManager { if (!window._originalFetch) { window._originalFetch = window.fetch; window.fetch = async (url, options) => { - const urlStr = url.toString(); + const urlStr = url instanceof URL ? url.toString() : url.url; if (urlStr.includes('/dict/') && urlStr.includes('.dat.gz')) { const filename = urlStr.split('/').pop(); const cdnUrl = `https://cdn.jsdelivr.net/npm/kuromoji@0.1.2/dict/${filename}`; @@ -527,7 +528,7 @@ export class LyricsManager { } // Setup MutationObserver to convert lyrics in am-lyrics component - setupLyricsObserver(amLyricsElement) { + async setupLyricsObserver(amLyricsElement) { this.stopLyricsObserver(); if (!amLyricsElement) return; @@ -575,7 +576,7 @@ export class LyricsManager { await this.convertLyricsContent(amLyricsElement); } if (this.isGeniusMode && this.currentGeniusData) { - this.applyGeniusAnnotations(amLyricsElement, this.currentGeniusData.referents); + await this.applyGeniusAnnotations(amLyricsElement, this.currentGeniusData.referents); } }, 100); }); @@ -591,10 +592,10 @@ export class LyricsManager { // Initial conversion if Romaji mode is enabled - single attempt, no periodic polling if (this.isRomajiMode) { - this.convertLyricsContent(amLyricsElement); + await this.convertLyricsContent(amLyricsElement); } if (this.isGeniusMode && this.currentGeniusData) { - this.applyGeniusAnnotations(amLyricsElement, this.currentGeniusData.referents); + await this.applyGeniusAnnotations(amLyricsElement, this.currentGeniusData.referents); } } @@ -692,7 +693,7 @@ export class LyricsManager { if (amLyricsElement) { if (this.isRomajiMode) { // Turning ON: Setup observer and convert immediately - this.setupLyricsObserver(amLyricsElement); + await this.setupLyricsObserver(amLyricsElement); await this.convertLyricsContent(amLyricsElement); } else { // Turning OFF: Stop observer @@ -1238,7 +1239,7 @@ export function clearFullscreenLyricsSync(container) { } } -export function clearLyricsPanelSync(audioPlayer, panel) { +export function clearLyricsPanelSync(_audioPlayer, panel) { if (panel && panel.lyricsCleanup) { panel.lyricsCleanup(); panel.lyricsCleanup = null; diff --git a/js/maloja.js b/js/maloja.js index cbc809e..5720748 100644 --- a/js/maloja.js +++ b/js/maloja.js @@ -135,8 +135,8 @@ export class MalojaScrobbler { scheduleScrobble(delay) { this.clearScrobbleTimer(); - this.scrobbleTimer = setTimeout(() => { - this.scrobbleCurrentTrack(); + this.scrobbleTimer = setTimeout(async () => { + await this.scrobbleCurrentTrack(); }, delay); } @@ -161,8 +161,8 @@ export class MalojaScrobbler { } } - onTrackChange(track) { - this.updateNowPlaying(track); + async onTrackChange(track) { + await this.updateNowPlaying(track); } onPlaybackStop() { diff --git a/js/metadata.js b/js/metadata.js index b826ffd..eab6ac9 100644 --- a/js/metadata.js +++ b/js/metadata.js @@ -37,7 +37,7 @@ export function prefetchMetadataObjects(track, api, coverBlob = null) { * @param {string} quality - Audio quality * @returns {Promise} - Audio blob with embedded metadata */ -export async function addMetadataToAudio(audioBlob, track, api, _quality, prefetchPromises) { +export async function addMetadataToAudio(audioBlob, track, _api, _quality, prefetchPromises) { const { coverFetch, lyricsFetch } = prefetchPromises; /** diff --git a/js/metadata.mp4.js b/js/metadata.mp4.js index 21e6682..c1f8f57 100644 --- a/js/metadata.mp4.js +++ b/js/metadata.mp4.js @@ -546,9 +546,9 @@ export function createStringAtom(type, value, truncateType = true) { export function createUserAtom(namespace, name, value) { const encoder = new TextEncoder(); - const dashBytes = encoder.encode('----'); // User-defined atom type + const _dashBytes = encoder.encode('----'); // User-defined atom type const namespaceBytes = encoder.encode(namespace); - const meanBytes = encoder.encode('mean'); // Standard 'mean' atom for namespace + const _meanBytes = encoder.encode('mean'); // Standard 'mean' atom for namespace const nameBytes = encoder.encode(name); const valueBytes = encoder.encode('\x00\x00\x00\x01\x00\x00\x00\x00' + value); diff --git a/js/multi-scrobbler.js b/js/multi-scrobbler.js index d4616ac..8fe9bcf 100644 --- a/js/multi-scrobbler.js +++ b/js/multi-scrobbler.js @@ -32,18 +32,26 @@ export class MultiScrobbler { ); } - updateNowPlaying(track) { - this.lastfm.updateNowPlaying(track); - this.listenbrainz.updateNowPlaying(track); - this.maloja.updateNowPlaying(track); - this.librefm.updateNowPlaying(track); + async updateNowPlaying(track) { + await Promise.allSettled( + [ + this.lastfm.updateNowPlaying(track), + this.listenbrainz.updateNowPlaying(track), + this.maloja.updateNowPlaying(track), + this.librefm.updateNowPlaying(track), + ].map((p) => p.catch(console.error)) + ); } - onTrackChange(track) { - this.lastfm.onTrackChange(track); - this.listenbrainz.onTrackChange(track); - this.maloja.onTrackChange(track); - this.librefm.onTrackChange(track); + async onTrackChange(track) { + await Promise.allSettled( + [ + this.lastfm.onTrackChange(track), + this.listenbrainz.onTrackChange(track), + this.maloja.onTrackChange(track), + this.librefm.onTrackChange(track), + ].map((p) => p.catch(console.error)) + ); } onPlaybackStop() { @@ -55,9 +63,11 @@ export class MultiScrobbler { // Love/Like tracks on all services that support it async loveTrack(track) { - await this.lastfm.loveTrack(track); - await this.librefm.loveTrack(track); - await this.listenbrainz.loveTrack(track); + await Promise.allSettled( + [this.lastfm.loveTrack(track), this.librefm.loveTrack(track), this.listenbrainz.loveTrack(track)].map((p) => + p.catch(console.error) + ) + ); // Maloja feedback could be added here when supported } } diff --git a/js/music-api.js b/js/music-api.js index 33d0ff4..0888c9f 100644 --- a/js/music-api.js +++ b/js/music-api.js @@ -4,8 +4,45 @@ import { LosslessAPI } from './api.js'; import { PodcastsAPI } from './podcasts-api.js'; import { musicProviderSettings } from './storage.js'; +/** + * MusicAPI - Singleton class that provides a unified interface for accessing music streaming services. + * + * Supports multiple providers (primarily Tidal) and includes functionality for searching, + * retrieving metadata, streaming, and managing playlists, artists, albums, tracks, and podcasts. + * + * @class MusicAPI + * @classdesc Manages API interactions with music providers and provides caching mechanisms + * for cover artwork and video metadata. + * + * @example + * // Initialize the MusicAPI + * await MusicAPI.initialize(settings); + * + * // Get the singleton instance + * const api = MusicAPI.instance; + * + * // Search for tracks + * const results = await api.search('query'); + * + * // Get a specific track + * const track = await api.getTrack('track-id'); + * + * // Get stream URL + * const streamUrl = await api.getStreamUrl('track-id', 'HIGH'); + * + * @property {LosslessAPI} tidalAPI - The Tidal API instance + * @property {PodcastsAPI} podcastsAPI - The Podcasts API instance + * @property {Object} _settings - Configuration settings + * @property {Map} videoArtworkCache - Cache for video artwork data + * + * @throws {Error} Throws if instance is accessed before initialization + * @throws {Error} Throws if initialize is called more than once + */ export class MusicAPI { static #instance = null; + /** + * @type {MusicAPI} + */ static get instance() { if (!MusicAPI.#instance) { throw new Error('MusicAPI not initialized. Call MusicAPI.initialize(settings) first.'); @@ -35,7 +72,7 @@ export class MusicAPI { } // Get the appropriate API based on provider - getAPI(provider = null) { + getAPI() { return this.tidalAPI; } @@ -101,31 +138,31 @@ export class MusicAPI { } // Get methods - async getTrack(id, quality, provider = null) { + async getTrack(id, quality) { const api = this.getAPI(); const cleanId = this.stripProviderPrefix(id); return api.getTrack(cleanId, quality); } - async getTrackMetadata(id, provider = null) { + async getTrackMetadata(id) { const api = this.getAPI(); const cleanId = this.stripProviderPrefix(id); return api.getTrackMetadata(cleanId); } - async getAlbum(id, provider = null) { + async getAlbum(id) { const api = this.getAPI(); const cleanId = this.stripProviderPrefix(id); return api.getAlbum(cleanId); } - async getArtist(id, provider = null) { + async getArtist(id) { const api = this.getAPI(); const cleanId = this.stripProviderPrefix(id); return api.getArtist(cleanId); } - async getArtistBiography(id, provider = null) { + async getArtistBiography(id) { const api = this.getAPI(); const cleanId = this.stripProviderPrefix(id); if (typeof api.getArtistBiography === 'function') { @@ -134,13 +171,13 @@ export class MusicAPI { return null; } - async getVideo(id, provider = null) { + async getVideo(id) { const api = this.getAPI(); const cleanId = this.stripProviderPrefix(id); return api.getVideo(cleanId); } - async getVideoStreamUrl(id, provider = null) { + async getVideoStreamUrl(id) { const api = this.getAPI(); const cleanId = this.stripProviderPrefix(id); if (typeof api.getVideoStreamUrl === 'function') { @@ -157,7 +194,7 @@ export class MusicAPI { return this.tidalAPI.getPlaylist(id); } - async getMix(id, _provider = null) { + async getMix(id) { // Mixes are always Tidal for now return this.tidalAPI.getMix(id); } @@ -172,7 +209,7 @@ export class MusicAPI { } // Stream methods - async getStreamUrl(id, quality, provider = null) { + async getStreamUrl(id, quality) { const api = this.getAPI(); const cleanId = this.stripProviderPrefix(id); return api.getStreamUrl(cleanId, quality); diff --git a/js/player.js b/js/player.js index 02cab2a..be3a0a6 100644 --- a/js/player.js +++ b/js/player.js @@ -133,7 +133,7 @@ export class Player { } this.loadQueueState(); - this.setupMediaSession(); + await this.setupMediaSession(); this.radioEnabled = radioSettings.isEnabled(); this.radioSeeds = []; @@ -142,19 +142,19 @@ export class Player { this.playbackSequence = 0; - window.addEventListener('beforeunload', () => { - this.saveQueueState(); + window.addEventListener('beforeunload', async () => { + await this.saveQueueState(); }); // Handle visibility change for iOS - AudioContext gets suspended when screen locks - document.addEventListener('visibilitychange', () => { + document.addEventListener('visibilitychange', async () => { const el = this.activeElement; if (document.visibilityState === 'visible' && !el.paused) { // Ensure audio context is resumed when user returns to the app if (!audioContextManager.isReady()) { audioContextManager.init(el); } - audioContextManager.resume(); + await audioContextManager.resume(); } if (document.visibilityState === 'visible' && this.autoplayBlocked) { this.autoplayBlocked = false; @@ -370,7 +370,7 @@ export class Player { } } - saveQueueState() { + async saveQueueState() { queueManager.saveQueue({ queue: this.queue, shuffledQueue: this.shuffledQueue, @@ -381,14 +381,14 @@ export class Player { }); if (window.renderQueueFunction) { - window.renderQueueFunction(); + await window.renderQueueFunction(); } } - setupMediaSession() { + async setupMediaSession() { if (!('mediaSession' in navigator)) return; - const setHandlers = () => { + const setHandlers = async () => { navigator.mediaSession.setActionHandler('play', async () => { const el = this.activeElement; // Initialize and resume audio context first (required for iOS lock screen) @@ -404,7 +404,7 @@ export class Player { } catch (e) { console.error('MediaSession play failed:', e); // If play fails, try to handle it like a regular play/pause - this.handlePlayPause(); + await this.handlePlayPause(); } }); @@ -429,7 +429,7 @@ export class Player { this.applyReplayGain(); } await audioContextManager.resume(); - this.playNext(); + await this.playNext(); }); if (!this.isIOS) { @@ -465,7 +465,7 @@ export class Player { this.video.addEventListener('playing', () => setHandlers(), { once: true }); } } else { - setHandlers(); + await setHandlers(); } } @@ -542,7 +542,7 @@ export class Player { video.play().catch(() => {}); await this.setupVideoQualitySelector(); }); - this.hls.on(Hls.Events.ERROR, (event, data) => { + this.hls.on(Hls.Events.ERROR, (_event, data) => { if (data.fatal) { console.warn('HLS fatal error:', data.type); if (fallbackImg) video.replaceWith(fallbackImg); @@ -578,7 +578,7 @@ export class Player { const levels = this.hls.levels; const qualityLabels = [ 'Auto', - ...levels.map((level, i) => { + ...levels.map((level) => { const height = level.height || 0; const bandwidth = level.bitrate || 0; if (height >= 1080) return '1080p'; @@ -645,7 +645,7 @@ export class Player { artist: video.artist || (video.artists && video.artists[0]) || 'Unknown Artist', album: video.album || { title: 'Video', cover: video.image || video.cover }, }; - this.setQueue([videoTrack], 0); + await this.setQueue([videoTrack], 0); await this.playTrackFromQueue(); } @@ -663,7 +663,7 @@ export class Player { const track = currentQueue[this.currentQueueIndex]; if (track.isUnavailable) { console.warn(`Attempted to play unavailable track: ${track.title}. Skipping...`); - this.playNext(); + await this.playNext(); return; } @@ -671,7 +671,7 @@ export class Player { const { contentBlockingSettings } = await import('./storage.js'); if (contentBlockingSettings.shouldHideTrack(track)) { console.warn(`Attempted to play blocked track: ${track.title}. Skipping...`); - this.playNext(); + await this.playNext(); return; } @@ -694,15 +694,15 @@ export class Player { this.currentQueueIndex >= currentQueue.length - 1 ) { console.log('[playTrackFromQueue] Fetching more tracks!'); - this.fetchMoreArtistPopularTracks().then((newTracks) => { + await this.fetchMoreArtistPopularTracks().then(async (newTracks) => { console.log('[playTrackFromQueue] Got tracks:', newTracks?.length); if (newTracks && newTracks.length > 0) { - this.addToQueue(newTracks); + await this.addToQueue(newTracks); } }); } - this.saveQueueState(); + await this.saveQueueState(); this.currentTrack = track; @@ -818,7 +818,7 @@ export class Player { if (!streamUrl) { console.warn(`Podcast episode ${trackTitle} audio URL is missing. Skipping.`); track.isUnavailable = true; - this.playNext(); + await this.playNext(); return; } @@ -851,7 +851,7 @@ export class Player { if (!streamUrl) { console.warn(`Track ${trackTitle} audio URL is missing. Skipping.`); track.isUnavailable = true; - this.playNext(); + await this.playNext(); return; } @@ -1018,7 +1018,7 @@ export class Player { } } - this.preloadNextTracks(); + void this.preloadNextTracks().catch(console.error); } catch (error) { if (this.playbackSequence !== currentSequence) return; if (error && (error.name === 'NotAllowedError' || error.name === 'AbortError')) { @@ -1041,6 +1041,8 @@ export class Player { this.isFallbackRetry = false; this.isFallbackInProgress = false; } + + return; } console.error(`Could not play track: ${trackTitle}`, error); @@ -1051,33 +1053,33 @@ export class Player { } } - playAtIndex(index) { + async playAtIndex(index) { const currentQueue = this.shuffleActive ? this.shuffledQueue : this.queue; if (index >= 0 && index < currentQueue.length) { this.currentQueueIndex = index; - this.playTrackFromQueue(0, 0); + await this.playTrackFromQueue(0, 0); } } - playNext(recursiveCount = 0) { + async playNext(recursiveCount = 0) { const currentQueue = this.getCurrentQueue(); const isLastTrack = this.currentQueueIndex >= currentQueue.length - 1; if (recursiveCount > currentQueue.length) { if (this.radioEnabled && isLastTrack) { - this.fetchRadioRecommendations().then(() => { + this.fetchRadioRecommendations().then(async () => { const updatedQueue = this.getCurrentQueue(); if (this.currentQueueIndex < updatedQueue.length - 1) { - this.playNext(0); + await this.playNext(0); } }); return; } if (this.artistPopularTracksState.artistId && this.artistPopularTracksState.hasMore) { - this.fetchMoreArtistPopularTracks().then((newTracks) => { + await this.fetchMoreArtistPopularTracks().then(async (newTracks) => { if (newTracks && newTracks.length > 0) { - this.addToQueue(newTracks); - this.playNext(0); + await this.addToQueue(newTracks); + await this.playNext(0); } else { this.activeElement.pause(); } @@ -1088,52 +1090,54 @@ export class Player { return; } - import('./storage.js').then(({ contentBlockingSettings }) => { - if ( - this.repeatMode === REPEAT_MODE.ONE && - !currentQueue[this.currentQueueIndex]?.isUnavailable && - !contentBlockingSettings.shouldHideTrack(currentQueue[this.currentQueueIndex]) - ) { - this.playTrackFromQueue(0, recursiveCount); - return; - } - - if (!isLastTrack) { - this.currentQueueIndex++; - const track = currentQueue[this.currentQueueIndex]; - if (track?.isUnavailable || contentBlockingSettings.shouldHideTrack(track)) { - return this.playNext(recursiveCount + 1); + import('./storage.js') + .then(async ({ contentBlockingSettings }) => { + if ( + this.repeatMode === REPEAT_MODE.ONE && + !currentQueue[this.currentQueueIndex]?.isUnavailable && + !contentBlockingSettings.shouldHideTrack(currentQueue[this.currentQueueIndex]) + ) { + await this.playTrackFromQueue(0, recursiveCount); + return; } - } else if (this.radioEnabled) { - this.fetchRadioRecommendations().then(() => { - const updatedQueue = this.getCurrentQueue(); - if (this.currentQueueIndex < updatedQueue.length - 1) { - this.playNext(0); - } - }); - return; - } else if (this.artistPopularTracksState.artistId && this.artistPopularTracksState.hasMore) { - this.fetchMoreArtistPopularTracks().then((newTracks) => { - if (newTracks && newTracks.length > 0) { - this.addToQueue(newTracks); - } - // Now play the next track (which is now at currentQueueIndex + 1 if tracks were added) + + if (!isLastTrack) { this.currentQueueIndex++; - this.playTrackFromQueue(0, recursiveCount); - }); - return; - } else if (this.repeatMode === REPEAT_MODE.ALL) { - this.currentQueueIndex = 0; - const track = currentQueue[this.currentQueueIndex]; - if (track?.isUnavailable || contentBlockingSettings.shouldHideTrack(track)) { - return this.playNext(recursiveCount + 1); + const track = currentQueue[this.currentQueueIndex]; + if (track?.isUnavailable || contentBlockingSettings.shouldHideTrack(track)) { + return this.playNext(recursiveCount + 1); + } + } else if (this.radioEnabled) { + this.fetchRadioRecommendations().then(async () => { + const updatedQueue = this.getCurrentQueue(); + if (this.currentQueueIndex < updatedQueue.length - 1) { + await this.playNext(0); + } + }); + return; + } else if (this.artistPopularTracksState.artistId && this.artistPopularTracksState.hasMore) { + await this.fetchMoreArtistPopularTracks().then(async (newTracks) => { + if (newTracks && newTracks.length > 0) { + await this.addToQueue(newTracks); + } + // Now play the next track (which is now at currentQueueIndex + 1 if tracks were added) + this.currentQueueIndex++; + await this.playTrackFromQueue(0, recursiveCount); + }); + return; + } else if (this.repeatMode === REPEAT_MODE.ALL) { + this.currentQueueIndex = 0; + const track = currentQueue[this.currentQueueIndex]; + if (track?.isUnavailable || contentBlockingSettings.shouldHideTrack(track)) { + return this.playNext(recursiveCount + 1); + } + } else { + return; } - } else { - return; - } - this.playTrackFromQueue(0, recursiveCount); - }); + await this.playTrackFromQueue(0, recursiveCount); + }) + .catch(console.error); } async enableRadio(seeds = []) { @@ -1141,20 +1145,20 @@ export class Player { radioSettings.setEnabled(true); if (seeds.length === 0) { - this.wipeQueue(); + await this.wipeQueue(); const pickedSeeds = await this.pickRadioSeeds(); if (pickedSeeds.length > 0) { this.radioSeeds = pickedSeeds; const initialQueue = [...pickedSeeds].sort(() => 0.5 - Math.random()).slice(0, 5); - this.setQueue(initialQueue, 0, true); - this.playAtIndex(0); + await this.setQueue(initialQueue, 0, true); + await this.playAtIndex(0); } } else { this.radioSeeds = Array.isArray(seeds) ? seeds : [seeds]; - this.wipeQueue(); + await this.wipeQueue(); const initialQueue = Array.isArray(seeds) ? seeds.slice(0, 5) : [seeds]; - this.setQueue(initialQueue, 0, true); - this.playAtIndex(0); + await this.setQueue(initialQueue, 0, true); + await this.playAtIndex(0); } const currentQueue = this.getCurrentQueue(); @@ -1217,7 +1221,7 @@ export class Player { if (newTracks.length > 0) { const tracksToAdd = newTracks.sort(() => 0.5 - Math.random()).slice(0, 5); - this.addToQueue(tracksToAdd); + await this.addToQueue(tracksToAdd); } } } catch (error) { @@ -1304,13 +1308,15 @@ export class Player { return; } - import('./storage.js').then(({ contentBlockingSettings }) => { - const track = currentQueue[this.currentQueueIndex]; - if (track?.isUnavailable || contentBlockingSettings.shouldHideTrack(track)) { - return this.playPrev(recursiveCount + 1); - } - this.playTrackFromQueue(0, recursiveCount); - }); + import('./storage.js') + .then(async ({ contentBlockingSettings }) => { + const track = currentQueue[this.currentQueueIndex]; + if (track?.isUnavailable || contentBlockingSettings.shouldHideTrack(track)) { + return this.playPrev(recursiveCount + 1); + } + await this.playTrackFromQueue(0, recursiveCount); + }) + .catch(console.error); } } @@ -1318,28 +1324,28 @@ export class Player { return this.currentTrack?.type === 'video' ? this.video : this.audio; } - handlePlayPause() { + async handlePlayPause() { const el = this.activeElement; const hasSource = el.src || el.currentSrc || el.srcObject || this.shakaInitialized; if (!hasSource || el.error) { if (this.currentTrack) { - this.playTrackFromQueue(0, 0); + await this.playTrackFromQueue(0, 0); } return; } if (el.paused) { - this.safePlay(el).catch((e) => { + this.safePlay(el).catch(async (e) => { if (e.name === 'NotAllowedError' || e.name === 'AbortError') return; console.error('Play failed, reloading track:', e); if (this.currentTrack) { - this.playTrackFromQueue(0, 0); + await this.playTrackFromQueue(0, 0); } }); } else { el.pause(); - this.saveQueueState(); + await this.saveQueueState(); } } @@ -1358,7 +1364,7 @@ export class Player { this.updateMediaSessionPositionState(); } - toggleShuffle() { + async toggleShuffle() { this.shuffleActive = !this.shuffleActive; if (this.shuffleActive) { @@ -1389,17 +1395,17 @@ export class Player { } this.preloadCache.clear(); - this.preloadNextTracks(); - this.saveQueueState(); + void this.preloadNextTracks().catch(console.error); + await this.saveQueueState(); } - toggleRepeat() { + async toggleRepeat() { this.repeatMode = (this.repeatMode + 1) % 3; - this.saveQueueState(); + await this.saveQueueState(); return this.repeatMode; } - setQueue(tracks, startIndex = 0, isRadio = false) { + async setQueue(tracks, startIndex = 0, isRadio = false) { if (!isRadio) { this.disableRadio(); } @@ -1407,7 +1413,7 @@ export class Player { this.currentQueueIndex = startIndex; this.shuffleActive = false; this.preloadCache.clear(); - this.saveQueueState(); + await this.saveQueueState(); } setArtistPopularTracksContext(artistId, initialTracks, offset = 15, hasMore = true) { @@ -1474,7 +1480,7 @@ export class Player { } } - addToQueue(trackOrTracks) { + async addToQueue(trackOrTracks) { const tracks = Array.isArray(trackOrTracks) ? trackOrTracks : [trackOrTracks]; this.queue.push(...tracks); @@ -1485,12 +1491,12 @@ export class Player { if (!this.currentTrack || this.currentQueueIndex === -1) { this.currentQueueIndex = this.getCurrentQueue().length - tracks.length; - this.playTrackFromQueue(0, 0); + await this.playTrackFromQueue(0, 0); } - this.saveQueueState(); + await this.saveQueueState(); } - addNextToQueue(trackOrTracks) { + async addNextToQueue(trackOrTracks) { const tracks = Array.isArray(trackOrTracks) ? trackOrTracks : [trackOrTracks]; const currentQueue = this.shuffleActive ? this.shuffledQueue : this.queue; const insertIndex = this.currentQueueIndex + 1; @@ -1504,11 +1510,11 @@ export class Player { this.originalQueueBeforeShuffle.push(...tracks); // Sync original queue } - this.saveQueueState(); - this.preloadNextTracks(); // Update preload since next track changed + await this.saveQueueState(); + void this.preloadNextTracks().catch(console.error); // Update preload since next track changed } - removeFromQueue(index) { + async removeFromQueue(index) { const currentQueue = this.shuffleActive ? this.shuffledQueue : this.queue; // If removing current track @@ -1532,11 +1538,11 @@ export class Player { } } - this.saveQueueState(); - this.preloadNextTracks(); + await this.saveQueueState(); + void this.preloadNextTracks().catch(console.error); } - clearQueue() { + async clearQueue() { if (this.currentTrack) { this.queue = [this.currentTrack]; @@ -1556,10 +1562,10 @@ export class Player { } this.preloadCache.clear(); - this.saveQueueState(); + await this.saveQueueState(); } - wipeQueue() { + async wipeQueue() { const el = this.activeElement; el.pause(); el.src = ''; @@ -1568,16 +1574,16 @@ export class Player { this.shuffledQueue = []; this.originalQueueBeforeShuffle = []; this.currentQueueIndex = -1; - this.saveQueueState(); + await this.saveQueueState(); if (UIRenderer.instance) { UIRenderer.instance.setCurrentTrack(null); } if (window.renderQueueFunction) { - window.renderQueueFunction(); + await window.renderQueueFunction(); } } - moveInQueue(fromIndex, toIndex) { + async moveInQueue(fromIndex, toIndex) { const currentQueue = this.shuffleActive ? this.shuffledQueue : this.queue; if (fromIndex < 0 || fromIndex >= currentQueue.length) return; @@ -1593,7 +1599,7 @@ export class Player { } else if (fromIndex > this.currentQueueIndex && toIndex <= this.currentQueueIndex) { this.currentQueueIndex++; } - this.saveQueueState(); + await this.saveQueueState(); } getCurrentQueue() { diff --git a/js/playlist-importer.js b/js/playlist-importer.js index 08332c6..a680497 100644 --- a/js/playlist-importer.js +++ b/js/playlist-importer.js @@ -41,11 +41,11 @@ function getTrackArtists(track) { /** * Generates CSV playlist export - * @param {Object} playlist - Playlist metadata + * @param {Object} _playlist - Playlist metadata * @param {Array} tracks - Array of track objects * @returns {string} CSV content */ -export function generateCSV(playlist, tracks) { +export function generateCSV(_playlist, tracks) { const headers = ['Track Name', 'Artist Name(s)', 'Album', 'Duration']; let content = headers.map((h) => `"${h}"`).join(',') + '\n'; diff --git a/js/profile.js b/js/profile.js index eeec04f..b9e8f42 100644 --- a/js/profile.js +++ b/js/profile.js @@ -248,30 +248,31 @@ export async function loadProfile(username) { } if (profile.lastfm_username && profile.privacy?.lastfm !== 'private') { - fetchLastFmRecentTracks(profile.lastfm_username).then(async (tracks) => { - if (tracks.length > 0) { - recentSection.style.display = 'block'; - recentContainer.innerHTML = tracks - .map((track, index) => { - const isNowPlaying = track['@attr']?.nowplaying === 'true'; - let image = getLastFmImage(track.image); - const hasImage = !!image; - if (!image) image = '/assets/appicon.png'; + fetchLastFmRecentTracks(profile.lastfm_username) + .then(async (tracks) => { + if (tracks.length > 0) { + recentSection.style.display = 'block'; + recentContainer.innerHTML = tracks + .map((track, index) => { + const isNowPlaying = track['@attr']?.nowplaying === 'true'; + let image = getLastFmImage(track.image); + const hasImage = !!image; + if (!image) image = '/assets/appicon.png'; - track._imgId = `scrobble-img-${index}`; - track._needsCover = !hasImage; + track._imgId = `scrobble-img-${index}`; + track._needsCover = !hasImage; - let dateDisplay = ''; - if (isNowPlaying) dateDisplay = 'Scrobbling now'; - else if (track.date) { - const date = new Date(track.date.uts * 1000); - dateDisplay = - date.toLocaleDateString() + - ' ' + - date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }); - } + let dateDisplay = ''; + if (isNowPlaying) dateDisplay = 'Scrobbling now'; + else if (track.date) { + const date = new Date(track.date.uts * 1000); + dateDisplay = + date.toLocaleDateString() + + ' ' + + date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }); + } - return ` + return `
@@ -283,39 +284,45 @@ export async function loadProfile(username) {
${dateDisplay}
`; - }) - .join(''); + }) + .join(''); - recentContainer.querySelectorAll('.track-item').forEach((item) => { - item.addEventListener('click', () => handleTrackClick(item.dataset.title, item.dataset.artist)); - item.addEventListener('contextmenu', (e) => { - e.preventDefault(); - return false; + recentContainer.querySelectorAll('.track-item').forEach((item) => { + item.addEventListener('click', () => handleTrackClick(item.dataset.title, item.dataset.artist)); + item.addEventListener('contextmenu', (e) => { + e.preventDefault(); + return false; + }); }); - }); - for (const track of tracks) { - if (track._needsCover) { - fetchFallbackCover(track.name, track.artist?.['#text'] || track.artist?.name, track._imgId); + for (const track of tracks) { + if (track._needsCover) { + await fetchFallbackCover( + track.name, + track.artist?.['#text'] || track.artist?.name, + track._imgId + ); + } } } - } - }); + }) + .catch(console.error); - fetchLastFmTopArtists(profile.lastfm_username).then(async (artists) => { - if (artists.length > 0 && topArtistsSection && topArtistsContainer) { - topArtistsSection.style.display = 'block'; - topArtistsContainer.innerHTML = artists - .map((artist, index) => { - let image = getLastFmImage(artist.image); - const hasImage = !!image; - if (!image) image = '/assets/appicon.png'; + fetchLastFmTopArtists(profile.lastfm_username) + .then(async (artists) => { + if (artists.length > 0 && topArtistsSection && topArtistsContainer) { + topArtistsSection.style.display = 'block'; + topArtistsContainer.innerHTML = artists + .map((artist, index) => { + let image = getLastFmImage(artist.image); + const hasImage = !!image; + if (!image) image = '/assets/appicon.png'; - const imgId = `top-artist-img-${index}`; - artist._imgId = imgId; - artist._needsCover = !hasImage; + const imgId = `top-artist-img-${index}`; + artist._imgId = imgId; + artist._needsCover = !hasImage; - return ` + return `
@@ -326,45 +333,47 @@ export async function loadProfile(username) {
`; - }) - .join(''); + }) + .join(''); - topArtistsContainer.querySelectorAll('.card').forEach((card) => { - card.addEventListener('click', () => handleArtistClick(card.dataset.name)); - card.addEventListener('contextmenu', (e) => { - e.preventDefault(); - return false; + topArtistsContainer.querySelectorAll('.card').forEach((card) => { + card.addEventListener('click', () => handleArtistClick(card.dataset.name)); + card.addEventListener('contextmenu', (e) => { + e.preventDefault(); + return false; + }); }); - }); - for (const artist of artists) { - if (artist._needsCover) { - fetchFallbackArtistImage(artist.name, artist._imgId); + for (const artist of artists) { + if (artist._needsCover) { + await fetchFallbackArtistImage(artist.name, artist._imgId); + } } } - } - }); + }) + .catch(console.error); - fetchLastFmTopAlbums(profile.lastfm_username).then(async (albums) => { - if (albums.length > 0 && topAlbumsSection && topAlbumsContainer) { - topAlbumsSection.style.display = 'block'; - topAlbumsContainer.innerHTML = albums - .map((album, index) => { - let image = getLastFmImage(album.image); - const hasImage = !!image; - if (!image) image = '/assets/appicon.png'; + fetchLastFmTopAlbums(profile.lastfm_username) + .then(async (albums) => { + if (albums.length > 0 && topAlbumsSection && topAlbumsContainer) { + topAlbumsSection.style.display = 'block'; + topAlbumsContainer.innerHTML = albums + .map((album, index) => { + let image = getLastFmImage(album.image); + const hasImage = !!image; + if (!image) image = '/assets/appicon.png'; - const imgId = `top-album-img-${index}`; - album._imgId = imgId; - album._needsCover = !hasImage; + const imgId = `top-album-img-${index}`; + album._imgId = imgId; + album._needsCover = !hasImage; - const artistName = - album.artist?.name || - album.artist?.['#text'] || - (typeof album.artist === 'string' ? album.artist : 'Unknown Artist'); - album._artistName = artistName; + const artistName = + album.artist?.name || + album.artist?.['#text'] || + (typeof album.artist === 'string' ? album.artist : 'Unknown Artist'); + album._artistName = artistName; - return ` + return `
@@ -375,45 +384,47 @@ export async function loadProfile(username) {
`; - }) - .join(''); + }) + .join(''); - topAlbumsContainer.querySelectorAll('.card').forEach((card) => { - card.addEventListener('click', () => handleAlbumClick(card.dataset.name, card.dataset.artist)); - card.addEventListener('contextmenu', (e) => { - e.preventDefault(); - return false; + topAlbumsContainer.querySelectorAll('.card').forEach((card) => { + card.addEventListener('click', () => handleAlbumClick(card.dataset.name, card.dataset.artist)); + card.addEventListener('contextmenu', (e) => { + e.preventDefault(); + return false; + }); }); - }); - for (const album of albums) { - if (album._needsCover) { - fetchFallbackAlbumCover(album.name, album._artistName, album._imgId); + for (const album of albums) { + if (album._needsCover) { + await fetchFallbackAlbumCover(album.name, album._artistName, album._imgId); + } } } - } - }); + }) + .catch(console.error); - fetchLastFmTopTracks(profile.lastfm_username).then(async (tracks) => { - if (tracks.length > 0 && topTracksSection && topTracksContainer) { - topTracksSection.style.display = 'block'; - topTracksContainer.innerHTML = tracks - .map((track, index) => { - let image = getLastFmImage(track.image); - const hasImage = !!image; - if (!image) image = '/assets/appicon.png'; + fetchLastFmTopTracks(profile.lastfm_username) + .then(async (tracks) => { + if (tracks.length > 0 && topTracksSection && topTracksContainer) { + topTracksSection.style.display = 'block'; + topTracksContainer.innerHTML = tracks + .map((track, index) => { + let image = getLastFmImage(track.image); + const hasImage = !!image; + if (!image) image = '/assets/appicon.png'; - const imgId = `top-track-img-${index}`; - track._imgId = imgId; - track._needsCover = !hasImage; + const imgId = `top-track-img-${index}`; + track._imgId = imgId; + track._needsCover = !hasImage; - const artistName = - track.artist?.name || - track.artist?.['#text'] || - (typeof track.artist === 'string' ? track.artist : 'Unknown Artist'); - track._artistName = artistName; + const artistName = + track.artist?.name || + track.artist?.['#text'] || + (typeof track.artist === 'string' ? track.artist : 'Unknown Artist'); + track._artistName = artistName; - return ` + return `
@@ -425,24 +436,25 @@ export async function loadProfile(username) {
${parseInt(track.playcount).toLocaleString()} plays
`; - }) - .join(''); + }) + .join(''); - topTracksContainer.querySelectorAll('.track-item').forEach((item) => { - item.addEventListener('click', () => handleTrackClick(item.dataset.title, item.dataset.artist)); - item.addEventListener('contextmenu', (e) => { - e.preventDefault(); - return false; + topTracksContainer.querySelectorAll('.track-item').forEach((item) => { + item.addEventListener('click', () => handleTrackClick(item.dataset.title, item.dataset.artist)); + item.addEventListener('contextmenu', (e) => { + e.preventDefault(); + return false; + }); }); - }); - for (const track of tracks) { - if (track._needsCover) { - fetchFallbackCover(track.name, track._artistName, track._imgId); + for (const track of tracks) { + if (track._needsCover) { + await fetchFallbackCover(track.name, track._artistName, track._imgId); + } } } - } - }); + }) + .catch(console.error); } const currentUser = await syncManager.getUserData(); @@ -483,8 +495,8 @@ export async function loadProfile(username) { } } -export function openEditProfile() { - syncManager.getUserData().then((data) => { +export async function openEditProfile() { + await syncManager.getUserData().then((data) => { if (!data || !data.profile) return; const p = data.profile; @@ -566,7 +578,7 @@ async function saveProfile() { try { await syncManager.updateProfile(data); editProfileModal.classList.remove('active'); - loadProfile(newUsername); + await loadProfile(newUsername); if (window.location.pathname.includes('/user/@')) { window.history.replaceState(null, '', `/user/@${newUsername}`); @@ -589,7 +601,7 @@ viewMyProfileBtn.addEventListener('click', async () => { if (data && data.profile && data.profile.username) { navigate(`/user/@${data.profile.username}`); } else { - openEditProfile(); + await openEditProfile(); } }); diff --git a/js/progressEvents.ts b/js/progressEvents.ts index d3618b0..61be090 100644 --- a/js/progressEvents.ts +++ b/js/progressEvents.ts @@ -1,9 +1,9 @@ declare global { - type MonochromeProgress = { + type MonochromeProgress = { stage: string; } & T; - type MonochromeProgressMessage = { + type MonochromeProgressMessage<_T = MonochromeProgress> = { message: string; }; diff --git a/js/settings.js b/js/settings.js index 19951e6..7900e13 100644 --- a/js/settings.js +++ b/js/settings.js @@ -48,7 +48,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'; +import { BulkDownloadMethod, modernSettings } from './ModernSettings.js'; async function getButterchurnPresets(...args) { const butterchurnModule = await import('./visualizers/butterchurn.js'); @@ -943,10 +943,10 @@ export async function initializeSettings(scrobbler, player, api, ui) { const showQualityBadgesToggle = document.getElementById('show-quality-badges-toggle'); if (showQualityBadgesToggle) { showQualityBadgesToggle.checked = qualityBadgeSettings.isEnabled(); - showQualityBadgesToggle.addEventListener('change', (e) => { + showQualityBadgesToggle.addEventListener('change', async (e) => { qualityBadgeSettings.setEnabled(e.target.checked); // Re-render queue if available, but don't force navigation to library - if (window.renderQueueFunction) window.renderQueueFunction(); + if (window.renderQueueFunction) await window.renderQueueFunction(); }); } @@ -979,15 +979,15 @@ export async function initializeSettings(scrobbler, player, api, ui) { if (!forceZipBlobSettingItem) return; const method = modernSettings.bulkDownloadMethod; // Only relevant when zip method is selected and the browser supports streaming - const visible = method === 'zip' && hasFileSystemAccess; + const visible = method === BulkDownloadMethod.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'; + const isFolderMethod = method === BulkDownloadMethod.Folder; + const isFolderOrLocal = isFolderMethod || method === BulkDownloadMethod.LocalMedia; if (rememberFolderSetting) { rememberFolderSetting.style.display = isFolderMethod && hasFolderPicker ? '' : 'none'; @@ -1022,8 +1022,8 @@ export async function initializeSettings(scrobbler, player, api, ui) { } // 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'; + if (currentMethod === BulkDownloadMethod.Folder || currentMethod === BulkDownloadMethod.LocalMedia) { + modernSettings.bulkDownloadMethod = BulkDownloadMethod.Zip; } } bulkDownloadMethod.value = modernSettings.bulkDownloadMethod; @@ -1033,7 +1033,7 @@ export async function initializeSettings(scrobbler, player, api, ui) { modernSettings.bulkDownloadMethod = newMethod; // When switching to 'local', prompt to select the local media folder if not yet configured - if (newMethod === 'local') { + if (newMethod === BulkDownloadMethod.LocalMedia) { const existingHandle = await db.getSetting('local_folder_handle'); if (!existingHandle) { let picked = false; @@ -1329,12 +1329,12 @@ export async function initializeSettings(scrobbler, player, api, ui) { autoeqHeadphoneSelect.appendChild(optgroup); // When user picks a popular headphone from the dropdown, load it - autoeqHeadphoneSelect.addEventListener('change', () => { + autoeqHeadphoneSelect.addEventListener('change', async () => { const selected = autoeqHeadphoneSelect.value; if (!selected) return; const popularEntry = POPULAR_HEADPHONES.find((hp) => hp.name === selected); if (popularEntry && (!autoeqSelectedEntry || autoeqSelectedEntry.name !== selected)) { - loadHeadphoneEntry(popularEntry); + await loadHeadphoneEntry(popularEntry); } }); } @@ -2433,7 +2433,12 @@ export async function initializeSettings(scrobbler, player, api, ui) { } const x = freqToX(f, pw); const y = mid - (Math.max(-dbRange, Math.min(dbRange, total)) / dbRange) * mid * 0.9; - first ? (ctx.moveTo(x, y), (first = false)) : ctx.lineTo(x, y); + if (first) { + ctx.moveTo(x, y); + first = false; + } else { + ctx.lineTo(x, y); + } } ctx.strokeStyle = 'rgba(255,255,255,0.9)'; ctx.lineWidth = 2; @@ -2650,7 +2655,7 @@ export async function initializeSettings(scrobbler, player, api, ui) { modelMap.get(baseName).push(entry); }); - modelMap.forEach((variants, name) => { + modelMap.forEach(async (variants, name) => { const wrapper = document.createElement('div'); const rawFirstChar = name[0]?.toUpperCase() || '#'; const firstLetter = /^[A-Z]$/.test(rawFirstChar) ? rawFirstChar : '#'; @@ -2676,19 +2681,19 @@ export async function initializeSettings(scrobbler, player, api, ui) { const subList = document.createElement('div'); subList.className = 'autoeq-db-sub-list'; - variants.forEach((entry) => { + for (const entry of variants) { const subItem = document.createElement('div'); subItem.className = 'autoeq-db-sub-item'; // Extract source from parentheses - const sourceMatch = entry.name.match(/\(([^)]+)\)\s*$/); + const sourceMatch = await entry.name.match(/\(([^)]+)\)\s*$/); const source = sourceMatch ? sourceMatch[1] : entry.type; subItem.innerHTML = `${entry.name}${source}`; - subItem.addEventListener('click', (e) => { + subItem.addEventListener('click', async (e) => { e.stopPropagation(); - loadHeadphoneEntry(entry); + await loadHeadphoneEntry(entry); }); subList.appendChild(subItem); - }); + } wrapper.appendChild(subList); @@ -4409,7 +4414,7 @@ export async function initializeSettings(scrobbler, player, api, ui) { if (initParaProfiles) initParaProfiles.style.display = 'none'; // Auto-load headphone database - loadFullDatabase(); + await loadFullDatabase(); // Auto-load default popular headphone if no saved profile is active const activeProfileId = equalizerSettings.getActiveAutoEQProfile(); @@ -4432,7 +4437,7 @@ export async function initializeSettings(scrobbler, player, api, ui) { if (autoeqRunBtn) autoeqRunBtn.disabled = false; requestAnimationFrame(drawAutoEQGraph); } else if (POPULAR_HEADPHONES.length > 0) { - loadHeadphoneEntry(POPULAR_HEADPHONES[0]); + await loadHeadphoneEntry(POPULAR_HEADPHONES[0]); } } @@ -4990,7 +4995,7 @@ export async function initializeSettings(scrobbler, player, api, ui) { const currentSource = homePageSettings.getEditorsPicksSource(); editorsPicksSourceSelect.value = currentSource; } - populateEditorsPicksSource(); + await populateEditorsPicksSource(); editorsPicksSourceSelect.addEventListener('change', (e) => { homePageSettings.setEditorsPicksSource(e.target.value); @@ -5365,7 +5370,7 @@ export async function initializeSettings(scrobbler, player, api, ui) { try { await syncManager.clearCloudData(); alert('Cloud data cleared successfully.'); - authManager.signOut(); + await authManager.signOut(); } catch (error) { console.error('Failed to clear cloud data:', error); alert('Failed to clear cloud data: ' + error.message); @@ -5716,7 +5721,7 @@ function initializeFontSettings() { }); // Google Fonts apply - fontGoogleApply.addEventListener('click', () => { + fontGoogleApply.addEventListener('click', async () => { const input = fontGoogleInput.value.trim(); if (!input) return; @@ -5735,16 +5740,16 @@ function initializeFontSettings() { // Not a URL, treat as font name } - fontSettings.loadGoogleFont(fontName); + await fontSettings.loadGoogleFont(fontName); }); // URL font apply - fontUrlApply.addEventListener('click', () => { + fontUrlApply.addEventListener('click', async () => { const url = fontUrlInput.value.trim(); const name = fontUrlName.value.trim(); if (!url) return; - fontSettings.loadFontFromUrl(url, name || 'CustomFont'); + await fontSettings.loadFontFromUrl(url, name || 'CustomFont'); }); // File upload diff --git a/js/side-panel.js b/js/side-panel.js index c3b6d8f..0f399ea 100644 --- a/js/side-panel.js +++ b/js/side-panel.js @@ -118,25 +118,25 @@ export class SidePanelManager { return this.currentView === view && this.panel.classList.contains('active'); } - refresh(view, renderControlsCallback, renderContentCallback, options = {}) { + async refresh(view, renderControlsCallback, renderContentCallback, options = {}) { if (this.isActive(view)) { if (renderControlsCallback) { this.controlsElement.innerHTML = ''; - renderControlsCallback(this.controlsElement); + await renderControlsCallback(this.controlsElement); } if (renderContentCallback) { if (!options.noClear) { this.contentElement.innerHTML = ''; } - renderContentCallback(this.contentElement); + await renderContentCallback(this.contentElement); } } } - updateContent(view, renderContentCallback) { + async updateContent(view, renderContentCallback) { if (this.isActive(view)) { this.contentElement.innerHTML = ''; - renderContentCallback(this.contentElement); + await renderContentCallback(this.contentElement); } } } diff --git a/js/storage.js b/js/storage.js index 78cb6b0..edce8ba 100644 --- a/js/storage.js +++ b/js/storage.js @@ -2653,18 +2653,18 @@ export const fontSettings = { document.documentElement.style.setProperty('--font-family', "'SF Pro Display', sans-serif"); }, - applyFont() { + async applyFont() { const config = this.getConfig(); switch (config.type) { case 'google': - this.loadGoogleFont(config.family); + await this.loadGoogleFont(config.family); break; case 'url': - this.loadFontFromUrl(config.url, config.family); + await this.loadFontFromUrl(config.url, config.family); break; case 'uploaded': - this.loadUploadedFont(config.fontId); + await this.loadUploadedFont(config.fontId); break; case 'preset': default: diff --git a/js/taglib.ts b/js/taglib.ts index 8866aa2..d22c8f3 100644 --- a/js/taglib.ts +++ b/js/taglib.ts @@ -22,7 +22,11 @@ export async function withTimeout(callback: () => Promise, timeout: number }) .catch((err) => { clearTimeout(timer); - reject(err); + if (err instanceof Error) { + reject(err); + } else { + reject(new Error(String(err))); + } }); }); } @@ -33,7 +37,7 @@ function toUint8Array(audioData: ArrayBufferLike | Uint8Array) { } return doTimed( - `Converting audio data (${(audioData as any)?.constructor?.name}) to Uint8Array`, + `Converting audio data (${(audioData as object)?.constructor?.name}) to Uint8Array`, () => new Uint8Array(audioData) ); } @@ -60,7 +64,7 @@ async function convertInputToTaglib( return (await doTimedAsync('Reading File from FileSystemHandle as Uint8Array', async () => { const file = await audioData.getFile(); const arrayBuffer = await file.arrayBuffer(); - return await toUint8Array(arrayBuffer); + return toUint8Array(arrayBuffer); })) as R; } else if ( !(audioData instanceof Uint8Array) && @@ -69,7 +73,7 @@ async function convertInputToTaglib( !('FileSystemFileEntry' in globalThis && audioData instanceof FileSystemFileEntry) && !('FileSystemFileHandle' in globalThis && audioData instanceof FileSystemFileHandle) ) { - return toUint8Array(audioData as any) as R; + return toUint8Array(audioData as unknown as ArrayBufferLike) as R; } return audioData as R; @@ -114,19 +118,19 @@ export async function addMetadataWithTagLib( if (error) { reject(new Error(error)); } else { - resolve(data!); + 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 ((audioData as Uint8Array)?.buffer instanceof ArrayBuffer) { + transferables.push((audioData as Uint8Array).buffer); } - if ((data as any).cover?.data?.buffer instanceof ArrayBuffer) { - transferables.push((data as any).cover.data.buffer); + if (data.cover?.data?.buffer instanceof ArrayBuffer) { + transferables.push(data.cover.data.buffer); } worker.postMessage({ ...data, type: 'Add', audioData, filename }, transferables); @@ -168,15 +172,15 @@ export async function getMetadataWithTagLib( if (error) { reject(new Error(error)); } else { - resolve(data!); + 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 ((audioData as Uint8Array)?.buffer instanceof ArrayBuffer) { + transferables.push((audioData as Uint8Array).buffer); } worker.postMessage({ type: 'Get', audioData, filename }, transferables); }), diff --git a/js/taglib.types.ts b/js/taglib.types.ts index 45b3b72..6bdde9a 100644 --- a/js/taglib.types.ts +++ b/js/taglib.types.ts @@ -52,7 +52,6 @@ export enum Mp4Stik { WhackedBookmark = 5, MusicVideo = 6, Movie = 9, - ShortFilm = 9, TVShow = 10, Booklet = 11, } diff --git a/js/taglib.worker.ts b/js/taglib.worker.ts index b50b5f0..71a0183 100644 --- a/js/taglib.worker.ts +++ b/js/taglib.worker.ts @@ -1,8 +1,8 @@ // filepath: /workspaces/monochrome/js/taglib.worker.ts -declare var self: DedicatedWorkerGlobalScope; +declare let self: DedicatedWorkerGlobalScope; import { ByteVector } from '!/@dantheman827/taglib-ts/src/byteVector.js'; -import { Mp4Tag, Mp4Item } from '!/@dantheman827/taglib-ts/src/mp4/mp4Tag.js'; +import { 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 { @@ -10,7 +10,6 @@ import { type _AddMetadataMessage, type _GetMetadataMessage, type AddMetadataMessage, - type GetMetadataMessage, type TagLibFileResponse, type TagLibMetadata, type TagLibMetadataResponse, @@ -18,6 +17,7 @@ import { type TagLibWorkerMessage, type TagLibWorkerResponse, } from './taglib.types'; +// eslint-disable-next-line @typescript-eslint/no-unused-vars 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'; @@ -38,7 +38,7 @@ export async function addMetadataToAudio(message: _AddMetadataMessage): Promise< const { audioData, audioRef, - filename, + filename: _filename, title, artist, writeArtistsSeparately = false, @@ -79,7 +79,7 @@ export async function addMetadataToAudio(message: _AddMetadataMessage): Promise< const isMp4 = underlying instanceof Mp4File; const isMpeg = underlying instanceof MpegFile; const isOgg = underlying instanceof OggVorbisFile; - const isWav = underlying instanceof WavFile; + const _isWav = underlying instanceof WavFile; const needsCombinedTrackDisc = isMp4 || isMpeg; @@ -137,7 +137,7 @@ 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; + const mp4Tag = underlying.tag(); mp4Tag.setItem('xid ', Mp4Item.fromStringList([`:isrc:${isrc}`])); } if (upc) props.replace('UPC', [upc]); @@ -145,8 +145,8 @@ export async function addMetadataToAudio(message: _AddMetadataMessage): Promise< 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; + // rtng is a byte item — must be set directly on the Mp4Tag + const mp4Tag = underlying.tag(); mp4Tag.setItem('rtng', Mp4Item.fromByte(explicit ? 1 : 0)); } else { props.replace('ITUNESADVISORY', [explicit ? '1' : '0']); @@ -154,7 +154,7 @@ export async function addMetadataToAudio(message: _AddMetadataMessage): Promise< } if (stik != null && isMp4) { - const mp4Tag = (underlying as Mp4File).tag() as Mp4Tag; + const mp4Tag = underlying.tag(); mp4Tag.setItem('stik', Mp4Item.fromByte(stik)); } @@ -177,7 +177,7 @@ export async function addMetadataToAudio(message: _AddMetadataMessage): Promise< await ref.save(); }); - const file = ref.file() as TagLibFile; + const file = ref.file(); if (!file) return audioData; const stream = file.stream(); @@ -207,7 +207,7 @@ export async function addMetadataToAudio(message: _AddMetadataMessage): Promise< } export async function getMetadataFromAudio(message: _GetMetadataMessage): Promise { - const { audioData, audioRef, filename } = message; + const { audioData, audioRef } = message; const data: TagLibReadMetadata = { duration: 0 }; const ref = @@ -263,7 +263,7 @@ export async function getMetadataFromAudio(message: _GetMetadataMessage): Promis data.isrc = props.get('ISRC')?.[0] || undefined; if (isMp4) { - const mp4Tag = (underlying as Mp4File).tag() as Mp4Tag; + const mp4Tag = underlying.tag(); data.explicit = mp4Tag.item('rtng')?.toByte() === 1; } else { data.explicit = props.get('ITUNESADVISORY')?.[0] === '1'; diff --git a/js/themeStore.js b/js/themeStore.js index 2f2cd19..27d2bb3 100644 --- a/js/themeStore.js +++ b/js/themeStore.js @@ -48,9 +48,9 @@ export class ThemeStore { } init() { - document.getElementById('open-theme-store-btn')?.addEventListener('click', () => { + document.getElementById('open-theme-store-btn')?.addEventListener('click', async () => { this.modal.classList.add('active'); - this.loadThemes(); + await this.loadThemes(); }); this.modal?.querySelector('.close-modal-btn')?.addEventListener('click', () => { @@ -59,14 +59,14 @@ export class ThemeStore { const tabs = this.modal?.querySelectorAll('.search-tab'); tabs?.forEach((tab) => { - tab.addEventListener('click', () => { + tab.addEventListener('click', async () => { tabs.forEach((t) => t.classList.remove('active')); this.modal.querySelectorAll('.search-tab-content').forEach((c) => c.classList.remove('active')); tab.classList.add('active'); const contentId = tab.dataset.tab === 'browse' ? 'theme-store-browse' : 'theme-store-upload'; document.getElementById(contentId)?.classList.add('active'); if (tab.dataset.tab === 'upload') { - this.checkAuth(); + await this.checkAuth(); } else { this.resetEditState(); } @@ -82,9 +82,9 @@ export class ThemeStore { this.uploadForm?.addEventListener('submit', (e) => this.handleUpload(e)); if (authManager) { - authManager.onAuthStateChanged(() => { + authManager.onAuthStateChanged(async () => { if (this.modal.classList.contains('active')) { - this.checkAuth(); + await this.checkAuth(); } }); } @@ -231,10 +231,10 @@ export class ThemeStore {
`; - div.addEventListener('click', (e) => { + div.addEventListener('click', async (e) => { if (e.target.closest('.delete-theme-btn')) { e.stopPropagation(); - this.deleteTheme(theme.id); + await this.deleteTheme(theme.id); return; } if (e.target.closest('.edit-theme-btn')) { @@ -266,7 +266,7 @@ export class ThemeStore { await this.pb.collection('themes').delete(themeId, { f_id: fbUser.$id }); alert('Theme deleted successfully.'); - this.loadThemes(); + await this.loadThemes(); } catch (err) { console.error('Failed to delete theme:', err); alert('Failed to delete theme. You might not have permission.'); @@ -467,6 +467,7 @@ export class ThemeStore { // Force reflow to ensure theme changes are applied immediately document.documentElement.style.display = 'none'; + // eslint-disable-next-line @typescript-eslint/no-unused-expressions document.documentElement.offsetHeight; document.documentElement.style.display = ''; @@ -572,7 +573,7 @@ export class ThemeStore { } this.modal.querySelector('[data-tab="browse"]').click(); - this.loadThemes(); + await this.loadThemes(); } catch (err) { console.error('Upload failed:', err); console.error('Response data:', err.data); diff --git a/js/tracker.js b/js/tracker.js index 7ae76e1..de42676 100644 --- a/js/tracker.js +++ b/js/tracker.js @@ -280,7 +280,7 @@ function renderTrackerTracks(container, tracks) { } // Create project card HTML - EXACTLY like album cards -export function createProjectCardHTML(era, artist, sheetId, trackCount) { +export function createProjectCardHTML(era, _artist, sheetId, trackCount) { const playBtnHTML = `