From 5e88d4e37bfb4ddae1f5d63dad6e006b0e0eef88 Mon Sep 17 00:00:00 2001 From: Jordan Eldredge Date: Mon, 14 Jul 2025 17:06:30 -0700 Subject: [PATCH 01/78] Ignore dist in built --- packages/webamp/package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/webamp/package.json b/packages/webamp/package.json index 225deddc..c7126dd1 100644 --- a/packages/webamp/package.json +++ b/packages/webamp/package.json @@ -6,7 +6,8 @@ "built", "!built/types/demo", "!built/*.html", - "!built/tsBuilt" + "!built/tsBuilt", + "!built/dist" ], "types": "built/types/js/webamp.d.ts", "browser": "built/webamp.bundle.min.mjs", From a4dec854068aed44f3d72debbe473d750c364ae7 Mon Sep 17 00:00:00 2001 From: Jordan Eldredge Date: Mon, 14 Jul 2025 17:57:20 -0700 Subject: [PATCH 02/78] Document butterchurn entrypoint --- examples/minimalMilkdrop/index.html | 9 ++++----- packages/webamp-docs/docs/05_features/02_mikdrop.md | 2 +- .../webamp-docs/docs/06_API/02_webamp-constructor.md | 2 +- packages/webamp-docs/docs/06_API/03_instance-methods.md | 6 +++--- 4 files changed, 9 insertions(+), 10 deletions(-) diff --git a/examples/minimalMilkdrop/index.html b/examples/minimalMilkdrop/index.html index 66b2ec34..9131c9b8 100755 --- a/examples/minimalMilkdrop/index.html +++ b/examples/minimalMilkdrop/index.html @@ -11,12 +11,11 @@ -``` - -## Include via a script tag - -This will make the Webamp constructor available as a `window` property: `window.Webamp` keep in mind that you will need to use the `type="module"` attribute on the script tag, - -```html - - ``` diff --git a/packages/webamp-docs/docs/06_API/00_entrypoints.md b/packages/webamp-docs/docs/06_API/00_entrypoints.md new file mode 100644 index 00000000..697f9491 --- /dev/null +++ b/packages/webamp-docs/docs/06_API/00_entrypoints.md @@ -0,0 +1,51 @@ +# Entrypoints + +The Webamp NPM library includes multiple pre-built bundles. This makes it easy to add Webamp to your project without having to configure a bundler or, if you are using a bundler, control the size of your initial bundle. + +All bundles are minified ES modules with provided TypeScript types. For examples of how to use these entrypoints, see the [examples page](../04_examples.md). + +The main bundles are exposed as the following [package entrypoints](https://nodejs.org/api/packages.html#package-entry-points): + +# `webamp/butterchurn` + +**Since** [v2.2.0](../12_changelog.md#220) + +:::tip +This is the recommended entrypoint to use if you want a fully featured Webamp and are not particularly concerned about bundle size. +::: + +This all-inclusive minified bundle has everything you need to enable all Webamp features, including the Milkdrop visualizer [Butterchurn](https://www.npmjs.com/package/butterchurn) and a collection of visualizer presets. It weighs in at about `520kB` minified and gzipped. + +```ts +import Webamp from "webamp/butterchurn"; +``` + +# `webamp/lazy` + +:::warning +Using this entrypoint requires that you have a rather sophisticated bundler setup. +::: + +This minified bundle omits three of Webamp's rather heavy dependencies which are generally not needed for initial render. By using this entrypoint you can optimize for very fast initial load while still ensuring all features work. It weighs in at about `200kB` minified and gzipped. + +- [Butterchurn](https://www.npmjs.com/package/butterchurn) - the Milkdrop visualizer, needed only once the user starts playing a track with the Milkdrop window open. +- [JSZip](https://www.npmjs.com/package/jszip) - Use for parsing user-provided skin. Needed only once the users selects a non-default skin. +- [music-metadata](https://www.npmjs.com/package/music-metadata) - Used for reading ID3 tags and other metadata from audio files. Needed only when the user loads a track for which `metaData` and `duration` information has not been provided. + +```ts +import Webamp from "webamp/lazy"; +``` + +For instructions on how to use this entrypoint, see the [Bundle Size Guide](../07_guides/03_bundle-size.md). + +# `webamp` + +:::warning +In a future version of Webamp, this entrypoint will become an alias of `webamp/butterchurn`. +::: + +For legacy reasons, this entrypoint is still available. It strikes a middle ground between the two entrypoints above. It includes JSZip and music-metadata, but does not include Butterchurn. This means that it can be used to create a Webamp instance with the Milkdrop visualizer disabled. It weighs in at about `300kB` minified and gzipped. + +```ts +import Webamp from "webamp"; +``` diff --git a/packages/webamp-docs/docs/12_changelog.md b/packages/webamp-docs/docs/12_changelog.md index cd17e56d..7f9d31de 100644 --- a/packages/webamp-docs/docs/12_changelog.md +++ b/packages/webamp-docs/docs/12_changelog.md @@ -16,7 +16,7 @@ This is the current version of Webamp. ### Improvements -- Added new `webamp/butterchurn` entrypoint which includes Milkdrop window by default. +- Added new [`webamp/butterchurn`](./06_API/00_entrypoints.md#webampbutterchurn) entrypoint which includes Milkdrop window by default. This is the recommended entrypoint for users who want a fully featured Webamp and are not particularly concerned about bundle size. - Butterchurn Milkdrop visualizer now always uses WebAssembly to sandbox code defined in visualizer presets. Read more about the security implications in [Speeding Up Webamp's Music Visualizer with WebAssembly](https://jordaneldredge.com/blog/speeding-up-winamps-music-visualizer-with-webassembly/#security). - Added new `Webamp` instance methods: - [`webamp.toggleShuffle`](./06_API/03_instance-methods.md#toggleshuffle-void) From e82db4cddd011268869befd7364fafbdebdaf95d Mon Sep 17 00:00:00 2001 From: Jordan Eldredge Date: Tue, 15 Jul 2025 16:41:24 -0700 Subject: [PATCH 04/78] Add example of using webamp/lazy entrypoint with vite --- examples/lazy/.gitignore | 24 ++ examples/lazy/README.md | 5 + examples/lazy/index.html | 13 + examples/lazy/package.json | 22 ++ examples/lazy/src/main.ts | 57 +++ examples/lazy/src/vite-env.d.ts | 1 + examples/lazy/tsconfig.json | 25 ++ pnpm-lock.yaml | 620 +++++++++++++++++++++++++++++++- 8 files changed, 764 insertions(+), 3 deletions(-) create mode 100644 examples/lazy/.gitignore create mode 100644 examples/lazy/README.md create mode 100644 examples/lazy/index.html create mode 100644 examples/lazy/package.json create mode 100644 examples/lazy/src/main.ts create mode 100644 examples/lazy/src/vite-env.d.ts create mode 100644 examples/lazy/tsconfig.json diff --git a/examples/lazy/.gitignore b/examples/lazy/.gitignore new file mode 100644 index 00000000..a547bf36 --- /dev/null +++ b/examples/lazy/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/examples/lazy/README.md b/examples/lazy/README.md new file mode 100644 index 00000000..2e22adf4 --- /dev/null +++ b/examples/lazy/README.md @@ -0,0 +1,5 @@ +# `webamp/lazy` Example + +Shows how it's possible to use Webamp with lazy loading and TypeScript. Uses [Vite](https://vitejs.dev/) for development and bundling. + +Pay special attention to the versions used in `package.json` since some beta versions are required for this to work. diff --git a/examples/lazy/index.html b/examples/lazy/index.html new file mode 100644 index 00000000..e6da3966 --- /dev/null +++ b/examples/lazy/index.html @@ -0,0 +1,13 @@ + + + + + + + Webamp + + +
+ + + diff --git a/examples/lazy/package.json b/examples/lazy/package.json new file mode 100644 index 00000000..8568daef --- /dev/null +++ b/examples/lazy/package.json @@ -0,0 +1,22 @@ +{ + "name": "lazy", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc && vite build", + "preview": "vite preview" + }, + "devDependencies": { + "typescript": "~5.8.3", + "vite": "^7.0.4" + }, + "dependencies": { + "butterchurn": "3.0.0-beta.5", + "butterchurn-presets": "3.0.0-beta.4", + "jszip": "^3.10.1", + "music-metadata": "^11.6.0", + "webamp": "^2.2.0" + } +} diff --git a/examples/lazy/src/main.ts b/examples/lazy/src/main.ts new file mode 100644 index 00000000..836906fe --- /dev/null +++ b/examples/lazy/src/main.ts @@ -0,0 +1,57 @@ +import Webamp from "webamp/lazy"; + +const webamp = new Webamp({ + initialTracks: [ + { + metaData: { + artist: "DJ Mike Llama", + title: "Llama Whippin' Intro", + }, + // NOTE: Your audio file must be served from the same domain as your HTML + // file, or served with permissive CORS HTTP headers: + // https://docs.webamp.org/docs/guides/cors + url: "https://cdn.jsdelivr.net/gh/captbaritone/webamp@43434d82cfe0e37286dbbe0666072dc3190a83bc/mp3/llama-2.91.mp3", + duration: 5.322286, + }, + ], + windowLayout: { + main: { position: { left: 0, top: 0 } }, + equalizer: { position: { left: 0, top: 116 } }, + playlist: { + position: { left: 0, top: 232 }, + size: { extraHeight: 4, extraWidth: 0 }, + }, + milkdrop: { + position: { left: 275, top: 0 }, + size: { extraHeight: 12, extraWidth: 7 }, + }, + }, + requireJSZip: async () => { + const JSZip = await import("jszip"); + return JSZip.default; + }, + // @ts-ignore + requireMusicMetadata: async () => { + return await import("music-metadata"); + }, + __butterchurnOptions: { + // @ts-ignore + importButterchurn: () => import("butterchurn"), + // @ts-ignore + getPresets: async () => { + const butterchurnPresets = await import( + // @ts-ignore + "butterchurn-presets/dist/base.js" + ); + // Convert the presets object + return Object.entries(butterchurnPresets.default).map( + ([name, preset]) => { + return { name, butterchurnPresetObject: preset }; + } + ); + }, + butterchurnOpen: true, + }, +}); + +webamp.renderWhenReady(document.getElementById("app")!); diff --git a/examples/lazy/src/vite-env.d.ts b/examples/lazy/src/vite-env.d.ts new file mode 100644 index 00000000..11f02fe2 --- /dev/null +++ b/examples/lazy/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/examples/lazy/tsconfig.json b/examples/lazy/tsconfig.json new file mode 100644 index 00000000..4f5edc24 --- /dev/null +++ b/examples/lazy/tsconfig.json @@ -0,0 +1,25 @@ +{ + "compilerOptions": { + "target": "ES2022", + "useDefineForClassFields": true, + "module": "ESNext", + "lib": ["ES2022", "DOM", "DOM.Iterable"], + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "moduleDetection": "force", + "noEmit": true, + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "erasableSyntaxOnly": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedSideEffectImports": true + }, + "include": ["src"] +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 68974207..83affde3 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -69,6 +69,31 @@ importers: specifier: ^5.6.2 version: 5.6.3 + examples/lazy: + dependencies: + butterchurn: + specifier: 3.0.0-beta.5 + version: 3.0.0-beta.5 + butterchurn-presets: + specifier: 3.0.0-beta.4 + version: 3.0.0-beta.4 + jszip: + specifier: ^3.10.1 + version: 3.10.1 + music-metadata: + specifier: ^11.6.0 + version: 11.6.0 + webamp: + specifier: ^2.2.0 + version: 2.2.0(@types/react-dom@18.2.24)(@types/react@18.2.74) + devDependencies: + typescript: + specifier: ~5.8.3 + version: 5.8.3 + vite: + specifier: ^7.0.4 + version: 7.0.4(@types/node@24.0.10)(jiti@1.21.7)(lightningcss@1.24.1)(terser@5.43.0) + packages/ani-cursor: dependencies: byte-data: @@ -1941,6 +1966,12 @@ packages: cpu: [ppc64] os: [aix] + '@esbuild/aix-ppc64@0.25.6': + resolution: {integrity: sha512-ShbM/3XxwuxjFiuVBHA+d3j5dyac0aEVVq1oluIDf71hUw0aRF59dV/efUsIwFnR6m8JNM2FjZOzmaZ8yG61kw==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + '@esbuild/android-arm64@0.16.3': resolution: {integrity: sha512-RolFVeinkeraDvN/OoRf1F/lP0KUfGNb5jxy/vkIMeRRChkrX/HTYN6TYZosRJs3a1+8wqpxAo5PI5hFmxyPRg==} engines: {node: '>=12'} @@ -1959,6 +1990,12 @@ packages: cpu: [arm64] os: [android] + '@esbuild/android-arm64@0.25.6': + resolution: {integrity: sha512-hd5zdUarsK6strW+3Wxi5qWws+rJhCCbMiC9QZyzoxfk5uHRIE8T287giQxzVpEvCwuJ9Qjg6bEjcRJcgfLqoA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + '@esbuild/android-arm@0.16.3': resolution: {integrity: sha512-mueuEoh+s1eRbSJqq9KNBQwI4QhQV6sRXIfTyLXSHGMpyew61rOK4qY21uKbXl1iBoMb0AdL1deWFCQVlN2qHA==} engines: {node: '>=12'} @@ -1977,6 +2014,12 @@ packages: cpu: [arm] os: [android] + '@esbuild/android-arm@0.25.6': + resolution: {integrity: sha512-S8ToEOVfg++AU/bHwdksHNnyLyVM+eMVAOf6yRKFitnwnbwwPNqKr3srzFRe7nzV69RQKb5DgchIX5pt3L53xg==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + '@esbuild/android-x64@0.16.3': resolution: {integrity: sha512-SFpTUcIT1bIJuCCBMCQWq1bL2gPTjWoLZdjmIhjdcQHaUfV41OQfho6Ici5uvvkMmZRXIUGpM3GxysP/EU7ifQ==} engines: {node: '>=12'} @@ -1995,6 +2038,12 @@ packages: cpu: [x64] os: [android] + '@esbuild/android-x64@0.25.6': + resolution: {integrity: sha512-0Z7KpHSr3VBIO9A/1wcT3NTy7EB4oNC4upJ5ye3R7taCc2GUdeynSLArnon5G8scPwaU866d3H4BCrE5xLW25A==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + '@esbuild/darwin-arm64@0.16.3': resolution: {integrity: sha512-DO8WykMyB+N9mIDfI/Hug70Dk1KipavlGAecxS3jDUwAbTpDXj0Lcwzw9svkhxfpCagDmpaTMgxWK8/C/XcXvw==} engines: {node: '>=12'} @@ -2013,6 +2062,12 @@ packages: cpu: [arm64] os: [darwin] + '@esbuild/darwin-arm64@0.25.6': + resolution: {integrity: sha512-FFCssz3XBavjxcFxKsGy2DYK5VSvJqa6y5HXljKzhRZ87LvEi13brPrf/wdyl/BbpbMKJNOr1Sd0jtW4Ge1pAA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + '@esbuild/darwin-x64@0.16.3': resolution: {integrity: sha512-uEqZQ2omc6BvWqdCiyZ5+XmxuHEi1SPzpVxXCSSV2+Sh7sbXbpeNhHIeFrIpRjAs0lI1FmA1iIOxFozKBhKgRQ==} engines: {node: '>=12'} @@ -2031,6 +2086,12 @@ packages: cpu: [x64] os: [darwin] + '@esbuild/darwin-x64@0.25.6': + resolution: {integrity: sha512-GfXs5kry/TkGM2vKqK2oyiLFygJRqKVhawu3+DOCk7OxLy/6jYkWXhlHwOoTb0WqGnWGAS7sooxbZowy+pK9Yg==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + '@esbuild/freebsd-arm64@0.16.3': resolution: {integrity: sha512-nJansp3sSXakNkOD5i5mIz2Is/HjzIhFs49b1tjrPrpCmwgBmH9SSzhC/Z1UqlkivqMYkhfPwMw1dGFUuwmXhw==} engines: {node: '>=12'} @@ -2049,6 +2110,12 @@ packages: cpu: [arm64] os: [freebsd] + '@esbuild/freebsd-arm64@0.25.6': + resolution: {integrity: sha512-aoLF2c3OvDn2XDTRvn8hN6DRzVVpDlj2B/F66clWd/FHLiHaG3aVZjxQX2DYphA5y/evbdGvC6Us13tvyt4pWg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + '@esbuild/freebsd-x64@0.16.3': resolution: {integrity: sha512-TfoDzLw+QHfc4a8aKtGSQ96Wa+6eimljjkq9HKR0rHlU83vw8aldMOUSJTUDxbcUdcgnJzPaX8/vGWm7vyV7ug==} engines: {node: '>=12'} @@ -2067,6 +2134,12 @@ packages: cpu: [x64] os: [freebsd] + '@esbuild/freebsd-x64@0.25.6': + resolution: {integrity: sha512-2SkqTjTSo2dYi/jzFbU9Plt1vk0+nNg8YC8rOXXea+iA3hfNJWebKYPs3xnOUf9+ZWhKAaxnQNUf2X9LOpeiMQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + '@esbuild/linux-arm64@0.16.3': resolution: {integrity: sha512-7I3RlsnxEFCHVZNBLb2w7unamgZ5sVwO0/ikE2GaYvYuUQs9Qte/w7TqWcXHtCwxvZx/2+F97ndiUQAWs47ZfQ==} engines: {node: '>=12'} @@ -2085,6 +2158,12 @@ packages: cpu: [arm64] os: [linux] + '@esbuild/linux-arm64@0.25.6': + resolution: {integrity: sha512-b967hU0gqKd9Drsh/UuAm21Khpoh6mPBSgz8mKRq4P5mVK8bpA+hQzmm/ZwGVULSNBzKdZPQBRT3+WuVavcWsQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + '@esbuild/linux-arm@0.16.3': resolution: {integrity: sha512-VwswmSYwVAAq6LysV59Fyqk3UIjbhuc6wb3vEcJ7HEJUtFuLK9uXWuFoH1lulEbE4+5GjtHi3MHX+w1gNHdOWQ==} engines: {node: '>=12'} @@ -2103,6 +2182,12 @@ packages: cpu: [arm] os: [linux] + '@esbuild/linux-arm@0.25.6': + resolution: {integrity: sha512-SZHQlzvqv4Du5PrKE2faN0qlbsaW/3QQfUUc6yO2EjFcA83xnwm91UbEEVx4ApZ9Z5oG8Bxz4qPE+HFwtVcfyw==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + '@esbuild/linux-ia32@0.16.3': resolution: {integrity: sha512-X8FDDxM9cqda2rJE+iblQhIMYY49LfvW4kaEjoFbTTQ4Go8G96Smj2w3BRTwA8IHGoi9dPOPGAX63dhuv19UqA==} engines: {node: '>=12'} @@ -2121,6 +2206,12 @@ packages: cpu: [ia32] os: [linux] + '@esbuild/linux-ia32@0.25.6': + resolution: {integrity: sha512-aHWdQ2AAltRkLPOsKdi3xv0mZ8fUGPdlKEjIEhxCPm5yKEThcUjHpWB1idN74lfXGnZ5SULQSgtr5Qos5B0bPw==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + '@esbuild/linux-loong64@0.16.3': resolution: {integrity: sha512-hIbeejCOyO0X9ujfIIOKjBjNAs9XD/YdJ9JXAy1lHA+8UXuOqbFe4ErMCqMr8dhlMGBuvcQYGF7+kO7waj2KHw==} engines: {node: '>=12'} @@ -2139,6 +2230,12 @@ packages: cpu: [loong64] os: [linux] + '@esbuild/linux-loong64@0.25.6': + resolution: {integrity: sha512-VgKCsHdXRSQ7E1+QXGdRPlQ/e08bN6WMQb27/TMfV+vPjjTImuT9PmLXupRlC90S1JeNNW5lzkAEO/McKeJ2yg==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + '@esbuild/linux-mips64el@0.16.3': resolution: {integrity: sha512-znFRzICT/V8VZQMt6rjb21MtAVJv/3dmKRMlohlShrbVXdBuOdDrGb+C2cZGQAR8RFyRe7HS6klmHq103WpmVw==} engines: {node: '>=12'} @@ -2157,6 +2254,12 @@ packages: cpu: [mips64el] os: [linux] + '@esbuild/linux-mips64el@0.25.6': + resolution: {integrity: sha512-WViNlpivRKT9/py3kCmkHnn44GkGXVdXfdc4drNmRl15zVQ2+D2uFwdlGh6IuK5AAnGTo2qPB1Djppj+t78rzw==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + '@esbuild/linux-ppc64@0.16.3': resolution: {integrity: sha512-EV7LuEybxhXrVTDpbqWF2yehYRNz5e5p+u3oQUS2+ZFpknyi1NXxr8URk4ykR8Efm7iu04//4sBg249yNOwy5Q==} engines: {node: '>=12'} @@ -2175,6 +2278,12 @@ packages: cpu: [ppc64] os: [linux] + '@esbuild/linux-ppc64@0.25.6': + resolution: {integrity: sha512-wyYKZ9NTdmAMb5730I38lBqVu6cKl4ZfYXIs31Baf8aoOtB4xSGi3THmDYt4BTFHk7/EcVixkOV2uZfwU3Q2Jw==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + '@esbuild/linux-riscv64@0.16.3': resolution: {integrity: sha512-uDxqFOcLzFIJ+r/pkTTSE9lsCEaV/Y6rMlQjUI9BkzASEChYL/aSQjZjchtEmdnVxDKETnUAmsaZ4pqK1eE5BQ==} engines: {node: '>=12'} @@ -2193,6 +2302,12 @@ packages: cpu: [riscv64] os: [linux] + '@esbuild/linux-riscv64@0.25.6': + resolution: {integrity: sha512-KZh7bAGGcrinEj4qzilJ4hqTY3Dg2U82c8bv+e1xqNqZCrCyc+TL9AUEn5WGKDzm3CfC5RODE/qc96OcbIe33w==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + '@esbuild/linux-s390x@0.16.3': resolution: {integrity: sha512-NbeREhzSxYwFhnCAQOQZmajsPYtX71Ufej3IQ8W2Gxskfz9DK58ENEju4SbpIj48VenktRASC52N5Fhyf/aliQ==} engines: {node: '>=12'} @@ -2211,6 +2326,12 @@ packages: cpu: [s390x] os: [linux] + '@esbuild/linux-s390x@0.25.6': + resolution: {integrity: sha512-9N1LsTwAuE9oj6lHMyyAM+ucxGiVnEqUdp4v7IaMmrwb06ZTEVCIs3oPPplVsnjPfyjmxwHxHMF8b6vzUVAUGw==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + '@esbuild/linux-x64@0.16.3': resolution: {integrity: sha512-SDiG0nCixYO9JgpehoKgScwic7vXXndfasjnD5DLbp1xltANzqZ425l7LSdHynt19UWOcDjG9wJJzSElsPvk0w==} engines: {node: '>=12'} @@ -2229,6 +2350,18 @@ packages: cpu: [x64] os: [linux] + '@esbuild/linux-x64@0.25.6': + resolution: {integrity: sha512-A6bJB41b4lKFWRKNrWoP2LHsjVzNiaurf7wyj/XtFNTsnPuxwEBWHLty+ZE0dWBKuSK1fvKgrKaNjBS7qbFKig==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-arm64@0.25.6': + resolution: {integrity: sha512-IjA+DcwoVpjEvyxZddDqBY+uJ2Snc6duLpjmkXm/v4xuS3H+3FkLZlDm9ZsAbF9rsfP3zeA0/ArNDORZgrxR/Q==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + '@esbuild/netbsd-x64@0.16.3': resolution: {integrity: sha512-AzbsJqiHEq1I/tUvOfAzCY15h4/7Ivp3ff/o1GpP16n48JMNAtbW0qui2WCgoIZArEHD0SUQ95gvR0oSO7ZbdA==} engines: {node: '>=12'} @@ -2247,6 +2380,18 @@ packages: cpu: [x64] os: [netbsd] + '@esbuild/netbsd-x64@0.25.6': + resolution: {integrity: sha512-dUXuZr5WenIDlMHdMkvDc1FAu4xdWixTCRgP7RQLBOkkGgwuuzaGSYcOpW4jFxzpzL1ejb8yF620UxAqnBrR9g==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-arm64@0.25.6': + resolution: {integrity: sha512-l8ZCvXP0tbTJ3iaqdNf3pjaOSd5ex/e6/omLIQCVBLmHTlfXW3zAxQ4fnDmPLOB1x9xrcSi/xtCWFwCZRIaEwg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + '@esbuild/openbsd-x64@0.16.3': resolution: {integrity: sha512-gSABi8qHl8k3Cbi/4toAzHiykuBuWLZs43JomTcXkjMZVkp0gj3gg9mO+9HJW/8GB5H89RX/V0QP4JGL7YEEVg==} engines: {node: '>=12'} @@ -2265,6 +2410,18 @@ packages: cpu: [x64] os: [openbsd] + '@esbuild/openbsd-x64@0.25.6': + resolution: {integrity: sha512-hKrmDa0aOFOr71KQ/19JC7az1P0GWtCN1t2ahYAf4O007DHZt/dW8ym5+CUdJhQ/qkZmI1HAF8KkJbEFtCL7gw==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/openharmony-arm64@0.25.6': + resolution: {integrity: sha512-+SqBcAWoB1fYKmpWoQP4pGtx+pUUC//RNYhFdbcSA16617cchuryuhOCRpPsjCblKukAckWsV+aQ3UKT/RMPcA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + '@esbuild/sunos-x64@0.16.3': resolution: {integrity: sha512-SF9Kch5Ete4reovvRO6yNjMxrvlfT0F0Flm+NPoUw5Z4Q3r1d23LFTgaLwm3Cp0iGbrU/MoUI+ZqwCv5XJijCw==} engines: {node: '>=12'} @@ -2283,6 +2440,12 @@ packages: cpu: [x64] os: [sunos] + '@esbuild/sunos-x64@0.25.6': + resolution: {integrity: sha512-dyCGxv1/Br7MiSC42qinGL8KkG4kX0pEsdb0+TKhmJZgCUDBGmyo1/ArCjNGiOLiIAgdbWgmWgib4HoCi5t7kA==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + '@esbuild/win32-arm64@0.16.3': resolution: {integrity: sha512-u5aBonZIyGopAZyOnoPAA6fGsDeHByZ9CnEzyML9NqntK6D/xl5jteZUKm/p6nD09+v3pTM6TuUIqSPcChk5gg==} engines: {node: '>=12'} @@ -2301,6 +2464,12 @@ packages: cpu: [arm64] os: [win32] + '@esbuild/win32-arm64@0.25.6': + resolution: {integrity: sha512-42QOgcZeZOvXfsCBJF5Afw73t4veOId//XD3i+/9gSkhSV6Gk3VPlWncctI+JcOyERv85FUo7RxuxGy+z8A43Q==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + '@esbuild/win32-ia32@0.16.3': resolution: {integrity: sha512-GlgVq1WpvOEhNioh74TKelwla9KDuAaLZrdxuuUgsP2vayxeLgVc+rbpIv0IYF4+tlIzq2vRhofV+KGLD+37EQ==} engines: {node: '>=12'} @@ -2319,6 +2488,12 @@ packages: cpu: [ia32] os: [win32] + '@esbuild/win32-ia32@0.25.6': + resolution: {integrity: sha512-4AWhgXmDuYN7rJI6ORB+uU9DHLq/erBbuMoAuB4VWJTu5KtCgcKYPynF0YI1VkBNuEfjNlLrFr9KZPJzrtLkrQ==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + '@esbuild/win32-x64@0.16.3': resolution: {integrity: sha512-5/JuTd8OWW8UzEtyf19fbrtMJENza+C9JoPIkvItgTBQ1FO2ZLvjbPO6Xs54vk0s5JB5QsfieUEshRQfu7ZHow==} engines: {node: '>=12'} @@ -2337,6 +2512,12 @@ packages: cpu: [x64] os: [win32] + '@esbuild/win32-x64@0.25.6': + resolution: {integrity: sha512-NgJPHHbEpLQgDH2MjQu90pzW/5vvXIZ7KOnPyNBm92A6WgZ/7b6fJyUBjoumLqeOQQGqY2QjQxRo97ah4Sj0cA==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + '@eslint-community/eslint-utils@4.4.0': resolution: {integrity: sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -3085,81 +3266,181 @@ packages: cpu: [arm] os: [android] + '@rollup/rollup-android-arm-eabi@4.45.0': + resolution: {integrity: sha512-2o/FgACbji4tW1dzXOqAV15Eu7DdgbKsF2QKcxfG4xbh5iwU7yr5RRP5/U+0asQliSYv5M4o7BevlGIoSL0LXg==} + cpu: [arm] + os: [android] + '@rollup/rollup-android-arm64@4.18.0': resolution: {integrity: sha512-avCea0RAP03lTsDhEyfy+hpfr85KfyTctMADqHVhLAF3MlIkq83CP8UfAHUssgXTYd+6er6PaAhx/QGv4L1EiA==} cpu: [arm64] os: [android] + '@rollup/rollup-android-arm64@4.45.0': + resolution: {integrity: sha512-PSZ0SvMOjEAxwZeTx32eI/j5xSYtDCRxGu5k9zvzoY77xUNssZM+WV6HYBLROpY5CkXsbQjvz40fBb7WPwDqtQ==} + cpu: [arm64] + os: [android] + '@rollup/rollup-darwin-arm64@4.18.0': resolution: {integrity: sha512-IWfdwU7KDSm07Ty0PuA/W2JYoZ4iTj3TUQjkVsO/6U+4I1jN5lcR71ZEvRh52sDOERdnNhhHU57UITXz5jC1/w==} cpu: [arm64] os: [darwin] + '@rollup/rollup-darwin-arm64@4.45.0': + resolution: {integrity: sha512-BA4yPIPssPB2aRAWzmqzQ3y2/KotkLyZukVB7j3psK/U3nVJdceo6qr9pLM2xN6iRP/wKfxEbOb1yrlZH6sYZg==} + cpu: [arm64] + os: [darwin] + '@rollup/rollup-darwin-x64@4.18.0': resolution: {integrity: sha512-n2LMsUz7Ynu7DoQrSQkBf8iNrjOGyPLrdSg802vk6XT3FtsgX6JbE8IHRvposskFm9SNxzkLYGSq9QdpLYpRNA==} cpu: [x64] os: [darwin] + '@rollup/rollup-darwin-x64@4.45.0': + resolution: {integrity: sha512-Pr2o0lvTwsiG4HCr43Zy9xXrHspyMvsvEw4FwKYqhli4FuLE5FjcZzuQ4cfPe0iUFCvSQG6lACI0xj74FDZKRA==} + cpu: [x64] + os: [darwin] + + '@rollup/rollup-freebsd-arm64@4.45.0': + resolution: {integrity: sha512-lYE8LkE5h4a/+6VnnLiL14zWMPnx6wNbDG23GcYFpRW1V9hYWHAw9lBZ6ZUIrOaoK7NliF1sdwYGiVmziUF4vA==} + cpu: [arm64] + os: [freebsd] + + '@rollup/rollup-freebsd-x64@4.45.0': + resolution: {integrity: sha512-PVQWZK9sbzpvqC9Q0GlehNNSVHR+4m7+wET+7FgSnKG3ci5nAMgGmr9mGBXzAuE5SvguCKJ6mHL6vq1JaJ/gvw==} + cpu: [x64] + os: [freebsd] + '@rollup/rollup-linux-arm-gnueabihf@4.18.0': resolution: {integrity: sha512-C/zbRYRXFjWvz9Z4haRxcTdnkPt1BtCkz+7RtBSuNmKzMzp3ZxdM28Mpccn6pt28/UWUCTXa+b0Mx1k3g6NOMA==} cpu: [arm] os: [linux] + '@rollup/rollup-linux-arm-gnueabihf@4.45.0': + resolution: {integrity: sha512-hLrmRl53prCcD+YXTfNvXd776HTxNh8wPAMllusQ+amcQmtgo3V5i/nkhPN6FakW+QVLoUUr2AsbtIRPFU3xIA==} + cpu: [arm] + os: [linux] + '@rollup/rollup-linux-arm-musleabihf@4.18.0': resolution: {integrity: sha512-l3m9ewPgjQSXrUMHg93vt0hYCGnrMOcUpTz6FLtbwljo2HluS4zTXFy2571YQbisTnfTKPZ01u/ukJdQTLGh9A==} cpu: [arm] os: [linux] + '@rollup/rollup-linux-arm-musleabihf@4.45.0': + resolution: {integrity: sha512-XBKGSYcrkdiRRjl+8XvrUR3AosXU0NvF7VuqMsm7s5nRy+nt58ZMB19Jdp1RdqewLcaYnpk8zeVs/4MlLZEJxw==} + cpu: [arm] + os: [linux] + '@rollup/rollup-linux-arm64-gnu@4.18.0': resolution: {integrity: sha512-rJ5D47d8WD7J+7STKdCUAgmQk49xuFrRi9pZkWoRD1UeSMakbcepWXPF8ycChBoAqs1pb2wzvbY6Q33WmN2ftw==} cpu: [arm64] os: [linux] + '@rollup/rollup-linux-arm64-gnu@4.45.0': + resolution: {integrity: sha512-fRvZZPUiBz7NztBE/2QnCS5AtqLVhXmUOPj9IHlfGEXkapgImf4W9+FSkL8cWqoAjozyUzqFmSc4zh2ooaeF6g==} + cpu: [arm64] + os: [linux] + '@rollup/rollup-linux-arm64-musl@4.18.0': resolution: {integrity: sha512-be6Yx37b24ZwxQ+wOQXXLZqpq4jTckJhtGlWGZs68TgdKXJgw54lUUoFYrg6Zs/kjzAQwEwYbp8JxZVzZLRepQ==} cpu: [arm64] os: [linux] + '@rollup/rollup-linux-arm64-musl@4.45.0': + resolution: {integrity: sha512-Btv2WRZOcUGi8XU80XwIvzTg4U6+l6D0V6sZTrZx214nrwxw5nAi8hysaXj/mctyClWgesyuxbeLylCBNauimg==} + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-loongarch64-gnu@4.45.0': + resolution: {integrity: sha512-Li0emNnwtUZdLwHjQPBxn4VWztcrw/h7mgLyHiEI5Z0MhpeFGlzaiBHpSNVOMB/xucjXTTcO+dhv469Djr16KA==} + cpu: [loong64] + os: [linux] + '@rollup/rollup-linux-powerpc64le-gnu@4.18.0': resolution: {integrity: sha512-hNVMQK+qrA9Todu9+wqrXOHxFiD5YmdEi3paj6vP02Kx1hjd2LLYR2eaN7DsEshg09+9uzWi2W18MJDlG0cxJA==} cpu: [ppc64] os: [linux] + '@rollup/rollup-linux-powerpc64le-gnu@4.45.0': + resolution: {integrity: sha512-sB8+pfkYx2kvpDCfd63d5ScYT0Fz1LO6jIb2zLZvmK9ob2D8DeVqrmBDE0iDK8KlBVmsTNzrjr3G1xV4eUZhSw==} + cpu: [ppc64] + os: [linux] + '@rollup/rollup-linux-riscv64-gnu@4.18.0': resolution: {integrity: sha512-ROCM7i+m1NfdrsmvwSzoxp9HFtmKGHEqu5NNDiZWQtXLA8S5HBCkVvKAxJ8U+CVctHwV2Gb5VUaK7UAkzhDjlg==} cpu: [riscv64] os: [linux] + '@rollup/rollup-linux-riscv64-gnu@4.45.0': + resolution: {integrity: sha512-5GQ6PFhh7E6jQm70p1aW05G2cap5zMOvO0se5JMecHeAdj5ZhWEHbJ4hiKpfi1nnnEdTauDXxPgXae/mqjow9w==} + cpu: [riscv64] + os: [linux] + + '@rollup/rollup-linux-riscv64-musl@4.45.0': + resolution: {integrity: sha512-N/euLsBd1rekWcuduakTo/dJw6U6sBP3eUq+RXM9RNfPuWTvG2w/WObDkIvJ2KChy6oxZmOSC08Ak2OJA0UiAA==} + cpu: [riscv64] + os: [linux] + '@rollup/rollup-linux-s390x-gnu@4.18.0': resolution: {integrity: sha512-0UyyRHyDN42QL+NbqevXIIUnKA47A+45WyasO+y2bGJ1mhQrfrtXUpTxCOrfxCR4esV3/RLYyucGVPiUsO8xjg==} cpu: [s390x] os: [linux] + '@rollup/rollup-linux-s390x-gnu@4.45.0': + resolution: {integrity: sha512-2l9sA7d7QdikL0xQwNMO3xURBUNEWyHVHfAsHsUdq+E/pgLTUcCE+gih5PCdmyHmfTDeXUWVhqL0WZzg0nua3g==} + cpu: [s390x] + os: [linux] + '@rollup/rollup-linux-x64-gnu@4.18.0': resolution: {integrity: sha512-xuglR2rBVHA5UsI8h8UbX4VJ470PtGCf5Vpswh7p2ukaqBGFTnsfzxUBetoWBWymHMxbIG0Cmx7Y9qDZzr648w==} cpu: [x64] os: [linux] + '@rollup/rollup-linux-x64-gnu@4.45.0': + resolution: {integrity: sha512-XZdD3fEEQcwG2KrJDdEQu7NrHonPxxaV0/w2HpvINBdcqebz1aL+0vM2WFJq4DeiAVT6F5SUQas65HY5JDqoPw==} + cpu: [x64] + os: [linux] + '@rollup/rollup-linux-x64-musl@4.18.0': resolution: {integrity: sha512-LKaqQL9osY/ir2geuLVvRRs+utWUNilzdE90TpyoX0eNqPzWjRm14oMEE+YLve4k/NAqCdPkGYDaDF5Sw+xBfg==} cpu: [x64] os: [linux] + '@rollup/rollup-linux-x64-musl@4.45.0': + resolution: {integrity: sha512-7ayfgvtmmWgKWBkCGg5+xTQ0r5V1owVm67zTrsEY1008L5ro7mCyGYORomARt/OquB9KY7LpxVBZes+oSniAAQ==} + cpu: [x64] + os: [linux] + '@rollup/rollup-win32-arm64-msvc@4.18.0': resolution: {integrity: sha512-7J6TkZQFGo9qBKH0pk2cEVSRhJbL6MtfWxth7Y5YmZs57Pi+4x6c2dStAUvaQkHQLnEQv1jzBUW43GvZW8OFqA==} cpu: [arm64] os: [win32] + '@rollup/rollup-win32-arm64-msvc@4.45.0': + resolution: {integrity: sha512-B+IJgcBnE2bm93jEW5kHisqvPITs4ddLOROAcOc/diBgrEiQJJ6Qcjby75rFSmH5eMGrqJryUgJDhrfj942apQ==} + cpu: [arm64] + os: [win32] + '@rollup/rollup-win32-ia32-msvc@4.18.0': resolution: {integrity: sha512-Txjh+IxBPbkUB9+SXZMpv+b/vnTEtFyfWZgJ6iyCmt2tdx0OF5WhFowLmnh8ENGNpfUlUZkdI//4IEmhwPieNg==} cpu: [ia32] os: [win32] + '@rollup/rollup-win32-ia32-msvc@4.45.0': + resolution: {integrity: sha512-+CXwwG66g0/FpWOnP/v1HnrGVSOygK/osUbu3wPRy8ECXjoYKjRAyfxYpDQOfghC5qPJYLPH0oN4MCOjwgdMug==} + cpu: [ia32] + os: [win32] + '@rollup/rollup-win32-x64-msvc@4.18.0': resolution: {integrity: sha512-UOo5FdvOL0+eIVTgS4tIdbW+TtnBLWg1YBCcU2KWM7nuNwRz9bksDX1bekJJCpu25N1DVWaCwnT39dVQxzqS8g==} cpu: [x64] os: [win32] + '@rollup/rollup-win32-x64-msvc@4.45.0': + resolution: {integrity: sha512-SRf1cytG7wqcHVLrBc9VtPK4pU5wxiB/lNIkNmW2ApKXIg+RpqwHfsaEK+e7eH4A1BpI6BX/aBWXxZCIrJg3uA==} + cpu: [x64] + os: [win32] + '@sentry/browser@5.9.1': resolution: {integrity: sha512-7AOabwp9yAH9h6Xe6TfDwlLxHbUSWs+SPWHI7bPlht2yDSAqkXYGSzRr5X0XQJX9oBQdx2cEPMqHyJrbNaP/og==} engines: {node: '>=6'} @@ -4110,6 +4391,9 @@ packages: resolution: {integrity: sha512-84xBncKNPBK8Ae89F65+SyVcOihrIbm/3N7to+GpRBHEUXGjA3ydWTMpcRW6jmFzkBQ/eqYy/y+J+NBpJWYjBg==} engines: {node: '>= 14.0.0'} + ani-cursor@0.0.5: + resolution: {integrity: sha512-gGxst72lG9TOwEfbVpX9vHhzUGw+4Ee2XB6AfYq5JP+bxBtpAjgnTBepCVxYF5t1TPrWHN23nWqLTflJOA3/ag==} + ansi-align@3.0.1: resolution: {integrity: sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==} @@ -6083,6 +6367,11 @@ packages: engines: {node: '>=12'} hasBin: true + esbuild@0.25.6: + resolution: {integrity: sha512-GVuzuUwtdsghE3ocJ9Bs8PNoF13HNQ5TXbEi2AhvVb8xU1Iwt9Fos9FEamfoee+u/TOsn7GUWc04lz46n2bbTg==} + engines: {node: '>=18'} + hasBin: true + esbuild@0.9.7: resolution: {integrity: sha512-VtUf6aQ89VTmMLKrWHYG50uByMF4JQlVysb8dmg6cOgW8JnFCipmz7p+HNBl+RR3LLCuBxFGVauAe2wfnF9bLg==} hasBin: true @@ -6512,6 +6801,14 @@ packages: picomatch: optional: true + fdir@6.4.6: + resolution: {integrity: sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + fecha@4.2.3: resolution: {integrity: sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==} @@ -11333,6 +11630,11 @@ packages: engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true + rollup@4.45.0: + resolution: {integrity: sha512-WLjEcJRIo7i3WDDgOIJqVI2d+lAC3EwvOGy+Xfq6hs+GQuAA4Di/H72xmXkOhrIWFg2PFYSKZYfH0f4vfKXN4A==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} + hasBin: true + rtlcss@4.3.0: resolution: {integrity: sha512-FI+pHEn7Wc4NqKXMXFM+VAYKEj/mRIcW4h24YVwVtyjI+EqGrLc2Hx/Ny0lrZ21cBWU2goLy36eqMcNj3AQJig==} engines: {node: '>=12.0.0'} @@ -12184,6 +12486,10 @@ packages: tiny-warning@1.0.3: resolution: {integrity: sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==} + tinyglobby@0.2.14: + resolution: {integrity: sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==} + engines: {node: '>=12.0.0'} + tinypool@1.1.1: resolution: {integrity: sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==} engines: {node: ^18.0.0 || >=20.0.0} @@ -12492,6 +12798,11 @@ packages: engines: {node: '>=14.17'} hasBin: true + typescript@5.8.3: + resolution: {integrity: sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==} + engines: {node: '>=14.17'} + hasBin: true + ufo@1.5.3: resolution: {integrity: sha512-Y7HYmWaFwPUmkoQCUIAYpKqkOf+SbVj/2fJJZ4RJMCfZp0rTGwRbzQD+HghfnhKOjL9E01okqz+ncJskGYfBNw==} @@ -12843,6 +13154,46 @@ packages: terser: optional: true + vite@7.0.4: + resolution: {integrity: sha512-SkaSguuS7nnmV7mfJ8l81JGBFV7Gvzp8IzgE8A8t23+AxuNX61Q5H1Tpz5efduSN7NHC8nQXD3sKQKZAu5mNEA==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + 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 + vm2@3.9.19: resolution: {integrity: sha512-J637XF0DHDMV57R6JyVsTak7nIL8gy5KH4r1HiwWLf/4GBbb5MKL5y7LpmF4A8E2nR6XmzpmMFQ7V7ppPTmUQg==} engines: {node: '>=6.0'} @@ -12884,6 +13235,9 @@ packages: web-vitals@0.2.4: resolution: {integrity: sha512-6BjspCO9VriYy12z356nL6JBS0GYeEcA457YyRzD+dD6XYCQ75NKhcOHUMHentOE7OcVCIXXDvOm0jKFfQG2Gg==} + webamp@2.2.0: + resolution: {integrity: sha512-XzKr65Z4d+4rxA1J//aPkZRqvPS0aqAxpryNKaWt/EDQ4uCJadxjr966QElagH+iZxWMCDekW5dV/dTx5b+WPQ==} + webidl-conversions@3.0.1: resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} @@ -13003,6 +13357,9 @@ packages: wildcard@2.0.1: resolution: {integrity: sha512-CC1bOL87PIWSBhDcTrdeLo6eGT7mCFtrg0uIJtqJUFyK+eJnzl8A1niH56uu7KMa5XFrtiV+AQuHO3n7DsHnLQ==} + winamp-eqf@1.0.0: + resolution: {integrity: sha512-yUIb4+lTYBKP4L6nPXdDj1CQBXlJ+/PrNAkT1VbTAgeFjX8lPxAthsUE5NxQP4s8SO4YMJemsrErZ49Bh+/Veg==} + winston-transport@4.7.0: resolution: {integrity: sha512-ajBj65K5I7denzer2IYW6+2bNIVqLGDHqDw3Ow8Ohh+vdW+rv4MZ6eiDvHoKhfJFZ2auyN8byXieDDJ96ViONg==} engines: {node: '>= 12.0.0'} @@ -15324,6 +15681,9 @@ snapshots: '@esbuild/aix-ppc64@0.20.2': optional: true + '@esbuild/aix-ppc64@0.25.6': + optional: true + '@esbuild/android-arm64@0.16.3': optional: true @@ -15333,6 +15693,9 @@ snapshots: '@esbuild/android-arm64@0.20.2': optional: true + '@esbuild/android-arm64@0.25.6': + optional: true + '@esbuild/android-arm@0.16.3': optional: true @@ -15342,6 +15705,9 @@ snapshots: '@esbuild/android-arm@0.20.2': optional: true + '@esbuild/android-arm@0.25.6': + optional: true + '@esbuild/android-x64@0.16.3': optional: true @@ -15351,6 +15717,9 @@ snapshots: '@esbuild/android-x64@0.20.2': optional: true + '@esbuild/android-x64@0.25.6': + optional: true + '@esbuild/darwin-arm64@0.16.3': optional: true @@ -15360,6 +15729,9 @@ snapshots: '@esbuild/darwin-arm64@0.20.2': optional: true + '@esbuild/darwin-arm64@0.25.6': + optional: true + '@esbuild/darwin-x64@0.16.3': optional: true @@ -15369,6 +15741,9 @@ snapshots: '@esbuild/darwin-x64@0.20.2': optional: true + '@esbuild/darwin-x64@0.25.6': + optional: true + '@esbuild/freebsd-arm64@0.16.3': optional: true @@ -15378,6 +15753,9 @@ snapshots: '@esbuild/freebsd-arm64@0.20.2': optional: true + '@esbuild/freebsd-arm64@0.25.6': + optional: true + '@esbuild/freebsd-x64@0.16.3': optional: true @@ -15387,6 +15765,9 @@ snapshots: '@esbuild/freebsd-x64@0.20.2': optional: true + '@esbuild/freebsd-x64@0.25.6': + optional: true + '@esbuild/linux-arm64@0.16.3': optional: true @@ -15396,6 +15777,9 @@ snapshots: '@esbuild/linux-arm64@0.20.2': optional: true + '@esbuild/linux-arm64@0.25.6': + optional: true + '@esbuild/linux-arm@0.16.3': optional: true @@ -15405,6 +15789,9 @@ snapshots: '@esbuild/linux-arm@0.20.2': optional: true + '@esbuild/linux-arm@0.25.6': + optional: true + '@esbuild/linux-ia32@0.16.3': optional: true @@ -15414,6 +15801,9 @@ snapshots: '@esbuild/linux-ia32@0.20.2': optional: true + '@esbuild/linux-ia32@0.25.6': + optional: true + '@esbuild/linux-loong64@0.16.3': optional: true @@ -15423,6 +15813,9 @@ snapshots: '@esbuild/linux-loong64@0.20.2': optional: true + '@esbuild/linux-loong64@0.25.6': + optional: true + '@esbuild/linux-mips64el@0.16.3': optional: true @@ -15432,6 +15825,9 @@ snapshots: '@esbuild/linux-mips64el@0.20.2': optional: true + '@esbuild/linux-mips64el@0.25.6': + optional: true + '@esbuild/linux-ppc64@0.16.3': optional: true @@ -15441,6 +15837,9 @@ snapshots: '@esbuild/linux-ppc64@0.20.2': optional: true + '@esbuild/linux-ppc64@0.25.6': + optional: true + '@esbuild/linux-riscv64@0.16.3': optional: true @@ -15450,6 +15849,9 @@ snapshots: '@esbuild/linux-riscv64@0.20.2': optional: true + '@esbuild/linux-riscv64@0.25.6': + optional: true + '@esbuild/linux-s390x@0.16.3': optional: true @@ -15459,6 +15861,9 @@ snapshots: '@esbuild/linux-s390x@0.20.2': optional: true + '@esbuild/linux-s390x@0.25.6': + optional: true + '@esbuild/linux-x64@0.16.3': optional: true @@ -15468,6 +15873,12 @@ snapshots: '@esbuild/linux-x64@0.20.2': optional: true + '@esbuild/linux-x64@0.25.6': + optional: true + + '@esbuild/netbsd-arm64@0.25.6': + optional: true + '@esbuild/netbsd-x64@0.16.3': optional: true @@ -15477,6 +15888,12 @@ snapshots: '@esbuild/netbsd-x64@0.20.2': optional: true + '@esbuild/netbsd-x64@0.25.6': + optional: true + + '@esbuild/openbsd-arm64@0.25.6': + optional: true + '@esbuild/openbsd-x64@0.16.3': optional: true @@ -15486,6 +15903,12 @@ snapshots: '@esbuild/openbsd-x64@0.20.2': optional: true + '@esbuild/openbsd-x64@0.25.6': + optional: true + + '@esbuild/openharmony-arm64@0.25.6': + optional: true + '@esbuild/sunos-x64@0.16.3': optional: true @@ -15495,6 +15918,9 @@ snapshots: '@esbuild/sunos-x64@0.20.2': optional: true + '@esbuild/sunos-x64@0.25.6': + optional: true + '@esbuild/win32-arm64@0.16.3': optional: true @@ -15504,6 +15930,9 @@ snapshots: '@esbuild/win32-arm64@0.20.2': optional: true + '@esbuild/win32-arm64@0.25.6': + optional: true + '@esbuild/win32-ia32@0.16.3': optional: true @@ -15513,6 +15942,9 @@ snapshots: '@esbuild/win32-ia32@0.20.2': optional: true + '@esbuild/win32-ia32@0.25.6': + optional: true + '@esbuild/win32-x64@0.16.3': optional: true @@ -15522,6 +15954,9 @@ snapshots: '@esbuild/win32-x64@0.20.2': optional: true + '@esbuild/win32-x64@0.25.6': + optional: true + '@eslint-community/eslint-utils@4.4.0(eslint@8.57.0)': dependencies: eslint: 8.57.0 @@ -16415,51 +16850,111 @@ snapshots: '@rollup/rollup-android-arm-eabi@4.18.0': optional: true + '@rollup/rollup-android-arm-eabi@4.45.0': + optional: true + '@rollup/rollup-android-arm64@4.18.0': optional: true + '@rollup/rollup-android-arm64@4.45.0': + optional: true + '@rollup/rollup-darwin-arm64@4.18.0': optional: true + '@rollup/rollup-darwin-arm64@4.45.0': + optional: true + '@rollup/rollup-darwin-x64@4.18.0': optional: true + '@rollup/rollup-darwin-x64@4.45.0': + optional: true + + '@rollup/rollup-freebsd-arm64@4.45.0': + optional: true + + '@rollup/rollup-freebsd-x64@4.45.0': + optional: true + '@rollup/rollup-linux-arm-gnueabihf@4.18.0': optional: true + '@rollup/rollup-linux-arm-gnueabihf@4.45.0': + optional: true + '@rollup/rollup-linux-arm-musleabihf@4.18.0': optional: true + '@rollup/rollup-linux-arm-musleabihf@4.45.0': + optional: true + '@rollup/rollup-linux-arm64-gnu@4.18.0': optional: true + '@rollup/rollup-linux-arm64-gnu@4.45.0': + optional: true + '@rollup/rollup-linux-arm64-musl@4.18.0': optional: true + '@rollup/rollup-linux-arm64-musl@4.45.0': + optional: true + + '@rollup/rollup-linux-loongarch64-gnu@4.45.0': + optional: true + '@rollup/rollup-linux-powerpc64le-gnu@4.18.0': optional: true + '@rollup/rollup-linux-powerpc64le-gnu@4.45.0': + optional: true + '@rollup/rollup-linux-riscv64-gnu@4.18.0': optional: true + '@rollup/rollup-linux-riscv64-gnu@4.45.0': + optional: true + + '@rollup/rollup-linux-riscv64-musl@4.45.0': + optional: true + '@rollup/rollup-linux-s390x-gnu@4.18.0': optional: true + '@rollup/rollup-linux-s390x-gnu@4.45.0': + optional: true + '@rollup/rollup-linux-x64-gnu@4.18.0': optional: true + '@rollup/rollup-linux-x64-gnu@4.45.0': + optional: true + '@rollup/rollup-linux-x64-musl@4.18.0': optional: true + '@rollup/rollup-linux-x64-musl@4.45.0': + optional: true + '@rollup/rollup-win32-arm64-msvc@4.18.0': optional: true + '@rollup/rollup-win32-arm64-msvc@4.45.0': + optional: true + '@rollup/rollup-win32-ia32-msvc@4.18.0': optional: true + '@rollup/rollup-win32-ia32-msvc@4.45.0': + optional: true + '@rollup/rollup-win32-x64-msvc@4.18.0': optional: true + '@rollup/rollup-win32-x64-msvc@4.45.0': + optional: true + '@sentry/browser@5.9.1': dependencies: '@sentry/core': 5.8.0 @@ -16864,11 +17359,11 @@ snapshots: '@types/eslint-scope@3.7.7': dependencies: '@types/eslint': 8.56.7 - '@types/estree': 1.0.8 + '@types/estree': 0.0.50 '@types/eslint@8.56.7': dependencies: - '@types/estree': 1.0.8 + '@types/estree': 0.0.50 '@types/json-schema': 7.0.15 '@types/estree-jsx@0.0.1': @@ -16877,7 +17372,7 @@ snapshots: '@types/estree-jsx@1.0.5': dependencies: - '@types/estree': 1.0.8 + '@types/estree': 0.0.50 '@types/estree@0.0.39': {} @@ -17749,6 +18244,11 @@ snapshots: '@algolia/requester-fetch': 5.32.0 '@algolia/requester-node-http': 5.32.0 + ani-cursor@0.0.5: + dependencies: + byte-data: 18.1.1 + riff-file: 1.0.3 + ansi-align@3.0.1: dependencies: string-width: 4.2.3 @@ -20038,6 +20538,35 @@ snapshots: '@esbuild/win32-ia32': 0.20.2 '@esbuild/win32-x64': 0.20.2 + esbuild@0.25.6: + optionalDependencies: + '@esbuild/aix-ppc64': 0.25.6 + '@esbuild/android-arm': 0.25.6 + '@esbuild/android-arm64': 0.25.6 + '@esbuild/android-x64': 0.25.6 + '@esbuild/darwin-arm64': 0.25.6 + '@esbuild/darwin-x64': 0.25.6 + '@esbuild/freebsd-arm64': 0.25.6 + '@esbuild/freebsd-x64': 0.25.6 + '@esbuild/linux-arm': 0.25.6 + '@esbuild/linux-arm64': 0.25.6 + '@esbuild/linux-ia32': 0.25.6 + '@esbuild/linux-loong64': 0.25.6 + '@esbuild/linux-mips64el': 0.25.6 + '@esbuild/linux-ppc64': 0.25.6 + '@esbuild/linux-riscv64': 0.25.6 + '@esbuild/linux-s390x': 0.25.6 + '@esbuild/linux-x64': 0.25.6 + '@esbuild/netbsd-arm64': 0.25.6 + '@esbuild/netbsd-x64': 0.25.6 + '@esbuild/openbsd-arm64': 0.25.6 + '@esbuild/openbsd-x64': 0.25.6 + '@esbuild/openharmony-arm64': 0.25.6 + '@esbuild/sunos-x64': 0.25.6 + '@esbuild/win32-arm64': 0.25.6 + '@esbuild/win32-ia32': 0.25.6 + '@esbuild/win32-x64': 0.25.6 + esbuild@0.9.7: {} escalade@3.2.0: {} @@ -20659,6 +21188,10 @@ snapshots: optionalDependencies: picomatch: 2.3.1 + fdir@6.4.6(picomatch@4.0.2): + optionalDependencies: + picomatch: 4.0.2 + fecha@4.2.3: {} feed@4.2.2: @@ -26761,6 +27294,32 @@ snapshots: '@rollup/rollup-win32-x64-msvc': 4.18.0 fsevents: 2.3.3 + rollup@4.45.0: + dependencies: + '@types/estree': 1.0.8 + optionalDependencies: + '@rollup/rollup-android-arm-eabi': 4.45.0 + '@rollup/rollup-android-arm64': 4.45.0 + '@rollup/rollup-darwin-arm64': 4.45.0 + '@rollup/rollup-darwin-x64': 4.45.0 + '@rollup/rollup-freebsd-arm64': 4.45.0 + '@rollup/rollup-freebsd-x64': 4.45.0 + '@rollup/rollup-linux-arm-gnueabihf': 4.45.0 + '@rollup/rollup-linux-arm-musleabihf': 4.45.0 + '@rollup/rollup-linux-arm64-gnu': 4.45.0 + '@rollup/rollup-linux-arm64-musl': 4.45.0 + '@rollup/rollup-linux-loongarch64-gnu': 4.45.0 + '@rollup/rollup-linux-powerpc64le-gnu': 4.45.0 + '@rollup/rollup-linux-riscv64-gnu': 4.45.0 + '@rollup/rollup-linux-riscv64-musl': 4.45.0 + '@rollup/rollup-linux-s390x-gnu': 4.45.0 + '@rollup/rollup-linux-x64-gnu': 4.45.0 + '@rollup/rollup-linux-x64-musl': 4.45.0 + '@rollup/rollup-win32-arm64-msvc': 4.45.0 + '@rollup/rollup-win32-ia32-msvc': 4.45.0 + '@rollup/rollup-win32-x64-msvc': 4.45.0 + fsevents: 2.3.3 + rtlcss@4.3.0: dependencies: escalade: 3.2.0 @@ -27829,6 +28388,11 @@ snapshots: tiny-warning@1.0.3: {} + tinyglobby@0.2.14: + dependencies: + fdir: 6.4.6(picomatch@4.0.2) + picomatch: 4.0.2 + tinypool@1.1.1: {} tinyqueue@1.2.3: {} @@ -28156,6 +28720,8 @@ snapshots: typescript@5.6.3: {} + typescript@5.8.3: {} + ufo@1.5.3: {} uint8array-extras@1.4.0: {} @@ -28577,6 +29143,21 @@ snapshots: lightningcss: 1.24.1 terser: 5.43.0 + vite@7.0.4(@types/node@24.0.10)(jiti@1.21.7)(lightningcss@1.24.1)(terser@5.43.0): + dependencies: + esbuild: 0.25.6 + fdir: 6.4.6(picomatch@4.0.2) + picomatch: 4.0.2 + postcss: 8.5.6 + rollup: 4.45.0 + tinyglobby: 0.2.14 + optionalDependencies: + '@types/node': 24.0.10 + fsevents: 2.3.3 + jiti: 1.21.7 + lightningcss: 1.24.1 + terser: 5.43.0 + vm2@3.9.19: dependencies: acorn: 8.15.0 @@ -28621,6 +29202,37 @@ snapshots: web-vitals@0.2.4: {} + webamp@2.2.0(@types/react-dom@18.2.24)(@types/react@18.2.74): + dependencies: + '@redux-devtools/extension': 3.3.0(redux@5.0.1) + '@sentry/browser': 5.9.1 + ani-cursor: 0.0.5 + butterchurn: 3.0.0-beta.5 + butterchurn-presets: 3.0.0-beta.4 + classnames: 2.5.1 + fscreen: 1.2.0 + invariant: 2.2.4 + jszip: 3.10.1 + lodash: 4.17.21 + milkdrop-preset-converter-aws: 0.1.6 + music-metadata: 11.6.0 + music-metadata-browser: 0.6.6 + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + react-redux: 8.1.3(@types/react-dom@18.2.24)(@types/react@18.2.74)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(redux@5.0.1) + redux: 5.0.1 + redux-sentry-middleware: 0.1.8 + redux-thunk: 2.4.2(redux@5.0.1) + reselect: 3.0.1 + strtok3: 10.3.1 + tinyqueue: 1.2.3 + winamp-eqf: 1.0.0 + transitivePeerDependencies: + - '@types/react' + - '@types/react-dom' + - react-native + - supports-color + webidl-conversions@3.0.1: {} webidl-conversions@7.0.0: {} @@ -28831,6 +29443,8 @@ snapshots: wildcard@2.0.1: {} + winamp-eqf@1.0.0: {} + winston-transport@4.7.0: dependencies: logform: 2.6.0 From 6f8f85c865a169fe02e6f8619ddfff98980f8239 Mon Sep 17 00:00:00 2001 From: Jordan Eldredge Date: Tue, 15 Jul 2025 16:58:52 -0700 Subject: [PATCH 05/78] Allow users to provide their own Butterchurn presets (#1309) * Allow users to provide their own Butterchurn presets * Ensure entrypoints have anchor links --- .../webamp-docs/docs/06_API/00_entrypoints.md | 6 ++-- .../docs/06_API/02_webamp-constructor.md | 12 ++++++++ packages/webamp-docs/docs/12_changelog.md | 8 +++-- packages/webamp/js/types.ts | 12 ++++++++ packages/webamp/js/webampLazy.tsx | 30 ++++++++++++++----- packages/webamp/js/webampWithButterchurn.ts | 19 +++++++----- 6 files changed, 67 insertions(+), 20 deletions(-) diff --git a/packages/webamp-docs/docs/06_API/00_entrypoints.md b/packages/webamp-docs/docs/06_API/00_entrypoints.md index 697f9491..aacf7f7e 100644 --- a/packages/webamp-docs/docs/06_API/00_entrypoints.md +++ b/packages/webamp-docs/docs/06_API/00_entrypoints.md @@ -6,7 +6,7 @@ All bundles are minified ES modules with provided TypeScript types. For examples The main bundles are exposed as the following [package entrypoints](https://nodejs.org/api/packages.html#package-entry-points): -# `webamp/butterchurn` +## `webamp/butterchurn` **Since** [v2.2.0](../12_changelog.md#220) @@ -20,7 +20,7 @@ This all-inclusive minified bundle has everything you need to enable all Webamp import Webamp from "webamp/butterchurn"; ``` -# `webamp/lazy` +## `webamp/lazy` :::warning Using this entrypoint requires that you have a rather sophisticated bundler setup. @@ -38,7 +38,7 @@ import Webamp from "webamp/lazy"; For instructions on how to use this entrypoint, see the [Bundle Size Guide](../07_guides/03_bundle-size.md). -# `webamp` +## `webamp` :::warning In a future version of Webamp, this entrypoint will become an alias of `webamp/butterchurn`. diff --git a/packages/webamp-docs/docs/06_API/02_webamp-constructor.md b/packages/webamp-docs/docs/06_API/02_webamp-constructor.md index 9c082ceb..23d15adb 100644 --- a/packages/webamp-docs/docs/06_API/02_webamp-constructor.md +++ b/packages/webamp-docs/docs/06_API/02_webamp-constructor.md @@ -254,3 +254,15 @@ const webamp = new Webamp({ // ...other config options }); ``` + +### `requireButterchurnPresets?: () => Promise` + +**Since** [unreleased](../12_changelog.md#unreleased) + +Milkdrop (Butterchurn) presets to be used. If not specified, the default presets +included in the bundle will be used. + +Presets are expected to be in Butterchurn's JSON format. You can find these `.json` files in: + +- The [Milkdrop Presets Collection](https://archive.org/details/milkdrops) at the Internet Archive. +- The [`butterchurn-presets@3.0.0-beta.4`](https://www.npmjs.com/package/butterchurn-presets/v/3.0.0-beta.4) NPM package diff --git a/packages/webamp-docs/docs/12_changelog.md b/packages/webamp-docs/docs/12_changelog.md index 7f9d31de..bacb1d55 100644 --- a/packages/webamp-docs/docs/12_changelog.md +++ b/packages/webamp-docs/docs/12_changelog.md @@ -1,12 +1,16 @@ # Changelog - +::: + +### Improvements + +- Added new [`requireButterchurnPresets`](./06_API/02_webamp-constructor.md#requirebutterchurnpresets---promisepreset) option when constructing a Webamp instance. This allows you to specify which Butterchurn presets to use for the Milkdrop visualizer. If you don't specify this option, Webamp will use the default Butterchurn presets. ## 2.2.0 diff --git a/packages/webamp/js/types.ts b/packages/webamp/js/types.ts index 802becc6..03f77d15 100644 --- a/packages/webamp/js/types.ts +++ b/packages/webamp/js/types.ts @@ -741,6 +741,18 @@ export interface Options { * https://developer.mozilla.org/en-US/docs/Web/API/Media_Session_API */ enableMediaSession?: boolean; + + /** + * Milkdrop (Butterchurn) presets to be used. If not specified, the default presets + * included in the bundle will be used. + * + * Presets are expected to be in Butterchurn's JSON format. You can find these + * `.json` files in: + * + * * The [Milkdrop Presets Collection](https://archive.org/details/milkdrops) at the Internet Archive. + * * The `butterchurn-presets@3.0.0-beta.4` NPM package + */ + requireButterchurnPresets?: () => Promise; } /** diff --git a/packages/webamp/js/webampLazy.tsx b/packages/webamp/js/webampLazy.tsx index 81ce91ec..2078e514 100644 --- a/packages/webamp/js/webampLazy.tsx +++ b/packages/webamp/js/webampLazy.tsx @@ -16,6 +16,7 @@ import { PlaylistTrack, PlayerMediaStatus, IMetadataApi, + Preset, } from "./types"; import getStore from "./store"; import App from "./components/App"; @@ -44,6 +45,7 @@ export interface PrivateOptions { export interface InjectableDependencies { requireJSZip: () => Promise; requireMusicMetadata: () => Promise; + requireButterchurnPresets?: () => Promise; } class Webamp { @@ -51,7 +53,9 @@ class Webamp { _actionEmitter: Emitter; _root: ReactDOM.Root | null; _disposable: Disposable; - options: Options & PrivateOptions & InjectableDependencies; // TODO: Make this _private + // TODO: Make this _private + options: Options & PrivateOptions & InjectableDependencies; + media: IMedia; // TODO: Make this _private store: Store; // TODO: Make this _private @@ -84,6 +88,7 @@ class Webamp { zIndex, requireJSZip, requireMusicMetadata, + requireButterchurnPresets, handleTrackDropEvent, handleAddUrlEvent, handleLoadListEvent, @@ -93,11 +98,22 @@ class Webamp { __customMediaClass, } = this.options; + const butterchurnOptions = __butterchurnOptions; + + if (requireButterchurnPresets != null) { + if (butterchurnOptions == null) { + throw new Error( + "You must pass `__butterchurnOptions` if you are using `requireButterchurnPresets`." + ); + } + butterchurnOptions.getPresets = requireButterchurnPresets; + } + // TODO: Make this much cleaner. let convertPreset = null; - if (__butterchurnOptions != null) { + if (butterchurnOptions != null) { const { importConvertPreset, presetConverterEndpoint } = - __butterchurnOptions; + butterchurnOptions; if (importConvertPreset != null && presetConverterEndpoint != null) { convertPreset = async (file: File): Promise => { @@ -148,14 +164,12 @@ class Webamp { this.store.dispatch({ type: "SET_Z_INDEX", zIndex }); } - if (options.__butterchurnOptions) { + if (butterchurnOptions) { this.store.dispatch({ type: "ENABLE_MILKDROP", - open: options.__butterchurnOptions.butterchurnOpen, + open: butterchurnOptions.butterchurnOpen, }); - this.store.dispatch( - Actions.initializePresets(options.__butterchurnOptions) - ); + this.store.dispatch(Actions.initializePresets(butterchurnOptions)); } const handleOnline = () => diff --git a/packages/webamp/js/webampWithButterchurn.ts b/packages/webamp/js/webampWithButterchurn.ts index a327a094..10ef253e 100644 --- a/packages/webamp/js/webampWithButterchurn.ts +++ b/packages/webamp/js/webampWithButterchurn.ts @@ -1,4 +1,4 @@ -import { Options } from "./types"; +import { Options, Preset } from "./types"; import { PrivateOptions } from "./webampLazy"; import Webamp from "./webamp"; // @ts-ignore @@ -19,18 +19,23 @@ const DEFAULT_BUTTERCHURN_WINDOW_LAYOUT = { }, }; +const DEFAULT_REQUIRE_BUTTERCHURN_PRESETS = async () => + Object.entries(butterchurnPresets).map(([name, preset]) => { + return { name, butterchurnPresetObject: preset as Object }; + }); + export default class WebampWithButterchurn extends Webamp { constructor(options: Options & PrivateOptions) { + const requireButterchurnPresets = + options.requireButterchurnPresets ?? DEFAULT_REQUIRE_BUTTERCHURN_PRESETS; super({ ...options, + requireButterchurnPresets, __butterchurnOptions: { importButterchurn: () => Promise.resolve(butterchurn), - // @ts-ignore - getPresets: () => { - return Object.entries(butterchurnPresets).map(([name, preset]) => { - return { name, butterchurnPresetObject: preset }; - }); - }, + // This should be considered deprecated, and users should instead supply + // the top level `requireButterchurnPresets` option. + getPresets: requireButterchurnPresets, butterchurnOpen: true, }, windowLayout: options.windowLayout ?? DEFAULT_BUTTERCHURN_WINDOW_LAYOUT, From 41cfbbb63ca86524e0ea34e8575be6ba5eeb38aa Mon Sep 17 00:00:00 2001 From: Jordan Eldredge Date: Tue, 15 Jul 2025 16:59:31 -0700 Subject: [PATCH 06/78] Update readme to reflect package changes --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 42057cff..50aa0ede 100755 --- a/README.md +++ b/README.md @@ -20,9 +20,9 @@ Webamp uses a [monorepo](https://en.wikipedia.org/wiki/Monorepo) approach, so in - [`packages/webamp`](https://github.com/captbaritone/webamp/tree/master/packages/webamp): The [Webamp NPM module](https://www.npmjs.com/package/webamp) - [`packages/webamp/demo`](https://github.com/captbaritone/webamp/tree/master/packages/webamp/demo): The demo site which lives at [webamp.org](https://webamp.org) +- [`packages/webamp-docs`](https://github.com/captbaritone/webamp/tree/master/packages/webamp-docs): The documentation site for Webamp the NPM library which lives at [docs.webamp.org](https://docs.webamp.org) - [`packages/ani-cursor`](https://github.com/captbaritone/webamp/tree/master/packages/ani-cursor): An NPM module for rendering animiated `.ani` cursors as CSS animations - [`packages/skin-database`](https://github.com/captbaritone/webamp/tree/master/packages/skin-database): The server component of https://skins.webamp.org which also runs our [Twitter bot](https://twitter.com/winampskins), and a Discord bot for our community chat -- [`packages/skin-museum-client`](https://github.com/captbaritone/webamp/tree/master/packages/skin-museum-client): The front-end component of https://skins.webamp.org. - [`packages/winamp-eqf`](https://github.com/captbaritone/webamp/tree/master/packages/winamp-eqf): An NPM module for parsing and constructing Winamp equalizer preset files (`.eqf`) - [`packages/webamp-modern`](https://github.com/captbaritone/webamp/tree/master/packages/webamp-modern): A prototype exploring rendering "modern" Winamp skins in the browser - [`examples`](https://github.com/captbaritone/webamp/tree/master/examples): A few examples showing how to use the NPM module From eaba9667e27cb42c324f07cc6aa9ddf8812be967 Mon Sep 17 00:00:00 2001 From: Jordan Eldredge Date: Tue, 15 Jul 2025 18:57:35 -0700 Subject: [PATCH 07/78] Try to fix netlify deploy --- deploy.sh | 6 ------ netlify.toml | 2 +- package.json | 2 +- 3 files changed, 2 insertions(+), 8 deletions(-) delete mode 100755 deploy.sh diff --git a/deploy.sh b/deploy.sh deleted file mode 100755 index 6cba5378..00000000 --- a/deploy.sh +++ /dev/null @@ -1,6 +0,0 @@ -#!/bin/bash -pnpm --filter ani-cursor build -pnpm --filter webamp build -pnpm --filter webamp build-library -pnpm --filter webamp-modern build -mv packages/webamp-modern/build packages/webamp/dist/demo-site/modern \ No newline at end of file diff --git a/netlify.toml b/netlify.toml index c80f98a6..0b597cc1 100644 --- a/netlify.toml +++ b/netlify.toml @@ -1,5 +1,5 @@ [build] -command = "pnpm deploy" +command = "pnpm run deploy" publish = "packages/webamp/dist/demo-site/" # A short URL for listeners of https://changelog.com/podcast/291 diff --git a/package.json b/package.json index edb7328e..3d28becf 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,7 @@ "test:unit": "jest", "lint": "eslint . --ext ts,tsx,js,jsx --rulesdir=packages/webamp-modern/tools/eslint-rules", "type-check": "pnpm --filter webamp type-check && pnpm --filter ani-cursor type-check && pnpm --filter skin-database type-check && pnpm --filter webamp-docs type-check && pnpm --filter winamp-eqf type-check", - "deploy": "sh deploy.sh", + "deploy": "npx turbo run deploy", "format": "prettier --write '**/*.{js,ts,tsx}'" }, "devDependencies": { From a30ab82ccc7f0836dbb643e7f8e47c3f52befcfd Mon Sep 17 00:00:00 2001 From: Jordan Eldredge Date: Tue, 15 Jul 2025 19:00:09 -0700 Subject: [PATCH 08/78] Add example showing how to use requireButterchurnPresets --- examples/multipleMilkdropPresets/README.md | 13 ++++++ examples/multipleMilkdropPresets/index.html | 45 +++++++++++++++++++++ 2 files changed, 58 insertions(+) create mode 100644 examples/multipleMilkdropPresets/README.md create mode 100755 examples/multipleMilkdropPresets/index.html diff --git a/examples/multipleMilkdropPresets/README.md b/examples/multipleMilkdropPresets/README.md new file mode 100644 index 00000000..9778227e --- /dev/null +++ b/examples/multipleMilkdropPresets/README.md @@ -0,0 +1,13 @@ +# Multiple Milkdrop Presets Example + +An example of overriding the default Milkdrop presets with a custom set of presets. + +This example fetches the Webamp bundle from a free CDN, and fetches the audio file and skin from a free CDN as well. + +You should be able to open this local html file in your browser and see Webamp working. + +``` +$ git clone git@github.com:captbaritone/webamp.git +$ cd webamp +$ open examples/multipleMilkdropPresets/index.html +``` diff --git a/examples/multipleMilkdropPresets/index.html b/examples/multipleMilkdropPresets/index.html new file mode 100755 index 00000000..1bf5e440 --- /dev/null +++ b/examples/multipleMilkdropPresets/index.html @@ -0,0 +1,45 @@ + + + + + + + + +
+ +
+ + + From e99b2ab6f710c8d021da2e9f2985d7193db58593 Mon Sep 17 00:00:00 2001 From: Jordan Eldredge Date: Tue, 15 Jul 2025 19:02:17 -0700 Subject: [PATCH 09/78] Fix deploy script --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 3d28becf..e8483856 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,7 @@ "test:unit": "jest", "lint": "eslint . --ext ts,tsx,js,jsx --rulesdir=packages/webamp-modern/tools/eslint-rules", "type-check": "pnpm --filter webamp type-check && pnpm --filter ani-cursor type-check && pnpm --filter skin-database type-check && pnpm --filter webamp-docs type-check && pnpm --filter winamp-eqf type-check", - "deploy": "npx turbo run deploy", + "deploy": "npx turbo run build", "format": "prettier --write '**/*.{js,ts,tsx}'" }, "devDependencies": { From acff24b7bb14f6d47dedc23c0f1a41d918d01db4 Mon Sep 17 00:00:00 2001 From: Jordan Eldredge Date: Tue, 15 Jul 2025 19:13:06 -0700 Subject: [PATCH 10/78] Try to avoid OOM on Netlify --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index e8483856..1476ae92 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,7 @@ "test:unit": "jest", "lint": "eslint . --ext ts,tsx,js,jsx --rulesdir=packages/webamp-modern/tools/eslint-rules", "type-check": "pnpm --filter webamp type-check && pnpm --filter ani-cursor type-check && pnpm --filter skin-database type-check && pnpm --filter webamp-docs type-check && pnpm --filter winamp-eqf type-check", - "deploy": "npx turbo run build", + "deploy": "npx turbo build --concurrency 1", "format": "prettier --write '**/*.{js,ts,tsx}'" }, "devDependencies": { From f35f1242cabb7d85674163aa3520d0fd59c93ec0 Mon Sep 17 00:00:00 2001 From: Jordan Eldredge Date: Tue, 15 Jul 2025 19:25:16 -0700 Subject: [PATCH 11/78] Try harder to avoid OOms in Netlify build --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 1476ae92..4144079b 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,7 @@ "test:unit": "jest", "lint": "eslint . --ext ts,tsx,js,jsx --rulesdir=packages/webamp-modern/tools/eslint-rules", "type-check": "pnpm --filter webamp type-check && pnpm --filter ani-cursor type-check && pnpm --filter skin-database type-check && pnpm --filter webamp-docs type-check && pnpm --filter winamp-eqf type-check", - "deploy": "npx turbo build --concurrency 1", + "deploy": "npx turbo webamp#build --concurrency 1", "format": "prettier --write '**/*.{js,ts,tsx}'" }, "devDependencies": { From 224b4b8058275f0b3dc98c65edee47d3a27f8541 Mon Sep 17 00:00:00 2001 From: Jordan Eldredge Date: Tue, 15 Jul 2025 19:33:26 -0700 Subject: [PATCH 12/78] Revive Webamp modern on Netlify --- package.json | 2 +- turbo.json | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 4144079b..778c7acc 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,7 @@ "test:unit": "jest", "lint": "eslint . --ext ts,tsx,js,jsx --rulesdir=packages/webamp-modern/tools/eslint-rules", "type-check": "pnpm --filter webamp type-check && pnpm --filter ani-cursor type-check && pnpm --filter skin-database type-check && pnpm --filter webamp-docs type-check && pnpm --filter winamp-eqf type-check", - "deploy": "npx turbo webamp#build --concurrency 1", + "deploy": "npx turbo webamp#build webamp-modern#build --concurrency 1 && mv packages/webamp-modern/build packages/webamp/dist/demo-site/modern", "format": "prettier --write '**/*.{js,ts,tsx}'" }, "devDependencies": { diff --git a/turbo.json b/turbo.json index 4ef5097c..8fbdc85f 100644 --- a/turbo.json +++ b/turbo.json @@ -47,6 +47,9 @@ "dependsOn": ["ani-cursor#build", "winamp-eqf#build"], "outputs": [] }, + "webamp-modern#build": { + "outputs": [] + }, "webamp-modern#test": { "outputs": [] }, From cb651adfafa1c3b9568a2ffa25fc9a887ab40049 Mon Sep 17 00:00:00 2001 From: Jordan Eldredge Date: Tue, 15 Jul 2025 20:00:41 -0700 Subject: [PATCH 13/78] Run library build one at a time to avoid OOM on Netlify --- package.json | 1 + packages/webamp/scripts/rollup.mjs | 28 ++++++++++++++++++++++++---- 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index 778c7acc..73760173 100644 --- a/package.json +++ b/package.json @@ -16,6 +16,7 @@ "lint": "eslint . --ext ts,tsx,js,jsx --rulesdir=packages/webamp-modern/tools/eslint-rules", "type-check": "pnpm --filter webamp type-check && pnpm --filter ani-cursor type-check && pnpm --filter skin-database type-check && pnpm --filter webamp-docs type-check && pnpm --filter winamp-eqf type-check", "deploy": "npx turbo webamp#build webamp-modern#build --concurrency 1 && mv packages/webamp-modern/build packages/webamp/dist/demo-site/modern", + "deploy-docs": "npx turbo webamp-docs#build", "format": "prettier --write '**/*.{js,ts,tsx}'" }, "devDependencies": { diff --git a/packages/webamp/scripts/rollup.mjs b/packages/webamp/scripts/rollup.mjs index 150f020b..5bfb4a2b 100644 --- a/packages/webamp/scripts/rollup.mjs +++ b/packages/webamp/scripts/rollup.mjs @@ -88,12 +88,32 @@ const BUNDLES = [ }, ]; +const PARALLEL_LIMIT = 1; + build(); async function build() { - console.log(`🚀 Building ${BUNDLES.length} bundles in parallel...`); + console.log( + `🚀 Building ${BUNDLES.length} bundles in parallel (limit: ${PARALLEL_LIMIT})...` + ); - const buildPromises = BUNDLES.map(async (bundleDesc) => { + let index = 0; + async function nextBatch() { + const batch = []; + for ( + let i = 0; + i < PARALLEL_LIMIT && index < BUNDLES.length; + i++, index++ + ) { + batch.push(runBundle(BUNDLES[index])); + } + await Promise.all(batch); + if (index < BUNDLES.length) { + await nextBatch(); + } + } + + async function runBundle(bundleDesc) { console.log(`📦 Building ${bundleDesc.name}...`); const plugins = getPlugins({ outputFile: bundleDesc.output.file, @@ -136,8 +156,8 @@ async function build() { ...bundleDesc.output, }); console.log(`✅ Completed ${bundleDesc.name}`); - }); + } - await Promise.all(buildPromises); + await nextBatch(); console.log(`🎉 All ${BUNDLES.length} bundles built successfully!`); } From 27ba138a3e34185debbd16816eeab5c69089d3db Mon Sep 17 00:00:00 2001 From: Jordan Eldredge Date: Tue, 15 Jul 2025 20:13:45 -0700 Subject: [PATCH 14/78] Try package-specific Netlify build --- package.json | 1 - packages/webamp-docs/netlify.toml | 6 ++++++ packages/webamp-docs/package.json | 1 + 3 files changed, 7 insertions(+), 1 deletion(-) create mode 100644 packages/webamp-docs/netlify.toml diff --git a/package.json b/package.json index 73760173..778c7acc 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,6 @@ "lint": "eslint . --ext ts,tsx,js,jsx --rulesdir=packages/webamp-modern/tools/eslint-rules", "type-check": "pnpm --filter webamp type-check && pnpm --filter ani-cursor type-check && pnpm --filter skin-database type-check && pnpm --filter webamp-docs type-check && pnpm --filter winamp-eqf type-check", "deploy": "npx turbo webamp#build webamp-modern#build --concurrency 1 && mv packages/webamp-modern/build packages/webamp/dist/demo-site/modern", - "deploy-docs": "npx turbo webamp-docs#build", "format": "prettier --write '**/*.{js,ts,tsx}'" }, "devDependencies": { diff --git a/packages/webamp-docs/netlify.toml b/packages/webamp-docs/netlify.toml new file mode 100644 index 00000000..5d1fed91 --- /dev/null +++ b/packages/webamp-docs/netlify.toml @@ -0,0 +1,6 @@ +[build] +command = "pnpm run build-turbo" +publish = "build" + +[build.environment] +NODE_VERSION = "22.11.0" diff --git a/packages/webamp-docs/package.json b/packages/webamp-docs/package.json index 4dc48b88..e8ce7244 100644 --- a/packages/webamp-docs/package.json +++ b/packages/webamp-docs/package.json @@ -6,6 +6,7 @@ "docusaurus": "docusaurus", "start": "docusaurus start", "build": "docusaurus build", + "build-turbo": "npx turbo webamp-docs#build", "swizzle": "docusaurus swizzle", "deploy": "docusaurus deploy", "clear": "docusaurus clear", From 0608b2f9c680236b7b40726629f7a41c1e0e3acb Mon Sep 17 00:00:00 2001 From: Jordan Eldredge Date: Mon, 21 Jul 2025 19:48:51 -0700 Subject: [PATCH 15/78] Fix marquee dragging --- packages/webamp-docs/docs/12_changelog.md | 4 ++++ packages/webamp/js/components/MainWindow/Marquee.tsx | 2 +- packages/webamp/js/utils.ts | 2 ++ 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/packages/webamp-docs/docs/12_changelog.md b/packages/webamp-docs/docs/12_changelog.md index bacb1d55..418e6e74 100644 --- a/packages/webamp-docs/docs/12_changelog.md +++ b/packages/webamp-docs/docs/12_changelog.md @@ -12,6 +12,10 @@ If you want access to the changes in this section before they are officially rel - Added new [`requireButterchurnPresets`](./06_API/02_webamp-constructor.md#requirebutterchurnpresets---promisepreset) option when constructing a Webamp instance. This allows you to specify which Butterchurn presets to use for the Milkdrop visualizer. If you don't specify this option, Webamp will use the default Butterchurn presets. +### Bug Fixes + +- Fix bug where marquee dragging would error and not work. + ## 2.2.0 :::info diff --git a/packages/webamp/js/components/MainWindow/Marquee.tsx b/packages/webamp/js/components/MainWindow/Marquee.tsx index 9c41e191..b4118102 100644 --- a/packages/webamp/js/components/MainWindow/Marquee.tsx +++ b/packages/webamp/js/components/MainWindow/Marquee.tsx @@ -104,7 +104,7 @@ function useDragX() { document.addEventListener("mousemove", handleMouseMove); document.addEventListener("touchmove", handleMouseMove); - document.addEventListener("touseup", handleMouseUp); + document.addEventListener("mouseup", handleMouseUp); document.addEventListener("touchend", handleMouseUp); return handleMouseUp; diff --git a/packages/webamp/js/utils.ts b/packages/webamp/js/utils.ts index b53181c7..675f6207 100644 --- a/packages/webamp/js/utils.ts +++ b/packages/webamp/js/utils.ts @@ -421,6 +421,8 @@ function getPos(e: PosEvent): { clientX: number; clientY: number } { case "mousemove": { return e as MouseEvent; } + case "pointerdown": + return e as React.PointerEvent; default: throw new Error(`Unexpected event type: ${e.type}`); } From a0cecb8f93b81df9c63ae23accd37e09ed0151d5 Mon Sep 17 00:00:00 2001 From: Jordan Eldredge Date: Mon, 21 Jul 2025 20:03:04 -0700 Subject: [PATCH 16/78] Make shade mode trigger on click like the rest of the buttons --- packages/webamp/js/components/MainWindow/Shade.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/webamp/js/components/MainWindow/Shade.tsx b/packages/webamp/js/components/MainWindow/Shade.tsx index ea0b72a1..04eac08d 100644 --- a/packages/webamp/js/components/MainWindow/Shade.tsx +++ b/packages/webamp/js/components/MainWindow/Shade.tsx @@ -9,7 +9,7 @@ const Shade = memo(() => { return ( e.stopPropagation()} title="Toggle Windowshade Mode" /> From a20bab187725360923d72b171715ca2cd5aa32d1 Mon Sep 17 00:00:00 2001 From: Jordan Eldredge Date: Mon, 21 Jul 2025 22:24:23 -0700 Subject: [PATCH 17/78] Spelling --- packages/webamp/js/components/MainWindow/Shuffle.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/webamp/js/components/MainWindow/Shuffle.tsx b/packages/webamp/js/components/MainWindow/Shuffle.tsx index b76ef5ab..bca809b2 100644 --- a/packages/webamp/js/components/MainWindow/Shuffle.tsx +++ b/packages/webamp/js/components/MainWindow/Shuffle.tsx @@ -2,7 +2,7 @@ import { memo } from "react"; import classnames from "classnames"; import * as Actions from "../../actionCreators"; import * as Selectors from "../../selectors"; -import ContextMenuWraper from "../ContextMenuWrapper"; +import ContextMenuWrapper from "../ContextMenuWrapper"; import { Node } from "../ContextMenu"; import { useTypedSelector, useActionCreator } from "../../hooks"; import WinampButton from "../WinampButton"; @@ -11,7 +11,7 @@ const Shuffle = memo(() => { const shuffle = useTypedSelector(Selectors.getShuffle); const handleClick = useActionCreator(Actions.toggleShuffle); return ( - ( { onClick={handleClick} title="Toggle Shuffle" /> - + ); }); From 6434ecc6261411a21ea306907a02760c035bc701 Mon Sep 17 00:00:00 2001 From: Jordan Eldredge Date: Fri, 3 Oct 2025 17:48:14 -0700 Subject: [PATCH 18/78] Upgrade Grats --- .../skin-database/api/graphql/schema.graphql | 1 + packages/skin-database/api/graphql/schema.ts | 51 +++++-------------- packages/skin-database/package.json | 2 +- pnpm-lock.yaml | 37 ++++++++------ 4 files changed, 38 insertions(+), 53 deletions(-) diff --git a/packages/skin-database/api/graphql/schema.graphql b/packages/skin-database/api/graphql/schema.graphql index 9eb0c82c..a7a02820 100644 --- a/packages/skin-database/api/graphql/schema.graphql +++ b/packages/skin-database/api/graphql/schema.graphql @@ -1,5 +1,6 @@ # Schema generated by Grats (https://grats.capt.dev) # Do not manually edit. Regenerate by running `npx grats`. + """ Indicates that a position is semantically non null: it is only null if there is a matching error in the `errors` array. In all other cases, the position is non-null. diff --git a/packages/skin-database/api/graphql/schema.ts b/packages/skin-database/api/graphql/schema.ts index 7b9f927a..4d9c0855 100644 --- a/packages/skin-database/api/graphql/schema.ts +++ b/packages/skin-database/api/graphql/schema.ts @@ -2,8 +2,9 @@ * Executable schema generated by Grats (https://grats.capt.dev) * Do not manually edit. Regenerate by running `npx grats`. */ -import { defaultFieldResolver, GraphQLSchema, GraphQLObjectType, GraphQLString, GraphQLBoolean, GraphQLInt, GraphQLInterfaceType, GraphQLList, GraphQLNonNull, GraphQLID, GraphQLEnumType, GraphQLInputObjectType } from "graphql"; -import { getUserContext as getUserContext } from "./index"; + +import { GraphQLSchema, GraphQLDirective, DirectiveLocation, GraphQLList, GraphQLInt, specifiedDirectives, GraphQLObjectType, GraphQLString, GraphQLBoolean, GraphQLInterfaceType, GraphQLNonNull, GraphQLID, GraphQLEnumType, defaultFieldResolver, GraphQLInputObjectType } from "graphql"; +import { getUserContext } from "./index"; import { fetch_archive_file_by_md5 as queryFetch_archive_file_by_md5Resolver } from "./../../data/ArchiveFileModel"; import { fetch_internet_archive_item_by_identifier as queryFetch_internet_archive_item_by_identifierResolver } from "./../../data/IaItemModel"; import { fetch_skin_by_md5 as queryFetch_skin_by_md5Resolver, search_classic_skins as querySearch_classic_skinsResolver, search_skins as querySearch_skinsResolver, skin_to_review as querySkin_to_reviewResolver } from "./resolvers/SkinResolver"; @@ -189,7 +190,6 @@ export function getSchema(): GraphQLSchema { args: { normalize_extension: { description: "If true, the the correct file extension (.wsz or .wal) will be .\nOtherwise, the original user-uploaded file extension will be used.", - name: "normalize_extension", type: new GraphQLNonNull(GraphQLBoolean), defaultValue: false } @@ -384,7 +384,6 @@ export function getSchema(): GraphQLSchema { args: { normalize_extension: { description: "If true, the the correct file extension (.wsz or .wal) will be .\nOtherwise, the original user-uploaded file extension will be used.", - name: "normalize_extension", type: new GraphQLNonNull(GraphQLBoolean), defaultValue: false } @@ -547,7 +546,6 @@ export function getSchema(): GraphQLSchema { args: { normalize_extension: { description: "If true, the the correct file extension (.wsz or .wal) will be .\nOtherwise, the original user-uploaded file extension will be used.", - name: "normalize_extension", type: new GraphQLNonNull(GraphQLBoolean), defaultValue: false } @@ -909,7 +907,6 @@ export function getSchema(): GraphQLSchema { type: ArchiveFileType, args: { md5: { - name: "md5", type: new GraphQLNonNull(GraphQLString) } }, @@ -923,7 +920,6 @@ export function getSchema(): GraphQLSchema { type: InternetArchiveItemType, args: { identifier: { - name: "identifier", type: new GraphQLNonNull(GraphQLString) } }, @@ -937,7 +933,6 @@ export function getSchema(): GraphQLSchema { type: SkinType, args: { md5: { - name: "md5", type: new GraphQLNonNull(GraphQLString) } }, @@ -951,7 +946,6 @@ export function getSchema(): GraphQLSchema { type: TweetType, args: { url: { - name: "url", type: new GraphQLNonNull(GraphQLString) } }, @@ -973,12 +967,10 @@ export function getSchema(): GraphQLSchema { type: ModernSkinsConnectionType, args: { first: { - name: "first", type: new GraphQLNonNull(GraphQLInt), defaultValue: 10 }, offset: { - name: "offset", type: new GraphQLNonNull(GraphQLInt), defaultValue: 0 } @@ -993,7 +985,6 @@ export function getSchema(): GraphQLSchema { type: NodeType, args: { id: { - name: "id", type: new GraphQLNonNull(GraphQLID) } }, @@ -1007,17 +998,14 @@ export function getSchema(): GraphQLSchema { type: new GraphQLList(ClassicSkinType), args: { first: { - name: "first", type: new GraphQLNonNull(GraphQLInt), defaultValue: 10 }, offset: { - name: "offset", type: new GraphQLNonNull(GraphQLInt), defaultValue: 0 }, query: { - name: "query", type: new GraphQLNonNull(GraphQLString) } }, @@ -1031,17 +1019,14 @@ export function getSchema(): GraphQLSchema { type: new GraphQLList(SkinType), args: { first: { - name: "first", type: new GraphQLNonNull(GraphQLInt), defaultValue: 10 }, offset: { - name: "offset", type: new GraphQLNonNull(GraphQLInt), defaultValue: 0 }, query: { - name: "query", type: new GraphQLNonNull(GraphQLString) } }, @@ -1063,21 +1048,17 @@ export function getSchema(): GraphQLSchema { type: SkinsConnectionType, args: { filter: { - name: "filter", type: SkinsFilterOptionType }, first: { - name: "first", type: new GraphQLNonNull(GraphQLInt), defaultValue: 10 }, offset: { - name: "offset", type: new GraphQLNonNull(GraphQLInt), defaultValue: 0 }, sort: { - name: "sort", type: SkinsSortOptionType } }, @@ -1099,17 +1080,14 @@ export function getSchema(): GraphQLSchema { type: TweetsConnectionType, args: { first: { - name: "first", type: new GraphQLNonNull(GraphQLInt), defaultValue: 10 }, offset: { - name: "offset", type: new GraphQLNonNull(GraphQLInt), defaultValue: 0 }, sort: { - name: "sort", type: TweetsSortOptionType } }, @@ -1123,7 +1101,6 @@ export function getSchema(): GraphQLSchema { type: new GraphQLList(SkinUploadType), args: { ids: { - name: "ids", type: new GraphQLNonNull(new GraphQLList(new GraphQLNonNull(GraphQLString))) } }, @@ -1138,7 +1115,6 @@ export function getSchema(): GraphQLSchema { type: new GraphQLList(SkinUploadType), args: { md5s: { - name: "md5s", type: new GraphQLNonNull(new GraphQLList(new GraphQLNonNull(GraphQLString))) } }, @@ -1205,7 +1181,6 @@ export function getSchema(): GraphQLSchema { type: new GraphQLList(UploadUrlType), args: { files: { - name: "files", type: new GraphQLNonNull(new GraphQLList(new GraphQLNonNull(UploadUrlRequestType))) } }, @@ -1219,11 +1194,9 @@ export function getSchema(): GraphQLSchema { type: GraphQLBoolean, args: { id: { - name: "id", type: new GraphQLNonNull(GraphQLString) }, md5: { - name: "md5", type: new GraphQLNonNull(GraphQLString) } }, @@ -1244,7 +1217,6 @@ export function getSchema(): GraphQLSchema { type: GraphQLBoolean, args: { md5: { - name: "md5", type: new GraphQLNonNull(GraphQLString) } }, @@ -1258,7 +1230,6 @@ export function getSchema(): GraphQLSchema { type: GraphQLBoolean, args: { md5: { - name: "md5", type: new GraphQLNonNull(GraphQLString) } }, @@ -1272,7 +1243,6 @@ export function getSchema(): GraphQLSchema { type: GraphQLBoolean, args: { md5: { - name: "md5", type: new GraphQLNonNull(GraphQLString) } }, @@ -1286,7 +1256,6 @@ export function getSchema(): GraphQLSchema { type: GraphQLBoolean, args: { md5: { - name: "md5", type: new GraphQLNonNull(GraphQLString) } }, @@ -1300,15 +1269,12 @@ export function getSchema(): GraphQLSchema { type: GraphQLBoolean, args: { email: { - name: "email", type: GraphQLString }, message: { - name: "message", type: new GraphQLNonNull(GraphQLString) }, url: { - name: "url", type: GraphQLString } }, @@ -1328,6 +1294,17 @@ export function getSchema(): GraphQLSchema { } }); return new GraphQLSchema({ + directives: [...specifiedDirectives, new GraphQLDirective({ + name: "semanticNonNull", + locations: [DirectiveLocation.FIELD_DEFINITION], + description: "Indicates that a position is semantically non null: it is only null if there is a matching error in the `errors` array.\nIn all other cases, the position is non-null.\n\nTools doing code generation may use this information to generate the position as non-null if field errors are handled out of band:\n\n```graphql\ntype User {\n # email is semantically non-null and can be generated as non-null by error-handling clients.\n email: String @semanticNonNull\n}\n```\n\nThe `levels` argument indicates what levels are semantically non null in case of lists:\n\n```graphql\ntype User {\n # friends is semantically non null\n friends: [User] @semanticNonNull # same as @semanticNonNull(levels: [0])\n\n # every friends[k] is semantically non null\n friends: [User] @semanticNonNull(levels: [1])\n\n # friends as well as every friends[k] is semantically non null\n friends: [User] @semanticNonNull(levels: [0, 1])\n}\n```\n\n`levels` are zero indexed.\nPassing a negative level or a level greater than the list dimension is an error.", + args: { + levels: { + type: new GraphQLList(GraphQLInt), + defaultValue: [0] + } + } + })], query: QueryType, mutation: MutationType, types: [RatingType, SkinUploadStatusType, SkinsFilterOptionType, SkinsSortOptionType, TweetsSortOptionType, NodeType, SkinType, UploadUrlRequestType, ArchiveFileType, ClassicSkinType, DatabaseStatisticsType, InternetArchiveItemType, ModernSkinType, ModernSkinsConnectionType, MutationType, QueryType, ReviewType, SkinUploadType, SkinsConnectionType, TweetType, TweetsConnectionType, UploadMutationsType, UploadUrlType, UserType] diff --git a/packages/skin-database/package.json b/packages/skin-database/package.json index f1345612..1e9ca15c 100644 --- a/packages/skin-database/package.json +++ b/packages/skin-database/package.json @@ -75,7 +75,7 @@ "@types/node-fetch": "^2.5.7", "@typescript-eslint/eslint-plugin": "^8.36.0", "@typescript-eslint/parser": "^8.36.0", - "grats": "^0.0.31", + "grats": "0.0.0-main-e655d1ae", "typescript": "^5.6.2" }, "jest": { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 83affde3..cef0895d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -279,8 +279,8 @@ importers: specifier: ^8.36.0 version: 8.36.0(eslint@8.57.0)(typescript@5.6.3) grats: - specifier: ^0.0.31 - version: 0.0.31 + specifier: 0.0.0-main-e655d1ae + version: 0.0.0-main-e655d1ae typescript: specifier: ^5.6.2 version: 5.6.3 @@ -5303,6 +5303,10 @@ packages: resolution: {integrity: sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==} engines: {node: '>=14'} + commander@14.0.1: + resolution: {integrity: sha512-2JkV3gUZUVrbNA+1sjBOYLsMZ5cEEl8GTFP2a4AVz5hvasAMCQ1D2l2le/cX+pV4N6ZU17zjUahLpIXRrnWL8A==} + engines: {node: '>=20'} + commander@2.20.3: resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} @@ -7367,9 +7371,9 @@ packages: resolution: {integrity: sha512-59LZHPdGZVh695Ud9lRzPBVTtlX9ZCV150Er2W43ro37wVof0ctenSaskPPjN7lVTIN8mSZt8PHUNKZuNQUuxw==} engines: {node: ^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0} - grats@0.0.31: - resolution: {integrity: sha512-nTxRZYWgi+s8xj06QfcqtZ4nReXKyZhYHd9+9pLLthorvBSr4hbHBdl5L9HpHBqaxatWx98I6wdS5+8c+FQuxA==} - engines: {node: '>=16 <=21', pnpm: '>=8 <=9'} + grats@0.0.0-main-e655d1ae: + resolution: {integrity: sha512-ZMMY7nVosPMTagAakS3BeenmBZrEzIryPm0hmsDf20CdlCBhOwiiiIXV4I/fcYXCPI0vng1Ns6c2i+OftFTYBQ==} + engines: {node: '>=16 <=23'} hasBin: true gray-matter@4.0.3: @@ -9610,6 +9614,7 @@ packages: node-domexception@1.0.0: resolution: {integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==} engines: {node: '>=10.5.0'} + deprecated: Use your platform's native DOMException instead node-emoji@2.2.0: resolution: {integrity: sha512-Z3lTE9pLaJF47NyMhd4ww1yFTAP8YhYI8SleJiHzM46Fgpm5cnNzSl9XfzFNqbaz+VlJrIj3fXQ4DeN1Rjm6cw==} @@ -12788,11 +12793,6 @@ packages: engines: {node: '>=4.2.0'} hasBin: true - typescript@5.5.4: - resolution: {integrity: sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==} - engines: {node: '>=14.17'} - hasBin: true - typescript@5.6.3: resolution: {integrity: sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==} engines: {node: '>=14.17'} @@ -12803,6 +12803,11 @@ packages: engines: {node: '>=14.17'} hasBin: true + typescript@5.9.2: + resolution: {integrity: sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==} + engines: {node: '>=14.17'} + hasBin: true + ufo@1.5.3: resolution: {integrity: sha512-Y7HYmWaFwPUmkoQCUIAYpKqkOf+SbVj/2fJJZ4RJMCfZp0rTGwRbzQD+HghfnhKOjL9E01okqz+ncJskGYfBNw==} @@ -19320,6 +19325,8 @@ snapshots: commander@10.0.1: {} + commander@14.0.1: {} + commander@2.20.3: {} commander@4.1.1: {} @@ -21876,12 +21883,12 @@ snapshots: graphql@16.8.1: {} - grats@0.0.31: + grats@0.0.0-main-e655d1ae: dependencies: - commander: 10.0.1 + commander: 14.0.1 graphql: 16.11.0 semver: 7.7.2 - typescript: 5.5.4 + typescript: 5.9.2 gray-matter@4.0.3: dependencies: @@ -28716,12 +28723,12 @@ snapshots: typescript@4.3.4: {} - typescript@5.5.4: {} - typescript@5.6.3: {} typescript@5.8.3: {} + typescript@5.9.2: {} + ufo@1.5.3: {} uint8array-extras@1.4.0: {} From 3c882550e3fb9388dc62553342f897f494b7ff9c Mon Sep 17 00:00:00 2001 From: Jordan Eldredge Date: Fri, 10 Oct 2025 16:22:51 -0700 Subject: [PATCH 19/78] Downgrade skin museum OG React --- packages/skin-museum-og/package.json | 4 +-- pnpm-lock.yaml | 54 ++++++++++++++++++++-------- 2 files changed, 42 insertions(+), 16 deletions(-) diff --git a/packages/skin-museum-og/package.json b/packages/skin-museum-og/package.json index ee8b2354..42296ecf 100644 --- a/packages/skin-museum-og/package.json +++ b/packages/skin-museum-og/package.json @@ -11,8 +11,8 @@ "dependencies": { "@vercel/og": "^0.0.20", "next": "13.0.3", - "react": "^19.1.0", - "react-dom": "^19.1.0" + "react": "18.2.0", + "react-dom": "18.2.0" }, "devDependencies": { "vercel": "^28.4.17" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index cef0895d..c4fc9adc 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -292,13 +292,13 @@ importers: version: 0.0.20 next: specifier: 13.0.3 - version: 13.0.3(@babel/core@7.27.4)(babel-plugin-macros@3.1.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + version: 13.0.3(@babel/core@7.27.4)(babel-plugin-macros@3.1.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) react: - specifier: ^19.1.0 - version: 19.1.0 + specifier: 18.2.0 + version: 18.2.0 react-dom: - specifier: ^19.1.0 - version: 19.1.0(react@19.1.0) + specifier: 18.2.0 + version: 18.2.0(react@18.2.0) devDependencies: vercel: specifier: ^28.4.17 @@ -11139,6 +11139,11 @@ packages: resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==} hasBin: true + react-dom@18.2.0: + resolution: {integrity: sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==} + peerDependencies: + react: ^18.2.0 + react-dom@19.1.0: resolution: {integrity: sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==} peerDependencies: @@ -11245,6 +11250,10 @@ packages: react: ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 react-dom: ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react@18.2.0: + resolution: {integrity: sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==} + engines: {node: '>=0.10.0'} + react@19.1.0: resolution: {integrity: sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==} engines: {node: '>=0.10.0'} @@ -11709,6 +11718,9 @@ packages: scheduler@0.20.2: resolution: {integrity: sha512-2eWfGgAqqWFGqtdMmcL5zCMK1U8KlXv8SQFGglL3CEtd0aDVDWgeF/YoCmvln55m5zSk3J/20hTaSBeSObsQDQ==} + scheduler@0.23.2: + resolution: {integrity: sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==} + scheduler@0.26.0: resolution: {integrity: sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==} @@ -24970,16 +24982,16 @@ snapshots: next-tick@1.1.0: {} - next@13.0.3(@babel/core@7.27.4)(babel-plugin-macros@3.1.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0): + next@13.0.3(@babel/core@7.27.4)(babel-plugin-macros@3.1.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0): dependencies: '@next/env': 13.0.3 '@swc/helpers': 0.4.11 caniuse-lite: 1.0.30001723 postcss: 8.4.14 - react: 19.1.0 - react-dom: 19.1.0(react@19.1.0) - styled-jsx: 5.1.0(@babel/core@7.27.4)(babel-plugin-macros@3.1.0)(react@19.1.0) - use-sync-external-store: 1.2.0(react@19.1.0) + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + styled-jsx: 5.1.0(@babel/core@7.27.4)(babel-plugin-macros@3.1.0)(react@18.2.0) + use-sync-external-store: 1.2.0(react@18.2.0) optionalDependencies: '@next/swc-android-arm-eabi': 13.0.3 '@next/swc-android-arm64': 13.0.3 @@ -26664,6 +26676,12 @@ snapshots: minimist: 1.2.8 strip-json-comments: 2.0.1 + react-dom@18.2.0(react@18.2.0): + dependencies: + loose-envify: 1.4.0 + react: 18.2.0 + scheduler: 0.23.2 + react-dom@19.1.0(react@19.1.0): dependencies: react: 19.1.0 @@ -26771,6 +26789,10 @@ snapshots: react: 19.1.0 react-dom: 19.1.0(react@19.1.0) + react@18.2.0: + dependencies: + loose-envify: 1.4.0 + react@19.1.0: {} read-cache@1.0.0: @@ -27404,6 +27426,10 @@ snapshots: loose-envify: 1.4.0 object-assign: 4.1.1 + scheduler@0.23.2: + dependencies: + loose-envify: 1.4.0 + scheduler@0.26.0: {} schema-dts@1.1.5: {} @@ -28185,10 +28211,10 @@ snapshots: dependencies: inline-style-parser: 0.2.4 - styled-jsx@5.1.0(@babel/core@7.27.4)(babel-plugin-macros@3.1.0)(react@19.1.0): + styled-jsx@5.1.0(@babel/core@7.27.4)(babel-plugin-macros@3.1.0)(react@18.2.0): dependencies: client-only: 0.0.1 - react: 19.1.0 + react: 18.2.0 optionalDependencies: '@babel/core': 7.27.4 babel-plugin-macros: 3.1.0 @@ -28958,9 +28984,9 @@ snapshots: urlpattern-polyfill@10.0.0: {} - use-sync-external-store@1.2.0(react@19.1.0): + use-sync-external-store@1.2.0(react@18.2.0): dependencies: - react: 19.1.0 + react: 18.2.0 use-sync-external-store@1.2.2(react@19.1.0): dependencies: From e5ed88c8ec0d245a16be8727bc66a1911347296a Mon Sep 17 00:00:00 2001 From: Jordan Eldredge Date: Fri, 10 Oct 2025 20:25:57 -0700 Subject: [PATCH 20/78] Bluesky bot --- packages/skin-database/cli.ts | 18 +- packages/skin-database/config.ts | 2 + packages/skin-database/data/skins.ts | 36 ++++ .../20251010000000_bluesky_posts.ts | 16 ++ packages/skin-database/package.json | 1 + packages/skin-database/tasks/bluesky.ts | 173 ++++++++++++++++++ pnpm-lock.yaml | 90 ++++++++- 7 files changed, 330 insertions(+), 6 deletions(-) create mode 100644 packages/skin-database/migrations/20251010000000_bluesky_posts.ts create mode 100644 packages/skin-database/tasks/bluesky.ts diff --git a/packages/skin-database/cli.ts b/packages/skin-database/cli.ts index b381a87a..50bb067d 100755 --- a/packages/skin-database/cli.ts +++ b/packages/skin-database/cli.ts @@ -35,6 +35,7 @@ import { setHashesForSkin } from "./skinHash"; import * as S3 from "./s3"; import { generateDescription } from "./services/openAi"; import KeyValue from "./data/KeyValue"; +import { postToBluesky } from "./tasks/bluesky"; async function withHandler( cb: (handler: DiscordEventHandler) => Promise @@ -81,21 +82,30 @@ program .argument("[md5]", "md5 of the skin to share") .option("-t, --twitter", "Share on Twitter") .option("-i, --instagram", "Share on Instagram") + .option("-b, --bluesky", "Share on Bluesky") .option("-m, --mastodon", "Share on Mastodon") - .action(async (md5, { twitter, instagram, mastodon }) => { - if (!twitter && !instagram && !mastodon) { - throw new Error("Expected at least one of --twitter or --instagram"); - } + .action(async (md5, { twitter, instagram, mastodon, bluesky }) => { await withDiscordClient(async (client) => { if (twitter) { await tweet(client, md5); + return; } if (instagram) { await insta(client, md5); + return; } if (mastodon) { await postToMastodon(client, md5); + return; } + if (bluesky) { + await postToBluesky(client, md5); + return; + } + + throw new Error( + "Expected at least one of --twitter, --instagram, --mastodon, --bluesky" + ); }); }); diff --git a/packages/skin-database/config.ts b/packages/skin-database/config.ts index bd02ce02..969d1586 100644 --- a/packages/skin-database/config.ts +++ b/packages/skin-database/config.ts @@ -29,6 +29,8 @@ export const INSTAGRAM_ACCOUNT_ID = env("INSTAGRAM_ACCOUNT_ID"); // Used for session encryption export const SECRET = env("SECRET"); export const NODE_ENV = env("NODE_ENV") || "production"; +export const BLUESKY_PASSWORD = env("BLUESKY_PASSWORD"); +export const BLUESKY_USERNAME = env("BLUESKY_USERNAME"); function env(key: string): string { const value = process.env[key]; diff --git a/packages/skin-database/data/skins.ts b/packages/skin-database/data/skins.ts index c8be20df..6d48f701 100644 --- a/packages/skin-database/data/skins.ts +++ b/packages/skin-database/data/skins.ts @@ -144,6 +144,17 @@ export async function markAsPostedToMastodon( ); } +export async function markAsPostedToBlueSky( + md5: string, + postId: string, + url: string +): Promise { + await knex("bluesky_posts").insert( + { skin_md5: md5, post_id: postId, url }, + [] + ); +} + // TODO: Also path actor export async function markAsNSFW(ctx: UserContext, md5: string): Promise { const index = { objectID: md5, nsfw: true }; @@ -550,6 +561,31 @@ export async function getSkinToPostToMastodon(): Promise { return skin.md5; } +export async function getSkinToPostToBluesky(): Promise { + // TODO: This does not account for skins that have been both approved and rejected + const postables = await knex("skins") + .leftJoin("skin_reviews", "skin_reviews.skin_md5", "=", "skins.md5") + .leftJoin("bluesky_posts", "bluesky_posts.skin_md5", "=", "skins.md5") + .leftJoin("tweets", "tweets.skin_md5", "=", "skins.md5") + .leftJoin("refreshes", "refreshes.skin_md5", "=", "skins.md5") + .where({ + "bluesky_posts.id": null, + skin_type: 1, + "skin_reviews.review": "APPROVED", + "refreshes.error": null, + }) + .where("likes", ">", 10) + .groupBy("skins.md5") + .orderByRaw("random()") + .limit(1); + + const skin = postables[0]; + if (skin == null) { + return null; + } + return skin.md5; +} + export async function getUnreviewedSkinCount(): Promise { const rows = await knex("skins") .where({ skin_type: 1 }) diff --git a/packages/skin-database/migrations/20251010000000_bluesky_posts.ts b/packages/skin-database/migrations/20251010000000_bluesky_posts.ts new file mode 100644 index 00000000..d3944268 --- /dev/null +++ b/packages/skin-database/migrations/20251010000000_bluesky_posts.ts @@ -0,0 +1,16 @@ +import * as Knex from "knex"; + +export async function up(knex: Knex): Promise { + await knex.raw( + `CREATE TABLE "bluesky_posts" ( + "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE, + "skin_md5" TEXT NOT NULL, + post_id text NOT NULL UNIQUE, + url text NOT NULL UNIQUE + );` + ); +} + +export async function down(knex: Knex): Promise { + await knex.raw(`DROP TABLE "bluesky_posts"`); +} diff --git a/packages/skin-database/package.json b/packages/skin-database/package.json index 1e9ca15c..7bdf7805 100644 --- a/packages/skin-database/package.json +++ b/packages/skin-database/package.json @@ -4,6 +4,7 @@ "main": "index.js", "license": "MIT", "dependencies": { + "@atproto/api": "^0.17.2", "@next/third-parties": "^15.3.3", "@sentry/node": "^5.27.3", "@sentry/tracing": "^5.27.3", diff --git a/packages/skin-database/tasks/bluesky.ts b/packages/skin-database/tasks/bluesky.ts new file mode 100644 index 00000000..6e7d9c14 --- /dev/null +++ b/packages/skin-database/tasks/bluesky.ts @@ -0,0 +1,173 @@ +import * as Skins from "../data/skins"; +import { + AppBskyEmbedImages, + AppBskyFeedPost, + AtpAgent, + BlobRef, + RichText, +} from "@atproto/api"; +import { + TWEET_BOT_CHANNEL_ID, + BLUESKY_USERNAME, + BLUESKY_PASSWORD, +} from "../config"; +import { Client } from "discord.js"; +import sharp from "sharp"; +import SkinModel from "../data/SkinModel"; +import UserContext from "../data/UserContext"; +import { withBufferAsTempFile } from "../utils"; +import fs from "fs"; + +const agent = new AtpAgent({ service: "https://bsky.social" }); + +export async function postToBluesky( + discordClient: Client, + md5: string | null +): Promise { + if (md5 == null) { + md5 = await Skins.getSkinToPostToBluesky(); + } + if (md5 == null) { + console.error("No skins to post to Bluesky"); + return; + } + const url = await post(md5); + + console.log("Going to post to discord"); + const tweetBotChannel = await discordClient.channels.fetch( + TWEET_BOT_CHANNEL_ID + ); + // @ts-ignore + await tweetBotChannel.send(url); + console.log("Posted to discord"); +} + +async function post(md5: string): Promise { + const ctx = new UserContext(); + const skin = await SkinModel.fromMd5Assert(ctx, md5); + const screenshot = await Skins.getScreenshotBuffer(md5); + const { width, height } = await sharp(screenshot).metadata(); + + const image = await sharp(screenshot) + .resize(width * 2, height * 2, { + kernel: sharp.kernel.nearest, + }) + .toBuffer(); + + const name = await skin.getFileName(); + const url = skin.getMuseumUrl(); + const screenshotFileName = await skin.getScreenshotFileName(); + + const status = `${name}\n`; // TODO: Should we add hashtags? + + await agent.login({ + identifier: BLUESKY_USERNAME!, + password: BLUESKY_PASSWORD!, + }); + + const blob = await withBufferAsTempFile( + image, + screenshotFileName, + async (filePath) => { + return uploadImageFromFilePath(agent, filePath); + } + ); + + const postData = await buildPost( + agent, + status, + buildImageEmbed(blob, width * 2, height * 2) + ); + const postResp = await agent.post(postData); + console.log(postResp); + + const postId = postResp.cid; + const postUrl = postResp.uri; + + await Skins.markAsPostedToBlueSky(md5, postId, postUrl); + + const prefix = "Try on the "; + const suffix = "Winamp Skin Museum"; + + agent.post({ + text: prefix + suffix, + createdAt: new Date().toISOString(), + facets: [ + { + $type: "app.bsky.richtext.facet", + index: { + byteStart: prefix.length, + byteEnd: prefix.length + suffix.length, + }, + features: [ + { + $type: "app.bsky.richtext.facet#link", + uri: url, + }, + ], + }, + ], + reply: { + root: postResp, + parent: postResp, + }, + $type: "app.bsky.feed.post", + }); + + // return permalink; + return postUrl; +} + +/** Build the embed data for an image. */ +function buildImageEmbed( + imgBlob: BlobRef, + width: number, + height: number +): AppBskyEmbedImages.Main { + const image = { + image: imgBlob, + aspectRatio: { width, height }, + alt: "", + }; + return { + $type: "app.bsky.embed.images", + images: [image], + }; +} + +/** Build the post data for an image. */ +async function buildPost( + agent: AtpAgent, + rawText: string, + imageEmbed: AppBskyEmbedImages.Main +): Promise { + const rt = new RichText({ text: rawText }); + await rt.detectFacets(agent); + const { text, facets } = rt; + return { + text, + facets, + $type: "app.bsky.feed.post", + createdAt: new Date().toISOString(), + embed: { + $type: "app.bsky.embed.recordWithMedia", + ...imageEmbed, + }, + }; +} + +/** Upload an image from a URL to Bluesky. */ +async function uploadImageFromFilePath( + agent: AtpAgent, + filePath: string +): Promise { + const imageBuff = fs.readFileSync(filePath); + const imgU8 = new Uint8Array(imageBuff); + + const dstResp = await agent.uploadBlob(imgU8); + if (!dstResp.success) { + console.log(dstResp); + throw new Error("Failed to upload image"); + } + return dstResp.data.blob; +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c4fc9adc..c91c9e80 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -124,6 +124,9 @@ importers: packages/skin-database: dependencies: + '@atproto/api': + specifier: ^0.17.2 + version: 0.17.2 '@next/third-parties': specifier: ^15.3.3 version: 15.3.3(next@15.3.3(@babel/core@7.27.4)(babel-plugin-macros@3.1.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react@19.1.0) @@ -198,7 +201,7 @@ importers: version: 2.7.0(encoding@0.1.13) openai: specifier: ^4.68.0 - version: 4.68.0(encoding@0.1.13) + version: 4.68.0(encoding@0.1.13)(zod@3.25.76) polygon-clipping: specifier: ^0.15.3 version: 0.15.7 @@ -786,6 +789,21 @@ packages: '@assemblyscript/loader@0.17.14': resolution: {integrity: sha512-+PVTOfla/0XMLRTQLJFPg4u40XcdTfon6GGea70hBGi8Pd7ZymIXyVUR+vK8wt5Jb4MVKTKPIz43Myyebw5mZA==} + '@atproto/api@0.17.2': + resolution: {integrity: sha512-luRY9YPaRQFpm3v7a1bTOaekQ/KPCG3gb0jVyaOtfMXDSfIZJh9lr9MtmGPdEp7AvfE8urkngZ+V/p8Ial3z2g==} + + '@atproto/common-web@0.4.3': + resolution: {integrity: sha512-nRDINmSe4VycJzPo6fP/hEltBcULFxt9Kw7fQk6405FyAWZiTluYHlXOnU7GkQfeUK44OENG1qFTBcmCJ7e8pg==} + + '@atproto/lexicon@0.5.1': + resolution: {integrity: sha512-y8AEtYmfgVl4fqFxqXAeGvhesiGkxiy3CWoJIfsFDDdTlZUC8DFnZrYhcqkIop3OlCkkljvpSJi1hbeC1tbi8A==} + + '@atproto/syntax@0.4.1': + resolution: {integrity: sha512-CJdImtLAiFO+0z3BWTtxwk6aY5w4t8orHTMVJgkf++QRJWTxPbIFko/0hrkADB7n2EruDxDSeAgfUGehpH6ngw==} + + '@atproto/xrpc@0.7.5': + resolution: {integrity: sha512-MUYNn5d2hv8yVegRL0ccHvTHAVj5JSnW07bkbiaz96UH45lvYNRVwt44z+yYVnb0/mvBzyD3/ZQ55TRGt7fHkA==} + '@babel/code-frame@7.27.1': resolution: {integrity: sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==} engines: {node: '>=6.9.0'} @@ -4663,6 +4681,9 @@ packages: resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==} engines: {node: '>= 0.4'} + await-lock@2.2.2: + resolution: {integrity: sha512-aDczADvlvTGajTDjcjpJMqRkOF6Qdz3YbPZm/PyW6tKPkx2hlYBzxMhEywM/tU72HrVZjgl5VCdRuMlA7pZ8Gw==} + aws-sdk@2.1592.0: resolution: {integrity: sha512-iwmS46jOEHMNodfrpNBJ5eHwjKAY05t/xYV2cp+KyzMX2yGgt2/EtWWnlcoMGBKR31qKTsjMj5ZPouC9/VeDOA==} engines: {node: '>= 10.0.0'} @@ -8204,6 +8225,9 @@ packages: isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + iso-datestring-validator@2.2.2: + resolution: {integrity: sha512-yLEMkBbLZTlVQqOnQ4FiMujR6T4DEcCb1xizmvXS+OxuhwcbtynoosRzdMA69zZCShCNAbi+gJ71FxZBBXx1SA==} + isobject@2.1.0: resolution: {integrity: sha512-+OUdGJlgjOBZDfxnDjYYG6zp487z0JGNQq3cYQYg5f5hKR+syHMsaztzGeml/4kGG55CSpKSpWTY+jYGgsHLgA==} engines: {node: '>=0.10.0'} @@ -9500,6 +9524,9 @@ packages: resolution: {integrity: sha512-2eznPJP8z2BFLX50tf0LuODrpINqP1RVIm/CObbTcBRITQgmC/TjcREF1NeTBzIcR5XO/ukWo+YHOjBbFwIupg==} hasBin: true + multiformats@9.9.0: + resolution: {integrity: sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg==} + multipipe@0.1.2: resolution: {integrity: sha512-7ZxrUybYv9NonoXgwoOqtStIu18D1c3eFZj27hqgf5kBrBF8Q+tE8V0MW8dKM5QLkQPh1JhhbKgHLY9kifov4Q==} @@ -12514,6 +12541,10 @@ packages: tinyqueue@1.2.3: resolution: {integrity: sha512-Qz9RgWuO9l8lT+Y9xvbzhPT2efIUIFd69N7eF7tJ9lnQl0iLj1M7peK7IoUGZL9DJHw9XftqLreccfxcQgYLxA==} + tlds@1.260.0: + resolution: {integrity: sha512-78+28EWBhCEE7qlyaHA9OR3IPvbCLiDh3Ckla593TksfFc9vfTsgvH7eS+dr3o9qr31gwGbogcI16yN91PoRjQ==} + hasBin: true + tmp@0.0.33: resolution: {integrity: sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==} engines: {node: '>=0.6.0'} @@ -12827,6 +12858,9 @@ packages: resolution: {integrity: sha512-ZPtzy0hu4cZjv3z5NW9gfKnNLjoz4y6uv4HlelAjDK7sY/xOkKZv9xK/WQpcsBB3jEybChz9DPC2U/+cusjJVQ==} engines: {node: '>=18'} + uint8arrays@3.0.0: + resolution: {integrity: sha512-HRCx0q6O9Bfbp+HHSfQQKD7wU70+lydKVt4EghkdOvlK/NlrF90z+eXV34mUd48rNvVJXwkrMSPpCATkct8fJA==} + unbox-primitive@1.0.2: resolution: {integrity: sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==} @@ -13548,6 +13582,9 @@ packages: zod@3.22.4: resolution: {integrity: sha512-iC+8Io04lddc+mVqQ9AZ7OQ2MrUKGN+oIQyq1vemgt46jwCwLfhq7/pwnBnNXXXZb8VTVLKwp9EDkx+ryxIWmg==} + zod@3.25.76: + resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==} + zwitch@2.0.4: resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==} @@ -13746,6 +13783,39 @@ snapshots: '@assemblyscript/loader@0.17.14': {} + '@atproto/api@0.17.2': + dependencies: + '@atproto/common-web': 0.4.3 + '@atproto/lexicon': 0.5.1 + '@atproto/syntax': 0.4.1 + '@atproto/xrpc': 0.7.5 + await-lock: 2.2.2 + multiformats: 9.9.0 + tlds: 1.260.0 + zod: 3.25.76 + + '@atproto/common-web@0.4.3': + dependencies: + graphemer: 1.4.0 + multiformats: 9.9.0 + uint8arrays: 3.0.0 + zod: 3.25.76 + + '@atproto/lexicon@0.5.1': + dependencies: + '@atproto/common-web': 0.4.3 + '@atproto/syntax': 0.4.1 + iso-datestring-validator: 2.2.2 + multiformats: 9.9.0 + zod: 3.25.76 + + '@atproto/syntax@0.4.1': {} + + '@atproto/xrpc@0.7.5': + dependencies: + '@atproto/lexicon': 0.5.1 + zod: 3.25.76 + '@babel/code-frame@7.27.1': dependencies: '@babel/helper-validator-identifier': 7.27.1 @@ -18518,6 +18588,8 @@ snapshots: dependencies: possible-typed-array-names: 1.0.0 + await-lock@2.2.2: {} + aws-sdk@2.1592.0: dependencies: buffer: 4.9.2 @@ -22832,6 +22904,8 @@ snapshots: isexe@2.0.0: {} + iso-datestring-validator@2.2.2: {} + isobject@2.1.0: dependencies: isarray: 1.0.0 @@ -24903,6 +24977,8 @@ snapshots: dns-packet: 5.6.1 thunky: 1.1.0 + multiformats@9.9.0: {} + multipipe@0.1.2: dependencies: duplexer2: 0.0.2 @@ -25339,7 +25415,7 @@ snapshots: is-docker: 2.2.1 is-wsl: 2.2.0 - openai@4.68.0(encoding@0.1.13): + openai@4.68.0(encoding@0.1.13)(zod@3.25.76): dependencies: '@types/node': 18.19.56 '@types/node-fetch': 2.6.11 @@ -25348,6 +25424,8 @@ snapshots: form-data-encoder: 1.7.2 formdata-node: 4.4.1 node-fetch: 2.7.0(encoding@0.1.13) + optionalDependencies: + zod: 3.25.76 transitivePeerDependencies: - encoding @@ -28430,6 +28508,8 @@ snapshots: tinyqueue@1.2.3: {} + tlds@1.260.0: {} + tmp@0.0.33: dependencies: os-tmpdir: 1.0.2 @@ -28759,6 +28839,10 @@ snapshots: uint8array-extras@1.4.0: {} + uint8arrays@3.0.0: + dependencies: + multiformats: 9.9.0 + unbox-primitive@1.0.2: dependencies: call-bind: 1.0.7 @@ -29668,4 +29752,6 @@ snapshots: zod@3.22.4: {} + zod@3.25.76: {} + zwitch@2.0.4: {} From 96ffdcda596ec29aac7b6aeeceebcaa90df0af40 Mon Sep 17 00:00:00 2001 From: Jordan Eldredge Date: Mon, 3 Nov 2025 07:54:59 -0800 Subject: [PATCH 21/78] Clean up log --- packages/skin-database/tasks/bluesky.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/skin-database/tasks/bluesky.ts b/packages/skin-database/tasks/bluesky.ts index 6e7d9c14..f5a23a94 100644 --- a/packages/skin-database/tasks/bluesky.ts +++ b/packages/skin-database/tasks/bluesky.ts @@ -166,7 +166,6 @@ async function uploadImageFromFilePath( const dstResp = await agent.uploadBlob(imgU8); if (!dstResp.success) { - console.log(dstResp); throw new Error("Failed to upload image"); } return dstResp.data.blob; From 50d5dbbf4ff4777dbe7b7e1d5121168a4cfdd065 Mon Sep 17 00:00:00 2001 From: Jordan Eldredge Date: Mon, 3 Nov 2025 08:04:54 -0800 Subject: [PATCH 22/78] Always use `.wsz` in museum pages --- .../skin-database/legacy-client/src/algolia.js | 18 ++++++++++-------- .../legacy-client/src/redux/epics.js | 4 ++-- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/packages/skin-database/legacy-client/src/algolia.js b/packages/skin-database/legacy-client/src/algolia.js index 90db2cec..cc2f5b36 100644 --- a/packages/skin-database/legacy-client/src/algolia.js +++ b/packages/skin-database/legacy-client/src/algolia.js @@ -1,4 +1,5 @@ import * as Utils from "./utils"; +import { gql } from "./utils"; import { algoliasearch } from "algoliasearch"; const client = algoliasearch("HQ9I5Z6IM5", "6466695ec3f624a5fccf46ec49680e51"); @@ -6,14 +7,15 @@ const client = algoliasearch("HQ9I5Z6IM5", "6466695ec3f624a5fccf46ec49680e51"); // Fallback search that uses SQLite. Useful for when we've exceeded the Algolia // search quota. export async function graphqlSearch(query) { - const queryText = Utils.gql` - query SearchQuery($query: String!) { - search_classic_skins(query: $query, first: 500) { - filename - md5 - nsfw - } -}`; + const queryText = gql` + query SearchQuery($query: String!) { + search_classic_skins(query: $query, first: 500) { + filename(normalize_extension: true) + md5 + nsfw + } + } + `; const data = await Utils.fetchGraphql(queryText, { query }); const hits = data.search_classic_skins.map((skin) => { return { diff --git a/packages/skin-database/legacy-client/src/redux/epics.js b/packages/skin-database/legacy-client/src/redux/epics.js index 6dd23dfa..65e8b952 100644 --- a/packages/skin-database/legacy-client/src/redux/epics.js +++ b/packages/skin-database/legacy-client/src/redux/epics.js @@ -218,7 +218,7 @@ const unloadedSkinEpic = (actions, _states) => count nodes { md5 - filename + filename(normalize_extension: true) nsfw } } @@ -481,7 +481,7 @@ const skinDataEpic = (actions, state) => { const QUERY = gql` query IndividualSkin($md5: String!) { fetch_skin_by_md5(md5: $md5) { - filename + filename(normalize_extension: true) nsfw } } From 4b405bc831d9b562fffa43f903550eddf213198e Mon Sep 17 00:00:00 2001 From: Jordan Eldredge Date: Mon, 3 Nov 2025 08:05:02 -0800 Subject: [PATCH 23/78] Clean up console logs --- packages/skin-database/app/graphql/route.ts | 2 +- packages/skin-database/data/SkinModel.ts | 3 --- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/packages/skin-database/app/graphql/route.ts b/packages/skin-database/app/graphql/route.ts index db44d309..df50b087 100644 --- a/packages/skin-database/app/graphql/route.ts +++ b/packages/skin-database/app/graphql/route.ts @@ -11,7 +11,7 @@ const { handleRequest } = createYogaInstance({ }, logger: { log: (message: string, context: Record) => { - console.log(message, context); + // console.log(message, context); }, logError: (message: string, context: Record) => { console.error(message, context); diff --git a/packages/skin-database/data/SkinModel.ts b/packages/skin-database/data/SkinModel.ts index 37e8d450..4cab1479 100644 --- a/packages/skin-database/data/SkinModel.ts +++ b/packages/skin-database/data/SkinModel.ts @@ -193,9 +193,6 @@ export default class SkinModel { const filename = file.getFileName(); const isReadme = IS_README.test(filename); const isNotReadme = IS_NOT_README.test(filename); - - console.log({ filename, isReadme, isNotReadme, md5: file.getFileMd5() }); - return isReadme && !isNotReadme; }); From 340e2249aea47f69e0d3c5758cfd04d069576219 Mon Sep 17 00:00:00 2001 From: Jordan Eldredge Date: Mon, 3 Nov 2025 15:39:26 -0800 Subject: [PATCH 24/78] Clean up outdated references to yarn --- packages/skin-database/cli.ts | 2 +- packages/skin-database/ecosystem.config.js | 4 ++-- packages/skin-database/scripts/reload.sh | 4 ++-- packages/skin-museum-og/README.md | 4 +--- packages/webamp-modern/tools/parse-mi.test.js | 2 +- packages/webamp/jest-puppeteer.config.js | 2 +- 6 files changed, 8 insertions(+), 10 deletions(-) diff --git a/packages/skin-database/cli.ts b/packages/skin-database/cli.ts index 50bb067d..1c6b96a1 100755 --- a/packages/skin-database/cli.ts +++ b/packages/skin-database/cli.ts @@ -171,7 +171,7 @@ program console.log("===================================="); } if (purge) { - // cat purge | xargs -I {} yarn cli skin --purge {} + // cat purge | xargs -I {} pnpm cli skin --purge {} await Skins.deleteSkin(md5); const purgedArr: string[] = (await KeyValue.get("purged")) || []; const purged = new Set(purgedArr); diff --git a/packages/skin-database/ecosystem.config.js b/packages/skin-database/ecosystem.config.js index 0416d7e7..7d847362 100644 --- a/packages/skin-database/ecosystem.config.js +++ b/packages/skin-database/ecosystem.config.js @@ -6,7 +6,7 @@ module.exports = { apps: [ { name: "skin-database-blue", - script: "yarn", + script: "pnpm", interpreter: "bash", args: "start", env: { @@ -16,7 +16,7 @@ module.exports = { }, { name: "skin-database-green", - script: "yarn", + script: "pnpm", interpreter: "bash", args: "start", env: { diff --git a/packages/skin-database/scripts/reload.sh b/packages/skin-database/scripts/reload.sh index 4c840ff2..9a12fdd6 100755 --- a/packages/skin-database/scripts/reload.sh +++ b/packages/skin-database/scripts/reload.sh @@ -7,10 +7,10 @@ source "$NVM_DIR/nvm.sh" nvm use 20 # Install dependencies -yarn install --frozen-lockfile +pnpm install --frozen-lockfile # Build the site -yarn run build +pnpm run build # Reload processes via PM2 pm2 reload ecosystem.config.js \ No newline at end of file diff --git a/packages/skin-museum-og/README.md b/packages/skin-museum-og/README.md index b12f3e33..dd6f7841 100644 --- a/packages/skin-museum-og/README.md +++ b/packages/skin-museum-og/README.md @@ -5,9 +5,7 @@ This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next First, run the development server: ```bash -npm run dev -# or -yarn dev +pnpm dev ``` Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. diff --git a/packages/webamp-modern/tools/parse-mi.test.js b/packages/webamp-modern/tools/parse-mi.test.js index d374cd0a..a265ad86 100644 --- a/packages/webamp-modern/tools/parse-mi.test.js +++ b/packages/webamp-modern/tools/parse-mi.test.js @@ -5,7 +5,7 @@ import { parseFile } from "../tools/parse-mi"; import path from "path"; /** - * This file basically ensures that `yarn extract-object-types` has been run. + * This file basically ensures that `pnpm extract-object-types` has been run. */ const compilers = path.join(__dirname, "../resources/maki_compiler/"); diff --git a/packages/webamp/jest-puppeteer.config.js b/packages/webamp/jest-puppeteer.config.js index 1664fdd4..42994f75 100644 --- a/packages/webamp/jest-puppeteer.config.js +++ b/packages/webamp/jest-puppeteer.config.js @@ -5,7 +5,7 @@ module.exports = { server: { // Note: We require the the build be run before these tests. // TODO: Figure out how to get this command to run the build. - command: "PORT=8080 yarn serve", + command: "PORT=8080 pnpm serve", port: 8080, // Jest Puppeteer will wait 5000ms for this port to come online. launchTimeout: 10000, // Jest Puppeteer will wait 10000ms for the server to start. debug: true, From 0705e9d89e0feb00b8344535e5054a56f1cfb5cc Mon Sep 17 00:00:00 2001 From: Jordan Eldredge Date: Tue, 4 Nov 2025 21:33:58 -0800 Subject: [PATCH 25/78] Stub out shorts --- .../app/(modern)/scroll/SkinScroller.tsx | 124 ++++++++++++++++++ .../app/(modern)/scroll/page.tsx | 40 ++++++ .../app/(modern)/scroll/scroll.css | 5 + 3 files changed, 169 insertions(+) create mode 100644 packages/skin-database/app/(modern)/scroll/SkinScroller.tsx create mode 100644 packages/skin-database/app/(modern)/scroll/page.tsx create mode 100644 packages/skin-database/app/(modern)/scroll/scroll.css diff --git a/packages/skin-database/app/(modern)/scroll/SkinScroller.tsx b/packages/skin-database/app/(modern)/scroll/SkinScroller.tsx new file mode 100644 index 00000000..27505159 --- /dev/null +++ b/packages/skin-database/app/(modern)/scroll/SkinScroller.tsx @@ -0,0 +1,124 @@ +"use client"; + +import { useState, useLayoutEffect } from "react"; + +export type ClientSkin = { + screenshotUrl: string; + fileName: string; + md5: string; + readmeStart: string; +}; + +type Props = { + initialSkins: ClientSkin[]; + getSkins: (offset: number) => Promise; +}; + +export default function SkinScroller({ initialSkins, getSkins }: Props) { + const [skins, setSkins] = useState(initialSkins); + const [visibleSkinIndex, setVisibleSkinIndex] = useState(0); + const [fetching, setFetching] = useState(false); + const [containerRef, setContainerRef] = useState(null); + + useLayoutEffect(() => { + if (containerRef == null) { + return; + } + + function onSnap(e) { + const md5 = e.snapTargetBlock.getAttribute("skin-md5"); + const index = parseInt(e.snapTargetBlock.getAttribute("skin-index")); + setVisibleSkinIndex(index); + } + + containerRef.addEventListener("scrollsnapchange", onSnap); + + return () => { + containerRef.removeEventListener("scrollsnapchange", onSnap); + }; + }, [containerRef]); + + useLayoutEffect(() => { + if (fetching) { + return; + } + if (visibleSkinIndex + 5 >= skins.length) { + setFetching(true); + getSkins(skins.length).then((newSkins) => { + setSkins([...skins, ...newSkins]); + setFetching(false); + }); + } + }, [visibleSkinIndex, skins, fetching]); + + return ( +
+ {skins.map((skin, i) => { + return ( +
+ {skin.fileName} +
+

+ {skin.fileName} +

+

+ {skin.readmeStart} +

+
+
+ ); + })} +
+ ); +} diff --git a/packages/skin-database/app/(modern)/scroll/page.tsx b/packages/skin-database/app/(modern)/scroll/page.tsx new file mode 100644 index 00000000..5bc0c14f --- /dev/null +++ b/packages/skin-database/app/(modern)/scroll/page.tsx @@ -0,0 +1,40 @@ +import ClassicSkinResolver from "../../../api/graphql/resolvers/ClassicSkinResolver"; +import SkinsConnection from "../../../api/graphql/SkinsConnection"; +import UserContext from "../../../data/UserContext"; +import "./scroll.css"; +import SkinScroller, { ClientSkin } from "./SkinScroller"; + +async function getClientSkins(offset: number): Promise { + "use server"; + const ctx = new UserContext(); + const connection = new SkinsConnection(10, offset, "MUSEUM", null); + const skins = await connection.nodes(ctx); + if (skins == null) { + return []; + } + const classicSkins = skins.filter( + (skin): skin is ClassicSkinResolver => skin instanceof ClassicSkinResolver + ); + const clientSkins: ClientSkin[] = await Promise.all( + classicSkins.map(async (skin) => { + const url = await skin.screenshot_url(); + const readmeText = await skin.readme_text(); + return { + screenshotUrl: url, + md5: skin.md5(), + fileName: await skin.filename(true), + readmeStart: readmeText ? readmeText.slice(0, 200) : "", + }; + }) + ); + return clientSkins; +} + +/** + * A tik-tok style scroll page where we display one skin at a time in full screen + */ +export default async function ScrollPage() { + const initialSkins = await getClientSkins(0); + + return ; +} diff --git a/packages/skin-database/app/(modern)/scroll/scroll.css b/packages/skin-database/app/(modern)/scroll/scroll.css new file mode 100644 index 00000000..0e2250f5 --- /dev/null +++ b/packages/skin-database/app/(modern)/scroll/scroll.css @@ -0,0 +1,5 @@ +body { + margin: 0; /* Remove default margin */ + height: 100vh; /* Set body height to viewport height */ + background-color: rgb(0, 0, 0); +} From fbe3a0090f1c3f638b8d625363840a7134d85b46 Mon Sep 17 00:00:00 2001 From: Jordan Eldredge Date: Tue, 4 Nov 2025 21:44:50 -0800 Subject: [PATCH 26/78] Fix some layout issues --- .../skin-database/app/(modern)/scroll/SkinScroller.tsx | 3 +++ packages/skin-database/app/(modern)/scroll/scroll.css | 9 +++++++++ 2 files changed, 12 insertions(+) diff --git a/packages/skin-database/app/(modern)/scroll/SkinScroller.tsx b/packages/skin-database/app/(modern)/scroll/SkinScroller.tsx index 27505159..aff10b26 100644 --- a/packages/skin-database/app/(modern)/scroll/SkinScroller.tsx +++ b/packages/skin-database/app/(modern)/scroll/SkinScroller.tsx @@ -66,6 +66,7 @@ export default function SkinScroller({ initialSkins, getSkins }: Props) { key={skin.md5} skin-md5={skin.md5} skin-index={i} + className="scroller" style={{ display: "flex", flexDirection: "column", @@ -100,6 +101,7 @@ export default function SkinScroller({ initialSkins, getSkins }: Props) { paddingBottom: "0", fontFamily: 'Arial, "Helvetica Neue", Helvetica, sans-serif', color: "#ccc", + wordBreak: "break-all", }} > {skin.fileName} @@ -111,6 +113,7 @@ export default function SkinScroller({ initialSkins, getSkins }: Props) { paddingTop: "0", color: "#999", fontFamily: 'monospace, "Courier New", Courier, monospace', + overflow: "hidden", }} > {skin.readmeStart} diff --git a/packages/skin-database/app/(modern)/scroll/scroll.css b/packages/skin-database/app/(modern)/scroll/scroll.css index 0e2250f5..ae1b15a6 100644 --- a/packages/skin-database/app/(modern)/scroll/scroll.css +++ b/packages/skin-database/app/(modern)/scroll/scroll.css @@ -3,3 +3,12 @@ body { height: 100vh; /* Set body height to viewport height */ background-color: rgb(0, 0, 0); } + +.scroller::-webkit-scrollbar { + display: none; /* Chrome, Safari, Edge */ +} + +.scroller { + -ms-overflow-style: none; /* IE and Edge */ + scrollbar-width: none; /* Firefox */ +} From 52ff84d29b189655b704283dc6c078c33845e64f Mon Sep 17 00:00:00 2001 From: Jordan Eldredge Date: Fri, 7 Nov 2025 15:23:02 -0800 Subject: [PATCH 27/78] Make scroll sessions more dynamic --- .../app/(modern)/scroll/Events.ts | 65 +++++++++ .../app/(modern)/scroll/SkinPage.tsx | 72 +++++++++ .../app/(modern)/scroll/SkinScroller.tsx | 137 ++++++++++-------- .../app/(modern)/scroll/page.tsx | 51 ++++--- packages/skin-database/data/SessionModel.ts | 29 ++++ packages/skin-database/data/skins.ts | 47 ++++++ .../legacy-client/src/redux/selectors.js | 6 + .../20251106000000_user_log_events.ts | 21 +++ .../20251107000000_session_tables.ts | 36 +++++ 9 files changed, 380 insertions(+), 84 deletions(-) create mode 100644 packages/skin-database/app/(modern)/scroll/Events.ts create mode 100644 packages/skin-database/app/(modern)/scroll/SkinPage.tsx create mode 100644 packages/skin-database/data/SessionModel.ts create mode 100644 packages/skin-database/migrations/20251106000000_user_log_events.ts create mode 100644 packages/skin-database/migrations/20251107000000_session_tables.ts diff --git a/packages/skin-database/app/(modern)/scroll/Events.ts b/packages/skin-database/app/(modern)/scroll/Events.ts new file mode 100644 index 00000000..ef917ee1 --- /dev/null +++ b/packages/skin-database/app/(modern)/scroll/Events.ts @@ -0,0 +1,65 @@ +"use server"; + +import { knex } from "../../../db"; + +export async function logUserEvent(sessionId: string, event: UserEvent) { + const timestamp = Date.now(); + + await knex("user_log_events").insert({ + session_id: sessionId, + timestamp: timestamp, + metadata: JSON.stringify(event), + }); +} + +type UserEvent = + | { + type: "session_start"; + } + | { + type: "session_end"; + reason: "unmount" | "before_unload"; + } + | { + type: "skin_view_start"; + skinMd5: string; + } + | { + type: "skin_view_end"; + skinMd5: string; + durationMs: number; + } + | { + type: "skins_fetch_start"; + offset: number; + } + | { + type: "skins_fetch_success"; + offset: number; + } + | { + type: "skins_fetch_failure"; + offset: number; + errorMessage: string; + } + | { + type: "readme_expand"; + skinMd5: string; + } + | { + type: "skin_download"; + skinMd5: string; + } + | { + type: "share_open"; + skinMd5: string; + } + | { + type: "share_success"; + skinMd5: string; + } + | { + type: "share_failure"; + skinMd5: string; + errorMessage: string; + }; diff --git a/packages/skin-database/app/(modern)/scroll/SkinPage.tsx b/packages/skin-database/app/(modern)/scroll/SkinPage.tsx new file mode 100644 index 00000000..f567c280 --- /dev/null +++ b/packages/skin-database/app/(modern)/scroll/SkinPage.tsx @@ -0,0 +1,72 @@ +"use client"; + +import { ClientSkin } from "./SkinScroller"; + +type Props = { + skin: ClientSkin; + index: number; + sessionId: string; +}; + +export default function SkinPage({ skin, index }: Props) { + return ( +
+ {skin.fileName} +
+

+ {skin.fileName} +

+

+ {skin.readmeStart} +

+
+
+ ); +} diff --git a/packages/skin-database/app/(modern)/scroll/SkinScroller.tsx b/packages/skin-database/app/(modern)/scroll/SkinScroller.tsx index aff10b26..3d3d467b 100644 --- a/packages/skin-database/app/(modern)/scroll/SkinScroller.tsx +++ b/packages/skin-database/app/(modern)/scroll/SkinScroller.tsx @@ -1,6 +1,8 @@ "use client"; -import { useState, useLayoutEffect } from "react"; +import { useState, useLayoutEffect, useEffect } from "react"; +import SkinPage from "./SkinPage"; +import { logUserEvent } from "./Events"; export type ClientSkin = { screenshotUrl: string; @@ -11,10 +13,15 @@ export type ClientSkin = { type Props = { initialSkins: ClientSkin[]; - getSkins: (offset: number) => Promise; + getSkins: (sessionId: string, offset: number) => Promise; + sessionId: string; }; -export default function SkinScroller({ initialSkins, getSkins }: Props) { +export default function SkinScroller({ + initialSkins, + getSkins, + sessionId, +}: Props) { const [skins, setSkins] = useState(initialSkins); const [visibleSkinIndex, setVisibleSkinIndex] = useState(0); const [fetching, setFetching] = useState(false); @@ -38,16 +45,72 @@ export default function SkinScroller({ initialSkins, getSkins }: Props) { }; }, [containerRef]); + useEffect(() => { + logUserEvent(sessionId, { + type: "session_start", + }); + + function beforeUnload() { + logUserEvent(sessionId, { + type: "session_end", + reason: "before_unload", + }); + } + + addEventListener("beforeunload", beforeUnload); + return () => { + removeEventListener("beforeunload", beforeUnload); + logUserEvent(sessionId, { + type: "session_end", + reason: "unmount", + }); + }; + }, []); + + useEffect(() => { + logUserEvent(sessionId, { + type: "skin_view_start", + skinMd5: skins[visibleSkinIndex].md5, + }); + const startTime = Date.now(); + return () => { + const durationMs = Date.now() - startTime; + logUserEvent(sessionId, { + type: "skin_view_end", + skinMd5: skins[visibleSkinIndex].md5, + durationMs, + }); + }; + }, [visibleSkinIndex, skins, fetching]); + useLayoutEffect(() => { if (fetching) { return; } if (visibleSkinIndex + 5 >= skins.length) { setFetching(true); - getSkins(skins.length).then((newSkins) => { - setSkins([...skins, ...newSkins]); - setFetching(false); + console.log("Fetching more skins..."); + logUserEvent(sessionId, { + type: "skins_fetch_start", + offset: skins.length, }); + getSkins(sessionId, skins.length) + .then((newSkins) => { + logUserEvent(sessionId, { + type: "skins_fetch_success", + offset: skins.length, + }); + setSkins([...skins, ...newSkins]); + setFetching(false); + }) + .catch((error) => { + logUserEvent(sessionId, { + type: "skins_fetch_failure", + offset: skins.length, + errorMessage: error.message, + }); + setFetching(false); + }); } }, [visibleSkinIndex, skins, fetching]); @@ -62,64 +125,12 @@ export default function SkinScroller({ initialSkins, getSkins }: Props) { > {skins.map((skin, i) => { return ( -
- {skin.fileName} -
-

- {skin.fileName} -

-

- {skin.readmeStart} -

-
-
+ skin={skin} + index={i} + sessionId={sessionId} + /> ); })} diff --git a/packages/skin-database/app/(modern)/scroll/page.tsx b/packages/skin-database/app/(modern)/scroll/page.tsx index 5bc0c14f..af52bf08 100644 --- a/packages/skin-database/app/(modern)/scroll/page.tsx +++ b/packages/skin-database/app/(modern)/scroll/page.tsx @@ -1,40 +1,49 @@ -import ClassicSkinResolver from "../../../api/graphql/resolvers/ClassicSkinResolver"; -import SkinsConnection from "../../../api/graphql/SkinsConnection"; import UserContext from "../../../data/UserContext"; +import SessionModel from "../../../data/SessionModel"; import "./scroll.css"; import SkinScroller, { ClientSkin } from "./SkinScroller"; +import { getScrollPage } from "../../../data/skins"; +import SkinModel from "../../../data/SkinModel"; -async function getClientSkins(offset: number): Promise { +async function getClientSkins(sessionId: string): Promise { "use server"; const ctx = new UserContext(); - const connection = new SkinsConnection(10, offset, "MUSEUM", null); - const skins = await connection.nodes(ctx); - if (skins == null) { - return []; - } - const classicSkins = skins.filter( - (skin): skin is ClassicSkinResolver => skin instanceof ClassicSkinResolver - ); - const clientSkins: ClientSkin[] = await Promise.all( - classicSkins.map(async (skin) => { - const url = await skin.screenshot_url(); - const readmeText = await skin.readme_text(); + + const page = await getScrollPage(sessionId); + + const skins = await Promise.all( + page.map(async (item) => { + const model = await SkinModel.fromMd5Assert(ctx, item.md5); + const readmeText = await model.getReadme(); return { - screenshotUrl: url, - md5: skin.md5(), - fileName: await skin.filename(true), + screenshotUrl: model.getScreenshotUrl(), + md5: item.md5, + // TODO: Normalize to .wsz + fileName: await model.getFileName(), readmeStart: readmeText ? readmeText.slice(0, 200) : "", }; }) ); - return clientSkins; + for (const skin of skins) { + SessionModel.addSkin(sessionId, skin.md5); + } + return skins; } /** * A tik-tok style scroll page where we display one skin at a time in full screen */ export default async function ScrollPage() { - const initialSkins = await getClientSkins(0); + // Create the session in the database + const sessionId = await SessionModel.create(); - return ; + const initialSkins = await getClientSkins(sessionId); + + return ( + + ); } diff --git a/packages/skin-database/data/SessionModel.ts b/packages/skin-database/data/SessionModel.ts new file mode 100644 index 00000000..b6d3c045 --- /dev/null +++ b/packages/skin-database/data/SessionModel.ts @@ -0,0 +1,29 @@ +import { knex } from "../db"; +import { randomUUID } from "crypto"; + +export default class SessionModel { + static async create(): Promise { + const sessionId = randomUUID(); + const startTime = Date.now(); + await knex("session").insert({ + id: sessionId, + start_time: startTime, + }); + return sessionId; + } + + static async addSkin(sessionId: string, skinMd5: string): Promise { + await knex("session_skin").insert({ + session_id: sessionId, + skin_md5: skinMd5, + }); + } + + static async getIncludedSkinCount(sessionId: string): Promise { + const result = await knex("session_skin") + .where({ session_id: sessionId }) + .count("* as count") + .first(); + return result ? (result.count as number) : 0; + } +} diff --git a/packages/skin-database/data/skins.ts b/packages/skin-database/data/skins.ts index 6d48f701..7fef938c 100644 --- a/packages/skin-database/data/skins.ts +++ b/packages/skin-database/data/skins.ts @@ -718,6 +718,53 @@ export type MuseumPage = Array<{ nsfw: boolean; }>; +export type ScrollPage = Array<{ + md5: string; +}>; +const PAGE_SIZE = 50; +const FRESHNESS_PERCENTAGE = 0.2; // 20% random skins + +const randomCount = Math.floor(PAGE_SIZE * FRESHNESS_PERCENTAGE); +const curatedCount = PAGE_SIZE - randomCount; + +export async function getScrollPage(sessionId: string): Promise { + const skins = await knex.raw( + ` +SELECT + museum_sort_order.skin_md5 +FROM + museum_sort_order +WHERE museum_sort_order.skin_md5 NOT IN (SELECT skin_md5 from session_skin WHERE session_id = ?) +LIMIT ?`, + [sessionId, curatedCount] + ); + + const randomSkins = await knex.raw( + ` +SELECT + skins.md5 as skin_md5 +FROM + skins +LEFT JOIN session_skin ON session_skin.skin_md5 = skins.md5 AND session_skin.session_id = ? +LEFT JOIN skin_reviews ON skin_reviews.skin_md5 = skins.md5 AND skin_reviews.review = 'NSFW' +WHERE + skins.skin_type = 1 AND session_skin.skin_md5 IS NULL AND skin_reviews.skin_md5 IS NULL +ORDER BY RANDOM() +LIMIT ?`, + [sessionId, randomCount] + ); + + // Note: Technically we could get duplicates if a random skin is also in museum_sort_order, + // but in practice this is rare and acceptable for the use case. + const allSkins = skins.concat(randomSkins); + // Shuffle the results + allSkins.sort(() => Math.random() - 0.5); + + return allSkins.map(({ skin_md5 }) => { + return { md5: skin_md5 }; + }); +} + export async function getMuseumPage({ offset, first, diff --git a/packages/skin-database/legacy-client/src/redux/selectors.js b/packages/skin-database/legacy-client/src/redux/selectors.js index 33bdfdd1..83b8ff6c 100644 --- a/packages/skin-database/legacy-client/src/redux/selectors.js +++ b/packages/skin-database/legacy-client/src/redux/selectors.js @@ -165,6 +165,12 @@ export const getRouteData = createSelector( getPermalinkUrlFromHash, skinData ) => { + const currentUrl = window.location.href; + // Hard code ignore "modern" paths for now. + if (currentUrl.includes("/scroll")) { + return { url: "/scroll/", title: "Scroll" }; + } + if (activeContentPage === REVIEW_PAGE) { return { url: "/review/", title: "Review" }; } diff --git a/packages/skin-database/migrations/20251106000000_user_log_events.ts b/packages/skin-database/migrations/20251106000000_user_log_events.ts new file mode 100644 index 00000000..1f35cc39 --- /dev/null +++ b/packages/skin-database/migrations/20251106000000_user_log_events.ts @@ -0,0 +1,21 @@ +import * as Knex from "knex"; + +export async function up(knex: Knex): Promise { + await knex.raw(` + CREATE TABLE user_log_events ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + session_id TEXT NOT NULL, + timestamp INTEGER NOT NULL, + metadata TEXT NOT NULL + ); + + CREATE INDEX idx_session_id ON user_log_events(session_id); + CREATE INDEX idx_timestamp ON user_log_events(timestamp); + `); +} + +export async function down(knex: Knex): Promise { + await knex.raw(` + DROP TABLE IF EXISTS user_log_events; + `); +} diff --git a/packages/skin-database/migrations/20251107000000_session_tables.ts b/packages/skin-database/migrations/20251107000000_session_tables.ts new file mode 100644 index 00000000..dc212648 --- /dev/null +++ b/packages/skin-database/migrations/20251107000000_session_tables.ts @@ -0,0 +1,36 @@ +import * as Knex from "knex"; + +export async function up(knex: Knex): Promise { + await knex.raw(` + CREATE TABLE session ( + id TEXT PRIMARY KEY, + start_time INTEGER NOT NULL + ); + `); + + await knex.raw(` + CREATE TABLE session_skin ( + session_id TEXT NOT NULL, + skin_md5 TEXT NOT NULL, + FOREIGN KEY (session_id) REFERENCES session(id) + ); + `); + + await knex.raw(` + CREATE INDEX idx_session_skin_session_id ON session_skin(session_id); + `); + + await knex.raw(` + CREATE INDEX idx_session_skin_skin_md5 ON session_skin(skin_md5); + `); +} + +export async function down(knex: Knex): Promise { + await knex.raw(` + DROP TABLE IF EXISTS session_skin; + `); + + await knex.raw(` + DROP TABLE IF EXISTS session; + `); +} From 0b2ff44b1cb724fdc9f079583f1361ea254d64b7 Mon Sep 17 00:00:00 2001 From: Jordan Eldredge Date: Fri, 7 Nov 2025 15:44:54 -0800 Subject: [PATCH 28/78] Action buttons --- .../app/(modern)/scroll/Events.ts | 18 ++ .../app/(modern)/scroll/SkinActionIcons.tsx | 233 ++++++++++++++++++ .../app/(modern)/scroll/SkinPage.tsx | 29 ++- .../app/(modern)/scroll/SkinScroller.tsx | 4 + .../app/(modern)/scroll/page.tsx | 10 +- packages/skin-database/package.json | 1 + pnpm-lock.yaml | 12 + 7 files changed, 295 insertions(+), 12 deletions(-) create mode 100644 packages/skin-database/app/(modern)/scroll/SkinActionIcons.tsx diff --git a/packages/skin-database/app/(modern)/scroll/Events.ts b/packages/skin-database/app/(modern)/scroll/Events.ts index ef917ee1..ce87d360 100644 --- a/packages/skin-database/app/(modern)/scroll/Events.ts +++ b/packages/skin-database/app/(modern)/scroll/Events.ts @@ -1,6 +1,8 @@ "use server"; import { knex } from "../../../db"; +import { markAsNSFW } from "../../../data/skins"; +import UserContext from "../../../data/UserContext"; export async function logUserEvent(sessionId: string, event: UserEvent) { const timestamp = Date.now(); @@ -10,6 +12,13 @@ export async function logUserEvent(sessionId: string, event: UserEvent) { timestamp: timestamp, metadata: JSON.stringify(event), }); + + // If this is a NSFW report, call the existing infrastructure + if (event.type === "skin_flag_nsfw") { + // Create an anonymous user context for the report + const ctx = new UserContext(); + await markAsNSFW(ctx, event.skinMd5); + } } type UserEvent = @@ -50,6 +59,15 @@ type UserEvent = type: "skin_download"; skinMd5: string; } + | { + type: "skin_like"; + skinMd5: string; + liked: boolean; + } + | { + type: "skin_flag_nsfw"; + skinMd5: string; + } | { type: "share_open"; skinMd5: string; diff --git a/packages/skin-database/app/(modern)/scroll/SkinActionIcons.tsx b/packages/skin-database/app/(modern)/scroll/SkinActionIcons.tsx new file mode 100644 index 00000000..be57407e --- /dev/null +++ b/packages/skin-database/app/(modern)/scroll/SkinActionIcons.tsx @@ -0,0 +1,233 @@ +"use client"; + +import { useState, ReactNode } from "react"; +import { Heart, Share2, Flag, Download } from "lucide-react"; +import { ClientSkin } from "./SkinScroller"; +import { logUserEvent } from "./Events"; + +type Props = { + skin: ClientSkin; + sessionId: string; +}; + +export default function SkinActionIcons({ skin, sessionId }: Props) { + return ( +
+ + + + +
+ ); +} + +// Implementation details below + +type ButtonProps = { + onClick: () => void; + disabled?: boolean; + opacity?: number; + "aria-label": string; + children: ReactNode; +}; + +function Button({ + onClick, + disabled = false, + opacity = 1, + "aria-label": ariaLabel, + children, +}: ButtonProps) { + return ( + + ); +} + +type LikeButtonProps = { + skin: ClientSkin; + sessionId: string; +}; + +function LikeButton({ skin, sessionId }: LikeButtonProps) { + const [isLiked, setIsLiked] = useState(false); + const [likeCount, setLikeCount] = useState(skin.likeCount); + + const handleLike = async () => { + const newLikedState = !isLiked; + setIsLiked(newLikedState); + + // Optimistically update the like count + setLikeCount((prevCount) => + newLikedState ? prevCount + 1 : prevCount - 1 + ); + + logUserEvent(sessionId, { + type: "skin_like", + skinMd5: skin.md5, + liked: newLikedState, + }); + }; + + return ( + + ); +} + +type ShareButtonProps = { + skin: ClientSkin; + sessionId: string; +}; + +function ShareButton({ skin, sessionId }: ShareButtonProps) { + const handleShare = async () => { + if (navigator.share) { + try { + logUserEvent(sessionId, { + type: "share_open", + skinMd5: skin.md5, + }); + + await navigator.share({ + title: skin.fileName, + text: `Check out this Winamp skin: ${skin.fileName}`, + url: skin.shareUrl, + }); + + logUserEvent(sessionId, { + type: "share_success", + skinMd5: skin.md5, + }); + } catch (error) { + // User cancelled or share failed + if (error instanceof Error && error.name !== "AbortError") { + console.error("Share failed:", error); + logUserEvent(sessionId, { + type: "share_failure", + skinMd5: skin.md5, + errorMessage: error.message, + }); + } + } + } else { + // Fallback: copy to clipboard + await navigator.clipboard.writeText(skin.shareUrl); + + logUserEvent(sessionId, { + type: "share_success", + skinMd5: skin.md5, + }); + alert("Share link copied to clipboard!"); + } + }; + + return ( + + ); +} + +type FlagButtonProps = { + skin: ClientSkin; + sessionId: string; +}; + +function FlagButton({ skin, sessionId }: FlagButtonProps) { + const [isFlagged, setIsFlagged] = useState(skin.nsfw); + + const handleFlagNsfw = async () => { + if (isFlagged) return; // Only allow flagging once + + setIsFlagged(true); + + logUserEvent(sessionId, { + type: "skin_flag_nsfw", + skinMd5: skin.md5, + }); + }; + + return ( + + ); +} + +type DownloadButtonProps = { + skin: ClientSkin; + sessionId: string; +}; + +function DownloadButton({ skin, sessionId }: DownloadButtonProps) { + const handleDownload = async () => { + logUserEvent(sessionId, { + type: "skin_download", + skinMd5: skin.md5, + }); + + // Trigger download + window.location.href = skin.downloadUrl; + }; + + return ( + + ); +} diff --git a/packages/skin-database/app/(modern)/scroll/SkinPage.tsx b/packages/skin-database/app/(modern)/scroll/SkinPage.tsx index f567c280..18614bfe 100644 --- a/packages/skin-database/app/(modern)/scroll/SkinPage.tsx +++ b/packages/skin-database/app/(modern)/scroll/SkinPage.tsx @@ -1,6 +1,7 @@ "use client"; import { ClientSkin } from "./SkinScroller"; +import SkinActionIcons from "./SkinActionIcons"; type Props = { skin: ClientSkin; @@ -8,7 +9,7 @@ type Props = { sessionId: string; }; -export default function SkinPage({ skin, index }: Props) { +export default function SkinPage({ skin, index, sessionId }: Props) { return (
- {skin.fileName} +
+ {skin.fileName} + + +
+
{ page.map(async (item) => { const model = await SkinModel.fromMd5Assert(ctx, item.md5); const readmeText = await model.getReadme(); + const fileName = await model.getFileName(); + const tweet = await model.getTweet(); + const likeCount = tweet ? tweet.getLikes() : 0; + return { screenshotUrl: model.getScreenshotUrl(), md5: item.md5, // TODO: Normalize to .wsz - fileName: await model.getFileName(), + fileName: fileName, readmeStart: readmeText ? readmeText.slice(0, 200) : "", + downloadUrl: model.getSkinUrl(), + shareUrl: `https://skins.webamp.org/skin/${item.md5}`, + nsfw: await model.getIsNsfw(), + likeCount: likeCount, }; }) ); diff --git a/packages/skin-database/package.json b/packages/skin-database/package.json index 7bdf7805..18a354da 100644 --- a/packages/skin-database/package.json +++ b/packages/skin-database/package.json @@ -25,6 +25,7 @@ "jszip": "^3.10.1", "knex": "^0.21.1", "lru-cache": "^6.0.0", + "lucide-react": "^0.553.0", "mastodon-api": "^1.3.0", "md5": "^2.2.1", "next": "^15.3.3", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c91c9e80..5712bd2a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -187,6 +187,9 @@ importers: lru-cache: specifier: ^6.0.0 version: 6.0.0 + lucide-react: + specifier: ^0.553.0 + version: 0.553.0(react@19.1.0) mastodon-api: specifier: ^1.3.0 version: 1.3.0 @@ -8944,6 +8947,11 @@ packages: lru_map@0.3.3: resolution: {integrity: sha512-Pn9cox5CsMYngeDbmChANltQl+5pi6XmTrraMSzhPmMBbmgcxmqWry0U3PGapCU1yB4/LqCcom7qhHZiF/jGfQ==} + lucide-react@0.553.0: + resolution: {integrity: sha512-BRgX5zrWmNy/lkVAe0dXBgd7XQdZ3HTf+Hwe3c9WK6dqgnj9h+hxV+MDncM88xDWlCq27+TKvHGE70ViODNILw==} + peerDependencies: + react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0 + magic-string@0.25.9: resolution: {integrity: sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==} @@ -23887,6 +23895,10 @@ snapshots: lru_map@0.3.3: {} + lucide-react@0.553.0(react@19.1.0): + dependencies: + react: 19.1.0 + magic-string@0.25.9: dependencies: sourcemap-codec: 1.4.8 From f3054192e6651e5ec7dd73a9a0e757998980bdb3 Mon Sep 17 00:00:00 2001 From: Jordan Eldredge Date: Fri, 7 Nov 2025 16:08:41 -0800 Subject: [PATCH 29/78] Make page dynamic and fix scroll loading on ios --- .../app/(modern)/scroll/Events.ts | 6 ++++ .../app/(modern)/scroll/SkinScroller.tsx | 31 ++++++++++++++----- .../app/(modern)/scroll/page.tsx | 3 ++ 3 files changed, 32 insertions(+), 8 deletions(-) diff --git a/packages/skin-database/app/(modern)/scroll/Events.ts b/packages/skin-database/app/(modern)/scroll/Events.ts index ce87d360..00a233d7 100644 --- a/packages/skin-database/app/(modern)/scroll/Events.ts +++ b/packages/skin-database/app/(modern)/scroll/Events.ts @@ -7,6 +7,12 @@ import UserContext from "../../../data/UserContext"; export async function logUserEvent(sessionId: string, event: UserEvent) { const timestamp = Date.now(); + console.log("Logging user event:", { + sessionId, + timestamp, + event, + }); + await knex("user_log_events").insert({ session_id: sessionId, timestamp: timestamp, diff --git a/packages/skin-database/app/(modern)/scroll/SkinScroller.tsx b/packages/skin-database/app/(modern)/scroll/SkinScroller.tsx index 97b26f9d..4e5bf1d3 100644 --- a/packages/skin-database/app/(modern)/scroll/SkinScroller.tsx +++ b/packages/skin-database/app/(modern)/scroll/SkinScroller.tsx @@ -36,18 +36,33 @@ export default function SkinScroller({ return; } - function onSnap(e) { - const md5 = e.snapTargetBlock.getAttribute("skin-md5"); - const index = parseInt(e.snapTargetBlock.getAttribute("skin-index")); - setVisibleSkinIndex(index); - } + // Use IntersectionObserver for cross-browser compatibility (iOS doesn't support scrollsnapchange) + const observer = new IntersectionObserver( + (entries) => { + entries.forEach((entry) => { + // When an element becomes mostly visible (> 50% intersecting) + if (entry.isIntersecting && entry.intersectionRatio > 0.5) { + const index = parseInt( + entry.target.getAttribute("skin-index") || "0" + ); + setVisibleSkinIndex(index); + } + }); + }, + { + root: containerRef, + threshold: 0.5, // Trigger when 50% of the element is visible + } + ); - containerRef.addEventListener("scrollsnapchange", onSnap); + // Observe all skin page elements + const skinElements = containerRef.querySelectorAll("[skin-index]"); + skinElements.forEach((element) => observer.observe(element)); return () => { - containerRef.removeEventListener("scrollsnapchange", onSnap); + observer.disconnect(); }; - }, [containerRef]); + }, [containerRef, skins.length]); useEffect(() => { logUserEvent(sessionId, { diff --git a/packages/skin-database/app/(modern)/scroll/page.tsx b/packages/skin-database/app/(modern)/scroll/page.tsx index 1152ce41..25576a49 100644 --- a/packages/skin-database/app/(modern)/scroll/page.tsx +++ b/packages/skin-database/app/(modern)/scroll/page.tsx @@ -5,6 +5,9 @@ import SkinScroller, { ClientSkin } from "./SkinScroller"; import { getScrollPage } from "../../../data/skins"; import SkinModel from "../../../data/SkinModel"; +// Ensure each page load gets a new session +export const dynamic = "force-dynamic"; + async function getClientSkins(sessionId: string): Promise { "use server"; const ctx = new UserContext(); From 608242b200fac4a805e6bf88eb400cd46002e798 Mon Sep 17 00:00:00 2001 From: Jordan Eldredge Date: Fri, 7 Nov 2025 17:28:21 -0800 Subject: [PATCH 30/78] Improve landscape screens for scroll --- .../app/(modern)/scroll/Events.ts | 9 +- .../app/(modern)/scroll/SkinScroller.tsx | 7 + .../app/(modern)/scroll/scroll.css | 8 +- packages/skin-database/cli.ts | 9 + .../tasks/computeScrollRanking.ts | 181 ++++++++++++++++++ 5 files changed, 210 insertions(+), 4 deletions(-) create mode 100644 packages/skin-database/tasks/computeScrollRanking.ts diff --git a/packages/skin-database/app/(modern)/scroll/Events.ts b/packages/skin-database/app/(modern)/scroll/Events.ts index 00a233d7..b43833f5 100644 --- a/packages/skin-database/app/(modern)/scroll/Events.ts +++ b/packages/skin-database/app/(modern)/scroll/Events.ts @@ -27,7 +27,7 @@ export async function logUserEvent(sessionId: string, event: UserEvent) { } } -type UserEvent = +export type UserEvent = | { type: "session_start"; } @@ -35,6 +35,13 @@ type UserEvent = type: "session_end"; reason: "unmount" | "before_unload"; } + /** + * @deprecated + */ + | { + type: "skin_view"; + skinMd5: string; + } | { type: "skin_view_start"; skinMd5: string; diff --git a/packages/skin-database/app/(modern)/scroll/SkinScroller.tsx b/packages/skin-database/app/(modern)/scroll/SkinScroller.tsx index 4e5bf1d3..ac7382f0 100644 --- a/packages/skin-database/app/(modern)/scroll/SkinScroller.tsx +++ b/packages/skin-database/app/(modern)/scroll/SkinScroller.tsx @@ -138,9 +138,16 @@ export default function SkinScroller({ ref={setContainerRef} style={{ height: "100vh", + width: "100vw", + maxWidth: "56.25vh", // 9:16 aspect ratio (100vh * 9/16) + margin: "0 auto", overflowY: "scroll", scrollSnapType: "y mandatory", + scrollbarWidth: "none", // Firefox + msOverflowStyle: "none", // IE and Edge + WebkitOverflowScrolling: "touch", // Smooth scrolling on iOS }} + className="hide-scrollbar" > {skins.map((skin, i) => { return ( diff --git a/packages/skin-database/app/(modern)/scroll/scroll.css b/packages/skin-database/app/(modern)/scroll/scroll.css index ae1b15a6..5ed3b090 100644 --- a/packages/skin-database/app/(modern)/scroll/scroll.css +++ b/packages/skin-database/app/(modern)/scroll/scroll.css @@ -1,14 +1,16 @@ body { margin: 0; /* Remove default margin */ height: 100vh; /* Set body height to viewport height */ - background-color: rgb(0, 0, 0); + background-color: #1a1a1a; /* Dark charcoal instead of pure black */ } -.scroller::-webkit-scrollbar { +.scroller::-webkit-scrollbar, +.hide-scrollbar::-webkit-scrollbar { display: none; /* Chrome, Safari, Edge */ } -.scroller { +.scroller, +.hide-scrollbar { -ms-overflow-style: none; /* IE and Edge */ scrollbar-width: none; /* Firefox */ } diff --git a/packages/skin-database/cli.ts b/packages/skin-database/cli.ts index 1c6b96a1..796687e2 100755 --- a/packages/skin-database/cli.ts +++ b/packages/skin-database/cli.ts @@ -36,6 +36,7 @@ import * as S3 from "./s3"; import { generateDescription } from "./services/openAi"; import KeyValue from "./data/KeyValue"; import { postToBluesky } from "./tasks/bluesky"; +import { computeSkinRankings } from "./tasks/computeScrollRanking"; async function withHandler( cb: (handler: DiscordEventHandler) => Promise @@ -308,6 +309,14 @@ program console.table([await Skins.getStats()]); }); +program + .command("compute-scroll-ranking") + .description("Analyze user event data and compute skin ranking scores.") + .action(async () => { + const rankings = await computeSkinRankings(); + console.log(JSON.stringify(rankings, null, 2)); + }); + program .command("process-uploads") .description("Process any unprocessed user uploads.") diff --git a/packages/skin-database/tasks/computeScrollRanking.ts b/packages/skin-database/tasks/computeScrollRanking.ts new file mode 100644 index 00000000..fc0147e8 --- /dev/null +++ b/packages/skin-database/tasks/computeScrollRanking.ts @@ -0,0 +1,181 @@ +import { knex } from "../db"; +import type { UserEvent } from "../app/(modern)/scroll/Events"; + +const WEIGHTS = { + viewDurationPerSecond: 0.1, + like: 10, + download: 5, + share: 15, + readmeExpand: 2, +}; + +interface SessionAggregate { + skinViewDurations: Map; + skinsLiked: Set; + skinsDownloaded: Set; + readmesExpanded: Set; + sharesSucceeded: Set; +} + +interface SkinRanking { + skinMd5: string; + totalViewDurationMs: number; + viewCount: number; + averageViewDurationMs: number; + likeCount: number; + downloadCount: number; + shareCount: number; + readmeExpandCount: number; + rankingScore: number; +} + +async function main() { + try { + const rankings = await computeSkinRankings(); + console.log(JSON.stringify(rankings, null, 2)); + } catch (error) { + console.error("Error during aggregation:", error); + throw error; + } finally { + await knex.destroy(); + } +} + +if (require.main === module) { + main(); +} + +export async function computeSkinRankings(): Promise { + const sessionMap = await buildSessionAggregates(); + + const skinDataMap = new Map< + string, + { + viewDurations: number[]; + likes: number; + downloads: number; + shares: number; + readmeExpands: number; + } + >(); + + function getSkinData(skinMd5: string) { + if (!skinDataMap.has(skinMd5)) { + skinDataMap.set(skinMd5, { + viewDurations: [], + likes: 0, + downloads: 0, + shares: 0, + readmeExpands: 0, + }); + } + return skinDataMap.get(skinMd5)!; + } + + for (const session of sessionMap.values()) { + for (const [skinMd5, duration] of session.skinViewDurations) { + getSkinData(skinMd5).viewDurations.push(duration); + } + for (const skinMd5 of session.skinsLiked) { + getSkinData(skinMd5).likes++; + } + for (const skinMd5 of session.skinsDownloaded) { + getSkinData(skinMd5).downloads++; + } + for (const skinMd5 of session.sharesSucceeded) { + getSkinData(skinMd5).shares++; + } + for (const skinMd5 of session.readmesExpanded) { + getSkinData(skinMd5).readmeExpands++; + } + } + + const rankings: SkinRanking[] = []; + + for (const [skinMd5, data] of skinDataMap) { + const totalViewDurationMs = data.viewDurations.reduce( + (sum, duration) => sum + duration, + 0 + ); + const viewCount = data.viewDurations.length; + const averageViewDurationMs = + viewCount > 0 ? totalViewDurationMs / viewCount : 0; + + const rankingScore = + (averageViewDurationMs / 1000) * WEIGHTS.viewDurationPerSecond + + data.likes * WEIGHTS.like + + data.downloads * WEIGHTS.download + + data.shares * WEIGHTS.share + + data.readmeExpands * WEIGHTS.readmeExpand; + + rankings.push({ + skinMd5, + totalViewDurationMs, + viewCount, + averageViewDurationMs, + likeCount: data.likes, + downloadCount: data.downloads, + shareCount: data.shares, + readmeExpandCount: data.readmeExpands, + rankingScore, + }); + } + + rankings.sort((a, b) => b.rankingScore - a.rankingScore); + return rankings; +} + +async function buildSessionAggregates(): Promise< + Map +> { + const events = await knex("user_log_events") + .select("session_id", "timestamp", "metadata") + .orderBy("timestamp", "asc"); + + const sessionMap = new Map(); + + function getSession(sessionId: string): SessionAggregate { + if (!sessionMap.has(sessionId)) { + sessionMap.set(sessionId, { + skinViewDurations: new Map(), + skinsLiked: new Set(), + skinsDownloaded: new Set(), + readmesExpanded: new Set(), + sharesSucceeded: new Set(), + }); + } + return sessionMap.get(sessionId)!; + } + + for (const row of events) { + const event: UserEvent = JSON.parse(row.metadata); + const session = getSession(row.session_id); + + switch (event.type) { + case "skin_view_end": + session.skinViewDurations.set(event.skinMd5, event.durationMs); + break; + case "readme_expand": + session.readmesExpanded.add(event.skinMd5); + break; + case "skin_download": + session.skinsDownloaded.add(event.skinMd5); + break; + case "skin_like": + if (event.liked) { + session.skinsLiked.add(event.skinMd5); + } else { + session.skinsLiked.delete(event.skinMd5); + } + break; + case "share_success": + session.sharesSucceeded.add(event.skinMd5); + break; + } + } + + return sessionMap; +} + +export { buildSessionAggregates }; +export type { SessionAggregate }; From 7afe3bd45b332786be87fb4dc4f5fc6c1d31b292 Mon Sep 17 00:00:00 2001 From: Jordan Eldredge Date: Fri, 7 Nov 2025 18:51:49 -0800 Subject: [PATCH 31/78] Stub out menu bar --- .../app/(modern)/scroll/BottomMenuBar.tsx | 340 ++++++++++++++++++ .../app/(modern)/scroll/Events.ts | 4 + .../app/(modern)/scroll/SkinPage.tsx | 10 +- .../app/(modern)/scroll/SkinScroller.tsx | 66 ++-- .../app/(modern)/scroll/layout.tsx | 46 +++ .../app/(modern)/scroll/page.tsx | 13 +- 6 files changed, 444 insertions(+), 35 deletions(-) create mode 100644 packages/skin-database/app/(modern)/scroll/BottomMenuBar.tsx create mode 100644 packages/skin-database/app/(modern)/scroll/layout.tsx diff --git a/packages/skin-database/app/(modern)/scroll/BottomMenuBar.tsx b/packages/skin-database/app/(modern)/scroll/BottomMenuBar.tsx new file mode 100644 index 00000000..db0f508c --- /dev/null +++ b/packages/skin-database/app/(modern)/scroll/BottomMenuBar.tsx @@ -0,0 +1,340 @@ +"use client"; + +import { + Smartphone, + Search, + Info, + Grid3x3, + Menu, + MessageSquare, + Upload, + Github, +} from "lucide-react"; +import Link from "next/link"; +import { useState, useEffect, useRef } from "react"; +import { usePathname } from "next/navigation"; +import { logUserEvent } from "./Events"; + +type Props = { + sessionId?: string; +}; + +export default function BottomMenuBar({ sessionId }: Props) { + const [isHamburgerOpen, setIsHamburgerOpen] = useState(false); + const menuRef = useRef(null); + const pathname = usePathname(); + + const handleMenuClick = (menuItem: string) => { + if (sessionId) { + logUserEvent(sessionId, { + type: "menu_click", + menuItem, + }); + } + }; + + const toggleHamburger = () => { + setIsHamburgerOpen(!isHamburgerOpen); + handleMenuClick("hamburger"); + }; + + // Close hamburger menu when clicking outside + useEffect(() => { + function handleClickOutside(event: MouseEvent) { + if ( + menuRef.current && + !menuRef.current.contains(event.target as Node) && + isHamburgerOpen + ) { + setIsHamburgerOpen(false); + } + } + + if (isHamburgerOpen) { + document.addEventListener("mousedown", handleClickOutside); + } + + return () => { + document.removeEventListener("mousedown", handleClickOutside); + }; + }, [isHamburgerOpen]); + + return ( + <> + {/* Hamburger Menu Overlay */} + {isHamburgerOpen && ( +
+ } + label="About" + onClick={() => { + handleMenuClick("about"); + setIsHamburgerOpen(false); + }} + /> + } + label="Upload" + onClick={() => { + handleMenuClick("upload"); + setIsHamburgerOpen(false); + }} + />{" "} + } + label="Feedback" + onClick={() => { + handleMenuClick("feedback"); + setIsHamburgerOpen(false); + }} + external + /> + } + label="GitHub" + onClick={() => { + handleMenuClick("feedback"); + setIsHamburgerOpen(false); + }} + external + /> +
+ )} + + {/* Bottom Menu Bar */} +
+ } + label="Feed" + onClick={() => handleMenuClick("feed")} + isActive={pathname === "/scroll"} + /> + } + label="Grid" + onClick={() => handleMenuClick("grid")} + isActive={pathname === "/"} + /> + } + label="Search" + onClick={() => handleMenuClick("search")} + isActive={false} + /> + } + label="Menu" + onClick={toggleHamburger} + isButton + isActive={false} + /> +
+ + ); +} + +type MenuButtonProps = { + href?: string; + icon: React.ReactNode; + label: string; + onClick: () => void; + isButton?: boolean; + isActive?: boolean; +}; + +function MenuButton({ + href, + icon, + label, + onClick, + isButton = false, + isActive = false, +}: MenuButtonProps) { + const touchTargetSize = "3.0rem"; + + const containerStyle = { + display: "flex", + flexDirection: "column" as const, + alignItems: "center", + justifyContent: "center", + gap: "0.25rem", + color: "#ccc", + textDecoration: "none", + cursor: "pointer", + transition: "color 0.2s ease", + background: "none", + border: "none", + padding: 0, + position: "relative" as const, + width: touchTargetSize, + minWidth: touchTargetSize, + }; + + const handleMouseEnter = (e: React.MouseEvent) => { + e.currentTarget.style.color = "#fff"; + }; + + const handleMouseLeave = (e: React.MouseEvent) => { + e.currentTarget.style.color = "#ccc"; + }; + + const content = ( + <> + {/* Active indicator line */} + {isActive && ( +
+ )} +
+ {icon} +
+ + {label} + + + ); + + if (isButton) { + return ( + + ); + } + + return ( + + {content} + + ); +} + +type HamburgerMenuItemProps = { + href: string; + icon: React.ReactNode; + label: string; + onClick: () => void; + external?: boolean; +}; + +function HamburgerMenuItem({ + href, + icon, + label, + onClick, + external = false, +}: HamburgerMenuItemProps) { + const content = ( +
{ + e.currentTarget.style.backgroundColor = "rgba(255, 255, 255, 0.05)"; + e.currentTarget.style.color = "#fff"; + }} + onMouseLeave={(e) => { + e.currentTarget.style.backgroundColor = "transparent"; + e.currentTarget.style.color = "#ccc"; + }} + > + {icon} + + {label} + +
+ ); + + if (external) { + return ( + + {content} + + ); + } + + return ( + + {content} + + ); +} diff --git a/packages/skin-database/app/(modern)/scroll/Events.ts b/packages/skin-database/app/(modern)/scroll/Events.ts index b43833f5..c2238578 100644 --- a/packages/skin-database/app/(modern)/scroll/Events.ts +++ b/packages/skin-database/app/(modern)/scroll/Events.ts @@ -93,4 +93,8 @@ export type UserEvent = type: "share_failure"; skinMd5: string; errorMessage: string; + } + | { + type: "menu_click"; + menuItem: string; }; diff --git a/packages/skin-database/app/(modern)/scroll/SkinPage.tsx b/packages/skin-database/app/(modern)/scroll/SkinPage.tsx index 18614bfe..f444eb24 100644 --- a/packages/skin-database/app/(modern)/scroll/SkinPage.tsx +++ b/packages/skin-database/app/(modern)/scroll/SkinPage.tsx @@ -19,20 +19,22 @@ export default function SkinPage({ skin, index, sessionId }: Props) { style={{ display: "flex", flexDirection: "column", + justifyContent: "center", width: "100%", height: "100vh", scrollSnapAlign: "start", scrollSnapStop: "always", position: "relative", + paddingTop: "2rem", // Space for top shadow + paddingBottom: "5rem", // Space for bottom menu bar + boxSizing: "border-box", }} > -
+
{skin.fileName}

- {skins.map((skin, i) => { - return ( - - ); - })} -

+ <> +
+ {skins.map((skin, i) => { + return ( + + ); + })} +
+ {/* Top shadow overlay */} +
+ ); } diff --git a/packages/skin-database/app/(modern)/scroll/layout.tsx b/packages/skin-database/app/(modern)/scroll/layout.tsx new file mode 100644 index 00000000..bb5a596a --- /dev/null +++ b/packages/skin-database/app/(modern)/scroll/layout.tsx @@ -0,0 +1,46 @@ +"use client"; + +import { ReactNode, createContext, useContext } from "react"; +import BottomMenuBar from "./BottomMenuBar"; + +type SessionContextType = { + sessionId: string | null; +}; + +const SessionContext = createContext({ sessionId: null }); + +export function useSession() { + return useContext(SessionContext); +} + +type LayoutWrapperProps = { + children: ReactNode; + sessionId: string; +}; + +export function LayoutWrapper({ children, sessionId }: LayoutWrapperProps) { + return ( + +
+ {children} + +
+
+ ); +} + +type Props = { + children: ReactNode; +}; + +export default function ScrollLayout({ children }: Props) { + return <>{children}; +} diff --git a/packages/skin-database/app/(modern)/scroll/page.tsx b/packages/skin-database/app/(modern)/scroll/page.tsx index 25576a49..76ba1609 100644 --- a/packages/skin-database/app/(modern)/scroll/page.tsx +++ b/packages/skin-database/app/(modern)/scroll/page.tsx @@ -4,6 +4,7 @@ import "./scroll.css"; import SkinScroller, { ClientSkin } from "./SkinScroller"; import { getScrollPage } from "../../../data/skins"; import SkinModel from "../../../data/SkinModel"; +import { LayoutWrapper } from "./layout"; // Ensure each page load gets a new session export const dynamic = "force-dynamic"; @@ -51,10 +52,12 @@ export default async function ScrollPage() { const initialSkins = await getClientSkins(sessionId); return ( - + + + ); } From 811fc977c4c4436f84f89d5b342ebdb33387758c Mon Sep 17 00:00:00 2001 From: Jordan Eldredge Date: Fri, 7 Nov 2025 18:56:10 -0800 Subject: [PATCH 32/78] Clean up --- .../app/(modern)/scroll/BottomMenuBar.tsx | 28 +--------- .../app/(modern)/scroll/layout.tsx | 51 ++++++------------- .../app/(modern)/scroll/page.tsx | 13 ++--- 3 files changed, 21 insertions(+), 71 deletions(-) diff --git a/packages/skin-database/app/(modern)/scroll/BottomMenuBar.tsx b/packages/skin-database/app/(modern)/scroll/BottomMenuBar.tsx index db0f508c..aea4ac0e 100644 --- a/packages/skin-database/app/(modern)/scroll/BottomMenuBar.tsx +++ b/packages/skin-database/app/(modern)/scroll/BottomMenuBar.tsx @@ -13,29 +13,14 @@ import { import Link from "next/link"; import { useState, useEffect, useRef } from "react"; import { usePathname } from "next/navigation"; -import { logUserEvent } from "./Events"; -type Props = { - sessionId?: string; -}; - -export default function BottomMenuBar({ sessionId }: Props) { +export default function BottomMenuBar() { const [isHamburgerOpen, setIsHamburgerOpen] = useState(false); const menuRef = useRef(null); const pathname = usePathname(); - const handleMenuClick = (menuItem: string) => { - if (sessionId) { - logUserEvent(sessionId, { - type: "menu_click", - menuItem, - }); - } - }; - const toggleHamburger = () => { setIsHamburgerOpen(!isHamburgerOpen); - handleMenuClick("hamburger"); }; // Close hamburger menu when clicking outside @@ -81,7 +66,6 @@ export default function BottomMenuBar({ sessionId }: Props) { icon={} label="About" onClick={() => { - handleMenuClick("about"); setIsHamburgerOpen(false); }} /> @@ -90,7 +74,6 @@ export default function BottomMenuBar({ sessionId }: Props) { icon={} label="Upload" onClick={() => { - handleMenuClick("upload"); setIsHamburgerOpen(false); }} />{" "} @@ -99,7 +82,6 @@ export default function BottomMenuBar({ sessionId }: Props) { icon={} label="Feedback" onClick={() => { - handleMenuClick("feedback"); setIsHamburgerOpen(false); }} external @@ -109,7 +91,6 @@ export default function BottomMenuBar({ sessionId }: Props) { icon={} label="GitHub" onClick={() => { - handleMenuClick("feedback"); setIsHamburgerOpen(false); }} external @@ -138,21 +119,18 @@ export default function BottomMenuBar({ sessionId }: Props) { href="/scroll" icon={} label="Feed" - onClick={() => handleMenuClick("feed")} isActive={pathname === "/scroll"} /> } label="Grid" - onClick={() => handleMenuClick("grid")} isActive={pathname === "/"} /> } label="Search" - onClick={() => handleMenuClick("search")} isActive={false} /> void; isButton?: boolean; isActive?: boolean; }; @@ -180,7 +157,6 @@ function MenuButton({ href, icon, label, - onClick, isButton = false, isActive = false, }: MenuButtonProps) { @@ -251,7 +227,6 @@ function MenuButton({ if (isButton) { return (
); @@ -151,6 +167,7 @@ type MenuButtonProps = { label: string; isButton?: boolean; isActive?: boolean; + onClick?: () => void; }; function MenuButton({ @@ -159,6 +176,7 @@ function MenuButton({ label, isButton = false, isActive = false, + onClick, }: MenuButtonProps) { const touchTargetSize = "3.0rem"; @@ -230,6 +248,7 @@ function MenuButton({ style={containerStyle} onMouseEnter={handleMouseEnter} onMouseLeave={handleMouseLeave} + onClick={onClick} > {content} diff --git a/packages/skin-database/app/(modern)/scroll/SkinPage.tsx b/packages/skin-database/app/(modern)/scroll/SkinPage.tsx index a5267b74..ba6103b4 100644 --- a/packages/skin-database/app/(modern)/scroll/SkinPage.tsx +++ b/packages/skin-database/app/(modern)/scroll/SkinPage.tsx @@ -1,5 +1,6 @@ "use client"; +import { unstable_ViewTransition as ViewTransition } from "react"; import { ClientSkin } from "./SkinScroller"; import SkinActionIcons from "./SkinActionIcons"; @@ -31,15 +32,17 @@ export default function SkinPage({ skin, index, sessionId }: Props) { }} >
- {skin.fileName} + + {skin.fileName} +
diff --git a/packages/skin-database/app/(modern)/scroll/SkinScroller.tsx b/packages/skin-database/app/(modern)/scroll/SkinScroller.tsx index f1a32590..6dde3208 100644 --- a/packages/skin-database/app/(modern)/scroll/SkinScroller.tsx +++ b/packages/skin-database/app/(modern)/scroll/SkinScroller.tsx @@ -4,6 +4,7 @@ import { useState, useLayoutEffect, useEffect } from "react"; import SkinPage from "./SkinPage"; import { logUserEvent } from "./Events"; import { useScrollHint } from "./useScrollHint"; +import { MOBILE_MAX_WIDTH } from "../../../legacy-client/src/constants"; export type ClientSkin = { screenshotUrl: string; @@ -158,8 +159,10 @@ export default function SkinScroller({
Promise; +}; + +function Cell({ + columnIndex, + rowIndex, + style, + data, +}: { + columnIndex: number; + rowIndex: number; + style: React.CSSProperties; + data: CellData; +}) { + const { skins, width, height, columnCount } = data; + const index = rowIndex * columnCount + columnIndex; + data.loadMoreSkins(index); + const skin = skins[index]; + + if (!skin) { + return null; + } + + return ( +
+
+ + + {skin.fileName} + + + {skin.nsfw && ( +
+ NSFW +
+ )} +
+
+ ); +} + +type SkinTableProps = { + initialSkins: GridSkin[]; + initialTotal: number; +}; + +export default function SkinTable({ + initialSkins, + initialTotal, +}: SkinTableProps) { + const { windowWidth, windowHeight } = useWindowSize(); + + // Initialize state with server-provided data + const [skins, setSkins] = useState(initialSkins); + const [loadedPages, setLoadedPages] = useState>(new Set([0])); + const isLoadingRef = useRef(false); + + const columnCount = Math.round(windowWidth / (SCREENSHOT_WIDTH * 0.9)); + const columnWidth = windowWidth / columnCount; + const rowHeight = columnWidth * SKIN_RATIO; + const pageSize = 50; // Number of skins to load per page + + const loadMoreSkins = useCallback( + async (startIndex: number) => { + const pageNumber = Math.floor(startIndex / pageSize); + + // Don't reload if we already have this page + if (loadedPages.has(pageNumber) || isLoadingRef.current) { + return; + } + + isLoadingRef.current = true; + try { + const offset = pageNumber * pageSize; + const newSkins = await getMuseumPageSkins(offset, pageSize); + setSkins((prev) => [...prev, ...newSkins]); + setLoadedPages((prev) => new Set([...prev, pageNumber])); + } catch (error) { + console.error("Failed to load skins:", error); + } finally { + isLoadingRef.current = false; + } + }, + [loadedPages, pageSize] + ); + + function itemKey({ + columnIndex, + rowIndex, + }: { + columnIndex: number; + rowIndex: number; + }) { + const index = rowIndex * columnCount + columnIndex; + const skin = skins[index]; + return skin ? skin.md5 : `empty-cell-${columnIndex}-${rowIndex}`; + } + + const gridRef = React.useRef(); + const itemRef = React.useRef(0); + + const onScroll = useMemo(() => { + const half = Math.round(columnCount / 2); + return (scrollData: { scrollTop: number }) => { + itemRef.current = + Math.round(scrollData.scrollTop / rowHeight) * columnCount + half; + }; + }, [columnCount, rowHeight, loadMoreSkins]); + + const itemData: CellData = useMemo( + () => ({ + skins, + columnCount, + width: columnWidth, + height: rowHeight, + loadMoreSkins, + }), + [skins, columnCount, columnWidth, rowHeight, loadMoreSkins] + ); + + return ( +
+ + {Cell} + +
+ ); +} diff --git a/packages/skin-database/app/(modern)/scroll/grid/InfiniteScrollGrid.tsx b/packages/skin-database/app/(modern)/scroll/grid/InfiniteScrollGrid.tsx new file mode 100644 index 00000000..eef304e9 --- /dev/null +++ b/packages/skin-database/app/(modern)/scroll/grid/InfiniteScrollGrid.tsx @@ -0,0 +1,198 @@ +"use client"; + +import { + useState, + useLayoutEffect, + useEffect, + useRef, + useCallback, + memo, +} from "react"; +import { FixedSizeGrid as Grid } from "react-window"; +import { useRouter } from "next/navigation"; +import { ClientSkin } from "../SkinScroller"; +import { + SCREENSHOT_WIDTH, + SKIN_RATIO, +} from "../../../../legacy-client/src/constants"; + +type Props = { + initialSkins: ClientSkin[]; + getSkins: (sessionId: string, offset: number) => Promise; + sessionId: string; +}; + +type CellProps = { + columnIndex: number; + rowIndex: number; + style: React.CSSProperties; + data: { + skins: ClientSkin[]; + columnCount: number; + requestSkinsIfNeeded: (index: number) => void; + }; +}; + +// Extract Cell as a separate component so we can use hooks +const GridCell = memo(({ columnIndex, rowIndex, style, data }: CellProps) => { + const { skins, columnCount, requestSkinsIfNeeded } = data; + const router = useRouter(); + const index = rowIndex * columnCount + columnIndex; + const skin = skins[index]; + + // Request more skins if this cell needs data + useEffect(() => { + if (!skin) { + requestSkinsIfNeeded(index); + } + }, [skin, index, requestSkinsIfNeeded]); + + if (!skin) { + return
; + } + + return ( +
{ + router.push(`/scroll/skin/${skin.md5}`); + }} + > +
+ {skin.fileName} +
+
+ ); +}); + +GridCell.displayName = "GridCell"; + +// Calculate grid dimensions based on window width +// Skins will be scaled to fill horizontally across multiple columns +function getGridDimensions(windowWidth: number) { + const scale = 1.0; // Can be adjusted for different sizes + const columnCount = Math.max( + 1, + Math.floor(windowWidth / (SCREENSHOT_WIDTH * scale)) + ); + const columnWidth = windowWidth / columnCount; + const rowHeight = columnWidth * SKIN_RATIO; + return { columnWidth, rowHeight, columnCount }; +} + +export default function InfiniteScrollGrid({ + initialSkins, + getSkins, + sessionId, +}: Props) { + const [skins, setSkins] = useState(initialSkins); + const [fetching, setFetching] = useState(false); + const [windowWidth, setWindowWidth] = useState(0); + const [windowHeight, setWindowHeight] = useState(0); + const gridRef = useRef(null); + const requestedIndicesRef = useRef>(new Set()); + + // Track window size + useLayoutEffect(() => { + function updateSize() { + setWindowWidth(window.innerWidth); + setWindowHeight(window.innerHeight); + } + updateSize(); + window.addEventListener("resize", updateSize); + return () => window.removeEventListener("resize", updateSize); + }, []); + + // Scroll to top when window width changes (column count changes) + useEffect(() => { + if (gridRef.current && windowWidth > 0) { + gridRef.current.scrollTo({ scrollLeft: 0, scrollTop: 0 }); + } + }, [windowWidth]); + + // Function to request more skins when a cell needs data + const requestSkinsIfNeeded = useCallback( + (index: number) => { + // Only fetch if this index is beyond our current data + if (index >= skins.length) { + // Calculate which batch this index belongs to + const batchSize = 50; // Fetch in batches + const batchStart = Math.floor(skins.length / batchSize) * batchSize; + + // Only fetch if we haven't already requested this batch + if (!requestedIndicesRef.current.has(batchStart) && !fetching) { + requestedIndicesRef.current.add(batchStart); + setFetching(true); + getSkins(sessionId, batchStart) + .then((newSkins) => { + setSkins((prevSkins) => [...prevSkins, ...newSkins]); + setFetching(false); + }) + .catch(() => { + requestedIndicesRef.current.delete(batchStart); + setFetching(false); + }); + } + } + }, + [skins.length, fetching, sessionId, getSkins] + ); + + const { columnWidth, rowHeight, columnCount } = + getGridDimensions(windowWidth); + + if (windowWidth === 0 || windowHeight === 0) { + return null; // Don't render until we have window dimensions + } + + const rowCount = Math.ceil(skins.length / columnCount); + + const itemData = { + skins, + columnCount, + requestSkinsIfNeeded, + }; + + return ( +
+ + {GridCell} + +
+ ); +} diff --git a/packages/skin-database/app/(modern)/scroll/grid/getMuseumPageSkins.ts b/packages/skin-database/app/(modern)/scroll/grid/getMuseumPageSkins.ts new file mode 100644 index 00000000..4be6b404 --- /dev/null +++ b/packages/skin-database/app/(modern)/scroll/grid/getMuseumPageSkins.ts @@ -0,0 +1,26 @@ +"use server"; + +import { getMuseumPage, getScreenshotUrl } from "../../../../data/skins"; + +export type GridSkin = { + md5: string; + screenshotUrl: string; + fileName: string; + nsfw: boolean; +}; + +export async function getMuseumPageSkins( + offset: number, + limit: number +): Promise { + const page = await getMuseumPage({ offset, first: limit }); + + const skins = page.map((item) => ({ + md5: item.md5, + screenshotUrl: getScreenshotUrl(item.md5), + fileName: item.fileName, + nsfw: item.nsfw, + })); + + return skins; +} diff --git a/packages/skin-database/app/(modern)/scroll/grid/layout.tsx b/packages/skin-database/app/(modern)/scroll/grid/layout.tsx new file mode 100644 index 00000000..22bce96c --- /dev/null +++ b/packages/skin-database/app/(modern)/scroll/grid/layout.tsx @@ -0,0 +1,9 @@ +import { ReactNode } from "react"; + +type LayoutProps = { + children: ReactNode; +}; + +export default function Layout({ children }: LayoutProps) { + return
{children}
; +} diff --git a/packages/skin-database/app/(modern)/scroll/grid/page.tsx b/packages/skin-database/app/(modern)/scroll/grid/page.tsx new file mode 100644 index 00000000..4a2a3dd2 --- /dev/null +++ b/packages/skin-database/app/(modern)/scroll/grid/page.tsx @@ -0,0 +1,13 @@ +import React from "react"; +import Grid from "./Grid"; +import { getMuseumPageSkins } from "./getMuseumPageSkins"; +import * as Skins from "../../../../data/skins"; + +export default async function SkinTable() { + const [initialSkins, skinCount] = await Promise.all([ + getMuseumPageSkins(0, 50), + Skins.getClassicSkinCount(), + ]); + + return ; +} diff --git a/packages/skin-database/app/(modern)/scroll/layout.tsx b/packages/skin-database/app/(modern)/scroll/layout.tsx index c2aaedf3..819a1135 100644 --- a/packages/skin-database/app/(modern)/scroll/layout.tsx +++ b/packages/skin-database/app/(modern)/scroll/layout.tsx @@ -8,14 +8,11 @@ type LayoutProps = { children: ReactNode; }; -export default function LayoutWrapper({ children }: LayoutProps) { +export default function Layout({ children }: LayoutProps) { return (
diff --git a/packages/skin-database/legacy-client/src/constants.js b/packages/skin-database/legacy-client/src/constants.js index d350d24d..c6cdb481 100644 --- a/packages/skin-database/legacy-client/src/constants.js +++ b/packages/skin-database/legacy-client/src/constants.js @@ -1,6 +1,7 @@ export const SCREENSHOT_WIDTH = 275; export const SCREENSHOT_HEIGHT = 348; export const SKIN_RATIO = SCREENSHOT_HEIGHT / SCREENSHOT_WIDTH; +export const MOBILE_MAX_WIDTH = "56.25vh"; // 9:16 aspect ratio (100vh * 9/16) for TikTok-style scroll export const ABOUT_PAGE = "ABOUT_PAGE"; export const UPLOAD_PAGE = "UPLOAD_PAGE"; export const REVIEW_PAGE = "REVIEW_PAGE"; diff --git a/packages/skin-database/next.config.js b/packages/skin-database/next.config.js index 566bf193..84a62cd4 100644 --- a/packages/skin-database/next.config.js +++ b/packages/skin-database/next.config.js @@ -1,6 +1,9 @@ /** @type {import('next').NextConfig} */ const nextConfig = { serverExternalPackages: ["knex", "imagemin-optipng", "discord.js"], + experimental: { + viewTransition: true, + }, }; module.exports = nextConfig; From a56cbc54c51ea8853aecda7efb09251f6752c7d1 Mon Sep 17 00:00:00 2001 From: Eris Lund <38136789+0x5066@users.noreply.github.com> Date: Fri, 28 Nov 2025 01:10:11 +0100 Subject: [PATCH 37/78] High bitrate/samplerate is now correctly clipped (#1318) * High bitrate/samplerate is now correctly clipped This makes the ``kbps`` and ``kHz`` displays in the main window correctly emulate what Winamp does with high bitrates/samplerates. * Moved globals to be local to their designated functions Division is no longer performed in each if condition Default to displaying "0" for the kbps and khz fields (doesn't seem to trigger, though) * Use padStart and slice to more properly format the data Added comment by Justin Frankel on the meaning of H and C * Display "0" while gathering bitrate and khz * Remove logging of kbps in console Co-authored-by: Jordan Eldredge * Assign ``finalKhz`` properly * Make CI hopefully happy --------- Co-authored-by: Jordan Eldredge --- packages/webamp/js/reducers/tracks.ts | 33 +++++++++++++++++++++++++-- packages/webamp/js/selectors.ts | 4 ++-- 2 files changed, 33 insertions(+), 4 deletions(-) diff --git a/packages/webamp/js/reducers/tracks.ts b/packages/webamp/js/reducers/tracks.ts index d73276af..2eabf495 100644 --- a/packages/webamp/js/reducers/tracks.ts +++ b/packages/webamp/js/reducers/tracks.ts @@ -6,6 +6,35 @@ export interface TracksState { [id: string]: PlaylistTrack; } +function massageKhz(khz: number) { + let finalKhz: String; + const khzNum: number = Math.round(khz / 1000); + + // there is no real need to run a condition for below 100khz + // when the other conditions (hopefully) take over + // ...also to make CI happy + finalKhz = String(khzNum); + if (khzNum <= 10) finalKhz = String(khzNum).slice(0, 1).padStart(2, " "); + if (khzNum >= 100) finalKhz = String(khzNum).slice(1, 3); + return finalKhz; +} + +function massageKbps(kbps: number) { + let finalKbps: String; + const bitrateNum: number = Math.round(kbps / 1000); + + finalKbps = String(bitrateNum); // present as is + if (bitrateNum <= 100) finalKbps = String(bitrateNum).padStart(3, " "); + if (bitrateNum <= 10) finalKbps = String(bitrateNum).padStart(3, " "); + // from Justin Frankel directly: + // IIRC H was for "hundred" and "C" was thousand, + // though why it was for thousand I have no idea lol, maybe it was a mistake... + if (bitrateNum >= 1000) finalKbps = String(bitrateNum).slice(0, 2) + "H"; + if (bitrateNum >= 10000) + finalKbps = String(bitrateNum).slice(0, 1).padStart(2, " ") + "C"; + return finalKbps; +} + const defaultPlaylistState: TracksState = {}; const tracks = ( @@ -80,8 +109,8 @@ const tracks = ( artist, album, albumArtUrl, - kbps: bitrate != null ? String(Math.round(bitrate / 1000)) : kbps, - khz: sampleRate != null ? String(Math.round(sampleRate / 1000)) : khz, + kbps: bitrate != null ? massageKbps(bitrate) : kbps, + khz: sampleRate != null ? massageKhz(sampleRate) : khz, channels: numberOfChannels != null ? numberOfChannels : channels, }, }; diff --git a/packages/webamp/js/selectors.ts b/packages/webamp/js/selectors.ts index b97b42a3..f973ac04 100644 --- a/packages/webamp/js/selectors.ts +++ b/packages/webamp/js/selectors.ts @@ -686,14 +686,14 @@ export const getMarqueeText = (state: AppState): string => { export const getKbps = createSelector( getCurrentTrack, (track: PlaylistTrack | null): string | null => { - return track != null ? track.kbps || null : null; + return track != null ? track.kbps || "0".padStart(3, " ") : null; } ); export const getKhz = createSelector( getCurrentTrack, (track: PlaylistTrack | null): string | null => { - return track != null ? track.khz || null : null; + return track != null ? track.khz || "0".padStart(2, " ") : null; } ); From 014c8eab28c4c5fe4d141fdfb6d819fa9df3fa95 Mon Sep 17 00:00:00 2001 From: Jordan Eldredge Date: Thu, 27 Nov 2025 16:24:04 -0800 Subject: [PATCH 38/78] Add missing BLUESKY env vars to test setup (#1319) The skin-database tests were failing in CI because BLUESKY_PASSWORD and BLUESKY_USERNAME environment variables were added to config.ts but not to the jest-setup.js file that provides dummy values for tests. --- packages/skin-database/jest-setup.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/skin-database/jest-setup.js b/packages/skin-database/jest-setup.js index 8074da14..6b3d9f75 100644 --- a/packages/skin-database/jest-setup.js +++ b/packages/skin-database/jest-setup.js @@ -31,3 +31,5 @@ process.env.INSTAGRAM_ACCESS_TOKEN = ""; process.env.INSTAGRAM_ACCOUNT_ID = ""; process.env.MASTODON_ACCESS_TOKEN = ""; process.env.SECRET = ""; +process.env.BLUESKY_PASSWORD = ""; +process.env.BLUESKY_USERNAME = ""; From b672de2515b2e1e1a83ebfd766a12beec5ca24ce Mon Sep 17 00:00:00 2001 From: Jordan Eldredge Date: Thu, 27 Nov 2025 17:46:44 -0800 Subject: [PATCH 39/78] Fix webamp-modern build outputs in turbo.json (#1320) The webamp-modern#build task had outputs set to an empty array, which meant turbo wouldn't cache/restore the build directory. This caused the deploy script to fail when the mv command couldn't find the build directory on cache hits. Changed outputs from [] to ["build/**"] to properly cache and restore the build output. --- turbo.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/turbo.json b/turbo.json index 8fbdc85f..b21de061 100644 --- a/turbo.json +++ b/turbo.json @@ -48,7 +48,7 @@ "outputs": [] }, "webamp-modern#build": { - "outputs": [] + "outputs": ["build/**"] }, "webamp-modern#test": { "outputs": [] From bbd1d1224e976f368f07c83684ddb0bc4c4912d9 Mon Sep 17 00:00:00 2001 From: Jordan Eldredge Date: Thu, 27 Nov 2025 18:31:27 -0800 Subject: [PATCH 40/78] Add ESLint to winamp-eqf and include in CI (#1322) - Add lint script to winamp-eqf package.json - Add winamp-eqf#lint task to turbo.json so it runs in CI --- packages/winamp-eqf/package.json | 1 + turbo.json | 1 + 2 files changed, 2 insertions(+) diff --git a/packages/winamp-eqf/package.json b/packages/winamp-eqf/package.json index e909d6c0..a852b599 100644 --- a/packages/winamp-eqf/package.json +++ b/packages/winamp-eqf/package.json @@ -23,6 +23,7 @@ "scripts": { "build": "tsc", "prepublishOnly": "npm run build", + "lint": "eslint src --ext ts,js", "test": "jest", "type-check": "tsc --noEmit" }, diff --git a/turbo.json b/turbo.json index b21de061..5f6fc4e1 100644 --- a/turbo.json +++ b/turbo.json @@ -66,6 +66,7 @@ }, "skin-database#lint": {}, "webamp-modern#lint": {}, + "winamp-eqf#lint": {}, "dev": { "cache": false, "persistent": true From 8358d4843c372dc0dfb55000a37651a46bab9d11 Mon Sep 17 00:00:00 2001 From: Jordan Eldredge Date: Thu, 27 Nov 2025 18:31:36 -0800 Subject: [PATCH 41/78] Add ESLint to ani-cursor and include in CI (#1323) - Add lint script to ani-cursor package.json - Add ani-cursor#lint task to turbo.json so it runs in CI --- packages/ani-cursor/package.json | 1 + turbo.json | 1 + 2 files changed, 2 insertions(+) diff --git a/packages/ani-cursor/package.json b/packages/ani-cursor/package.json index 0619a051..f03fb1a6 100644 --- a/packages/ani-cursor/package.json +++ b/packages/ani-cursor/package.json @@ -32,6 +32,7 @@ "scripts": { "build": "tsc", "type-check": "tsc --noEmit", + "lint": "eslint src --ext ts,js", "test": "jest", "prepublish": "tsc" }, diff --git a/turbo.json b/turbo.json index 5f6fc4e1..dcb221de 100644 --- a/turbo.json +++ b/turbo.json @@ -64,6 +64,7 @@ "webamp#lint": { "dependsOn": ["ani-cursor#build", "winamp-eqf#build"] }, + "ani-cursor#lint": {}, "skin-database#lint": {}, "webamp-modern#lint": {}, "winamp-eqf#lint": {}, From 642fb964d67565ff315dfa0107e3650d53c94fca Mon Sep 17 00:00:00 2001 From: Jordan Eldredge Date: Thu, 27 Nov 2025 18:34:41 -0800 Subject: [PATCH 42/78] Add skin-museum-og lint to CI (#1321) The skin-museum-og package has a lint script but it was not included in turbo.json, so it wasn't being checked in CI. This adds the skin-museum-og#lint task to turbo.json so it will be linted along with the other packages. --- turbo.json | 1 + 1 file changed, 1 insertion(+) diff --git a/turbo.json b/turbo.json index dcb221de..2d1ffbc9 100644 --- a/turbo.json +++ b/turbo.json @@ -66,6 +66,7 @@ }, "ani-cursor#lint": {}, "skin-database#lint": {}, + "skin-museum-og#lint": {}, "webamp-modern#lint": {}, "winamp-eqf#lint": {}, "dev": { From 1da77a640a14fd6a592156aa1b5858617be472f3 Mon Sep 17 00:00:00 2001 From: Jordan Eldredge Date: Thu, 27 Nov 2025 21:32:10 -0800 Subject: [PATCH 43/78] Consolidate ESLint configs into root (#1324) Move general-purpose lint rules from packages/webamp/.eslintrc to the root .eslintrc so they apply to all packages consistently. This includes: - Core JavaScript best practices (no-var, prefer-const, eqeqeq, etc.) - TypeScript-specific rules (@typescript-eslint/no-unused-vars with patterns) - Prettier integration Package-specific configs now only contain rules unique to their needs: - webamp: React, import, and react-hooks plugin rules - skin-database: Extends @typescript-eslint/recommended, disables rules that conflict with existing code style - webamp-modern: Unchanged (has root: true for isolation) Also fixes lint errors in skin-database: - Consolidate duplicate imports in App.js and Feedback.js - Add radix parameter to parseInt - Prefix unused function parameters with underscore - Convert var to let/const - Fix type import for Shooter --- .eslintrc | 96 +++++++++++++----- packages/skin-database/.eslintrc.js | 40 ++++---- .../app/(modern)/scroll/SkinScroller.tsx | 3 +- packages/skin-database/app/graphql/route.ts | 2 +- .../skin-database/legacy-client/src/App.js | 12 ++- .../legacy-client/src/Feedback.js | 4 +- .../src/components/DownloadText.js | 2 +- .../legacy-client/src/hashFile.js | 4 +- .../legacy-client/src/upload/UploadRow.js | 1 + .../skin-database/legacy-client/src/utils.js | 2 +- .../skin-database/tasks/screenshotSkin.ts | 1 + packages/skin-museum-og/pages/api/og.js | 4 +- packages/skin-museum-og/src/og/Frame.js | 2 - packages/webamp-docs/package.json | 1 + packages/webamp/.eslintrc | 97 +------------------ packages/webamp/demo/js/config.ts | 2 +- packages/webamp/demo/js/webampConfig.ts | 2 +- packages/webamp/js/actionCreators/files.ts | 4 +- packages/webamp/js/components/App.tsx | 2 +- packages/webamp/js/components/FFTNullsoft.ts | 8 +- .../PlaylistWindow/PlaylistMenu.tsx | 4 - .../PlaylistWindow/SortContextMenu.tsx | 6 -- .../webamp/js/components/ResizeTarget.tsx | 7 +- packages/webamp/js/components/VisPainter.ts | 19 ++-- .../webamp/js/components/WinampButton.tsx | 2 +- packages/webamp/js/media/elementSource.ts | 2 +- packages/webamp/js/playlistHtml.tsx | 2 +- packages/webamp/js/reducers/tracks.ts | 4 +- packages/webamp/js/reducers/windows.ts | 2 +- packages/webamp/js/resizeUtils.ts | 2 +- packages/webamp/js/selectors.ts | 2 - packages/webamp/js/skinParserUtils.ts | 6 +- packages/webamp/js/webampWithButterchurn.ts | 2 +- turbo.json | 1 + 34 files changed, 146 insertions(+), 204 deletions(-) diff --git a/.eslintrc b/.eslintrc index 213b7eff..4b36e433 100644 --- a/.eslintrc +++ b/.eslintrc @@ -4,15 +4,11 @@ "jsx": true, "sourceType": "module", "ecmaFeatures": { - "jsx": true, - "experimentalObjectRestSpread": true + "jsx": true } }, - "plugins": ["prettier"], + "plugins": ["prettier", "@typescript-eslint"], "settings": { - "react": { - "version": "15.2" - }, "import/resolver": { "node": { "extensions": [".js", ".ts", ".tsx"] @@ -21,27 +17,81 @@ }, "env": { "node": true, - "amd": true, "es6": true, "jest": true }, - "globals": { - "window": true, - "document": true, - "console": true, - "navigator": true, - "alert": true, - "Blob": true, - "fetch": true, - "FileReader": true, - "Element": true, - "AudioNode": true, - "MutationObserver": true, - "Image": true, - "location": true - }, "rules": { "prettier/prettier": "error", - "no-constant-binary-expression": "error" + "no-constant-binary-expression": "error", + "block-scoped-var": "warn", + "camelcase": "error", + "constructor-super": "error", + "dot-notation": "error", + "eqeqeq": ["error", "smart"], + "guard-for-in": "error", + "max-depth": ["warn", 4], + "new-cap": "error", + "no-caller": "error", + "no-const-assign": "error", + "no-debugger": "error", + "no-delete-var": "error", + "no-div-regex": "warn", + "no-dupe-args": "error", + "no-dupe-class-members": "error", + "no-dupe-keys": "error", + "no-duplicate-case": "error", + "no-duplicate-imports": "error", + "no-else-return": "error", + "no-empty-character-class": "error", + "no-eval": "error", + "no-ex-assign": "error", + "no-extend-native": "warn", + "no-extra-boolean-cast": "error", + "no-extra-semi": "error", + "no-fallthrough": "error", + "no-func-assign": "error", + "no-implied-eval": "error", + "no-inner-declarations": "error", + "no-irregular-whitespace": "error", + "no-label-var": "error", + "no-labels": "error", + "no-lone-blocks": "error", + "no-lonely-if": "error", + "no-multi-str": "error", + "no-nested-ternary": "warn", + "no-new-object": "error", + "no-new-symbol": "error", + "no-new-wrappers": "error", + "no-obj-calls": "error", + "no-octal": "error", + "no-octal-escape": "error", + "no-proto": "error", + "no-redeclare": "error", + "no-shadow": "warn", + "no-this-before-super": "error", + "no-throw-literal": "error", + "no-undef-init": "error", + "no-unneeded-ternary": "error", + "no-unreachable": "error", + "no-unused-expressions": "error", + "no-useless-rename": "error", + "no-var": "error", + "no-with": "error", + "prefer-arrow-callback": "warn", + "prefer-const": "error", + "prefer-spread": "error", + "prefer-template": "warn", + "radix": "error", + "no-return-await": "error", + "use-isnan": "error", + "valid-typeof": "error", + "@typescript-eslint/no-unused-vars": [ + "warn", + { + "argsIgnorePattern": "^_", + "varsIgnorePattern": "^_", + "caughtErrorsIgnorePattern": "^_" + } + ] } } diff --git a/packages/skin-database/.eslintrc.js b/packages/skin-database/.eslintrc.js index 08ad143d..ca0786ad 100644 --- a/packages/skin-database/.eslintrc.js +++ b/packages/skin-database/.eslintrc.js @@ -1,32 +1,28 @@ module.exports = { - env: { - node: true, - es2021: true, - jest: true, - }, - extends: ["eslint:recommended", "plugin:@typescript-eslint/recommended"], - parser: "@typescript-eslint/parser", - parserOptions: { - ecmaVersion: 12, - sourceType: "module", - }, - plugins: ["@typescript-eslint"], + extends: ["plugin:@typescript-eslint/recommended"], rules: { - // "no-console": "warn", + // Disable rules that conflict with the project's style "@typescript-eslint/no-var-requires": "off", - "@typescript-eslint/no-require-imports": "off", // Allow require() in JS files + "@typescript-eslint/no-require-imports": "off", "@typescript-eslint/ban-ts-comment": "off", "@typescript-eslint/explicit-module-boundary-types": "off", "@typescript-eslint/no-explicit-any": "off", "@typescript-eslint/no-namespace": "off", - "@typescript-eslint/no-unused-vars": [ - "warn", - { - argsIgnorePattern: "^_", - varsIgnorePattern: "^_", - caughtErrorsIgnorePattern: "^_", - }, - ], + // Override the base no-shadow rule since it conflicts with TypeScript + "no-shadow": "off", + // Relax rules for this project's existing style + camelcase: "off", + "dot-notation": "off", + eqeqeq: "off", + "no-undef-init": "off", + "no-return-await": "off", + "prefer-arrow-callback": "off", + "no-div-regex": "off", + "guard-for-in": "off", + "prefer-template": "off", + "no-else-return": "off", + "prefer-const": "off", + "new-cap": "off", }, ignorePatterns: ["dist/**"], }; diff --git a/packages/skin-database/app/(modern)/scroll/SkinScroller.tsx b/packages/skin-database/app/(modern)/scroll/SkinScroller.tsx index 6dde3208..c1843e45 100644 --- a/packages/skin-database/app/(modern)/scroll/SkinScroller.tsx +++ b/packages/skin-database/app/(modern)/scroll/SkinScroller.tsx @@ -64,7 +64,8 @@ export default function SkinScroller({ // When an element becomes mostly visible (> 50% intersecting) if (entry.isIntersecting && entry.intersectionRatio > 0.5) { const index = parseInt( - entry.target.getAttribute("skin-index") || "0" + entry.target.getAttribute("skin-index") || "0", + 10 ); setVisibleSkinIndex(index); } diff --git a/packages/skin-database/app/graphql/route.ts b/packages/skin-database/app/graphql/route.ts index df50b087..251701b5 100644 --- a/packages/skin-database/app/graphql/route.ts +++ b/packages/skin-database/app/graphql/route.ts @@ -10,7 +10,7 @@ const { handleRequest } = createYogaInstance({ return new UserContext(); }, logger: { - log: (message: string, context: Record) => { + log: (_message: string, _context: Record) => { // console.log(message, context); }, logError: (message: string, context: Record) => { diff --git a/packages/skin-database/legacy-client/src/App.js b/packages/skin-database/legacy-client/src/App.js index 420dae44..83ce90c6 100644 --- a/packages/skin-database/legacy-client/src/App.js +++ b/packages/skin-database/legacy-client/src/App.js @@ -1,18 +1,21 @@ "use client"; import React, { useCallback } from "react"; -import { connect } from "react-redux"; +import { connect, useSelector } from "react-redux"; import About from "./About"; import Feedback from "./Feedback"; import Header from "./Header"; import Overlay from "./Overlay"; import SkinTable from "./SkinTable"; import FocusedSkin from "./FocusedSkin"; -import { useSelector } from "react-redux"; import * as Selectors from "./redux/selectors"; import * as Actions from "./redux/actionCreators"; -import { ABOUT_PAGE, REVIEW_PAGE } from "./constants"; +import { + ABOUT_PAGE, + REVIEW_PAGE, + SCREENSHOT_WIDTH, + SKIN_RATIO, +} from "./constants"; import { useWindowSize, useScrollbarWidth, useActionCreator } from "./hooks"; -import { SCREENSHOT_WIDTH, SKIN_RATIO } from "./constants"; import UploadGrid from "./upload/UploadGrid"; import Metadata from "./components/Metadata"; import SkinReadme from "./SkinReadme"; @@ -78,6 +81,7 @@ function App(props) { windowWidth={windowWidthWithScrollabar} /> )} + {/* eslint-disable-next-line no-nested-ternary -- legacy code */} {props.showFeedbackForm ? ( diff --git a/packages/skin-database/legacy-client/src/Feedback.js b/packages/skin-database/legacy-client/src/Feedback.js index cea887a4..4c635c77 100644 --- a/packages/skin-database/legacy-client/src/Feedback.js +++ b/packages/skin-database/legacy-client/src/Feedback.js @@ -1,9 +1,7 @@ -import React, { useState } from "react"; +import React, { useState, useCallback } from "react"; import { getUrl } from "./redux/selectors"; import * as Actions from "./redux/actionCreators"; import { useActionCreator } from "./hooks"; - -import { useCallback } from "react"; import { useSelector } from "react-redux"; import { fetchGraphql, gql } from "./utils"; diff --git a/packages/skin-database/legacy-client/src/components/DownloadText.js b/packages/skin-database/legacy-client/src/components/DownloadText.js index 36dd4766..d966ac77 100644 --- a/packages/skin-database/legacy-client/src/components/DownloadText.js +++ b/packages/skin-database/legacy-client/src/components/DownloadText.js @@ -3,7 +3,7 @@ import React, { useLayoutEffect, useState } from "react"; function DownloadText({ text, children, ...restProps }) { const [url, setUrl] = useState(null); useLayoutEffect(() => { - var blob = new Blob([text], { + let blob = new Blob([text], { type: "text/plain;charset=utf-8", }); const url = URL.createObjectURL(blob); diff --git a/packages/skin-database/legacy-client/src/hashFile.js b/packages/skin-database/legacy-client/src/hashFile.js index 9ceca8a1..422d6509 100644 --- a/packages/skin-database/legacy-client/src/hashFile.js +++ b/packages/skin-database/legacy-client/src/hashFile.js @@ -1,7 +1,7 @@ import SparkMD5 from "spark-md5"; export function hashFile(file) { - var blobSlice = + let blobSlice = File.prototype.slice || File.prototype.mozSlice || File.prototype.webkitSlice; @@ -26,7 +26,7 @@ export function hashFile(file) { fileReader.onerror = reject; function loadNext() { - var start = currentChunk * chunkSize, + let start = currentChunk * chunkSize, end = start + chunkSize >= file.size ? file.size : start + chunkSize; fileReader.readAsArrayBuffer(blobSlice.call(file, start, end)); diff --git a/packages/skin-database/legacy-client/src/upload/UploadRow.js b/packages/skin-database/legacy-client/src/upload/UploadRow.js index a7347790..dbc13ccc 100644 --- a/packages/skin-database/legacy-client/src/upload/UploadRow.js +++ b/packages/skin-database/legacy-client/src/upload/UploadRow.js @@ -19,6 +19,7 @@ function Row({ name, loading, right, complete }) { position: "absolute", left: 0, top: 0, + // eslint-disable-next-line no-nested-ternary -- legacy code width: loading ? `90%` : complete ? `100%` : `0%`, transitionProperty: "all", // TODO: Try to learn how long it really takes diff --git a/packages/skin-database/legacy-client/src/utils.js b/packages/skin-database/legacy-client/src/utils.js index 0b73d741..9f52cd78 100644 --- a/packages/skin-database/legacy-client/src/utils.js +++ b/packages/skin-database/legacy-client/src/utils.js @@ -13,7 +13,7 @@ export function museumUrlFromHash(hash) { } export function getWindowSize() { - var w = window, + let w = window, d = document, e = d.documentElement, g = d.getElementsByTagName("body")[0], diff --git a/packages/skin-database/tasks/screenshotSkin.ts b/packages/skin-database/tasks/screenshotSkin.ts index 46c2902d..ebc33460 100644 --- a/packages/skin-database/tasks/screenshotSkin.ts +++ b/packages/skin-database/tasks/screenshotSkin.ts @@ -7,6 +7,7 @@ import * as Skins from "../data/skins"; import * as CloudFlare from "../CloudFlare"; import SkinModel from "../data/SkinModel"; +// eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-require-imports const Shooter = require("../shooter"); export async function screenshot(skin: SkinModel, shooter: typeof Shooter) { diff --git a/packages/skin-museum-og/pages/api/og.js b/packages/skin-museum-og/pages/api/og.js index 62c18364..5f2e9c96 100644 --- a/packages/skin-museum-og/pages/api/og.js +++ b/packages/skin-museum-og/pages/api/og.js @@ -98,11 +98,10 @@ async function searchImage(query) { }); } -function HeaderGrid({ skins, title }) { +function HeaderGrid({ skins, _title }) { return (
; } -interface State { - selected: boolean; -} - function PlaylistMenu(props: Props) { const [selected, setSelected] = useState(false); diff --git a/packages/webamp/js/components/PlaylistWindow/SortContextMenu.tsx b/packages/webamp/js/components/PlaylistWindow/SortContextMenu.tsx index ca899fc5..9c732639 100644 --- a/packages/webamp/js/components/PlaylistWindow/SortContextMenu.tsx +++ b/packages/webamp/js/components/PlaylistWindow/SortContextMenu.tsx @@ -4,12 +4,6 @@ import { Hr, Node } from "../ContextMenu"; import ContextMenuTarget from "../ContextMenuTarget"; import { useActionCreator } from "../../hooks"; -interface DispatchProps { - sortListByTitle: () => void; - reverseList: () => void; - randomizeList: () => void; -} - /* eslint-disable no-alert */ /* TODO: This should really be kitty-corner to the upper right hand corner of the MiscMenu */ export default function SortContextMenu() { diff --git a/packages/webamp/js/components/ResizeTarget.tsx b/packages/webamp/js/components/ResizeTarget.tsx index 2af3092c..bd34e4e5 100644 --- a/packages/webamp/js/components/ResizeTarget.tsx +++ b/packages/webamp/js/components/ResizeTarget.tsx @@ -15,7 +15,12 @@ interface Props { } function ResizeTarget(props: Props) { - const { currentSize, setWindowSize, widthOnly, ...passThroughProps } = props; + const { + currentSize, + setWindowSize: _setWindowSize, + widthOnly, + ...passThroughProps + } = props; const [mouseDown, setMouseDown] = useState(false); const [mouseStart, setMouseStart] = useState( null diff --git a/packages/webamp/js/components/VisPainter.ts b/packages/webamp/js/components/VisPainter.ts index 645abcd8..edb0540a 100644 --- a/packages/webamp/js/components/VisPainter.ts +++ b/packages/webamp/js/components/VisPainter.ts @@ -280,8 +280,8 @@ export class BarPaintHandler extends VisPaintHandler { paintAnalyzer() { if (!this._ctx) return; const ctx = this._ctx; - const w = ctx.canvas.width; - const h = ctx.canvas.height; + const _w = ctx.canvas.width; + const _h = ctx.canvas.height; ctx.fillStyle = this._color; const maxFreqIndex = 512; @@ -605,8 +605,8 @@ export class WavePaintHandler extends VisPaintHandler { this._dataArray = this._dataArray.slice(0, 576); const bandwidth = this._dataArray.length; - const width = this._ctx!.canvas.width; - const height = this._ctx!.canvas.height; + const _width = this._ctx!.canvas.width; + const _height = this._ctx!.canvas.height; // width would technically be correct, but if the main window is // in windowshade mode, it is set to 150, making sliceWidth look @@ -693,14 +693,9 @@ export class WavePaintHandler extends VisPaintHandler { // clamp y to be within a certain range, here it would be 0..10 if both windowShade and pixelDensity apply // else we clamp y to 0..15 or 0..3, depending on renderHeight if (this._vis.smallVis && this._vis.pixelDensity === 2) { - y = y < 0 ? 0 : y > 10 - 1 ? 10 - 1 : y; + y = Math.max(0, Math.min(10 - 1, y)); } else { - y = - y < 0 - ? 0 - : y > this._vis.renderHeight - 1 - ? this._vis.renderHeight - 1 - : y; + y = Math.max(0, Math.min(this._vis.renderHeight - 1, y)); } const v = y; if (x === 0) this._lastY = y; @@ -774,7 +769,7 @@ export class NoVisualizerHandler extends VisPaintHandler { paintFrame() { if (!this._ctx) return; - const ctx = this._ctx; + const _ctx = this._ctx; this.cleared = true; } } diff --git a/packages/webamp/js/components/WinampButton.tsx b/packages/webamp/js/components/WinampButton.tsx index 8fea9e0f..9e3c5ee0 100644 --- a/packages/webamp/js/components/WinampButton.tsx +++ b/packages/webamp/js/components/WinampButton.tsx @@ -62,7 +62,7 @@ export default function WinampButton({ } setActive(true); - function onRelease(ee: PointerEvent) { + function onRelease(_ee: PointerEvent) { setActive(false); document.removeEventListener("pointerup", onRelease); } diff --git a/packages/webamp/js/media/elementSource.ts b/packages/webamp/js/media/elementSource.ts index e0edc9e1..3e222255 100644 --- a/packages/webamp/js/media/elementSource.ts +++ b/packages/webamp/js/media/elementSource.ts @@ -123,7 +123,7 @@ export default class ElementSource { try { await this._audio.play(); // TODO #race - } catch (err) { + } catch (_err) { // } this._setStatus(MEDIA_STATUS.PLAYING); diff --git a/packages/webamp/js/playlistHtml.tsx b/packages/webamp/js/playlistHtml.tsx index 595f7222..deed977b 100644 --- a/packages/webamp/js/playlistHtml.tsx +++ b/packages/webamp/js/playlistHtml.tsx @@ -22,7 +22,7 @@ const noshadeStyle = { // We use all kinds of non-standard attributes and tags. So we create these fake // components to trick Typescript. -const Body = (props: any) => { +const _Body = (props: any) => { // @ts-ignore return ; }; diff --git a/packages/webamp/js/reducers/tracks.ts b/packages/webamp/js/reducers/tracks.ts index 2eabf495..512d6b7f 100644 --- a/packages/webamp/js/reducers/tracks.ts +++ b/packages/webamp/js/reducers/tracks.ts @@ -29,9 +29,9 @@ function massageKbps(kbps: number) { // from Justin Frankel directly: // IIRC H was for "hundred" and "C" was thousand, // though why it was for thousand I have no idea lol, maybe it was a mistake... - if (bitrateNum >= 1000) finalKbps = String(bitrateNum).slice(0, 2) + "H"; + if (bitrateNum >= 1000) finalKbps = `${String(bitrateNum).slice(0, 2)}H`; if (bitrateNum >= 10000) - finalKbps = String(bitrateNum).slice(0, 1).padStart(2, " ") + "C"; + finalKbps = `${String(bitrateNum).slice(0, 1).padStart(2, " ")}C`; return finalKbps; } diff --git a/packages/webamp/js/reducers/windows.ts b/packages/webamp/js/reducers/windows.ts index 3a05af7f..e3fc203a 100644 --- a/packages/webamp/js/reducers/windows.ts +++ b/packages/webamp/js/reducers/windows.ts @@ -215,7 +215,7 @@ const windows = ( return w; } // Pull out `hidden` since it's been removed from our state. - const { hidden, ...rest } = serializedW; + const { hidden: _hidden, ...rest } = serializedW; return { ...w, ...rest }; }), focused, diff --git a/packages/webamp/js/resizeUtils.ts b/packages/webamp/js/resizeUtils.ts index 7eba1566..acace84f 100644 --- a/packages/webamp/js/resizeUtils.ts +++ b/packages/webamp/js/resizeUtils.ts @@ -1,4 +1,4 @@ -import { WindowInfo, WindowId } from "./types"; +import { WindowInfo } from "./types"; interface NewGraph { [key: string]: { diff --git a/packages/webamp/js/selectors.ts b/packages/webamp/js/selectors.ts index f973ac04..a0ec2065 100644 --- a/packages/webamp/js/selectors.ts +++ b/packages/webamp/js/selectors.ts @@ -21,7 +21,6 @@ import { import { createSelector, defaultMemoize } from "reselect"; import * as Utils from "./utils"; import { - BANDS, TRACK_HEIGHT, WINDOW_RESIZE_SEGMENT_WIDTH, WINDOW_RESIZE_SEGMENT_HEIGHT, @@ -30,7 +29,6 @@ import { MEDIA_TAG_REQUEST_STATUS, WINDOWS, VISUALIZERS, - PLAYER_MEDIA_STATUS, } from "./constants"; import { createPlaylistURL } from "./playlistHtml"; import * as fromTracks from "./reducers/tracks"; diff --git a/packages/webamp/js/skinParserUtils.ts b/packages/webamp/js/skinParserUtils.ts index 30e3a31a..b7efa2af 100644 --- a/packages/webamp/js/skinParserUtils.ts +++ b/packages/webamp/js/skinParserUtils.ts @@ -47,7 +47,7 @@ export async function getFileFromZip( try { const contents = await lastFile.async(mode); return { contents, name: lastFile.name }; - } catch (e) { + } catch (_e) { console.warn( `Failed to extract "${fileName}.${ext}" from the skin archive.` ); @@ -66,10 +66,10 @@ export async function getImgFromBlob( // Use this faster native browser API if available. // NOTE: In some browsers `window.createImageBitmap` may not exist so this will throw. return await window.createImageBitmap(blob); - } catch (e) { + } catch (_e) { try { return await fallbackGetImgFromBlob(blob); - } catch (ee) { + } catch (_ee) { // Like Winamp we will silently fail on images that don't parse. return null; } diff --git a/packages/webamp/js/webampWithButterchurn.ts b/packages/webamp/js/webampWithButterchurn.ts index 10ef253e..ec50c056 100644 --- a/packages/webamp/js/webampWithButterchurn.ts +++ b/packages/webamp/js/webampWithButterchurn.ts @@ -1,4 +1,4 @@ -import { Options, Preset } from "./types"; +import { Options } from "./types"; import { PrivateOptions } from "./webampLazy"; import Webamp from "./webamp"; // @ts-ignore diff --git a/turbo.json b/turbo.json index 2d1ffbc9..2e31e31c 100644 --- a/turbo.json +++ b/turbo.json @@ -67,6 +67,7 @@ "ani-cursor#lint": {}, "skin-database#lint": {}, "skin-museum-og#lint": {}, + "webamp-docs#lint": {}, "webamp-modern#lint": {}, "winamp-eqf#lint": {}, "dev": { From 8fa7701b4739ec95c91da46a076505c3dc4277db Mon Sep 17 00:00:00 2001 From: Jordan Eldredge Date: Fri, 28 Nov 2025 11:51:02 -0800 Subject: [PATCH 44/78] Remove legacy JS files from winamp-eqf package (#1327) These files were duplicates of the TypeScript source files in src/. The package builds from src/*.ts to built/*.js, so these root-level .js files were unused and causing lint failures when running `pnpm run lint` from the monorepo root. --- packages/winamp-eqf/constants.js | 20 ---------------- packages/winamp-eqf/creator.js | 35 --------------------------- packages/winamp-eqf/index.js | 7 ------ packages/winamp-eqf/parser.js | 41 -------------------------------- 4 files changed, 103 deletions(-) delete mode 100644 packages/winamp-eqf/constants.js delete mode 100644 packages/winamp-eqf/creator.js delete mode 100644 packages/winamp-eqf/index.js delete mode 100644 packages/winamp-eqf/parser.js diff --git a/packages/winamp-eqf/constants.js b/packages/winamp-eqf/constants.js deleted file mode 100644 index a469e467..00000000 --- a/packages/winamp-eqf/constants.js +++ /dev/null @@ -1,20 +0,0 @@ -var PRESET_VALUES = [ - "hz60", - "hz170", - "hz310", - "hz600", - "hz1000", - "hz3000", - "hz6000", - "hz12000", - "hz14000", - "hz16000", - "preamp", -]; - -var HEADER = "Winamp EQ library file v1.1"; - -module.exports = { - PRESET_VALUES: PRESET_VALUES, - HEADER: HEADER, -}; diff --git a/packages/winamp-eqf/creator.js b/packages/winamp-eqf/creator.js deleted file mode 100644 index f1e2a81e..00000000 --- a/packages/winamp-eqf/creator.js +++ /dev/null @@ -1,35 +0,0 @@ -var CONSTANTS = require("./constants"); - -var FILL_SIZE = 4; -var PRESET_LENGTH = 257; - -function creator(data) { - var buffer = []; - for (var i = 0; i < CONSTANTS.HEADER.length; i++) { - buffer.push(CONSTANTS.HEADER.charCodeAt(i)); - } - buffer.push(26); // - var ending = "!--"; - for (var i = 0; i < ending.length; i++) { - buffer.push(ending.charCodeAt(i)); - } - if (!data.presets) { - throw new Error("Eqf data is missing presets"); - } - data.presets.forEach(function (preset) { - var k = 0; - for (; k < preset.name.length; k++) { - buffer.push(preset.name.charCodeAt(k)); - } - for (; k < PRESET_LENGTH; k++) { - buffer.push(0); - } - - CONSTANTS.PRESET_VALUES.forEach(function (valueName) { - buffer.push(64 - preset[valueName]); // Adjust for inverse values - }); - }); - return new Uint8Array(buffer).buffer; -} - -module.exports = creator; diff --git a/packages/winamp-eqf/index.js b/packages/winamp-eqf/index.js deleted file mode 100644 index 301f6c40..00000000 --- a/packages/winamp-eqf/index.js +++ /dev/null @@ -1,7 +0,0 @@ -var parser = require("./parser"); -var creator = require("./creator"); - -module.exports = { - parser: parser, - creator: creator, -}; diff --git a/packages/winamp-eqf/parser.js b/packages/winamp-eqf/parser.js deleted file mode 100644 index e8902fe7..00000000 --- a/packages/winamp-eqf/parser.js +++ /dev/null @@ -1,41 +0,0 @@ -var CONSTANTS = require("./constants"); - -function parser(arrayBuffer) { - var data = {}; - var i = 0; - var arr = new Int8Array(arrayBuffer); - // Parse header - data.type = String.fromCharCode.apply( - null, - arr.slice(i, CONSTANTS.HEADER.length) - ); - if (data.type !== CONSTANTS.HEADER) { - throw new Error("Invalid .eqf file."); - } - i += CONSTANTS.HEADER.length; - // Skip "!--" - i += 4; - // Get the presets - data.presets = []; - while (i < arr.length) { - var preset = {}; - // Get the name - var nameStart = i; - var nameEnd = nameStart + 257; // Str is fixed length - // Str is null terminated - while (arr[i] !== 0 && i <= nameEnd) { - i++; - } - preset.name = String.fromCharCode.apply(null, arr.slice(nameStart, i)); - i = nameEnd; // Skip over any unused bytes - - // Get the levels - CONSTANTS.PRESET_VALUES.forEach(function (valueName) { - preset[valueName] = 64 - arr[i++]; // Adjust for inverse values - }); - data.presets.push(preset); - } - return data; -} - -module.exports = parser; From d687f4b06c0c17ab9cd9f1d60bc37fc5657cdf62 Mon Sep 17 00:00:00 2001 From: Jordan Eldredge Date: Fri, 28 Nov 2025 11:53:25 -0800 Subject: [PATCH 45/78] Disable silly lint rule (#1328) --- .eslintrc | 1 - packages/webamp/demo/js/SoundCloud.ts | 1 - 2 files changed, 2 deletions(-) diff --git a/.eslintrc b/.eslintrc index 4b36e433..768016a5 100644 --- a/.eslintrc +++ b/.eslintrc @@ -82,7 +82,6 @@ "prefer-spread": "error", "prefer-template": "warn", "radix": "error", - "no-return-await": "error", "use-isnan": "error", "valid-typeof": "error", "@typescript-eslint/no-unused-vars": [ diff --git a/packages/webamp/demo/js/SoundCloud.ts b/packages/webamp/demo/js/SoundCloud.ts index bc966bbc..a375e053 100644 --- a/packages/webamp/demo/js/SoundCloud.ts +++ b/packages/webamp/demo/js/SoundCloud.ts @@ -42,7 +42,6 @@ export async function getPlaylist( } ); - // eslint-disable-next-line no-return-await return await result.json(); } From d159308352e5e852790447936da4e61eeef5c147 Mon Sep 17 00:00:00 2001 From: Jordan Eldredge Date: Fri, 28 Nov 2025 12:06:41 -0800 Subject: [PATCH 46/78] Use turbo lint for root lint script (#1329) This ensures `pnpm run lint` at the monorepo root runs the same lint tasks as CI (`npx turbo lint`), providing consistent behavior between local development and CI. --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 778c7acc..0e91f0b4 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,7 @@ "test:integration": "npx turbo run integration-tests", "test:all": "npx turbo run test integration-tests", "test:unit": "jest", - "lint": "eslint . --ext ts,tsx,js,jsx --rulesdir=packages/webamp-modern/tools/eslint-rules", + "lint": "npx turbo lint", "type-check": "pnpm --filter webamp type-check && pnpm --filter ani-cursor type-check && pnpm --filter skin-database type-check && pnpm --filter webamp-docs type-check && pnpm --filter winamp-eqf type-check", "deploy": "npx turbo webamp#build webamp-modern#build --concurrency 1 && mv packages/webamp-modern/build packages/webamp/dist/demo-site/modern", "format": "prettier --write '**/*.{js,ts,tsx}'" From b00e359a78da81ebb73bc71dfe92f1efa277afa7 Mon Sep 17 00:00:00 2001 From: Jordan Eldredge Date: Fri, 28 Nov 2025 12:19:10 -0800 Subject: [PATCH 47/78] Enable more lints (#1330) --- .eslintrc | 5 +++++ packages/skin-database/api/processUserUploads.ts | 6 +++--- .../skin-database/legacy-client/src/upload/uploadUtils.js | 4 +++- packages/webamp/js/components/Skin.tsx | 4 +++- packages/webamp/js/utils.ts | 2 +- packages/webamp/scripts/compileSkin.ts | 4 +++- 6 files changed, 18 insertions(+), 7 deletions(-) diff --git a/.eslintrc b/.eslintrc index 768016a5..04e3e07c 100644 --- a/.eslintrc +++ b/.eslintrc @@ -23,6 +23,11 @@ "rules": { "prettier/prettier": "error", "no-constant-binary-expression": "error", + "array-callback-return": "error", + "no-template-curly-in-string": "error", + "no-promise-executor-return": "error", + "no-constructor-return": "error", + "no-unsafe-optional-chaining": "error", "block-scoped-var": "warn", "camelcase": "error", "constructor-super": "error", diff --git a/packages/skin-database/api/processUserUploads.ts b/packages/skin-database/api/processUserUploads.ts index 25462fd0..37999e1f 100644 --- a/packages/skin-database/api/processUserUploads.ts +++ b/packages/skin-database/api/processUserUploads.ts @@ -37,9 +37,9 @@ const ONE_MINUTE_IN_MS = 1000 * 60; function timeout(p: Promise, duration: number): Promise { return Promise.race([ p, - new Promise((resolve, reject) => - setTimeout(() => reject("timeout"), duration) - ), + new Promise((_resolve, reject) => { + setTimeout(() => reject("timeout"), duration); + }), ]); } diff --git a/packages/skin-database/legacy-client/src/upload/uploadUtils.js b/packages/skin-database/legacy-client/src/upload/uploadUtils.js index aae91518..76da5a7c 100644 --- a/packages/skin-database/legacy-client/src/upload/uploadUtils.js +++ b/packages/skin-database/legacy-client/src/upload/uploadUtils.js @@ -22,7 +22,9 @@ export async function upload(fileObj) { console.warn( `Request to ${uploadUrl} returned 503, going to retry again in 5 seconds. ${retries} retries left...` ); - await new Promise((resolve) => setTimeout(resolve, 5000)); + await new Promise((resolve) => { + setTimeout(resolve, 5000); + }); continue; } diff --git a/packages/webamp/js/components/Skin.tsx b/packages/webamp/js/components/Skin.tsx index e6f9be5b..aef827a7 100644 --- a/packages/webamp/js/components/Skin.tsx +++ b/packages/webamp/js/components/Skin.tsx @@ -89,7 +89,7 @@ const getCssRules = createSelector( } const cursorRules = cursorSelector .map(normalizeCursorSelector) - .map((selector) => { + .map((selector): string | null => { switch (cursor.type) { case "cur": return `${selector} {cursor: url(${cursor.url}), auto}`; @@ -101,6 +101,8 @@ const getCssRules = createSelector( return null; } } + default: + return null; } }) .filter(Boolean); diff --git a/packages/webamp/js/utils.ts b/packages/webamp/js/utils.ts index 675f6207..732528c8 100644 --- a/packages/webamp/js/utils.ts +++ b/packages/webamp/js/utils.ts @@ -94,7 +94,7 @@ export const parseViscolors = (text: string): string[] => { .map((line) => regex.exec(line)) .filter(Boolean) .map((matches) => (matches as RegExpExecArray).slice(1, 4).join(",")) - .map((rgb, i) => { + .forEach((rgb, i) => { colors[i] = `rgb(${rgb})`; }); return colors; diff --git a/packages/webamp/scripts/compileSkin.ts b/packages/webamp/scripts/compileSkin.ts index 63dc75ce..7bb2a92e 100644 --- a/packages/webamp/scripts/compileSkin.ts +++ b/packages/webamp/scripts/compileSkin.ts @@ -20,7 +20,9 @@ import puppeteer from "puppeteer"; return; } // TODO: Wait for node to be ready - await new Promise((resolve) => setTimeout(resolve, 500)); + await new Promise((resolve) => { + setTimeout(resolve, 500); + }); try { const css: string = await page.evaluate( () => document.getElementById("webamp-skin")?.innerText || "" From 964a7c5f2fb6f8bab6ba2b2e863d6cab6a1bd304 Mon Sep 17 00:00:00 2001 From: Jordan Eldredge Date: Wed, 3 Dec 2025 15:12:22 -0800 Subject: [PATCH 48/78] Upgrade next (#1333) --- packages/skin-database/package.json | 3 +- pnpm-lock.yaml | 235 +++++++++++++++------------- 2 files changed, 127 insertions(+), 111 deletions(-) diff --git a/packages/skin-database/package.json b/packages/skin-database/package.json index 18a354da..5daa66d0 100644 --- a/packages/skin-database/package.json +++ b/packages/skin-database/package.json @@ -28,7 +28,7 @@ "lucide-react": "^0.553.0", "mastodon-api": "^1.3.0", "md5": "^2.2.1", - "next": "^15.3.3", + "next": "^15.3.6", "node-fetch": "^2.6.7", "openai": "^4.68.0", "polygon-clipping": "^0.15.3", @@ -75,6 +75,7 @@ "@types/jest": "^30.0.0", "@types/lru-cache": "^5.1.0", "@types/node-fetch": "^2.5.7", + "@types/react": "^19.1.0", "@typescript-eslint/eslint-plugin": "^8.36.0", "@typescript-eslint/parser": "^8.36.0", "grats": "0.0.0-main-e655d1ae", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5712bd2a..7ea92060 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -129,7 +129,7 @@ importers: version: 0.17.2 '@next/third-parties': specifier: ^15.3.3 - version: 15.3.3(next@15.3.3(@babel/core@7.27.4)(babel-plugin-macros@3.1.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react@19.1.0) + version: 15.3.3(next@15.3.6(@babel/core@7.27.4)(babel-plugin-macros@3.1.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react@19.1.0) '@sentry/node': specifier: ^5.27.3 version: 5.30.0 @@ -197,8 +197,8 @@ importers: specifier: ^2.2.1 version: 2.3.0 next: - specifier: ^15.3.3 - version: 15.3.3(@babel/core@7.27.4)(babel-plugin-macros@3.1.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + specifier: ^15.3.6 + version: 15.3.6(@babel/core@7.27.4)(babel-plugin-macros@3.1.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) node-fetch: specifier: ^2.6.7 version: 2.7.0(encoding@0.1.13) @@ -222,7 +222,7 @@ importers: version: 11.7.1(react@19.1.0) react-redux: specifier: ^9.2.0 - version: 9.2.0(@types/react@18.2.74)(react@19.1.0)(redux@5.0.1) + version: 9.2.0(@types/react@19.2.7)(react@19.1.0)(redux@5.0.1) react-window: specifier: ^1.8.1 version: 1.8.11(react-dom@19.1.0(react@19.1.0))(react@19.1.0) @@ -278,6 +278,9 @@ importers: '@types/node-fetch': specifier: ^2.5.7 version: 2.6.11 + '@types/react': + specifier: ^19.1.0 + version: 19.2.7 '@typescript-eslint/eslint-plugin': specifier: ^8.36.0 version: 8.36.0(@typescript-eslint/parser@8.36.0(eslint@8.57.0)(typescript@5.6.3))(eslint@8.57.0)(typescript@5.6.3) @@ -531,13 +534,13 @@ importers: dependencies: '@docusaurus/core': specifier: 3.8.1 - version: 3.8.1(@mdx-js/react@3.1.0(@types/react@18.2.74)(react@19.1.0))(@swc/core@1.4.12(@swc/helpers@0.5.15))(acorn@8.15.0)(bufferutil@4.0.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(typescript@5.6.3)(utf-8-validate@5.0.10) + version: 3.8.1(@mdx-js/react@3.1.0(@types/react@19.2.7)(react@19.1.0))(@swc/core@1.4.12(@swc/helpers@0.5.15))(acorn@8.15.0)(bufferutil@4.0.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(typescript@5.6.3)(utf-8-validate@5.0.10) '@docusaurus/preset-classic': specifier: 3.8.1 - version: 3.8.1(@algolia/client-search@5.32.0)(@mdx-js/react@3.1.0(@types/react@18.2.74)(react@19.1.0))(@swc/core@1.4.12(@swc/helpers@0.5.15))(@types/react@18.2.74)(acorn@8.15.0)(bufferutil@4.0.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(search-insights@2.17.3)(typescript@5.6.3)(utf-8-validate@5.0.10) + version: 3.8.1(@algolia/client-search@5.32.0)(@mdx-js/react@3.1.0(@types/react@19.2.7)(react@19.1.0))(@swc/core@1.4.12(@swc/helpers@0.5.15))(@types/react@19.2.7)(acorn@8.15.0)(bufferutil@4.0.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(search-insights@2.17.3)(typescript@5.6.3)(utf-8-validate@5.0.10) '@mdx-js/react': specifier: ^3.0.0 - version: 3.1.0(@types/react@18.2.74)(react@19.1.0) + version: 3.1.0(@types/react@19.2.7)(react@19.1.0) clsx: specifier: ^2.0.0 version: 2.1.1 @@ -2910,8 +2913,8 @@ packages: '@next/env@13.0.3': resolution: {integrity: sha512-/4WzeG61Ot/PxsghXkSqQJ6UohFfwXoZ3dtsypmR9EBP+OIax9JRq0trq8Z/LCT9Aq4JbihVkaazRWguORjTAw==} - '@next/env@15.3.3': - resolution: {integrity: sha512-OdiMrzCl2Xi0VTjiQQUK0Xh7bJHnOuET2s+3V+Y40WJBAXrJeGA3f+I8MZJ/YQ3mVGi5XGR1L66oFlgqXhQ4Vw==} + '@next/env@15.3.6': + resolution: {integrity: sha512-/cK+QPcfRbDZxmI/uckT4lu9pHCfRIPBLqy88MhE+7Vg5hKrEYc333Ae76dn/cw2FBP2bR/GoK/4DU+U7by/Nw==} '@next/swc-android-arm-eabi@13.0.3': resolution: {integrity: sha512-uxfUoj65CdFc1gX2q7GtBX3DhKv9Kn343LMqGNvXyuTpYTGMmIiVY7b9yF8oLWRV0gVKqhZBZifUmoPE8SJU6Q==} @@ -2931,8 +2934,8 @@ packages: cpu: [arm64] os: [darwin] - '@next/swc-darwin-arm64@15.3.3': - resolution: {integrity: sha512-WRJERLuH+O3oYB4yZNVahSVFmtxRNjNF1I1c34tYMoJb0Pve+7/RaLAJJizyYiFhjYNGHRAE1Ri2Fd23zgDqhg==} + '@next/swc-darwin-arm64@15.3.5': + resolution: {integrity: sha512-lM/8tilIsqBq+2nq9kbTW19vfwFve0NR7MxfkuSUbRSgXlMQoJYg+31+++XwKVSXk4uT23G2eF/7BRIKdn8t8w==} engines: {node: '>= 10'} cpu: [arm64] os: [darwin] @@ -2943,8 +2946,8 @@ packages: cpu: [x64] os: [darwin] - '@next/swc-darwin-x64@15.3.3': - resolution: {integrity: sha512-XHdzH/yBc55lu78k/XwtuFR/ZXUTcflpRXcsu0nKmF45U96jt1tsOZhVrn5YH+paw66zOANpOnFQ9i6/j+UYvw==} + '@next/swc-darwin-x64@15.3.5': + resolution: {integrity: sha512-WhwegPQJ5IfoUNZUVsI9TRAlKpjGVK0tpJTL6KeiC4cux9774NYE9Wu/iCfIkL/5J8rPAkqZpG7n+EfiAfidXA==} engines: {node: '>= 10'} cpu: [x64] os: [darwin] @@ -2967,8 +2970,8 @@ packages: cpu: [arm64] os: [linux] - '@next/swc-linux-arm64-gnu@15.3.3': - resolution: {integrity: sha512-VZ3sYL2LXB8znNGcjhocikEkag/8xiLgnvQts41tq6i+wql63SMS1Q6N8RVXHw5pEUjiof+II3HkDd7GFcgkzw==} + '@next/swc-linux-arm64-gnu@15.3.5': + resolution: {integrity: sha512-LVD6uMOZ7XePg3KWYdGuzuvVboxujGjbcuP2jsPAN3MnLdLoZUXKRc6ixxfs03RH7qBdEHCZjyLP/jBdCJVRJQ==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] @@ -2979,8 +2982,8 @@ packages: cpu: [arm64] os: [linux] - '@next/swc-linux-arm64-musl@15.3.3': - resolution: {integrity: sha512-h6Y1fLU4RWAp1HPNJWDYBQ+e3G7sLckyBXhmH9ajn8l/RSMnhbuPBV/fXmy3muMcVwoJdHL+UtzRzs0nXOf9SA==} + '@next/swc-linux-arm64-musl@15.3.5': + resolution: {integrity: sha512-k8aVScYZ++BnS2P69ClK7v4nOu702jcF9AIHKu6llhHEtBSmM2zkPGl9yoqbSU/657IIIb0QHpdxEr0iW9z53A==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] @@ -2991,8 +2994,8 @@ packages: cpu: [x64] os: [linux] - '@next/swc-linux-x64-gnu@15.3.3': - resolution: {integrity: sha512-jJ8HRiF3N8Zw6hGlytCj5BiHyG/K+fnTKVDEKvUCyiQ/0r5tgwO7OgaRiOjjRoIx2vwLR+Rz8hQoPrnmFbJdfw==} + '@next/swc-linux-x64-gnu@15.3.5': + resolution: {integrity: sha512-2xYU0DI9DGN/bAHzVwADid22ba5d/xrbrQlr2U+/Q5WkFUzeL0TDR963BdrtLS/4bMmKZGptLeg6282H/S2i8A==} engines: {node: '>= 10'} cpu: [x64] os: [linux] @@ -3003,8 +3006,8 @@ packages: cpu: [x64] os: [linux] - '@next/swc-linux-x64-musl@15.3.3': - resolution: {integrity: sha512-HrUcTr4N+RgiiGn3jjeT6Oo208UT/7BuTr7K0mdKRBtTbT4v9zJqCDKO97DUqqoBK1qyzP1RwvrWTvU6EPh/Cw==} + '@next/swc-linux-x64-musl@15.3.5': + resolution: {integrity: sha512-TRYIqAGf1KCbuAB0gjhdn5Ytd8fV+wJSM2Nh2is/xEqR8PZHxfQuaiNhoF50XfY90sNpaRMaGhF6E+qjV1b9Tg==} engines: {node: '>= 10'} cpu: [x64] os: [linux] @@ -3015,8 +3018,8 @@ packages: cpu: [arm64] os: [win32] - '@next/swc-win32-arm64-msvc@15.3.3': - resolution: {integrity: sha512-SxorONgi6K7ZUysMtRF3mIeHC5aA3IQLmKFQzU0OuhuUYwpOBc1ypaLJLP5Bf3M9k53KUUUj4vTPwzGvl/NwlQ==} + '@next/swc-win32-arm64-msvc@15.3.5': + resolution: {integrity: sha512-h04/7iMEUSMY6fDGCvdanKqlO1qYvzNxntZlCzfE8i5P0uqzVQWQquU1TIhlz0VqGQGXLrFDuTJVONpqGqjGKQ==} engines: {node: '>= 10'} cpu: [arm64] os: [win32] @@ -3033,8 +3036,8 @@ packages: cpu: [x64] os: [win32] - '@next/swc-win32-x64-msvc@15.3.3': - resolution: {integrity: sha512-4QZG6F8enl9/S2+yIiOiju0iCTFd93d8VC1q9LZS4p/Xuk81W2QDjCFeoogmrWWkAD59z8ZxepBQap2dKS5ruw==} + '@next/swc-win32-x64-msvc@15.3.5': + resolution: {integrity: sha512-5fhH6fccXxnX2KhllnGhkYMndhOiLOLEiVGYjP2nizqeGWkN10sA9taATlXwake2E2XMvYZjjz0Uj7T0y+z1yw==} engines: {node: '>= 10'} cpu: [x64] os: [win32] @@ -4010,6 +4013,9 @@ packages: '@types/react@18.2.74': resolution: {integrity: sha512-9AEqNZZyBx8OdZpxzQlaFEVCSFUM2YXJH46yPOiOpm078k6ZLOCcuAzGum/zK8YBwY+dbahVNbHrbgrAwIRlqw==} + '@types/react@19.2.7': + resolution: {integrity: sha512-MWtvHrGZLFttgeEj28VXHxpmwYbor/ATPYbBfSFZEIRK0ecCFLl2Qo55z52Hss+UV9CRN7trSeq1zbgx7YDWWg==} + '@types/resolve@1.17.1': resolution: {integrity: sha512-yy7HuzQhj0dhGpD8RLXSZWEkLsV9ibvxvi6EiJ3bkqLAO1RGo0WbkWQiwpRlSFymTJRz0d3k5LM3kkx8ArDbLw==} @@ -5711,6 +5717,9 @@ packages: csstype@3.1.3: resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} + csstype@3.2.3: + resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} + currently-unhandled@0.4.1: resolution: {integrity: sha512-/fITjgjGU50vjQ4FH6eUoYu+iUoUKIXws2hL15JJpIR+BbTxaXQsMuuyjtNh2WqsSBS5nsaZHFsFecyw5CCAng==} engines: {node: '>=0.10.0'} @@ -9605,8 +9614,8 @@ packages: sass: optional: true - next@15.3.3: - resolution: {integrity: sha512-JqNj29hHNmCLtNvd090SyRbXJiivQ+58XjCcrC50Crb5g5u2zi7Y2YivbsEfzk6AtVI80akdOQbaMZwWB1Hthw==} + next@15.3.6: + resolution: {integrity: sha512-oI6D1zbbsh6JzzZFDCSHnnx6Qpvd1fSkVJu/5d8uluqnxzuoqtodVZjYvNovooznUq8udSAiKp7MbwlfZ8Gm6w==} engines: {node: ^18.18.0 || ^19.8.0 || >= 20.0.0} hasBin: true peerDependencies: @@ -14946,14 +14955,14 @@ snapshots: '@docsearch/css@3.9.0': {} - '@docsearch/react@3.9.0(@algolia/client-search@5.32.0)(@types/react@18.2.74)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(search-insights@2.17.3)': + '@docsearch/react@3.9.0(@algolia/client-search@5.32.0)(@types/react@19.2.7)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(search-insights@2.17.3)': dependencies: '@algolia/autocomplete-core': 1.17.9(@algolia/client-search@5.32.0)(algoliasearch@5.28.0)(search-insights@2.17.3) '@algolia/autocomplete-preset-algolia': 1.17.9(@algolia/client-search@5.32.0)(algoliasearch@5.28.0) '@docsearch/css': 3.9.0 algoliasearch: 5.28.0 optionalDependencies: - '@types/react': 18.2.74 + '@types/react': 19.2.7 react: 19.1.0 react-dom: 19.1.0(react@19.1.0) search-insights: 2.17.3 @@ -15029,7 +15038,7 @@ snapshots: - uglify-js - webpack-cli - '@docusaurus/core@3.8.1(@mdx-js/react@3.1.0(@types/react@18.2.74)(react@19.1.0))(@swc/core@1.4.12(@swc/helpers@0.5.15))(acorn@8.15.0)(bufferutil@4.0.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(typescript@5.6.3)(utf-8-validate@5.0.10)': + '@docusaurus/core@3.8.1(@mdx-js/react@3.1.0(@types/react@19.2.7)(react@19.1.0))(@swc/core@1.4.12(@swc/helpers@0.5.15))(acorn@8.15.0)(bufferutil@4.0.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(typescript@5.6.3)(utf-8-validate@5.0.10)': dependencies: '@docusaurus/babel': 3.8.1(@swc/core@1.4.12(@swc/helpers@0.5.15))(acorn@8.15.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) '@docusaurus/bundler': 3.8.1(@swc/core@1.4.12(@swc/helpers@0.5.15))(acorn@8.15.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(typescript@5.6.3) @@ -15038,7 +15047,7 @@ snapshots: '@docusaurus/utils': 3.8.1(@swc/core@1.4.12(@swc/helpers@0.5.15))(acorn@8.15.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) '@docusaurus/utils-common': 3.8.1(@swc/core@1.4.12(@swc/helpers@0.5.15))(acorn@8.15.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) '@docusaurus/utils-validation': 3.8.1(@swc/core@1.4.12(@swc/helpers@0.5.15))(acorn@8.15.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@mdx-js/react': 3.1.0(@types/react@18.2.74)(react@19.1.0) + '@mdx-js/react': 3.1.0(@types/react@19.2.7)(react@19.1.0) boxen: 6.2.1 chalk: 4.1.2 chokidar: 3.6.0 @@ -15161,13 +15170,13 @@ snapshots: - uglify-js - webpack-cli - '@docusaurus/plugin-content-blog@3.8.1(@docusaurus/plugin-content-docs@3.8.1(@mdx-js/react@3.1.0(@types/react@18.2.74)(react@19.1.0))(@swc/core@1.4.12(@swc/helpers@0.5.15))(acorn@8.15.0)(bufferutil@4.0.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(typescript@5.6.3)(utf-8-validate@5.0.10))(@mdx-js/react@3.1.0(@types/react@18.2.74)(react@19.1.0))(@swc/core@1.4.12(@swc/helpers@0.5.15))(acorn@8.15.0)(bufferutil@4.0.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(typescript@5.6.3)(utf-8-validate@5.0.10)': + '@docusaurus/plugin-content-blog@3.8.1(@docusaurus/plugin-content-docs@3.8.1(@mdx-js/react@3.1.0(@types/react@19.2.7)(react@19.1.0))(@swc/core@1.4.12(@swc/helpers@0.5.15))(acorn@8.15.0)(bufferutil@4.0.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(typescript@5.6.3)(utf-8-validate@5.0.10))(@mdx-js/react@3.1.0(@types/react@19.2.7)(react@19.1.0))(@swc/core@1.4.12(@swc/helpers@0.5.15))(acorn@8.15.0)(bufferutil@4.0.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(typescript@5.6.3)(utf-8-validate@5.0.10)': dependencies: - '@docusaurus/core': 3.8.1(@mdx-js/react@3.1.0(@types/react@18.2.74)(react@19.1.0))(@swc/core@1.4.12(@swc/helpers@0.5.15))(acorn@8.15.0)(bufferutil@4.0.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(typescript@5.6.3)(utf-8-validate@5.0.10) + '@docusaurus/core': 3.8.1(@mdx-js/react@3.1.0(@types/react@19.2.7)(react@19.1.0))(@swc/core@1.4.12(@swc/helpers@0.5.15))(acorn@8.15.0)(bufferutil@4.0.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(typescript@5.6.3)(utf-8-validate@5.0.10) '@docusaurus/logger': 3.8.1 '@docusaurus/mdx-loader': 3.8.1(@swc/core@1.4.12(@swc/helpers@0.5.15))(acorn@8.15.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@docusaurus/plugin-content-docs': 3.8.1(@mdx-js/react@3.1.0(@types/react@18.2.74)(react@19.1.0))(@swc/core@1.4.12(@swc/helpers@0.5.15))(acorn@8.15.0)(bufferutil@4.0.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(typescript@5.6.3)(utf-8-validate@5.0.10) - '@docusaurus/theme-common': 3.8.1(@docusaurus/plugin-content-docs@3.8.1(@mdx-js/react@3.1.0(@types/react@18.2.74)(react@19.1.0))(@swc/core@1.4.12(@swc/helpers@0.5.15))(acorn@8.15.0)(bufferutil@4.0.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(typescript@5.6.3)(utf-8-validate@5.0.10))(@swc/core@1.4.12(@swc/helpers@0.5.15))(acorn@8.15.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@docusaurus/plugin-content-docs': 3.8.1(@mdx-js/react@3.1.0(@types/react@19.2.7)(react@19.1.0))(@swc/core@1.4.12(@swc/helpers@0.5.15))(acorn@8.15.0)(bufferutil@4.0.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(typescript@5.6.3)(utf-8-validate@5.0.10) + '@docusaurus/theme-common': 3.8.1(@docusaurus/plugin-content-docs@3.8.1(@mdx-js/react@3.1.0(@types/react@19.2.7)(react@19.1.0))(@swc/core@1.4.12(@swc/helpers@0.5.15))(acorn@8.15.0)(bufferutil@4.0.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(typescript@5.6.3)(utf-8-validate@5.0.10))(@swc/core@1.4.12(@swc/helpers@0.5.15))(acorn@8.15.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) '@docusaurus/types': 3.8.1(@swc/core@1.4.12(@swc/helpers@0.5.15))(acorn@8.15.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) '@docusaurus/utils': 3.8.1(@swc/core@1.4.12(@swc/helpers@0.5.15))(acorn@8.15.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) '@docusaurus/utils-common': 3.8.1(@swc/core@1.4.12(@swc/helpers@0.5.15))(acorn@8.15.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) @@ -15203,13 +15212,13 @@ snapshots: - utf-8-validate - webpack-cli - '@docusaurus/plugin-content-docs@3.8.1(@mdx-js/react@3.1.0(@types/react@18.2.74)(react@19.1.0))(@swc/core@1.4.12(@swc/helpers@0.5.15))(acorn@8.15.0)(bufferutil@4.0.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(typescript@5.6.3)(utf-8-validate@5.0.10)': + '@docusaurus/plugin-content-docs@3.8.1(@mdx-js/react@3.1.0(@types/react@19.2.7)(react@19.1.0))(@swc/core@1.4.12(@swc/helpers@0.5.15))(acorn@8.15.0)(bufferutil@4.0.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(typescript@5.6.3)(utf-8-validate@5.0.10)': dependencies: - '@docusaurus/core': 3.8.1(@mdx-js/react@3.1.0(@types/react@18.2.74)(react@19.1.0))(@swc/core@1.4.12(@swc/helpers@0.5.15))(acorn@8.15.0)(bufferutil@4.0.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(typescript@5.6.3)(utf-8-validate@5.0.10) + '@docusaurus/core': 3.8.1(@mdx-js/react@3.1.0(@types/react@19.2.7)(react@19.1.0))(@swc/core@1.4.12(@swc/helpers@0.5.15))(acorn@8.15.0)(bufferutil@4.0.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(typescript@5.6.3)(utf-8-validate@5.0.10) '@docusaurus/logger': 3.8.1 '@docusaurus/mdx-loader': 3.8.1(@swc/core@1.4.12(@swc/helpers@0.5.15))(acorn@8.15.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) '@docusaurus/module-type-aliases': 3.8.1(@swc/core@1.4.12(@swc/helpers@0.5.15))(acorn@8.15.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@docusaurus/theme-common': 3.8.1(@docusaurus/plugin-content-docs@3.8.1(@mdx-js/react@3.1.0(@types/react@18.2.74)(react@19.1.0))(@swc/core@1.4.12(@swc/helpers@0.5.15))(acorn@8.15.0)(bufferutil@4.0.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(typescript@5.6.3)(utf-8-validate@5.0.10))(@swc/core@1.4.12(@swc/helpers@0.5.15))(acorn@8.15.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@docusaurus/theme-common': 3.8.1(@docusaurus/plugin-content-docs@3.8.1(@mdx-js/react@3.1.0(@types/react@19.2.7)(react@19.1.0))(@swc/core@1.4.12(@swc/helpers@0.5.15))(acorn@8.15.0)(bufferutil@4.0.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(typescript@5.6.3)(utf-8-validate@5.0.10))(@swc/core@1.4.12(@swc/helpers@0.5.15))(acorn@8.15.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) '@docusaurus/types': 3.8.1(@swc/core@1.4.12(@swc/helpers@0.5.15))(acorn@8.15.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) '@docusaurus/utils': 3.8.1(@swc/core@1.4.12(@swc/helpers@0.5.15))(acorn@8.15.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) '@docusaurus/utils-common': 3.8.1(@swc/core@1.4.12(@swc/helpers@0.5.15))(acorn@8.15.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) @@ -15244,9 +15253,9 @@ snapshots: - utf-8-validate - webpack-cli - '@docusaurus/plugin-content-pages@3.8.1(@mdx-js/react@3.1.0(@types/react@18.2.74)(react@19.1.0))(@swc/core@1.4.12(@swc/helpers@0.5.15))(acorn@8.15.0)(bufferutil@4.0.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(typescript@5.6.3)(utf-8-validate@5.0.10)': + '@docusaurus/plugin-content-pages@3.8.1(@mdx-js/react@3.1.0(@types/react@19.2.7)(react@19.1.0))(@swc/core@1.4.12(@swc/helpers@0.5.15))(acorn@8.15.0)(bufferutil@4.0.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(typescript@5.6.3)(utf-8-validate@5.0.10)': dependencies: - '@docusaurus/core': 3.8.1(@mdx-js/react@3.1.0(@types/react@18.2.74)(react@19.1.0))(@swc/core@1.4.12(@swc/helpers@0.5.15))(acorn@8.15.0)(bufferutil@4.0.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(typescript@5.6.3)(utf-8-validate@5.0.10) + '@docusaurus/core': 3.8.1(@mdx-js/react@3.1.0(@types/react@19.2.7)(react@19.1.0))(@swc/core@1.4.12(@swc/helpers@0.5.15))(acorn@8.15.0)(bufferutil@4.0.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(typescript@5.6.3)(utf-8-validate@5.0.10) '@docusaurus/mdx-loader': 3.8.1(@swc/core@1.4.12(@swc/helpers@0.5.15))(acorn@8.15.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) '@docusaurus/types': 3.8.1(@swc/core@1.4.12(@swc/helpers@0.5.15))(acorn@8.15.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) '@docusaurus/utils': 3.8.1(@swc/core@1.4.12(@swc/helpers@0.5.15))(acorn@8.15.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) @@ -15275,9 +15284,9 @@ snapshots: - utf-8-validate - webpack-cli - '@docusaurus/plugin-css-cascade-layers@3.8.1(@mdx-js/react@3.1.0(@types/react@18.2.74)(react@19.1.0))(@swc/core@1.4.12(@swc/helpers@0.5.15))(acorn@8.15.0)(bufferutil@4.0.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(typescript@5.6.3)(utf-8-validate@5.0.10)': + '@docusaurus/plugin-css-cascade-layers@3.8.1(@mdx-js/react@3.1.0(@types/react@19.2.7)(react@19.1.0))(@swc/core@1.4.12(@swc/helpers@0.5.15))(acorn@8.15.0)(bufferutil@4.0.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(typescript@5.6.3)(utf-8-validate@5.0.10)': dependencies: - '@docusaurus/core': 3.8.1(@mdx-js/react@3.1.0(@types/react@18.2.74)(react@19.1.0))(@swc/core@1.4.12(@swc/helpers@0.5.15))(acorn@8.15.0)(bufferutil@4.0.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(typescript@5.6.3)(utf-8-validate@5.0.10) + '@docusaurus/core': 3.8.1(@mdx-js/react@3.1.0(@types/react@19.2.7)(react@19.1.0))(@swc/core@1.4.12(@swc/helpers@0.5.15))(acorn@8.15.0)(bufferutil@4.0.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(typescript@5.6.3)(utf-8-validate@5.0.10) '@docusaurus/types': 3.8.1(@swc/core@1.4.12(@swc/helpers@0.5.15))(acorn@8.15.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) '@docusaurus/utils': 3.8.1(@swc/core@1.4.12(@swc/helpers@0.5.15))(acorn@8.15.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) '@docusaurus/utils-validation': 3.8.1(@swc/core@1.4.12(@swc/helpers@0.5.15))(acorn@8.15.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) @@ -15303,9 +15312,9 @@ snapshots: - utf-8-validate - webpack-cli - '@docusaurus/plugin-debug@3.8.1(@mdx-js/react@3.1.0(@types/react@18.2.74)(react@19.1.0))(@swc/core@1.4.12(@swc/helpers@0.5.15))(acorn@8.15.0)(bufferutil@4.0.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(typescript@5.6.3)(utf-8-validate@5.0.10)': + '@docusaurus/plugin-debug@3.8.1(@mdx-js/react@3.1.0(@types/react@19.2.7)(react@19.1.0))(@swc/core@1.4.12(@swc/helpers@0.5.15))(acorn@8.15.0)(bufferutil@4.0.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(typescript@5.6.3)(utf-8-validate@5.0.10)': dependencies: - '@docusaurus/core': 3.8.1(@mdx-js/react@3.1.0(@types/react@18.2.74)(react@19.1.0))(@swc/core@1.4.12(@swc/helpers@0.5.15))(acorn@8.15.0)(bufferutil@4.0.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(typescript@5.6.3)(utf-8-validate@5.0.10) + '@docusaurus/core': 3.8.1(@mdx-js/react@3.1.0(@types/react@19.2.7)(react@19.1.0))(@swc/core@1.4.12(@swc/helpers@0.5.15))(acorn@8.15.0)(bufferutil@4.0.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(typescript@5.6.3)(utf-8-validate@5.0.10) '@docusaurus/types': 3.8.1(@swc/core@1.4.12(@swc/helpers@0.5.15))(acorn@8.15.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) '@docusaurus/utils': 3.8.1(@swc/core@1.4.12(@swc/helpers@0.5.15))(acorn@8.15.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) fs-extra: 11.3.0 @@ -15332,9 +15341,9 @@ snapshots: - utf-8-validate - webpack-cli - '@docusaurus/plugin-google-analytics@3.8.1(@mdx-js/react@3.1.0(@types/react@18.2.74)(react@19.1.0))(@swc/core@1.4.12(@swc/helpers@0.5.15))(acorn@8.15.0)(bufferutil@4.0.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(typescript@5.6.3)(utf-8-validate@5.0.10)': + '@docusaurus/plugin-google-analytics@3.8.1(@mdx-js/react@3.1.0(@types/react@19.2.7)(react@19.1.0))(@swc/core@1.4.12(@swc/helpers@0.5.15))(acorn@8.15.0)(bufferutil@4.0.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(typescript@5.6.3)(utf-8-validate@5.0.10)': dependencies: - '@docusaurus/core': 3.8.1(@mdx-js/react@3.1.0(@types/react@18.2.74)(react@19.1.0))(@swc/core@1.4.12(@swc/helpers@0.5.15))(acorn@8.15.0)(bufferutil@4.0.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(typescript@5.6.3)(utf-8-validate@5.0.10) + '@docusaurus/core': 3.8.1(@mdx-js/react@3.1.0(@types/react@19.2.7)(react@19.1.0))(@swc/core@1.4.12(@swc/helpers@0.5.15))(acorn@8.15.0)(bufferutil@4.0.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(typescript@5.6.3)(utf-8-validate@5.0.10) '@docusaurus/types': 3.8.1(@swc/core@1.4.12(@swc/helpers@0.5.15))(acorn@8.15.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) '@docusaurus/utils-validation': 3.8.1(@swc/core@1.4.12(@swc/helpers@0.5.15))(acorn@8.15.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) react: 19.1.0 @@ -15359,9 +15368,9 @@ snapshots: - utf-8-validate - webpack-cli - '@docusaurus/plugin-google-gtag@3.8.1(@mdx-js/react@3.1.0(@types/react@18.2.74)(react@19.1.0))(@swc/core@1.4.12(@swc/helpers@0.5.15))(acorn@8.15.0)(bufferutil@4.0.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(typescript@5.6.3)(utf-8-validate@5.0.10)': + '@docusaurus/plugin-google-gtag@3.8.1(@mdx-js/react@3.1.0(@types/react@19.2.7)(react@19.1.0))(@swc/core@1.4.12(@swc/helpers@0.5.15))(acorn@8.15.0)(bufferutil@4.0.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(typescript@5.6.3)(utf-8-validate@5.0.10)': dependencies: - '@docusaurus/core': 3.8.1(@mdx-js/react@3.1.0(@types/react@18.2.74)(react@19.1.0))(@swc/core@1.4.12(@swc/helpers@0.5.15))(acorn@8.15.0)(bufferutil@4.0.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(typescript@5.6.3)(utf-8-validate@5.0.10) + '@docusaurus/core': 3.8.1(@mdx-js/react@3.1.0(@types/react@19.2.7)(react@19.1.0))(@swc/core@1.4.12(@swc/helpers@0.5.15))(acorn@8.15.0)(bufferutil@4.0.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(typescript@5.6.3)(utf-8-validate@5.0.10) '@docusaurus/types': 3.8.1(@swc/core@1.4.12(@swc/helpers@0.5.15))(acorn@8.15.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) '@docusaurus/utils-validation': 3.8.1(@swc/core@1.4.12(@swc/helpers@0.5.15))(acorn@8.15.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) '@types/gtag.js': 0.0.12 @@ -15387,9 +15396,9 @@ snapshots: - utf-8-validate - webpack-cli - '@docusaurus/plugin-google-tag-manager@3.8.1(@mdx-js/react@3.1.0(@types/react@18.2.74)(react@19.1.0))(@swc/core@1.4.12(@swc/helpers@0.5.15))(acorn@8.15.0)(bufferutil@4.0.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(typescript@5.6.3)(utf-8-validate@5.0.10)': + '@docusaurus/plugin-google-tag-manager@3.8.1(@mdx-js/react@3.1.0(@types/react@19.2.7)(react@19.1.0))(@swc/core@1.4.12(@swc/helpers@0.5.15))(acorn@8.15.0)(bufferutil@4.0.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(typescript@5.6.3)(utf-8-validate@5.0.10)': dependencies: - '@docusaurus/core': 3.8.1(@mdx-js/react@3.1.0(@types/react@18.2.74)(react@19.1.0))(@swc/core@1.4.12(@swc/helpers@0.5.15))(acorn@8.15.0)(bufferutil@4.0.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(typescript@5.6.3)(utf-8-validate@5.0.10) + '@docusaurus/core': 3.8.1(@mdx-js/react@3.1.0(@types/react@19.2.7)(react@19.1.0))(@swc/core@1.4.12(@swc/helpers@0.5.15))(acorn@8.15.0)(bufferutil@4.0.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(typescript@5.6.3)(utf-8-validate@5.0.10) '@docusaurus/types': 3.8.1(@swc/core@1.4.12(@swc/helpers@0.5.15))(acorn@8.15.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) '@docusaurus/utils-validation': 3.8.1(@swc/core@1.4.12(@swc/helpers@0.5.15))(acorn@8.15.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) react: 19.1.0 @@ -15414,9 +15423,9 @@ snapshots: - utf-8-validate - webpack-cli - '@docusaurus/plugin-sitemap@3.8.1(@mdx-js/react@3.1.0(@types/react@18.2.74)(react@19.1.0))(@swc/core@1.4.12(@swc/helpers@0.5.15))(acorn@8.15.0)(bufferutil@4.0.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(typescript@5.6.3)(utf-8-validate@5.0.10)': + '@docusaurus/plugin-sitemap@3.8.1(@mdx-js/react@3.1.0(@types/react@19.2.7)(react@19.1.0))(@swc/core@1.4.12(@swc/helpers@0.5.15))(acorn@8.15.0)(bufferutil@4.0.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(typescript@5.6.3)(utf-8-validate@5.0.10)': dependencies: - '@docusaurus/core': 3.8.1(@mdx-js/react@3.1.0(@types/react@18.2.74)(react@19.1.0))(@swc/core@1.4.12(@swc/helpers@0.5.15))(acorn@8.15.0)(bufferutil@4.0.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(typescript@5.6.3)(utf-8-validate@5.0.10) + '@docusaurus/core': 3.8.1(@mdx-js/react@3.1.0(@types/react@19.2.7)(react@19.1.0))(@swc/core@1.4.12(@swc/helpers@0.5.15))(acorn@8.15.0)(bufferutil@4.0.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(typescript@5.6.3)(utf-8-validate@5.0.10) '@docusaurus/logger': 3.8.1 '@docusaurus/types': 3.8.1(@swc/core@1.4.12(@swc/helpers@0.5.15))(acorn@8.15.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) '@docusaurus/utils': 3.8.1(@swc/core@1.4.12(@swc/helpers@0.5.15))(acorn@8.15.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) @@ -15446,9 +15455,9 @@ snapshots: - utf-8-validate - webpack-cli - '@docusaurus/plugin-svgr@3.8.1(@mdx-js/react@3.1.0(@types/react@18.2.74)(react@19.1.0))(@swc/core@1.4.12(@swc/helpers@0.5.15))(acorn@8.15.0)(bufferutil@4.0.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(typescript@5.6.3)(utf-8-validate@5.0.10)': + '@docusaurus/plugin-svgr@3.8.1(@mdx-js/react@3.1.0(@types/react@19.2.7)(react@19.1.0))(@swc/core@1.4.12(@swc/helpers@0.5.15))(acorn@8.15.0)(bufferutil@4.0.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(typescript@5.6.3)(utf-8-validate@5.0.10)': dependencies: - '@docusaurus/core': 3.8.1(@mdx-js/react@3.1.0(@types/react@18.2.74)(react@19.1.0))(@swc/core@1.4.12(@swc/helpers@0.5.15))(acorn@8.15.0)(bufferutil@4.0.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(typescript@5.6.3)(utf-8-validate@5.0.10) + '@docusaurus/core': 3.8.1(@mdx-js/react@3.1.0(@types/react@19.2.7)(react@19.1.0))(@swc/core@1.4.12(@swc/helpers@0.5.15))(acorn@8.15.0)(bufferutil@4.0.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(typescript@5.6.3)(utf-8-validate@5.0.10) '@docusaurus/types': 3.8.1(@swc/core@1.4.12(@swc/helpers@0.5.15))(acorn@8.15.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) '@docusaurus/utils': 3.8.1(@swc/core@1.4.12(@swc/helpers@0.5.15))(acorn@8.15.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) '@docusaurus/utils-validation': 3.8.1(@swc/core@1.4.12(@swc/helpers@0.5.15))(acorn@8.15.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) @@ -15477,22 +15486,22 @@ snapshots: - utf-8-validate - webpack-cli - '@docusaurus/preset-classic@3.8.1(@algolia/client-search@5.32.0)(@mdx-js/react@3.1.0(@types/react@18.2.74)(react@19.1.0))(@swc/core@1.4.12(@swc/helpers@0.5.15))(@types/react@18.2.74)(acorn@8.15.0)(bufferutil@4.0.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(search-insights@2.17.3)(typescript@5.6.3)(utf-8-validate@5.0.10)': + '@docusaurus/preset-classic@3.8.1(@algolia/client-search@5.32.0)(@mdx-js/react@3.1.0(@types/react@19.2.7)(react@19.1.0))(@swc/core@1.4.12(@swc/helpers@0.5.15))(@types/react@19.2.7)(acorn@8.15.0)(bufferutil@4.0.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(search-insights@2.17.3)(typescript@5.6.3)(utf-8-validate@5.0.10)': dependencies: - '@docusaurus/core': 3.8.1(@mdx-js/react@3.1.0(@types/react@18.2.74)(react@19.1.0))(@swc/core@1.4.12(@swc/helpers@0.5.15))(acorn@8.15.0)(bufferutil@4.0.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(typescript@5.6.3)(utf-8-validate@5.0.10) - '@docusaurus/plugin-content-blog': 3.8.1(@docusaurus/plugin-content-docs@3.8.1(@mdx-js/react@3.1.0(@types/react@18.2.74)(react@19.1.0))(@swc/core@1.4.12(@swc/helpers@0.5.15))(acorn@8.15.0)(bufferutil@4.0.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(typescript@5.6.3)(utf-8-validate@5.0.10))(@mdx-js/react@3.1.0(@types/react@18.2.74)(react@19.1.0))(@swc/core@1.4.12(@swc/helpers@0.5.15))(acorn@8.15.0)(bufferutil@4.0.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(typescript@5.6.3)(utf-8-validate@5.0.10) - '@docusaurus/plugin-content-docs': 3.8.1(@mdx-js/react@3.1.0(@types/react@18.2.74)(react@19.1.0))(@swc/core@1.4.12(@swc/helpers@0.5.15))(acorn@8.15.0)(bufferutil@4.0.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(typescript@5.6.3)(utf-8-validate@5.0.10) - '@docusaurus/plugin-content-pages': 3.8.1(@mdx-js/react@3.1.0(@types/react@18.2.74)(react@19.1.0))(@swc/core@1.4.12(@swc/helpers@0.5.15))(acorn@8.15.0)(bufferutil@4.0.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(typescript@5.6.3)(utf-8-validate@5.0.10) - '@docusaurus/plugin-css-cascade-layers': 3.8.1(@mdx-js/react@3.1.0(@types/react@18.2.74)(react@19.1.0))(@swc/core@1.4.12(@swc/helpers@0.5.15))(acorn@8.15.0)(bufferutil@4.0.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(typescript@5.6.3)(utf-8-validate@5.0.10) - '@docusaurus/plugin-debug': 3.8.1(@mdx-js/react@3.1.0(@types/react@18.2.74)(react@19.1.0))(@swc/core@1.4.12(@swc/helpers@0.5.15))(acorn@8.15.0)(bufferutil@4.0.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(typescript@5.6.3)(utf-8-validate@5.0.10) - '@docusaurus/plugin-google-analytics': 3.8.1(@mdx-js/react@3.1.0(@types/react@18.2.74)(react@19.1.0))(@swc/core@1.4.12(@swc/helpers@0.5.15))(acorn@8.15.0)(bufferutil@4.0.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(typescript@5.6.3)(utf-8-validate@5.0.10) - '@docusaurus/plugin-google-gtag': 3.8.1(@mdx-js/react@3.1.0(@types/react@18.2.74)(react@19.1.0))(@swc/core@1.4.12(@swc/helpers@0.5.15))(acorn@8.15.0)(bufferutil@4.0.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(typescript@5.6.3)(utf-8-validate@5.0.10) - '@docusaurus/plugin-google-tag-manager': 3.8.1(@mdx-js/react@3.1.0(@types/react@18.2.74)(react@19.1.0))(@swc/core@1.4.12(@swc/helpers@0.5.15))(acorn@8.15.0)(bufferutil@4.0.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(typescript@5.6.3)(utf-8-validate@5.0.10) - '@docusaurus/plugin-sitemap': 3.8.1(@mdx-js/react@3.1.0(@types/react@18.2.74)(react@19.1.0))(@swc/core@1.4.12(@swc/helpers@0.5.15))(acorn@8.15.0)(bufferutil@4.0.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(typescript@5.6.3)(utf-8-validate@5.0.10) - '@docusaurus/plugin-svgr': 3.8.1(@mdx-js/react@3.1.0(@types/react@18.2.74)(react@19.1.0))(@swc/core@1.4.12(@swc/helpers@0.5.15))(acorn@8.15.0)(bufferutil@4.0.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(typescript@5.6.3)(utf-8-validate@5.0.10) - '@docusaurus/theme-classic': 3.8.1(@swc/core@1.4.12(@swc/helpers@0.5.15))(@types/react@18.2.74)(acorn@8.15.0)(bufferutil@4.0.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(typescript@5.6.3)(utf-8-validate@5.0.10) - '@docusaurus/theme-common': 3.8.1(@docusaurus/plugin-content-docs@3.8.1(@mdx-js/react@3.1.0(@types/react@18.2.74)(react@19.1.0))(@swc/core@1.4.12(@swc/helpers@0.5.15))(acorn@8.15.0)(bufferutil@4.0.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(typescript@5.6.3)(utf-8-validate@5.0.10))(@swc/core@1.4.12(@swc/helpers@0.5.15))(acorn@8.15.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@docusaurus/theme-search-algolia': 3.8.1(@algolia/client-search@5.32.0)(@mdx-js/react@3.1.0(@types/react@18.2.74)(react@19.1.0))(@swc/core@1.4.12(@swc/helpers@0.5.15))(@types/react@18.2.74)(acorn@8.15.0)(bufferutil@4.0.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(search-insights@2.17.3)(typescript@5.6.3)(utf-8-validate@5.0.10) + '@docusaurus/core': 3.8.1(@mdx-js/react@3.1.0(@types/react@19.2.7)(react@19.1.0))(@swc/core@1.4.12(@swc/helpers@0.5.15))(acorn@8.15.0)(bufferutil@4.0.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(typescript@5.6.3)(utf-8-validate@5.0.10) + '@docusaurus/plugin-content-blog': 3.8.1(@docusaurus/plugin-content-docs@3.8.1(@mdx-js/react@3.1.0(@types/react@19.2.7)(react@19.1.0))(@swc/core@1.4.12(@swc/helpers@0.5.15))(acorn@8.15.0)(bufferutil@4.0.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(typescript@5.6.3)(utf-8-validate@5.0.10))(@mdx-js/react@3.1.0(@types/react@19.2.7)(react@19.1.0))(@swc/core@1.4.12(@swc/helpers@0.5.15))(acorn@8.15.0)(bufferutil@4.0.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(typescript@5.6.3)(utf-8-validate@5.0.10) + '@docusaurus/plugin-content-docs': 3.8.1(@mdx-js/react@3.1.0(@types/react@19.2.7)(react@19.1.0))(@swc/core@1.4.12(@swc/helpers@0.5.15))(acorn@8.15.0)(bufferutil@4.0.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(typescript@5.6.3)(utf-8-validate@5.0.10) + '@docusaurus/plugin-content-pages': 3.8.1(@mdx-js/react@3.1.0(@types/react@19.2.7)(react@19.1.0))(@swc/core@1.4.12(@swc/helpers@0.5.15))(acorn@8.15.0)(bufferutil@4.0.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(typescript@5.6.3)(utf-8-validate@5.0.10) + '@docusaurus/plugin-css-cascade-layers': 3.8.1(@mdx-js/react@3.1.0(@types/react@19.2.7)(react@19.1.0))(@swc/core@1.4.12(@swc/helpers@0.5.15))(acorn@8.15.0)(bufferutil@4.0.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(typescript@5.6.3)(utf-8-validate@5.0.10) + '@docusaurus/plugin-debug': 3.8.1(@mdx-js/react@3.1.0(@types/react@19.2.7)(react@19.1.0))(@swc/core@1.4.12(@swc/helpers@0.5.15))(acorn@8.15.0)(bufferutil@4.0.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(typescript@5.6.3)(utf-8-validate@5.0.10) + '@docusaurus/plugin-google-analytics': 3.8.1(@mdx-js/react@3.1.0(@types/react@19.2.7)(react@19.1.0))(@swc/core@1.4.12(@swc/helpers@0.5.15))(acorn@8.15.0)(bufferutil@4.0.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(typescript@5.6.3)(utf-8-validate@5.0.10) + '@docusaurus/plugin-google-gtag': 3.8.1(@mdx-js/react@3.1.0(@types/react@19.2.7)(react@19.1.0))(@swc/core@1.4.12(@swc/helpers@0.5.15))(acorn@8.15.0)(bufferutil@4.0.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(typescript@5.6.3)(utf-8-validate@5.0.10) + '@docusaurus/plugin-google-tag-manager': 3.8.1(@mdx-js/react@3.1.0(@types/react@19.2.7)(react@19.1.0))(@swc/core@1.4.12(@swc/helpers@0.5.15))(acorn@8.15.0)(bufferutil@4.0.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(typescript@5.6.3)(utf-8-validate@5.0.10) + '@docusaurus/plugin-sitemap': 3.8.1(@mdx-js/react@3.1.0(@types/react@19.2.7)(react@19.1.0))(@swc/core@1.4.12(@swc/helpers@0.5.15))(acorn@8.15.0)(bufferutil@4.0.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(typescript@5.6.3)(utf-8-validate@5.0.10) + '@docusaurus/plugin-svgr': 3.8.1(@mdx-js/react@3.1.0(@types/react@19.2.7)(react@19.1.0))(@swc/core@1.4.12(@swc/helpers@0.5.15))(acorn@8.15.0)(bufferutil@4.0.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(typescript@5.6.3)(utf-8-validate@5.0.10) + '@docusaurus/theme-classic': 3.8.1(@swc/core@1.4.12(@swc/helpers@0.5.15))(@types/react@19.2.7)(acorn@8.15.0)(bufferutil@4.0.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(typescript@5.6.3)(utf-8-validate@5.0.10) + '@docusaurus/theme-common': 3.8.1(@docusaurus/plugin-content-docs@3.8.1(@mdx-js/react@3.1.0(@types/react@19.2.7)(react@19.1.0))(@swc/core@1.4.12(@swc/helpers@0.5.15))(acorn@8.15.0)(bufferutil@4.0.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(typescript@5.6.3)(utf-8-validate@5.0.10))(@swc/core@1.4.12(@swc/helpers@0.5.15))(acorn@8.15.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@docusaurus/theme-search-algolia': 3.8.1(@algolia/client-search@5.32.0)(@mdx-js/react@3.1.0(@types/react@19.2.7)(react@19.1.0))(@swc/core@1.4.12(@swc/helpers@0.5.15))(@types/react@19.2.7)(acorn@8.15.0)(bufferutil@4.0.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(search-insights@2.17.3)(typescript@5.6.3)(utf-8-validate@5.0.10) '@docusaurus/types': 3.8.1(@swc/core@1.4.12(@swc/helpers@0.5.15))(acorn@8.15.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) react: 19.1.0 react-dom: 19.1.0(react@19.1.0) @@ -15520,25 +15529,25 @@ snapshots: '@docusaurus/react-loadable@6.0.0(react@19.1.0)': dependencies: - '@types/react': 18.2.74 + '@types/react': 19.2.7 react: 19.1.0 - '@docusaurus/theme-classic@3.8.1(@swc/core@1.4.12(@swc/helpers@0.5.15))(@types/react@18.2.74)(acorn@8.15.0)(bufferutil@4.0.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(typescript@5.6.3)(utf-8-validate@5.0.10)': + '@docusaurus/theme-classic@3.8.1(@swc/core@1.4.12(@swc/helpers@0.5.15))(@types/react@19.2.7)(acorn@8.15.0)(bufferutil@4.0.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(typescript@5.6.3)(utf-8-validate@5.0.10)': dependencies: - '@docusaurus/core': 3.8.1(@mdx-js/react@3.1.0(@types/react@18.2.74)(react@19.1.0))(@swc/core@1.4.12(@swc/helpers@0.5.15))(acorn@8.15.0)(bufferutil@4.0.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(typescript@5.6.3)(utf-8-validate@5.0.10) + '@docusaurus/core': 3.8.1(@mdx-js/react@3.1.0(@types/react@19.2.7)(react@19.1.0))(@swc/core@1.4.12(@swc/helpers@0.5.15))(acorn@8.15.0)(bufferutil@4.0.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(typescript@5.6.3)(utf-8-validate@5.0.10) '@docusaurus/logger': 3.8.1 '@docusaurus/mdx-loader': 3.8.1(@swc/core@1.4.12(@swc/helpers@0.5.15))(acorn@8.15.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) '@docusaurus/module-type-aliases': 3.8.1(@swc/core@1.4.12(@swc/helpers@0.5.15))(acorn@8.15.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@docusaurus/plugin-content-blog': 3.8.1(@docusaurus/plugin-content-docs@3.8.1(@mdx-js/react@3.1.0(@types/react@18.2.74)(react@19.1.0))(@swc/core@1.4.12(@swc/helpers@0.5.15))(acorn@8.15.0)(bufferutil@4.0.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(typescript@5.6.3)(utf-8-validate@5.0.10))(@mdx-js/react@3.1.0(@types/react@18.2.74)(react@19.1.0))(@swc/core@1.4.12(@swc/helpers@0.5.15))(acorn@8.15.0)(bufferutil@4.0.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(typescript@5.6.3)(utf-8-validate@5.0.10) - '@docusaurus/plugin-content-docs': 3.8.1(@mdx-js/react@3.1.0(@types/react@18.2.74)(react@19.1.0))(@swc/core@1.4.12(@swc/helpers@0.5.15))(acorn@8.15.0)(bufferutil@4.0.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(typescript@5.6.3)(utf-8-validate@5.0.10) - '@docusaurus/plugin-content-pages': 3.8.1(@mdx-js/react@3.1.0(@types/react@18.2.74)(react@19.1.0))(@swc/core@1.4.12(@swc/helpers@0.5.15))(acorn@8.15.0)(bufferutil@4.0.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(typescript@5.6.3)(utf-8-validate@5.0.10) - '@docusaurus/theme-common': 3.8.1(@docusaurus/plugin-content-docs@3.8.1(@mdx-js/react@3.1.0(@types/react@18.2.74)(react@19.1.0))(@swc/core@1.4.12(@swc/helpers@0.5.15))(acorn@8.15.0)(bufferutil@4.0.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(typescript@5.6.3)(utf-8-validate@5.0.10))(@swc/core@1.4.12(@swc/helpers@0.5.15))(acorn@8.15.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@docusaurus/plugin-content-blog': 3.8.1(@docusaurus/plugin-content-docs@3.8.1(@mdx-js/react@3.1.0(@types/react@19.2.7)(react@19.1.0))(@swc/core@1.4.12(@swc/helpers@0.5.15))(acorn@8.15.0)(bufferutil@4.0.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(typescript@5.6.3)(utf-8-validate@5.0.10))(@mdx-js/react@3.1.0(@types/react@19.2.7)(react@19.1.0))(@swc/core@1.4.12(@swc/helpers@0.5.15))(acorn@8.15.0)(bufferutil@4.0.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(typescript@5.6.3)(utf-8-validate@5.0.10) + '@docusaurus/plugin-content-docs': 3.8.1(@mdx-js/react@3.1.0(@types/react@19.2.7)(react@19.1.0))(@swc/core@1.4.12(@swc/helpers@0.5.15))(acorn@8.15.0)(bufferutil@4.0.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(typescript@5.6.3)(utf-8-validate@5.0.10) + '@docusaurus/plugin-content-pages': 3.8.1(@mdx-js/react@3.1.0(@types/react@19.2.7)(react@19.1.0))(@swc/core@1.4.12(@swc/helpers@0.5.15))(acorn@8.15.0)(bufferutil@4.0.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(typescript@5.6.3)(utf-8-validate@5.0.10) + '@docusaurus/theme-common': 3.8.1(@docusaurus/plugin-content-docs@3.8.1(@mdx-js/react@3.1.0(@types/react@19.2.7)(react@19.1.0))(@swc/core@1.4.12(@swc/helpers@0.5.15))(acorn@8.15.0)(bufferutil@4.0.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(typescript@5.6.3)(utf-8-validate@5.0.10))(@swc/core@1.4.12(@swc/helpers@0.5.15))(acorn@8.15.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) '@docusaurus/theme-translations': 3.8.1 '@docusaurus/types': 3.8.1(@swc/core@1.4.12(@swc/helpers@0.5.15))(acorn@8.15.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) '@docusaurus/utils': 3.8.1(@swc/core@1.4.12(@swc/helpers@0.5.15))(acorn@8.15.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) '@docusaurus/utils-common': 3.8.1(@swc/core@1.4.12(@swc/helpers@0.5.15))(acorn@8.15.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) '@docusaurus/utils-validation': 3.8.1(@swc/core@1.4.12(@swc/helpers@0.5.15))(acorn@8.15.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@mdx-js/react': 3.1.0(@types/react@18.2.74)(react@19.1.0) + '@mdx-js/react': 3.1.0(@types/react@19.2.7)(react@19.1.0) clsx: 2.1.1 copy-text-to-clipboard: 3.2.0 infima: 0.2.0-alpha.45 @@ -15572,11 +15581,11 @@ snapshots: - utf-8-validate - webpack-cli - '@docusaurus/theme-common@3.8.1(@docusaurus/plugin-content-docs@3.8.1(@mdx-js/react@3.1.0(@types/react@18.2.74)(react@19.1.0))(@swc/core@1.4.12(@swc/helpers@0.5.15))(acorn@8.15.0)(bufferutil@4.0.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(typescript@5.6.3)(utf-8-validate@5.0.10))(@swc/core@1.4.12(@swc/helpers@0.5.15))(acorn@8.15.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + '@docusaurus/theme-common@3.8.1(@docusaurus/plugin-content-docs@3.8.1(@mdx-js/react@3.1.0(@types/react@19.2.7)(react@19.1.0))(@swc/core@1.4.12(@swc/helpers@0.5.15))(acorn@8.15.0)(bufferutil@4.0.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(typescript@5.6.3)(utf-8-validate@5.0.10))(@swc/core@1.4.12(@swc/helpers@0.5.15))(acorn@8.15.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': dependencies: '@docusaurus/mdx-loader': 3.8.1(@swc/core@1.4.12(@swc/helpers@0.5.15))(acorn@8.15.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) '@docusaurus/module-type-aliases': 3.8.1(@swc/core@1.4.12(@swc/helpers@0.5.15))(acorn@8.15.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@docusaurus/plugin-content-docs': 3.8.1(@mdx-js/react@3.1.0(@types/react@18.2.74)(react@19.1.0))(@swc/core@1.4.12(@swc/helpers@0.5.15))(acorn@8.15.0)(bufferutil@4.0.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(typescript@5.6.3)(utf-8-validate@5.0.10) + '@docusaurus/plugin-content-docs': 3.8.1(@mdx-js/react@3.1.0(@types/react@19.2.7)(react@19.1.0))(@swc/core@1.4.12(@swc/helpers@0.5.15))(acorn@8.15.0)(bufferutil@4.0.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(typescript@5.6.3)(utf-8-validate@5.0.10) '@docusaurus/utils': 3.8.1(@swc/core@1.4.12(@swc/helpers@0.5.15))(acorn@8.15.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) '@docusaurus/utils-common': 3.8.1(@swc/core@1.4.12(@swc/helpers@0.5.15))(acorn@8.15.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) '@types/history': 4.7.11 @@ -15597,13 +15606,13 @@ snapshots: - uglify-js - webpack-cli - '@docusaurus/theme-search-algolia@3.8.1(@algolia/client-search@5.32.0)(@mdx-js/react@3.1.0(@types/react@18.2.74)(react@19.1.0))(@swc/core@1.4.12(@swc/helpers@0.5.15))(@types/react@18.2.74)(acorn@8.15.0)(bufferutil@4.0.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(search-insights@2.17.3)(typescript@5.6.3)(utf-8-validate@5.0.10)': + '@docusaurus/theme-search-algolia@3.8.1(@algolia/client-search@5.32.0)(@mdx-js/react@3.1.0(@types/react@19.2.7)(react@19.1.0))(@swc/core@1.4.12(@swc/helpers@0.5.15))(@types/react@19.2.7)(acorn@8.15.0)(bufferutil@4.0.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(search-insights@2.17.3)(typescript@5.6.3)(utf-8-validate@5.0.10)': dependencies: - '@docsearch/react': 3.9.0(@algolia/client-search@5.32.0)(@types/react@18.2.74)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(search-insights@2.17.3) - '@docusaurus/core': 3.8.1(@mdx-js/react@3.1.0(@types/react@18.2.74)(react@19.1.0))(@swc/core@1.4.12(@swc/helpers@0.5.15))(acorn@8.15.0)(bufferutil@4.0.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(typescript@5.6.3)(utf-8-validate@5.0.10) + '@docsearch/react': 3.9.0(@algolia/client-search@5.32.0)(@types/react@19.2.7)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(search-insights@2.17.3) + '@docusaurus/core': 3.8.1(@mdx-js/react@3.1.0(@types/react@19.2.7)(react@19.1.0))(@swc/core@1.4.12(@swc/helpers@0.5.15))(acorn@8.15.0)(bufferutil@4.0.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(typescript@5.6.3)(utf-8-validate@5.0.10) '@docusaurus/logger': 3.8.1 - '@docusaurus/plugin-content-docs': 3.8.1(@mdx-js/react@3.1.0(@types/react@18.2.74)(react@19.1.0))(@swc/core@1.4.12(@swc/helpers@0.5.15))(acorn@8.15.0)(bufferutil@4.0.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(typescript@5.6.3)(utf-8-validate@5.0.10) - '@docusaurus/theme-common': 3.8.1(@docusaurus/plugin-content-docs@3.8.1(@mdx-js/react@3.1.0(@types/react@18.2.74)(react@19.1.0))(@swc/core@1.4.12(@swc/helpers@0.5.15))(acorn@8.15.0)(bufferutil@4.0.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(typescript@5.6.3)(utf-8-validate@5.0.10))(@swc/core@1.4.12(@swc/helpers@0.5.15))(acorn@8.15.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@docusaurus/plugin-content-docs': 3.8.1(@mdx-js/react@3.1.0(@types/react@19.2.7)(react@19.1.0))(@swc/core@1.4.12(@swc/helpers@0.5.15))(acorn@8.15.0)(bufferutil@4.0.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(typescript@5.6.3)(utf-8-validate@5.0.10) + '@docusaurus/theme-common': 3.8.1(@docusaurus/plugin-content-docs@3.8.1(@mdx-js/react@3.1.0(@types/react@19.2.7)(react@19.1.0))(@swc/core@1.4.12(@swc/helpers@0.5.15))(acorn@8.15.0)(bufferutil@4.0.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(typescript@5.6.3)(utf-8-validate@5.0.10))(@swc/core@1.4.12(@swc/helpers@0.5.15))(acorn@8.15.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) '@docusaurus/theme-translations': 3.8.1 '@docusaurus/utils': 3.8.1(@swc/core@1.4.12(@swc/helpers@0.5.15))(acorn@8.15.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) '@docusaurus/utils-validation': 3.8.1(@swc/core@1.4.12(@swc/helpers@0.5.15))(acorn@8.15.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) @@ -16554,10 +16563,10 @@ snapshots: - acorn - supports-color - '@mdx-js/react@3.1.0(@types/react@18.2.74)(react@19.1.0)': + '@mdx-js/react@3.1.0(@types/react@19.2.7)(react@19.1.0)': dependencies: '@types/mdx': 2.0.13 - '@types/react': 18.2.74 + '@types/react': 19.2.7 react: 19.1.0 '@mrmlnc/readdir-enhanced@2.2.1': @@ -16567,7 +16576,7 @@ snapshots: '@next/env@13.0.3': {} - '@next/env@15.3.3': {} + '@next/env@15.3.6': {} '@next/swc-android-arm-eabi@13.0.3': optional: true @@ -16578,13 +16587,13 @@ snapshots: '@next/swc-darwin-arm64@13.0.3': optional: true - '@next/swc-darwin-arm64@15.3.3': + '@next/swc-darwin-arm64@15.3.5': optional: true '@next/swc-darwin-x64@13.0.3': optional: true - '@next/swc-darwin-x64@15.3.3': + '@next/swc-darwin-x64@15.3.5': optional: true '@next/swc-freebsd-x64@13.0.3': @@ -16596,31 +16605,31 @@ snapshots: '@next/swc-linux-arm64-gnu@13.0.3': optional: true - '@next/swc-linux-arm64-gnu@15.3.3': + '@next/swc-linux-arm64-gnu@15.3.5': optional: true '@next/swc-linux-arm64-musl@13.0.3': optional: true - '@next/swc-linux-arm64-musl@15.3.3': + '@next/swc-linux-arm64-musl@15.3.5': optional: true '@next/swc-linux-x64-gnu@13.0.3': optional: true - '@next/swc-linux-x64-gnu@15.3.3': + '@next/swc-linux-x64-gnu@15.3.5': optional: true '@next/swc-linux-x64-musl@13.0.3': optional: true - '@next/swc-linux-x64-musl@15.3.3': + '@next/swc-linux-x64-musl@15.3.5': optional: true '@next/swc-win32-arm64-msvc@13.0.3': optional: true - '@next/swc-win32-arm64-msvc@15.3.3': + '@next/swc-win32-arm64-msvc@15.3.5': optional: true '@next/swc-win32-ia32-msvc@13.0.3': @@ -16629,12 +16638,12 @@ snapshots: '@next/swc-win32-x64-msvc@13.0.3': optional: true - '@next/swc-win32-x64-msvc@15.3.3': + '@next/swc-win32-x64-msvc@15.3.5': optional: true - '@next/third-parties@15.3.3(next@15.3.3(@babel/core@7.27.4)(babel-plugin-macros@3.1.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react@19.1.0)': + '@next/third-parties@15.3.3(next@15.3.6(@babel/core@7.27.4)(babel-plugin-macros@3.1.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react@19.1.0)': dependencies: - next: 15.3.3(@babel/core@7.27.4)(babel-plugin-macros@3.1.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + next: 15.3.6(@babel/core@7.27.4)(babel-plugin-macros@3.1.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) react: 19.1.0 third-party-capital: 1.0.20 @@ -17673,6 +17682,10 @@ snapshots: '@types/prop-types': 15.7.12 csstype: 3.1.3 + '@types/react@19.2.7': + dependencies: + csstype: 3.2.3 + '@types/resolve@1.17.1': dependencies: '@types/node': 24.0.10 @@ -17852,7 +17865,7 @@ snapshots: chalk: 4.1.2 css-what: 6.1.0 cssesc: 3.0.0 - csstype: 3.1.3 + csstype: 3.2.3 deep-object-diff: 1.1.9 deepmerge: 4.3.1 media-query-parser: 2.0.2 @@ -19842,6 +19855,8 @@ snapshots: csstype@3.1.3: {} + csstype@3.2.3: {} + currently-unhandled@0.4.1: dependencies: array-find-index: 1.0.2 @@ -25098,9 +25113,9 @@ snapshots: - '@babel/core' - babel-plugin-macros - next@15.3.3(@babel/core@7.27.4)(babel-plugin-macros@3.1.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0): + next@15.3.6(@babel/core@7.27.4)(babel-plugin-macros@3.1.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0): dependencies: - '@next/env': 15.3.3 + '@next/env': 15.3.6 '@swc/counter': 0.1.3 '@swc/helpers': 0.5.15 busboy: 1.6.0 @@ -25110,14 +25125,14 @@ snapshots: react-dom: 19.1.0(react@19.1.0) styled-jsx: 5.1.6(@babel/core@7.27.4)(babel-plugin-macros@3.1.0)(react@19.1.0) optionalDependencies: - '@next/swc-darwin-arm64': 15.3.3 - '@next/swc-darwin-x64': 15.3.3 - '@next/swc-linux-arm64-gnu': 15.3.3 - '@next/swc-linux-arm64-musl': 15.3.3 - '@next/swc-linux-x64-gnu': 15.3.3 - '@next/swc-linux-x64-musl': 15.3.3 - '@next/swc-win32-arm64-msvc': 15.3.3 - '@next/swc-win32-x64-msvc': 15.3.3 + '@next/swc-darwin-arm64': 15.3.5 + '@next/swc-darwin-x64': 15.3.5 + '@next/swc-linux-arm64-gnu': 15.3.5 + '@next/swc-linux-arm64-musl': 15.3.5 + '@next/swc-linux-x64-gnu': 15.3.5 + '@next/swc-linux-x64-musl': 15.3.5 + '@next/swc-win32-arm64-msvc': 15.3.5 + '@next/swc-win32-x64-msvc': 15.3.5 sharp: 0.34.2 transitivePeerDependencies: - '@babel/core' @@ -26817,13 +26832,13 @@ snapshots: react-dom: 19.1.0(react@19.1.0) redux: 5.0.1 - react-redux@9.2.0(@types/react@18.2.74)(react@19.1.0)(redux@5.0.1): + react-redux@9.2.0(@types/react@19.2.7)(react@19.1.0)(redux@5.0.1): dependencies: '@types/use-sync-external-store': 0.0.6 react: 19.1.0 use-sync-external-store: 1.5.0(react@19.1.0) optionalDependencies: - '@types/react': 18.2.74 + '@types/react': 19.2.7 redux: 5.0.1 react-refresh@0.14.0: {} From a6b0350a00dd5ea4acf48cdf2e189f4df0f1af07 Mon Sep 17 00:00:00 2001 From: Jordan Eldredge Date: Thu, 10 Jul 2025 01:54:57 -0400 Subject: [PATCH 49/78] Get syncing to the archive working again --- .../graphql/resolvers/ClassicSkinResolver.ts | 6 +- packages/skin-database/data/SkinModel.ts | 5 +- .../skin-database/services/internetArchive.ts | 64 ++++++++++++++++--- packages/skin-database/tasks/syncToArchive.ts | 36 +++++++++-- packages/skin-database/utils.ts | 1 + 5 files changed, 92 insertions(+), 20 deletions(-) diff --git a/packages/skin-database/api/graphql/resolvers/ClassicSkinResolver.ts b/packages/skin-database/api/graphql/resolvers/ClassicSkinResolver.ts index ae879bd2..56a3dbb2 100644 --- a/packages/skin-database/api/graphql/resolvers/ClassicSkinResolver.ts +++ b/packages/skin-database/api/graphql/resolvers/ClassicSkinResolver.ts @@ -24,11 +24,7 @@ export default class ClassicSkinResolver implements NodeResolver, ISkin { return toId(this.__typename, this.md5()); } async filename(normalize_extension?: boolean): Promise { - const filename = await this._model.getFileName(); - if (normalize_extension) { - return path.parse(filename).name + ".wsz"; - } - return filename; + return await this._model.getFileName(normalize_extension); } museum_url(): string { diff --git a/packages/skin-database/data/SkinModel.ts b/packages/skin-database/data/SkinModel.ts index 4cab1479..d7543c39 100644 --- a/packages/skin-database/data/SkinModel.ts +++ b/packages/skin-database/data/SkinModel.ts @@ -158,7 +158,7 @@ export default class SkinModel { return "UNREVIEWED"; } - async getFileName(): Promise { + async getFileName(normalizeExtension?: boolean): Promise { const files = await this.getFiles(); if (files.length === 0) { throw new Error(`Could not find file for skin with md5 ${this.getMd5()}`); @@ -167,6 +167,9 @@ export default class SkinModel { if (!filename.match(/\.(zip)|(wsz)|(wal)$/i)) { throw new Error("Expected filename to end with zip, wsz or wal."); } + if (normalizeExtension) { + return path.parse(filename).name + ".wsz"; + } return filename; } diff --git a/packages/skin-database/services/internetArchive.ts b/packages/skin-database/services/internetArchive.ts index da89100e..5d483c8d 100644 --- a/packages/skin-database/services/internetArchive.ts +++ b/packages/skin-database/services/internetArchive.ts @@ -1,5 +1,16 @@ import fetch from "node-fetch"; -import { exec } from "../utils"; +import { execFile } from "../utils"; +import path from "path"; + +// Path to the ia command in the virtual environment +const IA_COMMAND = path.join(__dirname, "../.venv/bin/ia"); + +// Environment variables for the virtual environment +const getVenvEnv = () => ({ + ...process.env, + PATH: `${path.join(__dirname, "../.venv/bin")}:${process.env.PATH}`, + VIRTUAL_ENV: path.join(__dirname, "../.venv"), +}); export async function fetchMetadata(identifier: string): Promise { const r = await fetch(`https://archive.org/metadata/${identifier}`); @@ -11,9 +22,8 @@ export async function fetchMetadata(identifier: string): Promise { } export async function fetchTasks(identifier: string): Promise { - const command = `ia tasks ${identifier}`; - const result = await exec(command, { - encoding: "utf8", + const result = await execFile(IA_COMMAND, ['tasks', identifier], { + env: getVenvEnv(), }); return result.stdout .trim() @@ -25,12 +35,47 @@ export async function uploadFile( identifier: string, filepath: string ): Promise { - const command = `ia upload ${identifier} "${filepath}"`; - await exec(command, { encoding: "utf8" }); + await execFile(IA_COMMAND, ['upload', identifier, filepath], { + env: getVenvEnv(), + }); +} + +export async function uploadFiles( + identifier: string, + filepaths: string[], + metadata?: { [key: string]: string } +): Promise { + const args = ['upload', identifier, ...filepaths]; + + if (metadata) { + Object.entries(metadata).forEach(([key, value]) => { + args.push(`--metadata=${key}:${value}`); + }); + } + + await execFile(IA_COMMAND, args, { env: getVenvEnv() }); +} + +export async function uploadFiles( + identifier: string, + filepaths: string[], + metadata?: { [key: string]: string } +): Promise { + const args = ['upload', identifier, ...filepaths]; + + if (metadata) { + Object.entries(metadata).forEach(([key, value]) => { + args.push(`--metadata=${key}:${value}`); + }); + } + + await execFile(IA_COMMAND, args, { env: getVenvEnv() }); } export async function identifierExists(identifier: string): Promise { - const result = await exec(`ia metadata ${identifier}`); + const result = await execFile(IA_COMMAND, ['metadata', identifier], { + env: getVenvEnv(), + }); const data = JSON.parse(result.stdout); return Object.keys(data).length > 0; } @@ -40,7 +85,6 @@ export async function setMetadata( data: { [key: string]: string } ) { const pairs = Object.entries(data).map(([key, value]) => `${key}:${value}`); - const args = pairs.map((pair) => `--modify="${pair}"`); - const command = `ia metadata ${identifier} ${args.join(" ")}`; - await exec(command); + const args = ['metadata', identifier, ...pairs.map((pair) => `--modify=${pair}`)]; + await execFile(IA_COMMAND, args, { env: getVenvEnv() }); } diff --git a/packages/skin-database/tasks/syncToArchive.ts b/packages/skin-database/tasks/syncToArchive.ts index 0498e73d..3d0ddd22 100644 --- a/packages/skin-database/tasks/syncToArchive.ts +++ b/packages/skin-database/tasks/syncToArchive.ts @@ -8,7 +8,7 @@ import SkinModel from "../data/SkinModel"; import * as Parallel from "async-parallel"; import IaItemModel from "../data/IaItemModel"; import DiscordEventHandler from "../api/DiscordEventHandler"; -import { exec } from "../utils"; +import { exec, execFile } from "../utils"; import * as IAService from "../services/internetArchive"; export async function findItemsMissingImages(): Promise { @@ -192,7 +192,7 @@ async function getNewIdentifier(filename: string): Promise { } export async function archive(skin: SkinModel): Promise { - const filename = await skin.getFileName(); + const filename = await skin.getFileName(true); const screenshotFilename = await skin.getScreenshotFileName(); const title = `Winamp Skin: ${filename}`; @@ -207,8 +207,36 @@ export async function archive(skin: SkinModel): Promise { console.log(`Going to try to upload with identifier "${identifier}"...`); - const command = `ia upload ${identifier} "${skinFile}" "${screenshotFile}" --metadata="collection:winampskins" --metadata="skintype:wsz" --metadata="mediatype:software" --metadata="title:${title}"`; - await exec(command, { encoding: "utf8" }); + // Path to the ia command in the virtual environment + const IA_COMMAND = path.join(__dirname, "../.venv/bin/ia"); + + // Environment variables for the virtual environment + const venvEnv = { + ...process.env, + PATH: `${path.join(__dirname, "../.venv/bin")}:${process.env.PATH}`, + VIRTUAL_ENV: path.join(__dirname, "../.venv"), + }; + + const metadata = { + collection: "winampskins", + skintype: "wsz", + mediatype: "software", + title: title, + }; + + // Build arguments array for ia upload command + const args = [ + "upload", + identifier, + skinFile, + screenshotFile, + `--metadata=collection:${metadata.collection}`, + `--metadata=skintype:${metadata.skintype}`, + `--metadata=mediatype:${metadata.mediatype}`, + `--metadata=title:${metadata.title}`, + ]; + + await execFile(IA_COMMAND, args, { env: venvEnv }); await knex("ia_items").insert({ skin_md5: skin.getMd5(), identifier }); return identifier; } diff --git a/packages/skin-database/utils.ts b/packages/skin-database/utils.ts index 2bcf783a..b5d69ede 100644 --- a/packages/skin-database/utils.ts +++ b/packages/skin-database/utils.ts @@ -6,6 +6,7 @@ import child_process from "child_process"; import path from "path"; export const exec = util.promisify(child_process.exec); +export const execFile = util.promisify(child_process.execFile); const temp = _temp.track(); From 4b793c30b21f207a7b9dfce4ce2331cd33487703 Mon Sep 17 00:00:00 2001 From: Jordan Eldredge Date: Mon, 3 Nov 2025 11:31:01 -0500 Subject: [PATCH 50/78] Deploy take 1 --- packages/skin-database/scripts/deploy.ts | 290 +++++++++++++++++++++++ 1 file changed, 290 insertions(+) create mode 100644 packages/skin-database/scripts/deploy.ts diff --git a/packages/skin-database/scripts/deploy.ts b/packages/skin-database/scripts/deploy.ts new file mode 100644 index 00000000..1e695a00 --- /dev/null +++ b/packages/skin-database/scripts/deploy.ts @@ -0,0 +1,290 @@ +/** + * Simple Blue/Green Deployment Script + * This script handles deploying to the inactive instance and switching traffic + * + * Usage: npx tsx scripts/deploy.ts + */ + +import { execSync } from "child_process"; +import { readFileSync, copyFileSync } from "fs"; +import * as readline from "readline"; + +// ANSI color codes +const colors = { + red: "\x1b[0;31m", + green: "\x1b[0;32m", + blue: "\x1b[0;34m", + cyan: "\x1b[0;36m", + yellow: "\x1b[1;33m", + bold: "\x1b[1m", + reset: "\x1b[0m", +} as const; + +// Configuration +const APACHE_CONFIG = "/etc/apache2/sites-enabled/api.webamp.org-le-ssl.conf"; +const BLUE_PORT = 3001; +const GREEN_PORT = 3002; + +type Color = "blue" | "green"; + +interface DeploymentState { + currentColor: Color; + currentPort: number; + newColor: Color; + newPort: number; +} + +function log(message: string, color?: keyof typeof colors): void { + if (color) { + console.log(`${colors[color]}${message}${colors.reset}`); + } else { + console.log(message); + } +} + +function logBlank(): void { + console.log(); +} + +function exec(command: string, description: string): void { + try { + execSync(command, { stdio: "inherit" }); + } catch (error) { + log(`✗ Failed to ${description}`, "red"); + throw error; + } +} + +function execSilent(command: string): string { + return execSync(command, { encoding: "utf8" }); +} + +function detectCurrentDeployment(): DeploymentState { + log("→ Detecting current active deployment...", "cyan"); + + const apacheConfig = readFileSync(APACHE_CONFIG, "utf8"); + const isBlueActive = apacheConfig.includes(`localhost:${BLUE_PORT}`); + + if (isBlueActive) { + log( + ` Current active: ${colors.blue}blue${colors.reset} (port ${BLUE_PORT})` + ); + log( + ` Deploying to: ${colors.green}green${colors.reset} (port ${GREEN_PORT})` + ); + logBlank(); + return { + currentColor: "blue", + currentPort: BLUE_PORT, + newColor: "green", + newPort: GREEN_PORT, + }; + } else { + log( + ` Current active: ${colors.green}green${colors.reset} (port ${GREEN_PORT})` + ); + log( + ` Deploying to: ${colors.blue}blue${colors.reset} (port ${BLUE_PORT})` + ); + logBlank(); + return { + currentColor: "green", + currentPort: GREEN_PORT, + newColor: "blue", + newPort: BLUE_PORT, + }; + } +} + +async function promptForConfirmation( + newPort: number, + newColor: Color +): Promise { + const colorCode = newColor === "blue" ? colors.blue : colors.green; + log("========================================", "cyan"); + log(" MANUAL VALIDATION REQUIRED", "yellow"); + log("========================================", "cyan"); + logBlank(); + log(` Test the new ${colorCode}${newColor}${colors.reset} deployment at:`); + log(` ${colors.bold}https://${newColor}.api.webamp.org${colors.reset}`); + logBlank(); + log(` You can test it with:`); + log( + ` ${colors.cyan}curl -I https://${newColor}.api.webamp.org${colors.reset}` + ); + logBlank(); + + const rl = readline.createInterface({ + input: process.stdin, + output: process.stdout, + }); + + return new Promise((resolve) => { + rl.question(" Does everything look good? (yes/no): ", (answer) => { + rl.close(); + logBlank(); + resolve(answer.toLowerCase() === "yes"); + }); + }); +} + +function switchApacheConfig(state: DeploymentState): void { + const colorCode = state.newColor === "blue" ? colors.blue : colors.green; + log( + `→ Switching production to ${colorCode}${state.newColor}${colors.reset}...`, + "cyan" + ); + log(" Updating Apache configuration...", "cyan"); + + // Backup current config + const backupPath = `${APACHE_CONFIG}.backup`; + copyFileSync(APACHE_CONFIG, backupPath); + + // Update the port in Apache config + exec( + `sudo sed -i 's/localhost:${state.currentPort}/localhost:${state.newPort}/g' "${APACHE_CONFIG}"`, + "update Apache configuration" + ); + + // Reload Apache + exec("sudo systemctl reload apache2", "reload Apache"); + + log("✓ Apache configuration updated and reloaded", "cyan"); + logBlank(); + + // Verify the change + const updatedConfig = readFileSync(APACHE_CONFIG, "utf8"); + if (!updatedConfig.includes(`localhost:${state.newPort}`)) { + throw new Error("Configuration update verification failed"); + } +} + +function restoreBackup(): void { + log(" Restoring backup...", "yellow"); + const backupPath = `${APACHE_CONFIG}.backup`; + exec(`sudo cp "${backupPath}" "${APACHE_CONFIG}"`, "restore backup"); + exec("sudo systemctl reload apache2", "reload Apache"); + log("✓ Backup restored", "yellow"); +} + +async function main(): Promise { + try { + log("========================================", "cyan"); + log(" Blue/Green Deployment Script", "cyan"); + log("========================================", "cyan"); + logBlank(); + + // Step 1: Detect current deployment + const state = detectCurrentDeployment(); + + // Step 2: Pull from GitHub + log("→ Pulling latest code from GitHub...", "cyan"); + exec("git pull --rebase origin master", "pull from GitHub"); + log("✓ Code updated", "cyan"); + logBlank(); + + // Step 3: Install dependencies + log("→ Installing dependencies...", "cyan"); + exec("yarn install --frozen-lockfile", "install dependencies"); + log("✓ Dependencies installed", "cyan"); + logBlank(); + + // Step 4: Build the site + log("→ Building the site...", "cyan"); + exec("yarn build", "build the site"); + log("✓ Build complete", "cyan"); + logBlank(); + + // Step 5: Deploy to inactive instance + const newColorCode = state.newColor === "blue" ? colors.blue : colors.green; + log( + `→ Restarting ${newColorCode}${state.newColor}${colors.reset} instance...`, + "cyan" + ); + exec( + `pm2 restart skin-database-${state.newColor}`, + `restart ${state.newColor} instance` + ); + log( + `✓ ${newColorCode}${state.newColor}${colors.reset} instance restarted`, + "cyan" + ); + logBlank(); + + // Wait for the service to start + log("→ Waiting for service to be ready...", "cyan"); + await new Promise((resolve) => setTimeout(resolve, 5000)); + + // Check if the service is running + const pm2List = execSilent("pm2 list"); + const isRunning = + pm2List.includes(`skin-database-${state.newColor}`) && + pm2List.includes("online"); + + if (isRunning) { + log( + `✓ ${newColorCode}${state.newColor}${colors.reset} instance is running`, + "cyan" + ); + } else { + log( + `✗ ${newColorCode}${state.newColor}${colors.reset} instance failed to start!`, + "red" + ); + log(` Check PM2 logs: pm2 logs skin-database-${state.newColor}`, "red"); + process.exit(1); + } + logBlank(); + + // Step 6: Manual validation prompt + const confirmed = await promptForConfirmation( + state.newPort, + state.newColor + ); + + if (!confirmed) { + log("✗ Deployment cancelled!", "red"); + log( + ` The ${newColorCode}${state.newColor}${colors.reset} instance is running but not active in production.` + ); + log( + ` You can rollback by restarting: pm2 restart skin-database-${state.newColor}` + ); + process.exit(1); + } + + // Step 7: Switch Apache configuration + switchApacheConfig(state); + + // Success message + const currentColorCode = + state.currentColor === "blue" ? colors.blue : colors.green; + log("========================================", "cyan"); + log(" DEPLOYMENT SUCCESSFUL!", "cyan"); + log("========================================", "cyan"); + logBlank(); + log( + ` Active deployment: ${newColorCode}${state.newColor}${colors.reset} (port ${state.newPort})` + ); + log( + ` Previous deployment: ${currentColorCode}${state.currentColor}${colors.reset} (port ${state.currentPort}) - still running as backup` + ); + logBlank(); + log("Note: If you need to rollback:", "yellow"); + log(` 1. Edit: ${APACHE_CONFIG}`); + log(` 2. Change port back to ${state.currentPort}`); + log(` 3. Run: sudo systemctl reload apache2`); + } catch (error) { + if ( + error instanceof Error && + error.message === "Configuration update verification failed" + ) { + log("✗ Configuration update failed!", "red"); + restoreBackup(); + } + process.exit(1); + } +} + +// Run the deployment +main(); From af219342966384887b4b9d07bd251d56ce27e46e Mon Sep 17 00:00:00 2001 From: Jordan Eldredge Date: Mon, 3 Nov 2025 11:38:03 -0500 Subject: [PATCH 51/78] Use node version from nvm --- packages/skin-database/scripts/deploy.ts | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/packages/skin-database/scripts/deploy.ts b/packages/skin-database/scripts/deploy.ts index 1e695a00..6ee83e16 100644 --- a/packages/skin-database/scripts/deploy.ts +++ b/packages/skin-database/scripts/deploy.ts @@ -48,7 +48,7 @@ function logBlank(): void { function exec(command: string, description: string): void { try { - execSync(command, { stdio: "inherit" }); + execSync(command, { stdio: "inherit", shell: "/bin/bash" }); } catch (error) { log(`✗ Failed to ${description}`, "red"); throw error; @@ -183,15 +183,27 @@ async function main(): Promise { log("✓ Code updated", "cyan"); logBlank(); - // Step 3: Install dependencies + // Step 3: Ensure correct Node version + log("→ Ensuring correct Node version...", "cyan"); + exec( + "source ~/.nvm/nvm.sh && nvm install", + "ensure correct Node version from .nvmrc" + ); + log("✓ Node version verified", "cyan"); + logBlank(); + + // Step 4: Install dependencies log("→ Installing dependencies...", "cyan"); - exec("yarn install --frozen-lockfile", "install dependencies"); + exec( + "source ~/.nvm/nvm.sh && nvm exec yarn install --frozen-lockfile", + "install dependencies" + ); log("✓ Dependencies installed", "cyan"); logBlank(); - // Step 4: Build the site + // Step 5: Build the site log("→ Building the site...", "cyan"); - exec("yarn build", "build the site"); + exec("source ~/.nvm/nvm.sh && nvm exec yarn build", "build the site"); log("✓ Build complete", "cyan"); logBlank(); From 18ee5418b6e93c605d8619327b6845d1e9bc840e Mon Sep 17 00:00:00 2001 From: Jordan Eldredge Date: Mon, 3 Nov 2025 20:15:00 -0500 Subject: [PATCH 52/78] Avoid circular imports --- packages/skin-database/api/graphql/index.ts | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/packages/skin-database/api/graphql/index.ts b/packages/skin-database/api/graphql/index.ts index f6f43393..c879c934 100644 --- a/packages/skin-database/api/graphql/index.ts +++ b/packages/skin-database/api/graphql/index.ts @@ -1,8 +1,3 @@ -import { Router } from "express"; -import { createYoga, YogaInitialContext } from "graphql-yoga"; - -// import DEFAULT_QUERY from "./defaultQuery"; -import { getSchema } from "./schema"; import UserContext from "../../data/UserContext.js"; /** @gqlContext */ @@ -13,17 +8,3 @@ export function getUserContext(ctx: Ctx): UserContext { return ctx.ctx; } -const router = Router(); - -const yoga = createYoga({ - schema: getSchema(), - context: (ctx: YogaInitialContext) => { - // @ts-expect-error - return ctx.req; - }, -}); - -// Bind GraphQL Yoga to the graphql endpoint to avoid rendering the playground on any path -router.use("", yoga); - -export default router; From bd6c978d792a79743def70845933867f0f76a027 Mon Sep 17 00:00:00 2001 From: Jordan Eldredge Date: Wed, 5 Nov 2025 00:36:00 -0500 Subject: [PATCH 53/78] Remove duplicate function --- .../skin-database/services/internetArchive.ts | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/packages/skin-database/services/internetArchive.ts b/packages/skin-database/services/internetArchive.ts index 5d483c8d..1097871d 100644 --- a/packages/skin-database/services/internetArchive.ts +++ b/packages/skin-database/services/internetArchive.ts @@ -56,22 +56,6 @@ export async function uploadFiles( await execFile(IA_COMMAND, args, { env: getVenvEnv() }); } -export async function uploadFiles( - identifier: string, - filepaths: string[], - metadata?: { [key: string]: string } -): Promise { - const args = ['upload', identifier, ...filepaths]; - - if (metadata) { - Object.entries(metadata).forEach(([key, value]) => { - args.push(`--metadata=${key}:${value}`); - }); - } - - await execFile(IA_COMMAND, args, { env: getVenvEnv() }); -} - export async function identifierExists(identifier: string): Promise { const result = await execFile(IA_COMMAND, ['metadata', identifier], { env: getVenvEnv(), From 26a6002ce88c11137049725b072fcb8731adb902 Mon Sep 17 00:00:00 2001 From: Jordan Eldredge Date: Fri, 7 Nov 2025 18:45:40 -0500 Subject: [PATCH 54/78] Fix deploy --- packages/skin-database/ecosystem.config.js | 13 +++++++++++-- packages/skin-database/next.config.js | 7 +++++++ 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/packages/skin-database/ecosystem.config.js b/packages/skin-database/ecosystem.config.js index 7d847362..04aef2b6 100644 --- a/packages/skin-database/ecosystem.config.js +++ b/packages/skin-database/ecosystem.config.js @@ -6,7 +6,7 @@ module.exports = { apps: [ { name: "skin-database-blue", - script: "pnpm", + script: "yarn", interpreter: "bash", args: "start", env: { @@ -16,7 +16,7 @@ module.exports = { }, { name: "skin-database-green", - script: "pnpm", + script: "yarn", interpreter: "bash", args: "start", env: { @@ -24,5 +24,14 @@ module.exports = { PORT: 3002, }, }, + { + name: "skin-bot", + script: "yarn", + interpreter: "bash", + args: "bot", + env: { + NODE_ENV: "production", + }, + }, ], }; diff --git a/packages/skin-database/next.config.js b/packages/skin-database/next.config.js index 84a62cd4..fb6aea14 100644 --- a/packages/skin-database/next.config.js +++ b/packages/skin-database/next.config.js @@ -1,5 +1,12 @@ /** @type {import('next').NextConfig} */ const nextConfig = { + typescript: { + // !! WARN !! + // Dangerously allow production builds to successfully complete even if + // your project has type errors. + // !! WARN !! + ignoreBuildErrors: true, + }, serverExternalPackages: ["knex", "imagemin-optipng", "discord.js"], experimental: { viewTransition: true, From 1f875a6155a4a6b8dc93d90f1cf64d7a08958a06 Mon Sep 17 00:00:00 2001 From: Jordan Eldredge Date: Wed, 3 Dec 2025 18:20:18 -0500 Subject: [PATCH 55/78] Migrate from pm2 to systemd --- packages/skin-database/ecosystem.config.js | 37 ---------------------- 1 file changed, 37 deletions(-) delete mode 100644 packages/skin-database/ecosystem.config.js diff --git a/packages/skin-database/ecosystem.config.js b/packages/skin-database/ecosystem.config.js deleted file mode 100644 index 04aef2b6..00000000 --- a/packages/skin-database/ecosystem.config.js +++ /dev/null @@ -1,37 +0,0 @@ -// To toggle between blue and green deployments: -// sudo vim /etc/apache2/sites-enabled/api.webamp.org-le-ssl.conf -// Update port number -// sudo systemctl reload apache2 -module.exports = { - apps: [ - { - name: "skin-database-blue", - script: "yarn", - interpreter: "bash", - args: "start", - env: { - NODE_ENV: "production", - PORT: 3001, - }, - }, - { - name: "skin-database-green", - script: "yarn", - interpreter: "bash", - args: "start", - env: { - NODE_ENV: "production", - PORT: 3002, - }, - }, - { - name: "skin-bot", - script: "yarn", - interpreter: "bash", - args: "bot", - env: { - NODE_ENV: "production", - }, - }, - ], -}; From 50a7c2df49ca21c622dfc06e366fce3234e4c01b Mon Sep 17 00:00:00 2001 From: Jordan Eldredge Date: Mon, 15 Dec 2025 22:12:14 -0800 Subject: [PATCH 56/78] Fix lints (#1335) * Fix lints * Fix typechecking --- packages/skin-database/api/graphql/index.ts | 1 - .../graphql/resolvers/ClassicSkinResolver.ts | 1 - .../app/(modern)/scroll/SkinPage.tsx | 1 + .../app/(modern)/scroll/grid/Grid.tsx | 3 ++- .../skin-database/app/(modern)/table/Table.tsx | 4 ++-- packages/skin-database/scripts/deploy.ts | 4 +++- .../skin-database/services/internetArchive.ts | 18 +++++++++++------- packages/skin-database/tasks/syncToArchive.ts | 2 +- 8 files changed, 20 insertions(+), 14 deletions(-) diff --git a/packages/skin-database/api/graphql/index.ts b/packages/skin-database/api/graphql/index.ts index c879c934..10b59dec 100644 --- a/packages/skin-database/api/graphql/index.ts +++ b/packages/skin-database/api/graphql/index.ts @@ -7,4 +7,3 @@ export type Ctx = Express.Request; export function getUserContext(ctx: Ctx): UserContext { return ctx.ctx; } - diff --git a/packages/skin-database/api/graphql/resolvers/ClassicSkinResolver.ts b/packages/skin-database/api/graphql/resolvers/ClassicSkinResolver.ts index 56a3dbb2..8cadeb05 100644 --- a/packages/skin-database/api/graphql/resolvers/ClassicSkinResolver.ts +++ b/packages/skin-database/api/graphql/resolvers/ClassicSkinResolver.ts @@ -1,7 +1,6 @@ import { ISkin } from "./CommonSkinResolver"; import { NodeResolver, toId } from "./NodeResolver"; import ReviewResolver from "./ReviewResolver"; -import path from "path"; import { ID, Int } from "grats"; import SkinModel from "../../../data/SkinModel"; diff --git a/packages/skin-database/app/(modern)/scroll/SkinPage.tsx b/packages/skin-database/app/(modern)/scroll/SkinPage.tsx index ba6103b4..56ed57ae 100644 --- a/packages/skin-database/app/(modern)/scroll/SkinPage.tsx +++ b/packages/skin-database/app/(modern)/scroll/SkinPage.tsx @@ -1,5 +1,6 @@ "use client"; +// @ts-expect-error - unstable_ViewTransition is not yet in @types/react import { unstable_ViewTransition as ViewTransition } from "react"; import { ClientSkin } from "./SkinScroller"; import SkinActionIcons from "./SkinActionIcons"; diff --git a/packages/skin-database/app/(modern)/scroll/grid/Grid.tsx b/packages/skin-database/app/(modern)/scroll/grid/Grid.tsx index 49ede509..d61e64f3 100644 --- a/packages/skin-database/app/(modern)/scroll/grid/Grid.tsx +++ b/packages/skin-database/app/(modern)/scroll/grid/Grid.tsx @@ -4,6 +4,7 @@ import React, { useMemo, useCallback, useRef, + // @ts-expect-error - unstable_ViewTransition is not yet in @types/react unstable_ViewTransition as ViewTransition, } from "react"; @@ -141,7 +142,7 @@ export default function SkinTable({ return skin ? skin.md5 : `empty-cell-${columnIndex}-${rowIndex}`; } - const gridRef = React.useRef(); + const gridRef = React.useRef(null); const itemRef = React.useRef(0); const onScroll = useMemo(() => { diff --git a/packages/skin-database/app/(modern)/table/Table.tsx b/packages/skin-database/app/(modern)/table/Table.tsx index da6d1d53..45b6ab5e 100644 --- a/packages/skin-database/app/(modern)/table/Table.tsx +++ b/packages/skin-database/app/(modern)/table/Table.tsx @@ -111,8 +111,8 @@ function SkinTableUnbound({ } return skin ? skin.hash : `unfectched-index-${requestToken}`; } - const gridRef = React.useRef(); - const itemRef = React.useRef(); + const gridRef = React.useRef(null); + const itemRef = React.useRef(0); React.useLayoutEffect(() => { if (gridRef.current == null) { return; diff --git a/packages/skin-database/scripts/deploy.ts b/packages/skin-database/scripts/deploy.ts index 6ee83e16..b04c765b 100644 --- a/packages/skin-database/scripts/deploy.ts +++ b/packages/skin-database/scripts/deploy.ts @@ -225,7 +225,9 @@ async function main(): Promise { // Wait for the service to start log("→ Waiting for service to be ready...", "cyan"); - await new Promise((resolve) => setTimeout(resolve, 5000)); + await new Promise((resolve) => { + setTimeout(resolve, 5000); + }); // Check if the service is running const pm2List = execSilent("pm2 list"); diff --git a/packages/skin-database/services/internetArchive.ts b/packages/skin-database/services/internetArchive.ts index 1097871d..6dd91c8d 100644 --- a/packages/skin-database/services/internetArchive.ts +++ b/packages/skin-database/services/internetArchive.ts @@ -22,7 +22,7 @@ export async function fetchMetadata(identifier: string): Promise { } export async function fetchTasks(identifier: string): Promise { - const result = await execFile(IA_COMMAND, ['tasks', identifier], { + const result = await execFile(IA_COMMAND, ["tasks", identifier], { env: getVenvEnv(), }); return result.stdout @@ -35,7 +35,7 @@ export async function uploadFile( identifier: string, filepath: string ): Promise { - await execFile(IA_COMMAND, ['upload', identifier, filepath], { + await execFile(IA_COMMAND, ["upload", identifier, filepath], { env: getVenvEnv(), }); } @@ -45,19 +45,19 @@ export async function uploadFiles( filepaths: string[], metadata?: { [key: string]: string } ): Promise { - const args = ['upload', identifier, ...filepaths]; - + const args = ["upload", identifier, ...filepaths]; + if (metadata) { Object.entries(metadata).forEach(([key, value]) => { args.push(`--metadata=${key}:${value}`); }); } - + await execFile(IA_COMMAND, args, { env: getVenvEnv() }); } export async function identifierExists(identifier: string): Promise { - const result = await execFile(IA_COMMAND, ['metadata', identifier], { + const result = await execFile(IA_COMMAND, ["metadata", identifier], { env: getVenvEnv(), }); const data = JSON.parse(result.stdout); @@ -69,6 +69,10 @@ export async function setMetadata( data: { [key: string]: string } ) { const pairs = Object.entries(data).map(([key, value]) => `${key}:${value}`); - const args = ['metadata', identifier, ...pairs.map((pair) => `--modify=${pair}`)]; + const args = [ + "metadata", + identifier, + ...pairs.map((pair) => `--modify=${pair}`), + ]; await execFile(IA_COMMAND, args, { env: getVenvEnv() }); } diff --git a/packages/skin-database/tasks/syncToArchive.ts b/packages/skin-database/tasks/syncToArchive.ts index 3d0ddd22..feeaff9a 100644 --- a/packages/skin-database/tasks/syncToArchive.ts +++ b/packages/skin-database/tasks/syncToArchive.ts @@ -8,7 +8,7 @@ import SkinModel from "../data/SkinModel"; import * as Parallel from "async-parallel"; import IaItemModel from "../data/IaItemModel"; import DiscordEventHandler from "../api/DiscordEventHandler"; -import { exec, execFile } from "../utils"; +import { execFile } from "../utils"; import * as IAService from "../services/internetArchive"; export async function findItemsMissingImages(): Promise { From 33003a8f8fafdb2ffcdcf50f46e8adff578b4103 Mon Sep 17 00:00:00 2001 From: Jordan Eldredge Date: Mon, 15 Dec 2025 22:21:26 -0800 Subject: [PATCH 57/78] Patch butterchurn to use eel-wasm 98 to fix mod bug (#1334) --- package.json | 7 +- patches/butterchurn@3.0.0-beta.5.patch | 2356 ++++++++++++++++++++++++ pnpm-lock.yaml | 13 +- 3 files changed, 2371 insertions(+), 5 deletions(-) create mode 100644 patches/butterchurn@3.0.0-beta.5.patch diff --git a/package.json b/package.json index 0e91f0b4..ce42d467 100644 --- a/package.json +++ b/package.json @@ -43,5 +43,10 @@ "prettier": { "trailingComma": "es5" }, - "version": "0.0.0-next-87012d8d" + "version": "0.0.0-next-87012d8d", + "pnpm": { + "patchedDependencies": { + "butterchurn@3.0.0-beta.5": "patches/butterchurn@3.0.0-beta.5.patch" + } + } } diff --git a/patches/butterchurn@3.0.0-beta.5.patch b/patches/butterchurn@3.0.0-beta.5.patch new file mode 100644 index 00000000..2101d485 --- /dev/null +++ b/patches/butterchurn@3.0.0-beta.5.patch @@ -0,0 +1,2356 @@ +diff --git a/dist/butterchurn.js b/dist/butterchurn.js +index bc11139fbe6e0757842662f9d3a59e07c9866468..a26aa8c376de3a758649ebb9adb73c94c074382b 100644 +--- a/dist/butterchurn.js ++++ b/dist/butterchurn.js +@@ -184,2333 +184,7 @@ window.memcpy = function memcpy(megabuf, dst, src, len) { + }; + /* eslint-enable */ + +-var src = {}; +- +-var parser$1 = {}; +- +-var preProcessor = {}; +- +-var hasRequiredPreProcessor; +- +-function requirePreProcessor () { +- if (hasRequiredPreProcessor) return preProcessor; +- hasRequiredPreProcessor = 1; +- Object.defineProperty(preProcessor, "__esModule", { value: true }); +- function getLoc(mapper, destCol) { +- let previousAnchor = { destCol: 1, srcCol: 1, srcLine: 1 }; +- // Find the last anchor with a destCol > destCol. +- // Reversed: Find the first anchor with a destCol <= destCol +- // TODO: Use binary search +- mapper.forEach(anchor => { +- if (anchor.destCol > destCol) { +- return; +- } +- previousAnchor = anchor; +- }); +- const remainingColumns = destCol - previousAnchor.destCol; +- return { +- column: previousAnchor.srcCol + remainingColumns, +- line: previousAnchor.srcLine, +- }; +- } +- preProcessor.getLoc = getLoc; +- // Started with this function: https://github.com/WACUP/vis_milk2/blob/cc2e85aed44373d0b6b2115c0806ec035856860a/vis_milk2/state.cpp#L1532-L1557 +- function preProcess(src) { +- const mapper = []; +- let srcLine = 1; +- let dest = ""; +- let lineStart = 0; +- let inlineComment = false; +- let blockComment = false; +- let emitAnchor = false; +- for (let i = 0; i < src.length; i++) { +- const char = src[i]; +- if (emitAnchor) { +- const destCol = dest.length + 1; +- const srcCol = i - lineStart + 1; +- mapper.push({ destCol, srcCol, srcLine }); +- emitAnchor = false; +- } +- if (char === "\n") { +- inlineComment = false; +- srcLine++; +- lineStart = i + 1; +- emitAnchor = true; +- } +- else if (char === "\r" && src[i + 1] === "\n") { +- i++; +- inlineComment = false; +- srcLine++; +- lineStart = i + 1; +- emitAnchor = true; +- } +- else if (blockComment && char === "*" && src[i + 1] === "/") { +- // TODO: What if we are not currently in a block comment? +- blockComment = false; +- i++; +- emitAnchor = true; +- } +- else if ((char === "\\" && src[i + 1] === "\\") || +- (char === "/" && src[i + 1] === "/")) { +- inlineComment = true; +- i++; +- } +- else if (char === "/" && src[i + 1] === "*") { +- blockComment = true; +- i++; +- } +- else if (!inlineComment && !blockComment) { +- dest += char; +- } +- } +- return [dest, mapper]; +- } +- preProcessor.preProcess = preProcess; +- +- return preProcessor; +-} +- +-function commonjsRequire(path) { +- throw new Error('Could not dynamically require "' + path + '". Please configure the dynamicRequireTargets or/and ignoreDynamicRequires option of @rollup/plugin-commonjs appropriately for this require call to work.'); +-} +- +-var parser = {}; +- +-/* parser generated by jison 0.4.18 */ +- +-var hasRequiredParser$1; +- +-function requireParser$1 () { +- if (hasRequiredParser$1) return parser; +- hasRequiredParser$1 = 1; +- (function (exports) { +- /* +- Returns a Parser object of the following structure: +- +- Parser: { +- yy: {} +- } +- +- Parser.prototype: { +- yy: {}, +- trace: function(), +- symbols_: {associative list: name ==> number}, +- terminals_: {associative list: number ==> name}, +- productions_: [...], +- performAction: function anonymous(yytext, yyleng, yylineno, yy, yystate, $$, _$), +- table: [...], +- defaultActions: {...}, +- parseError: function(str, hash), +- parse: function(input), +- +- lexer: { +- EOF: 1, +- parseError: function(str, hash), +- setInput: function(input), +- input: function(), +- unput: function(str), +- more: function(), +- less: function(n), +- pastInput: function(), +- upcomingInput: function(), +- showPosition: function(), +- test_match: function(regex_match_array, rule_index), +- next: function(), +- lex: function(), +- begin: function(condition), +- popState: function(), +- _currentRules: function(), +- topState: function(), +- pushState: function(condition), +- +- options: { +- ranges: boolean (optional: true ==> token location info will include a .range[] member) +- flex: boolean (optional: true ==> flex-like lexing behaviour where the rules are tested exhaustively to find the longest match) +- backtrack_lexer: boolean (optional: true ==> lexer regexes are tested in order and for each matching regex the action code is invoked; the lexer terminates the scan when a token is returned by the action code) +- }, +- +- performAction: function(yy, yy_, $avoiding_name_collisions, YY_START), +- rules: [...], +- conditions: {associative list: name ==> set}, +- } +- } +- +- +- token location info (@$, _$, etc.): { +- first_line: n, +- last_line: n, +- first_column: n, +- last_column: n, +- range: [start_number, end_number] (where the numbers are indexes into the input string, regular zero-based) +- } +- +- +- the parseError function receives a 'hash' object with these members for lexer and parser errors: { +- text: (matched text) +- token: (the produced terminal token, if any) +- line: (yylineno) +- } +- while parser (grammar) errors will also provide these members, i.e. parser errors deliver a superset of attributes: { +- loc: (yylloc) +- expected: (string describing the set of expected tokens) +- recoverable: (boolean: TRUE when the parser has a error recovery rule available for this particular error) +- } +- */ +- var parser = (function () { +- var o = function (k, v, o, l) { for (o = o || {}, l = k.length; l--; o[k[l]] = v) +- ; return o; }, $V0 = [1, 18], $V1 = [1, 7], $V2 = [1, 19], $V3 = [1, 20], $V4 = [1, 14], $V5 = [1, 15], $V6 = [1, 16], $V7 = [1, 33], $V8 = [1, 31], $V9 = [1, 23], $Va = [1, 22], $Vb = [1, 24], $Vc = [1, 25], $Vd = [1, 26], $Ve = [1, 27], $Vf = [1, 28], $Vg = [1, 29], $Vh = [1, 30], $Vi = [5, 8, 15, 18, 20, 28, 29, 32, 33, 34, 35, 36, 37, 38], $Vj = [5, 15, 18], $Vk = [5, 12, 15, 17, 18, 24, 25, 28, 29, 30], $Vl = [1, 57], $Vm = [5, 8, 12, 15, 17, 18, 24, 25, 28, 29, 30], $Vn = [15, 18], $Vo = [5, 8, 15, 18, 28, 29, 38], $Vp = [5, 8, 15, 18, 28, 29, 32, 33, 38], $Vq = [5, 8, 15, 18, 28, 29, 32, 33, 34, 37, 38], $Vr = [5, 8, 15, 18, 28, 29, 32, 33, 34, 35, 36, 37, 38], $Vs = [5, 8, 15, 18], $Vt = [5, 8, 15, 18, 20, 22, 28, 29, 32, 33, 34, 35, 36, 37, 38]; +- var parser = { trace: function trace() { }, +- yy: {}, +- symbols_: { "error": 2, "SCRIPT": 3, "expression": 4, "EOF": 5, "expressionsOptionalTrailingSemi": 6, "separator": 7, ";": 8, "expressions": 9, "EXPRESSION_BLOCK": 10, "IDENTIFIER": 11, "IDENTIFIER_TOKEN": 12, "argument": 13, "arguments": 14, ",": 15, "FUNCTION_CALL": 16, "(": 17, ")": 18, "LOGICAL_EXPRESSION": 19, "LOGICAL_OPERATOR_TOKEN": 20, "ASSIGNMENT": 21, "ASSIGNMENT_OPERATOR_TOKEN": 22, "number": 23, "DIGITS_TOKEN": 24, ".": 25, "NUMBER_LITERAL": 26, "UNARY_EXPRESSION": 27, "-": 28, "+": 29, "!": 30, "BINARY_EXPRESSION": 31, "*": 32, "/": 33, "%": 34, "&": 35, "|": 36, "^": 37, "COMPARISON_TOKEN": 38, "$accept": 0, "$end": 1 }, +- terminals_: { 2: "error", 5: "EOF", 8: ";", 12: "IDENTIFIER_TOKEN", 15: ",", 17: "(", 18: ")", 20: "LOGICAL_OPERATOR_TOKEN", 22: "ASSIGNMENT_OPERATOR_TOKEN", 24: "DIGITS_TOKEN", 25: ".", 28: "-", 29: "+", 30: "!", 32: "*", 33: "/", 34: "%", 35: "&", 36: "|", 37: "^", 38: "COMPARISON_TOKEN" }, +- productions_: [0, [3, 2], [3, 2], [3, 1], [7, 1], [7, 2], [9, 2], [9, 3], [6, 1], [6, 2], [10, 1], [11, 1], [13, 1], [13, 1], [14, 1], [14, 3], [16, 3], [16, 4], [19, 3], [21, 3], [21, 3], [23, 1], [23, 2], [23, 3], [23, 2], [23, 1], [26, 1], [27, 2], [27, 2], [27, 2], [31, 3], [31, 3], [31, 3], [31, 3], [31, 3], [31, 3], [31, 3], [31, 3], [31, 3], [4, 1], [4, 1], [4, 3], [4, 1], [4, 1], [4, 1], [4, 1], [4, 1], [4, 3]], +- performAction: function anonymous(yytext, yyleng, yylineno, yy, yystate /* action[1] */, $$ /* vstack */, _$ /* lstack */) { +- /* this == yyval */ +- var $0 = $$.length - 1; +- switch (yystate) { +- case 1: +- return { type: 'SCRIPT', body: [$$[$0 - 1]], loc: this._$ }; +- case 2: +- return { type: 'SCRIPT', body: $$[$0 - 1], loc: this._$ }; +- case 3: +- return { type: 'SCRIPT', body: [], loc: this._$ }; +- case 6: +- this.$ = [$$[$0 - 1]]; +- break; +- case 7: +- this.$ = $$[$0 - 2].concat([$$[$0 - 1]]); +- break; +- case 8: +- this.$ = $$[$0]; +- break; +- case 9: +- this.$ = $$[$0 - 1].concat([$$[$0]]); +- break; +- case 10: +- this.$ = { type: 'EXPRESSION_BLOCK', body: $$[$0], loc: this._$ }; +- break; +- case 11: +- this.$ = { type: 'IDENTIFIER', value: $$[$0].toLowerCase(), loc: this._$ }; +- break; +- case 14: +- this.$ = [$$[$0]]; +- break; +- case 15: +- this.$ = $$[$0 - 2].concat([$$[$0]]); +- break; +- case 16: +- this.$ = { type: 'CALL_EXPRESSION', callee: $$[$0 - 2], arguments: [], loc: this._$ }; +- break; +- case 17: +- this.$ = { type: 'CALL_EXPRESSION', callee: $$[$0 - 3], arguments: $$[$0 - 1], loc: this._$ }; +- break; +- case 18: +- this.$ = { type: 'LOGICAL_EXPRESSION', left: $$[$0 - 2], right: $$[$0], operator: $$[$0 - 1], loc: this._$ }; +- break; +- case 19: +- case 20: +- this.$ = { type: 'ASSIGNMENT_EXPRESSION', left: $$[$0 - 2], operator: $$[$0 - 1], right: $$[$0], loc: this._$ }; +- break; +- case 21: +- this.$ = Number($$[$0]); +- break; +- case 22: +- this.$ = Number($$[$0 - 1]); +- break; +- case 23: +- this.$ = Number($$[$0 - 2] + $$[$0 - 1] + $$[$0]); +- break; +- case 24: +- this.$ = Number('0' + $$[$0 - 1] + $$[$0]); +- break; +- case 25: +- this.$ = 0; +- break; +- case 26: +- this.$ = { type: 'NUMBER_LITERAL', value: $$[$0], loc: this._$ }; +- break; +- case 27: +- case 28: +- case 29: +- this.$ = { type: 'UNARY_EXPRESSION', value: $$[$0], operator: $$[$0 - 1], loc: this._$ }; +- break; +- case 30: +- case 31: +- case 32: +- case 33: +- case 34: +- case 35: +- case 36: +- case 37: +- case 38: +- this.$ = { type: 'BINARY_EXPRESSION', left: $$[$0 - 2], right: $$[$0], operator: $$[$0 - 1], loc: this._$ }; +- break; +- case 41: +- case 47: +- this.$ = $$[$0 - 1]; +- break; +- } +- }, +- table: [{ 3: 1, 4: 2, 5: [1, 4], 6: 3, 9: 13, 11: 11, 12: $V0, 16: 10, 17: $V1, 19: 12, 21: 9, 23: 17, 24: $V2, 25: $V3, 26: 8, 27: 6, 28: $V4, 29: $V5, 30: $V6, 31: 5 }, { 1: [3] }, { 5: [1, 21], 7: 32, 8: $V7, 20: $V8, 28: $V9, 29: $Va, 32: $Vb, 33: $Vc, 34: $Vd, 35: $Ve, 36: $Vf, 37: $Vg, 38: $Vh }, { 5: [1, 34] }, { 1: [2, 3] }, o($Vi, [2, 39]), o($Vi, [2, 40]), { 4: 35, 6: 37, 9: 13, 10: 36, 11: 11, 12: $V0, 16: 10, 17: $V1, 19: 12, 21: 9, 23: 17, 24: $V2, 25: $V3, 26: 8, 27: 6, 28: $V4, 29: $V5, 30: $V6, 31: 5 }, o($Vi, [2, 42]), o($Vi, [2, 43]), o($Vi, [2, 44], { 22: [1, 38] }), o($Vi, [2, 45], { 17: [1, 40], 22: [1, 39] }), o($Vi, [2, 46]), o($Vj, [2, 8], { 31: 5, 27: 6, 26: 8, 21: 9, 16: 10, 11: 11, 19: 12, 23: 17, 4: 41, 12: $V0, 17: $V1, 24: $V2, 25: $V3, 28: $V4, 29: $V5, 30: $V6 }), { 4: 42, 11: 11, 12: $V0, 16: 10, 17: $V1, 19: 12, 21: 9, 23: 17, 24: $V2, 25: $V3, 26: 8, 27: 6, 28: $V4, 29: $V5, 30: $V6, 31: 5 }, { 4: 43, 11: 11, 12: $V0, 16: 10, 17: $V1, 19: 12, 21: 9, 23: 17, 24: $V2, 25: $V3, 26: 8, 27: 6, 28: $V4, 29: $V5, 30: $V6, 31: 5 }, { 4: 44, 11: 11, 12: $V0, 16: 10, 17: $V1, 19: 12, 21: 9, 23: 17, 24: $V2, 25: $V3, 26: 8, 27: 6, 28: $V4, 29: $V5, 30: $V6, 31: 5 }, o($Vi, [2, 26]), o([5, 8, 15, 17, 18, 20, 22, 28, 29, 32, 33, 34, 35, 36, 37, 38], [2, 11]), o($Vi, [2, 21], { 25: [1, 45] }), o($Vi, [2, 25], { 24: [1, 46] }), { 1: [2, 1] }, { 4: 47, 11: 11, 12: $V0, 16: 10, 17: $V1, 19: 12, 21: 9, 23: 17, 24: $V2, 25: $V3, 26: 8, 27: 6, 28: $V4, 29: $V5, 30: $V6, 31: 5 }, { 4: 48, 11: 11, 12: $V0, 16: 10, 17: $V1, 19: 12, 21: 9, 23: 17, 24: $V2, 25: $V3, 26: 8, 27: 6, 28: $V4, 29: $V5, 30: $V6, 31: 5 }, { 4: 49, 11: 11, 12: $V0, 16: 10, 17: $V1, 19: 12, 21: 9, 23: 17, 24: $V2, 25: $V3, 26: 8, 27: 6, 28: $V4, 29: $V5, 30: $V6, 31: 5 }, { 4: 50, 11: 11, 12: $V0, 16: 10, 17: $V1, 19: 12, 21: 9, 23: 17, 24: $V2, 25: $V3, 26: 8, 27: 6, 28: $V4, 29: $V5, 30: $V6, 31: 5 }, { 4: 51, 11: 11, 12: $V0, 16: 10, 17: $V1, 19: 12, 21: 9, 23: 17, 24: $V2, 25: $V3, 26: 8, 27: 6, 28: $V4, 29: $V5, 30: $V6, 31: 5 }, { 4: 52, 11: 11, 12: $V0, 16: 10, 17: $V1, 19: 12, 21: 9, 23: 17, 24: $V2, 25: $V3, 26: 8, 27: 6, 28: $V4, 29: $V5, 30: $V6, 31: 5 }, { 4: 53, 11: 11, 12: $V0, 16: 10, 17: $V1, 19: 12, 21: 9, 23: 17, 24: $V2, 25: $V3, 26: 8, 27: 6, 28: $V4, 29: $V5, 30: $V6, 31: 5 }, { 4: 54, 11: 11, 12: $V0, 16: 10, 17: $V1, 19: 12, 21: 9, 23: 17, 24: $V2, 25: $V3, 26: 8, 27: 6, 28: $V4, 29: $V5, 30: $V6, 31: 5 }, { 4: 55, 11: 11, 12: $V0, 16: 10, 17: $V1, 19: 12, 21: 9, 23: 17, 24: $V2, 25: $V3, 26: 8, 27: 6, 28: $V4, 29: $V5, 30: $V6, 31: 5 }, { 4: 56, 11: 11, 12: $V0, 16: 10, 17: $V1, 19: 12, 21: 9, 23: 17, 24: $V2, 25: $V3, 26: 8, 27: 6, 28: $V4, 29: $V5, 30: $V6, 31: 5 }, o($Vk, [2, 6], { 8: $Vl }), o($Vm, [2, 4]), { 1: [2, 2] }, { 7: 32, 8: $V7, 18: [1, 58], 20: $V8, 28: $V9, 29: $Va, 32: $Vb, 33: $Vc, 34: $Vd, 35: $Ve, 36: $Vf, 37: $Vg, 38: $Vh }, { 18: [1, 59] }, o($Vn, [2, 10]), { 4: 60, 11: 11, 12: $V0, 16: 10, 17: $V1, 19: 12, 21: 9, 23: 17, 24: $V2, 25: $V3, 26: 8, 27: 6, 28: $V4, 29: $V5, 30: $V6, 31: 5 }, { 4: 61, 11: 11, 12: $V0, 16: 10, 17: $V1, 19: 12, 21: 9, 23: 17, 24: $V2, 25: $V3, 26: 8, 27: 6, 28: $V4, 29: $V5, 30: $V6, 31: 5 }, { 4: 65, 6: 37, 9: 13, 10: 66, 11: 11, 12: $V0, 13: 64, 14: 63, 16: 10, 17: $V1, 18: [1, 62], 19: 12, 21: 9, 23: 17, 24: $V2, 25: $V3, 26: 8, 27: 6, 28: $V4, 29: $V5, 30: $V6, 31: 5 }, o($Vj, [2, 9], { 7: 67, 8: $V7, 20: $V8, 28: $V9, 29: $Va, 32: $Vb, 33: $Vc, 34: $Vd, 35: $Ve, 36: $Vf, 37: $Vg, 38: $Vh }), o($Vo, [2, 27], { 20: $V8, 32: $Vb, 33: $Vc, 34: $Vd, 35: $Ve, 36: $Vf, 37: $Vg }), o($Vo, [2, 28], { 20: $V8, 32: $Vb, 33: $Vc, 34: $Vd, 35: $Ve, 36: $Vf, 37: $Vg }), o($Vo, [2, 29], { 20: $V8, 32: $Vb, 33: $Vc, 34: $Vd, 35: $Ve, 36: $Vf, 37: $Vg }), o($Vi, [2, 22], { 24: [1, 68] }), o($Vi, [2, 24]), o($Vo, [2, 30], { 20: $V8, 32: $Vb, 33: $Vc, 34: $Vd, 35: $Ve, 36: $Vf, 37: $Vg }), o($Vo, [2, 31], { 20: $V8, 32: $Vb, 33: $Vc, 34: $Vd, 35: $Ve, 36: $Vf, 37: $Vg }), o($Vp, [2, 32], { 20: $V8, 34: $Vd, 35: $Ve, 36: $Vf, 37: $Vg }), o($Vp, [2, 33], { 20: $V8, 34: $Vd, 35: $Ve, 36: $Vf, 37: $Vg }), o($Vq, [2, 34], { 20: $V8, 35: $Ve, 36: $Vf }), o($Vr, [2, 35], { 20: $V8 }), o($Vr, [2, 36], { 20: $V8 }), o($Vq, [2, 37], { 20: $V8, 35: $Ve, 36: $Vf }), o($Vs, [2, 38], { 20: $V8, 28: $V9, 29: $Va, 32: $Vb, 33: $Vc, 34: $Vd, 35: $Ve, 36: $Vf, 37: $Vg, 38: $Vh }), o($Vi, [2, 18]), o($Vm, [2, 5]), o($Vi, [2, 41]), o($Vi, [2, 47]), o($Vs, [2, 20], { 20: $V8, 28: $V9, 29: $Va, 32: $Vb, 33: $Vc, 34: $Vd, 35: $Ve, 36: $Vf, 37: $Vg, 38: $Vh }), o($Vs, [2, 19], { 20: $V8, 28: $V9, 29: $Va, 32: $Vb, 33: $Vc, 34: $Vd, 35: $Ve, 36: $Vf, 37: $Vg, 38: $Vh }), o($Vt, [2, 16]), { 15: [1, 70], 18: [1, 69] }, o($Vn, [2, 14]), o($Vn, [2, 12], { 7: 32, 8: $V7, 20: $V8, 28: $V9, 29: $Va, 32: $Vb, 33: $Vc, 34: $Vd, 35: $Ve, 36: $Vf, 37: $Vg, 38: $Vh }), o($Vn, [2, 13]), o($Vk, [2, 7], { 8: $Vl }), o($Vi, [2, 23]), o($Vt, [2, 17]), { 4: 65, 6: 37, 9: 13, 10: 66, 11: 11, 12: $V0, 13: 71, 16: 10, 17: $V1, 19: 12, 21: 9, 23: 17, 24: $V2, 25: $V3, 26: 8, 27: 6, 28: $V4, 29: $V5, 30: $V6, 31: 5 }, o($Vn, [2, 15])], +- defaultActions: { 4: [2, 3], 21: [2, 1], 34: [2, 2] }, +- parseError: function parseError(str, hash) { +- if (hash.recoverable) { +- this.trace(str); +- } +- else { +- var error = new Error(str); +- error.hash = hash; +- throw error; +- } +- }, +- parse: function parse(input) { +- var self = this, stack = [0], vstack = [null], lstack = [], table = this.table, yytext = '', yylineno = 0, yyleng = 0, TERROR = 2, EOF = 1; +- var args = lstack.slice.call(arguments, 1); +- var lexer = Object.create(this.lexer); +- var sharedState = { yy: {} }; +- for (var k in this.yy) { +- if (Object.prototype.hasOwnProperty.call(this.yy, k)) { +- sharedState.yy[k] = this.yy[k]; +- } +- } +- lexer.setInput(input, sharedState.yy); +- sharedState.yy.lexer = lexer; +- sharedState.yy.parser = this; +- if (typeof lexer.yylloc == 'undefined') { +- lexer.yylloc = {}; +- } +- var yyloc = lexer.yylloc; +- lstack.push(yyloc); +- var ranges = lexer.options && lexer.options.ranges; +- if (typeof sharedState.yy.parseError === 'function') { +- this.parseError = sharedState.yy.parseError; +- } +- else { +- this.parseError = Object.getPrototypeOf(this).parseError; +- } +- var lex = function () { +- var token; +- token = lexer.lex() || EOF; +- if (typeof token !== 'number') { +- token = self.symbols_[token] || token; +- } +- return token; +- }; +- var symbol, state, action, r, yyval = {}, p, len, newState, expected; +- while (true) { +- state = stack[stack.length - 1]; +- if (this.defaultActions[state]) { +- action = this.defaultActions[state]; +- } +- else { +- if (symbol === null || typeof symbol == 'undefined') { +- symbol = lex(); +- } +- action = table[state] && table[state][symbol]; +- } +- if (typeof action === 'undefined' || !action.length || !action[0]) { +- var errStr = ''; +- expected = []; +- for (p in table[state]) { +- if (this.terminals_[p] && p > TERROR) { +- expected.push('\'' + this.terminals_[p] + '\''); +- } +- } +- if (lexer.showPosition) { +- errStr = 'Parse error on line ' + (yylineno + 1) + ':\n' + lexer.showPosition() + '\nExpecting ' + expected.join(', ') + ', got \'' + (this.terminals_[symbol] || symbol) + '\''; +- } +- else { +- errStr = 'Parse error on line ' + (yylineno + 1) + ': Unexpected ' + (symbol == EOF ? 'end of input' : '\'' + (this.terminals_[symbol] || symbol) + '\''); +- } +- this.parseError(errStr, { +- text: lexer.match, +- token: this.terminals_[symbol] || symbol, +- line: lexer.yylineno, +- loc: yyloc, +- expected: expected +- }); +- } +- if (action[0] instanceof Array && action.length > 1) { +- throw new Error('Parse Error: multiple actions possible at state: ' + state + ', token: ' + symbol); +- } +- switch (action[0]) { +- case 1: +- stack.push(symbol); +- vstack.push(lexer.yytext); +- lstack.push(lexer.yylloc); +- stack.push(action[1]); +- symbol = null; +- { +- yyleng = lexer.yyleng; +- yytext = lexer.yytext; +- yylineno = lexer.yylineno; +- yyloc = lexer.yylloc; +- } +- break; +- case 2: +- len = this.productions_[action[1]][1]; +- yyval.$ = vstack[vstack.length - len]; +- yyval._$ = { +- first_line: lstack[lstack.length - (len || 1)].first_line, +- last_line: lstack[lstack.length - 1].last_line, +- first_column: lstack[lstack.length - (len || 1)].first_column, +- last_column: lstack[lstack.length - 1].last_column +- }; +- if (ranges) { +- yyval._$.range = [ +- lstack[lstack.length - (len || 1)].range[0], +- lstack[lstack.length - 1].range[1] +- ]; +- } +- r = this.performAction.apply(yyval, [ +- yytext, +- yyleng, +- yylineno, +- sharedState.yy, +- action[1], +- vstack, +- lstack +- ].concat(args)); +- if (typeof r !== 'undefined') { +- return r; +- } +- if (len) { +- stack = stack.slice(0, -1 * len * 2); +- vstack = vstack.slice(0, -1 * len); +- lstack = lstack.slice(0, -1 * len); +- } +- stack.push(this.productions_[action[1]][0]); +- vstack.push(yyval.$); +- lstack.push(yyval._$); +- newState = table[stack[stack.length - 2]][stack[stack.length - 1]]; +- stack.push(newState); +- break; +- case 3: +- return true; +- } +- } +- return true; +- } }; +- /* generated by jison-lex 0.3.4 */ +- var lexer = (function () { +- var lexer = ({ +- EOF: 1, +- parseError: function parseError(str, hash) { +- if (this.yy.parser) { +- this.yy.parser.parseError(str, hash); +- } +- else { +- throw new Error(str); +- } +- }, +- // resets the lexer, sets new input +- setInput: function (input, yy) { +- this.yy = yy || this.yy || {}; +- this._input = input; +- this._more = this._backtrack = this.done = false; +- this.yylineno = this.yyleng = 0; +- this.yytext = this.matched = this.match = ''; +- this.conditionStack = ['INITIAL']; +- this.yylloc = { +- first_line: 1, +- first_column: 0, +- last_line: 1, +- last_column: 0 +- }; +- if (this.options.ranges) { +- this.yylloc.range = [0, 0]; +- } +- this.offset = 0; +- return this; +- }, +- // consumes and returns one char from the input +- input: function () { +- var ch = this._input[0]; +- this.yytext += ch; +- this.yyleng++; +- this.offset++; +- this.match += ch; +- this.matched += ch; +- var lines = ch.match(/(?:\r\n?|\n).*/g); +- if (lines) { +- this.yylineno++; +- this.yylloc.last_line++; +- } +- else { +- this.yylloc.last_column++; +- } +- if (this.options.ranges) { +- this.yylloc.range[1]++; +- } +- this._input = this._input.slice(1); +- return ch; +- }, +- // unshifts one char (or a string) into the input +- unput: function (ch) { +- var len = ch.length; +- var lines = ch.split(/(?:\r\n?|\n)/g); +- this._input = ch + this._input; +- this.yytext = this.yytext.substr(0, this.yytext.length - len); +- //this.yyleng -= len; +- this.offset -= len; +- var oldLines = this.match.split(/(?:\r\n?|\n)/g); +- this.match = this.match.substr(0, this.match.length - 1); +- this.matched = this.matched.substr(0, this.matched.length - 1); +- if (lines.length - 1) { +- this.yylineno -= lines.length - 1; +- } +- var r = this.yylloc.range; +- this.yylloc = { +- first_line: this.yylloc.first_line, +- last_line: this.yylineno + 1, +- first_column: this.yylloc.first_column, +- last_column: lines ? +- (lines.length === oldLines.length ? this.yylloc.first_column : 0) +- + oldLines[oldLines.length - lines.length].length - lines[0].length : +- this.yylloc.first_column - len +- }; +- if (this.options.ranges) { +- this.yylloc.range = [r[0], r[0] + this.yyleng - len]; +- } +- this.yyleng = this.yytext.length; +- return this; +- }, +- // When called from action, caches matched text and appends it on next action +- more: function () { +- this._more = true; +- return this; +- }, +- // When called from action, signals the lexer that this rule fails to match the input, so the next matching rule (regex) should be tested instead. +- reject: function () { +- if (this.options.backtrack_lexer) { +- this._backtrack = true; +- } +- else { +- return this.parseError('Lexical error on line ' + (this.yylineno + 1) + '. You can only invoke reject() in the lexer when the lexer is of the backtracking persuasion (options.backtrack_lexer = true).\n' + this.showPosition(), { +- text: "", +- token: null, +- line: this.yylineno +- }); +- } +- return this; +- }, +- // retain first n characters of the match +- less: function (n) { +- this.unput(this.match.slice(n)); +- }, +- // displays already matched input, i.e. for error messages +- pastInput: function () { +- var past = this.matched.substr(0, this.matched.length - this.match.length); +- return (past.length > 20 ? '...' : '') + past.substr(-20).replace(/\n/g, ""); +- }, +- // displays upcoming input, i.e. for error messages +- upcomingInput: function () { +- var next = this.match; +- if (next.length < 20) { +- next += this._input.substr(0, 20 - next.length); +- } +- return (next.substr(0, 20) + (next.length > 20 ? '...' : '')).replace(/\n/g, ""); +- }, +- // displays the character position where the lexing error occurred, i.e. for error messages +- showPosition: function () { +- var pre = this.pastInput(); +- var c = new Array(pre.length + 1).join("-"); +- return pre + this.upcomingInput() + "\n" + c + "^"; +- }, +- // test the lexed token: return FALSE when not a match, otherwise return token +- test_match: function (match, indexed_rule) { +- var token, lines, backup; +- if (this.options.backtrack_lexer) { +- // save context +- backup = { +- yylineno: this.yylineno, +- yylloc: { +- first_line: this.yylloc.first_line, +- last_line: this.last_line, +- first_column: this.yylloc.first_column, +- last_column: this.yylloc.last_column +- }, +- yytext: this.yytext, +- match: this.match, +- matches: this.matches, +- matched: this.matched, +- yyleng: this.yyleng, +- offset: this.offset, +- _more: this._more, +- _input: this._input, +- yy: this.yy, +- conditionStack: this.conditionStack.slice(0), +- done: this.done +- }; +- if (this.options.ranges) { +- backup.yylloc.range = this.yylloc.range.slice(0); +- } +- } +- lines = match[0].match(/(?:\r\n?|\n).*/g); +- if (lines) { +- this.yylineno += lines.length; +- } +- this.yylloc = { +- first_line: this.yylloc.last_line, +- last_line: this.yylineno + 1, +- first_column: this.yylloc.last_column, +- last_column: lines ? +- lines[lines.length - 1].length - lines[lines.length - 1].match(/\r?\n?/)[0].length : +- this.yylloc.last_column + match[0].length +- }; +- this.yytext += match[0]; +- this.match += match[0]; +- this.matches = match; +- this.yyleng = this.yytext.length; +- if (this.options.ranges) { +- this.yylloc.range = [this.offset, this.offset += this.yyleng]; +- } +- this._more = false; +- this._backtrack = false; +- this._input = this._input.slice(match[0].length); +- this.matched += match[0]; +- token = this.performAction.call(this, this.yy, this, indexed_rule, this.conditionStack[this.conditionStack.length - 1]); +- if (this.done && this._input) { +- this.done = false; +- } +- if (token) { +- return token; +- } +- else if (this._backtrack) { +- // recover context +- for (var k in backup) { +- this[k] = backup[k]; +- } +- return false; // rule action called reject() implying the next rule should be tested instead. +- } +- return false; +- }, +- // return next match in input +- next: function () { +- if (this.done) { +- return this.EOF; +- } +- if (!this._input) { +- this.done = true; +- } +- var token, match, tempMatch, index; +- if (!this._more) { +- this.yytext = ''; +- this.match = ''; +- } +- var rules = this._currentRules(); +- for (var i = 0; i < rules.length; i++) { +- tempMatch = this._input.match(this.rules[rules[i]]); +- if (tempMatch && (!match || tempMatch[0].length > match[0].length)) { +- match = tempMatch; +- index = i; +- if (this.options.backtrack_lexer) { +- token = this.test_match(tempMatch, rules[i]); +- if (token !== false) { +- return token; +- } +- else if (this._backtrack) { +- match = false; +- continue; // rule action called reject() implying a rule MISmatch. +- } +- else { +- // else: this is a lexer rule which consumes input without producing a token (e.g. whitespace) +- return false; +- } +- } +- else if (!this.options.flex) { +- break; +- } +- } +- } +- if (match) { +- token = this.test_match(match, rules[index]); +- if (token !== false) { +- return token; +- } +- // else: this is a lexer rule which consumes input without producing a token (e.g. whitespace) +- return false; +- } +- if (this._input === "") { +- return this.EOF; +- } +- else { +- return this.parseError('Lexical error on line ' + (this.yylineno + 1) + '. Unrecognized text.\n' + this.showPosition(), { +- text: "", +- token: null, +- line: this.yylineno +- }); +- } +- }, +- // return next match that has a token +- lex: function lex() { +- var r = this.next(); +- if (r) { +- return r; +- } +- else { +- return this.lex(); +- } +- }, +- // activates a new lexer condition state (pushes the new lexer condition state onto the condition stack) +- begin: function begin(condition) { +- this.conditionStack.push(condition); +- }, +- // pop the previously active lexer condition state off the condition stack +- popState: function popState() { +- var n = this.conditionStack.length - 1; +- if (n > 0) { +- return this.conditionStack.pop(); +- } +- else { +- return this.conditionStack[0]; +- } +- }, +- // produce the lexer rule set which is active for the currently active lexer condition state +- _currentRules: function _currentRules() { +- if (this.conditionStack.length && this.conditionStack[this.conditionStack.length - 1]) { +- return this.conditions[this.conditionStack[this.conditionStack.length - 1]].rules; +- } +- else { +- return this.conditions["INITIAL"].rules; +- } +- }, +- // return the currently active lexer condition state; when an index argument is provided it produces the N-th previous condition state, if available +- topState: function topState(n) { +- n = this.conditionStack.length - 1 - Math.abs(n || 0); +- if (n >= 0) { +- return this.conditionStack[n]; +- } +- else { +- return "INITIAL"; +- } +- }, +- // alias for begin(condition) +- pushState: function pushState(condition) { +- this.begin(condition); +- }, +- // return the number of states currently on the stack +- stateStackSize: function stateStackSize() { +- return this.conditionStack.length; +- }, +- options: {}, +- performAction: function anonymous(yy, yy_, $avoiding_name_collisions, YY_START) { +- switch ($avoiding_name_collisions) { +- case 0: /* skip whitespace */ +- break; +- case 1: +- return 24; +- case 2: +- return 38; +- case 3: +- return 22; +- case 4: +- return 20; +- case 5: +- return 12; +- case 6: +- return 5; +- case 7: +- return yy_.yytext[0]; +- } +- }, +- rules: [/^(?:\s+)/, /^(?:[0-9]+)/, /^(?:(==|!=|<=|>=|<|>))/, /^(?:[+\-*/%]?=)/, /^(?:(\&\&)|\|\|)/, /^(?:[a-zA-Z_][a-zA-Z0-9._]*)/, /^(?:$)/, /^(?:.)/], +- conditions: { "INITIAL": { "rules": [0, 1, 2, 3, 4, 5, 6, 7], "inclusive": true } } +- }); +- return lexer; +- })(); +- parser.lexer = lexer; +- function Parser() { +- this.yy = {}; +- } +- Parser.prototype = parser; +- parser.Parser = Parser; +- return new Parser; +- })(); +- if (typeof commonjsRequire !== 'undefined' && 'object' !== 'undefined') { +- exports.parser = parser; +- exports.Parser = parser.Parser; +- exports.parse = function () { return parser.parse.apply(parser, arguments); }; +- exports.main = () => { }; +- } +- +- } (parser)); +- return parser; +-} +- +-var envParser; +-var hasRequiredEnvParser; +- +-function requireEnvParser () { +- if (hasRequiredEnvParser) return envParser; +- hasRequiredEnvParser = 1; +- // This module is in charge of selecting either the pre-built module or the one +- // that builds it a runtime with jison. +- let parserImpl = requireParser$1(); +- /* +- let parserImpl = null; +- // Always use the dynamic one, while we figure out why the built one doens't work. +- if (process.env.NODE_ENV === "production") { +- parserImpl = require("../build/parser"); +- } else { +- parserImpl = require("../tools/buildParser"); +- } +- */ +- envParser = { parse: parserImpl.parse }; +- +- return envParser; +-} +- +-var astUtils = {}; +- +-var hasRequiredAstUtils; +- +-function requireAstUtils () { +- if (hasRequiredAstUtils) return astUtils; +- hasRequiredAstUtils = 1; +- Object.defineProperty(astUtils, "__esModule", { value: true }); +- const CHILDREN = { +- ASSIGNMENT_EXPRESSION: [ +- { type: "NODE", key: "right" }, +- ], +- SCRIPT: [{ type: "ARRAY", key: "body" }], +- EXPRESSION_BLOCK: [{ type: "ARRAY", key: "body" }], +- UNARY_EXPRESSION: [{ type: "NODE", key: "value" }], +- NUMBER_LITERAL: [], +- IDENTIFIER: [], +- CALL_EXPRESSION: [ +- { type: "ARRAY", key: "arguments" }, +- { type: "NODE", key: "callee" }, +- ], +- BINARY_EXPRESSION: [ +- { type: "NODE", key: "left" }, +- { type: "NODE", key: "right" }, +- ], +- LOGICAL_EXPRESSION: [ +- { type: "NODE", key: "left" }, +- { type: "NODE", key: "right" }, +- ], +- }; +- function mapAst(ast, cb) { +- const children = CHILDREN[ast.type]; +- let newAst = ast; +- if (children == null) { +- throw new Error(`Unknown children definition for ${ast.type}`); +- } +- children.forEach(child => { +- if (child.type === "NODE") { +- const orignalChild = ast[child.key]; +- const newChild = mapAst(orignalChild, cb); +- if (newChild !== orignalChild) { +- newAst = Object.assign(Object.assign({}, newAst), { [child.key]: newChild }); +- } +- } +- else if (child.type === "ARRAY") { +- const orignalChildren = ast[child.key]; +- const newChildren = orignalChildren.map(originalChild => mapAst(originalChild, cb)); +- const childrenHaveChanged = orignalChildren.some((child, i) => child !== newChildren[i]); +- if (childrenHaveChanged) { +- newAst = Object.assign(Object.assign({}, newAst), { [child.key]: newChildren }); +- } +- } +- }); +- return cb(newAst); +- } +- astUtils.mapAst = mapAst; +- +- return astUtils; +-} +- +-var errorUtils = {}; +- +-var utils = {}; +- +-var hasRequiredUtils; +- +-function requireUtils () { +- if (hasRequiredUtils) return utils; +- hasRequiredUtils = 1; +- Object.defineProperty(utils, "__esModule", { value: true }); +- function arrayJoin(arr, joiner) { +- const newArr = []; +- for (let i = 0; i < arr.length; i++) { +- newArr.push(arr[i]); +- const last = i === arr.length - 1; +- if (!last) { +- newArr.push(joiner); +- } +- } +- return newArr; +- } +- utils.arrayJoin = arrayJoin; +- function flatten(arr) { +- return [].concat.apply([], arr); +- } +- utils.flatten = flatten; +- function times(n, cb) { +- return new Array(n).fill(null).map((_, i) => cb(i)); +- } +- utils.times = times; +- function repeat(n, char) { +- return new Array(n).fill(char).join(""); +- } +- utils.repeat = repeat; +- // Maintain an ordered list of indexes for namespace/key pairs. +- // In Wasm binary variables are referenced by their index. In our emitter we +- // want to emit variables indexes as we encounter their names. This data +- // structure lets us issue variable indexes on demmand and then iterate through +- // them post facto. +- // +- // `null` may be passed for the namespace argument in order to get a global +- // variable that exists in all namespaces. +- class ScopedIdMap { +- constructor() { +- this._map = new Map(); +- } +- // Get the index of a given namespace/key pair +- get(namespace, key) { +- const jointKey = namespace == null ? key : `${namespace}::${key}`; +- if (!this._map.has(jointKey)) { +- this._map.set(jointKey, this._map.size); +- } +- // @ts-ignore We know the key is here. +- return this._map.get(jointKey); +- } +- size() { +- return this._map.size; +- } +- } +- utils.ScopedIdMap = ScopedIdMap; +- function formatList(list) { +- if (list.length === 0) { +- throw new Error("Cannot format an empty list"); +- } +- if (list.length === 1) { +- return list[0]; +- } +- const quoted = list.map(name => `"${name}"`); +- const last = quoted.pop(); +- return quoted.join(", ") + ` and ${last}`; +- } +- utils.formatList = formatList; +- +- return utils; +-} +- +-var hasRequiredErrorUtils; +- +-function requireErrorUtils () { +- if (hasRequiredErrorUtils) return errorUtils; +- hasRequiredErrorUtils = 1; +- Object.defineProperty(errorUtils, "__esModule", { value: true }); +- const utils_1 = requireUtils(); +- /* Build up a string showing a formatted source location in context with line numbers. +- * +- * 2 | z = sqr(y, 10); +- * > 3 | x = wat(); +- * | ^^^ +- * 4 | y = 100; +- */ +- function printLoc(loc, rawSource, contextLines = 1) { +- const firstIndex = Math.max(loc.first_line - 1 - contextLines, 0); +- const lastIndex = loc.last_line + contextLines; +- const sourceLines = rawSource.split("\n").slice(firstIndex, lastIndex); +- const annotatedLines = sourceLines.map((line, i) => { +- const lineNumber = i + firstIndex + 1; +- const inRange = lineNumber >= loc.first_line && lineNumber <= loc.last_line; +- const gutter = inRange ? ">" : " "; +- return `${gutter} ${lineNumber} | ${line}`; +- }); +- if (loc.first_line === loc.last_line) { +- const padding = utils_1.repeat(loc.first_column, " "); +- const underline = utils_1.repeat(loc.last_column - loc.first_column, "^"); +- const insertIndex = loc.first_line - firstIndex; +- annotatedLines.splice(insertIndex, 0, ` | ${padding}${underline}`); +- } +- return annotatedLines.join("\n"); +- } +- errorUtils.printLoc = printLoc; +- class CompilerError extends Error { +- constructor(message, loc, rawSource) { +- super(message); +- // TODO: Create an error message that encourages users to open an issue at +- // https://github.com/captbaritone/eel-wasm/issues is they see this, and gives +- // Them an easy way to attach the right context. +- this.sourceContext = printLoc(loc, rawSource); +- this.loc = loc; +- } +- } +- class UserError extends CompilerError { +- } +- function createUserError(message, loc, rawSource) { +- return new UserError(message, loc, rawSource); +- } +- errorUtils.createUserError = createUserError; +- function createCompilerError(message, loc, rawSource) { +- return new CompilerError(message, loc, rawSource); +- } +- errorUtils.createCompilerError = createCompilerError; +- +- return errorUtils; +-} +- +-var hasRequiredParser; +- +-function requireParser () { +- if (hasRequiredParser) return parser$1; +- hasRequiredParser = 1; +- Object.defineProperty(parser$1, "__esModule", { value: true }); +- const preProcessor_1 = requirePreProcessor(); +- const envParser_1 = requireEnvParser(); +- const astUtils_1 = requireAstUtils(); +- const errorUtils_1 = requireErrorUtils(); +- function mapLoc(loc, mapper) { +- const first = preProcessor_1.getLoc(mapper, loc.first_column); +- const last = preProcessor_1.getLoc(mapper, loc.last_column); +- return { +- first_column: first.column, +- last_column: last.column, +- first_line: first.line, +- last_line: last.line, +- }; +- } +- function parse(code) { +- const [processedCode, mapper] = preProcessor_1.preProcess(code); +- try { +- const ast = envParser_1.parse(processedCode); +- return astUtils_1.mapAst(ast, (node) => { +- if (node.loc.first_line !== 1 || node.loc.last_line != 1) { +- throw errorUtils_1.createCompilerError("Unexpected multiline", node.loc, code); +- } +- return Object.assign(Object.assign({}, node), { loc: mapLoc(node.loc, mapper) }); +- }); +- } +- catch (e) { +- if (e.hash == null) { +- throw e; +- } +- throw errorUtils_1.createUserError(`Parse Error: ${e.message.split("\n")[3]}`, mapLoc(e.hash.loc, mapper), code); +- } +- } +- parser$1.parse = parse; +- +- return parser$1; +-} +- +-var compiler = {}; +- +-var emitter = {}; +- +-var encoding = {}; +- +-var ieee754 = {}; +- +-var hasRequiredIeee754; +- +-function requireIeee754 () { +- if (hasRequiredIeee754) return ieee754; +- hasRequiredIeee754 = 1; +- Object.defineProperty(ieee754, "__esModule", { value: true }); +- // Copied from https://github.com/feross/ieee754/blob/master/index.js +- function write(buffer, value) { +- // Originally these four were arguments, but we only ever use it like this. +- const offset = 0; +- let mLen = 52; +- const nBytes = 8; +- var e, m, c; +- var eLen = nBytes * 8 - mLen - 1; +- var eMax = (1 << eLen) - 1; +- var eBias = eMax >> 1; +- var rt = mLen === 23 ? Math.pow(2, -24) - Math.pow(2, -77) : 0; +- var i = 0 ; +- var d = 1 ; +- var s = value < 0 || (value === 0 && 1 / value < 0) ? 1 : 0; +- value = Math.abs(value); +- if (isNaN(value) || value === Infinity) { +- m = isNaN(value) ? 1 : 0; +- e = eMax; +- } +- else { +- e = Math.floor(Math.log(value) / Math.LN2); +- if (value * (c = Math.pow(2, -e)) < 1) { +- e--; +- c *= 2; +- } +- if (e + eBias >= 1) { +- value += rt / c; +- } +- else { +- value += rt * Math.pow(2, 1 - eBias); +- } +- if (value * c >= 2) { +- e++; +- c /= 2; +- } +- if (e + eBias >= eMax) { +- m = 0; +- e = eMax; +- } +- else if (e + eBias >= 1) { +- m = (value * c - 1) * Math.pow(2, mLen); +- e = e + eBias; +- } +- else { +- m = value * Math.pow(2, eBias - 1) * Math.pow(2, mLen); +- e = 0; +- } +- } +- for (; mLen >= 8; buffer[offset + i] = m & 0xff, i += d, m /= 256, mLen -= 8) { } +- e = (e << mLen) | m; +- eLen += mLen; +- for (; eLen > 0; buffer[offset + i] = e & 0xff, i += d, e /= 256, eLen -= 8) { } +- buffer[offset + i - d] |= s * 128; +- } +- ieee754.write = write; +- +- return ieee754; +-} +- +-var hasRequiredEncoding; +- +-function requireEncoding () { +- if (hasRequiredEncoding) return encoding; +- hasRequiredEncoding = 1; +- (function (exports) { +- var __importStar = (encoding && encoding.__importStar) || function (mod) { +- if (mod && mod.__esModule) return mod; +- var result = {}; +- if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; +- result["default"] = mod; +- return result; +- }; +- Object.defineProperty(exports, "__esModule", { value: true }); +- const ieee754 = __importStar(requireIeee754()); +- const utils_1 = requireUtils(); +- exports.MAGIC = [0x00, 0x61, 0x73, 0x6d]; +- exports.WASM_VERSION = [0x01, 0x00, 0x00, 0x00]; +- exports.EPSILON = 0.00001; +- // An intial attempt to construct a Wasm binary by hand. +- /* +- 0 custom section +- 1 type section +- 2 import section +- 3 function section +- 4 table section +- 5 memory section +- 6 global section +- 7 export section +- 8 start section +- 9 element section +- 10 code section +- 11 data section +- */ +- // https://webassembly.github.io/spec/core/binary/modules.html#sections +- exports.SECTION = { +- TYPE: 1, +- IMPORT: 2, +- FUNC: 3, +- MEMORY: 5, +- GLOBAL: 6, +- EXPORT: 7, +- CODE: 10, +- }; +- exports.EXPORT_TYPE = { +- FUNC: 0x00, +- TABLE: 0x01, +- MEMORY: 0x02, +- GLOBAL: 0x03, +- }; +- exports.op = { +- /* +- * Control Instructions +- * https://webassembly.github.io/spec/core/binary/instructions.html#control-instructions +- */ +- // unreachable: 0x00, +- // nop: 0x01, +- block: (blockType) => [0x02, blockType], +- loop: (blockType) => [0x03, blockType], +- if: (retType) => [0x04, retType], +- else: 0x05, +- end: 0x0b, +- // br: (i: number) => [0x0c, ...signedLEB128(i)], +- br_if: (i) => [0x0d, ...unsignedLEB128(i)], +- // br_table: 0x0d, +- // return: 0x0f, +- call: (i) => [0x10, ...unsignedLEB128(i)], +- // call_indirect: 0x11, +- /* +- * Parametric Instructions +- * https://webassembly.github.io/spec/core/binary/instructions.html#parametric-instructions +- */ +- drop: 0x1a, +- select: 0x1b, +- /* +- * Variable Instructions +- * https://webassembly.github.io/spec/core/binary/instructions.html#variable-instructions +- */ +- local_get: (i) => [0x20, ...unsignedLEB128(i)], +- local_set: (i) => [0x21, ...unsignedLEB128(i)], +- local_tee: (i) => [0x22, ...unsignedLEB128(i)], +- global_get: (i) => [0x23, ...unsignedLEB128(i)], +- global_set: (i) => [0x24, ...unsignedLEB128(i)], +- /* +- * Memory Instructions +- * https://webassembly.github.io/spec/core/binary/instructions.html#memory-instructions +- */ +- f64_load: (align, offset) => [ +- 0x2b, +- ...unsignedLEB128(align), +- ...unsignedLEB128(offset), +- ], +- f64_store: (align, offset) => [ +- 0x39, +- ...unsignedLEB128(align), +- ...unsignedLEB128(offset), +- ], +- /* +- * Numeric Instructions +- * https://webassembly.github.io/spec/core/binary/instructions.html#numeric-instructions +- */ +- i32_const: (i) => [0x41, ...signedLEB128(i)], +- // i64_const: 0x42, +- // f32_const: 0x43, +- f64_const: (i) => [0x44, ...encodef64(i)], +- i32_eqz: 0x45, +- // i32_eq: 0x46, +- i32_ne: 0x47, +- i32_lt_s: 0x48, +- i32_lt_u: 0x49, +- i32_gt_s: 0x4a, +- // i32_gt_u: 0x4b, +- i32_le_s: 0x4c, +- i32_le_u: 0x4d, +- i32_ge_s: 0x4e, +- // i32_ge_u: 0x4f, +- // [0x50...0x5a] i64 +- // [0x5b...0x60] f32 +- f64_eq: 0x61, +- f64_ne: 0x62, +- f64_lt: 0x63, +- f64_gt: 0x64, +- f64_le: 0x65, +- f64_ge: 0x66, +- // i32_clz: 0x67, +- // i32_ctz: 0x68, +- // i32_popcnt: 0x69, +- i32_add: 0x6a, +- i32_sub: 0x6b, +- i32_mul: 0x6c, +- // i32_div_s: 0x6d, +- // i32_div_u: 0x6e, +- i32_rem_s: 0x6f, +- // i32_rem_u: 0x70, +- i32_and: 0x71, +- i32_or: 0x72, +- // [0x73...0x78] More i32 +- // [0x79...0x8a] More i64 +- i64_rem_s: 0x81, +- i64_and: 0x83, +- i64_or: 0x84, +- // [0x8b...0x98] More f32 +- f64_abs: 0x99, +- f64_neg: 0x9a, +- f64_ceil: 0x9b, +- f64_floor: 0x9c, +- // f64_trunc: 0x9d, +- // f64_nearest: 0x9e, +- f64_sqrt: 0x9f, +- f64_add: 0xa0, +- f64_sub: 0xa1, +- f64_mul: 0xa2, +- f64_div: 0xa3, +- f64_min: 0xa4, +- f64_max: 0xa5, +- // f64_copysign: 0xa6, +- i32_trunc_f64_s: 0xaa, +- i32_trunc_f64_u: 0xab, +- i64_trunc_s_f64: 0xb0, +- f64_convert_i64_s: 0xb9, +- f64_convert_i32_s: 0xb7, +- }; +- // https://webassembly.github.io/spec/core/binary/instructions.html#binary-blocktype +- exports.VAL_TYPE = { +- i32: 0x7f, +- i64: 0x7e, +- f32: 0x7d, +- f64: 0x7c, +- }; +- exports.MUTABILITY = { +- const: 0x00, +- var: 0x01, +- }; +- exports.BLOCK = { +- void: 0x40, +- i32: 0x7f, +- i64: 0x7e, +- f32: 0x7d, +- f64: 0x7c, +- }; +- // http://webassembly.github.io/spec/core/binary/types.html#function-types +- exports.FUNCTION_TYPE = 0x60; +- // I think these might actually be specific to importdesc +- exports.MEMORY_IDX = 0x02; +- exports.GLOBAL_TYPE = 0x03; +- exports.TYPE_IDX = 0x00; +- // Takes an f64 on the stack and leaves an int32 boolean representing if it's +- // within epsilon of zero. +- exports.IS_ZEROISH = [ +- exports.op.f64_abs, +- ...exports.op.f64_const(exports.EPSILON), +- exports.op.f64_lt, +- ]; +- exports.IS_NOT_ZEROISH = [ +- exports.op.f64_abs, +- ...exports.op.f64_const(exports.EPSILON), +- exports.op.f64_gt, +- ]; +- // f64 +- function encodef64(num) { +- const arr = new Uint8Array(8); +- ieee754.write(arr, num); +- return arr; +- } +- exports.encodef64 = encodef64; +- exports.encodeString = (str) => [str.length].concat(str.split("").map(s => s.charCodeAt(0))); +- function unsignedLEB128(n) { +- const buffer = []; +- do { +- let byte = n & 0x7f; +- n >>>= 7; +- if (n !== 0) { +- byte |= 0x80; +- } +- buffer.push(byte); +- } while (n !== 0); +- return buffer; +- } +- exports.unsignedLEB128 = unsignedLEB128; +- // https://github.com/shmishtopher/wasm-LEB128/blob/2f1039636e758293e571f996e8012c4d69f4b58f/lib/index.js#L6 +- function signedLEB128(value) { +- let bytes = []; +- let byte = 0x00; +- let size = Math.ceil(Math.log2(Math.abs(value))); +- let negative = value < 0; +- let more = true; +- while (more) { +- byte = value & 127; +- value = value >> 7; +- if (negative) { +- value = value | -(1 << (size - 7)); +- } +- if ((value == 0 && (byte & 0x40) == 0) || +- (value == -1 && (byte & 0x40) == 0x40)) { +- more = false; +- } +- else { +- byte = byte | 128; +- } +- bytes.push(byte); +- } +- return bytes; +- } +- exports.signedLEB128 = signedLEB128; +- // https://webassembly.github.io/spec/core/binary/conventions.html#binary-vec +- // Vectors are encoded with their length followed by their element sequence +- exports.encodeFlatVector = (data) => unsignedLEB128(data.length).concat(data); +- exports.encodeNestedVector = (data) => unsignedLEB128(data.length).concat(utils_1.flatten(data)); +- // subSections is an array of arrays +- function encodeSection(type, subSections) { +- // Sections are all optional, so if we get an empty vector of subSections, we +- // can omit the whole section. +- if (subSections.length === 0) { +- return []; +- } +- // The size of this vector is not needed for decoding, but can be +- // used to skip sections when navigating through a binary. +- // TODO: Remove this assertion once we are more confident in our output. +- const vec = exports.encodeFlatVector(exports.encodeNestedVector(subSections)); +- vec.unshift(type); +- return vec; +- } +- exports.encodeSection = encodeSection; +- +- } (encoding)); +- return encoding; +-} +- +-var shims = {}; +- +-var hasRequiredShims; +- +-function requireShims () { +- if (hasRequiredShims) return shims; +- hasRequiredShims = 1; +- Object.defineProperty(shims, "__esModule", { value: true }); +- const EPSILON = 0.00001; +- const shims$1 = { +- // TODO: Reimplement some of these functions natively in Wasm? +- sin: Math.sin, +- cos: Math.cos, +- tan: Math.tan, +- asin: Math.asin, +- acos: Math.acos, +- atan: Math.atan, +- atan2: Math.atan2, +- rand: a => Math.random() * a, +- pow: Math.pow, +- log: Math.log, +- log10: Math.log10, +- exp: Math.exp, +- sigmoid: function (x, y) { +- const t = 1 + Math.exp(-x * y); +- return Math.abs(t) > EPSILON ? 1.0 / t : 0; +- }, +- }; +- shims.default = shims$1; +- +- return shims; +-} +- +-var wasmFunctions = {}; +- +-var constants = {}; +- +-var hasRequiredConstants; +- +-function requireConstants () { +- if (hasRequiredConstants) return constants; +- hasRequiredConstants = 1; +- (function (exports) { +- Object.defineProperty(exports, "__esModule", { value: true }); +- // https://webassembly.github.io/spec/core/exec/runtime.html#page-size +- const WASM_PAGE_SIZE = 65536; +- const BYTES_PER_F64 = 8; +- const BUFFER_COUNT = 2; +- // https://github.com/WACUP/vis_milk2/blob/de9625a89e724afe23ed273b96b8e48496095b6c/ns-eel2/ns-eel.h#L136 +- exports.MAX_LOOP_COUNT = 1048576; +- // The number of items allowed in each buffer (megabuf/gmegabuf). +- // https://github.com/WACUP/vis_milk2/blob/de9625a89e724afe23ed273b96b8e48496095b6c/ns-eel2/ns-eel.h#L145 +- exports.BUFFER_SIZE = 65536 * 128; +- exports.WASM_MEMORY_SIZE = Math.ceil((exports.BUFFER_SIZE * BYTES_PER_F64 * BUFFER_COUNT) / WASM_PAGE_SIZE); +- +- } (constants)); +- return constants; +-} +- +-var hasRequiredWasmFunctions; +- +-function requireWasmFunctions () { +- if (hasRequiredWasmFunctions) return wasmFunctions; +- hasRequiredWasmFunctions = 1; +- Object.defineProperty(wasmFunctions, "__esModule", { value: true }); +- const encoding_1 = requireEncoding(); +- const constants_1 = requireConstants(); +- wasmFunctions.localFuncMap = { +- sqr: { +- args: [encoding_1.VAL_TYPE.f64], +- returns: [encoding_1.VAL_TYPE.f64], +- binary: [...encoding_1.op.local_get(0), ...encoding_1.op.local_get(0), encoding_1.op.f64_mul], +- }, +- bor: { +- args: [encoding_1.VAL_TYPE.f64, encoding_1.VAL_TYPE.f64], +- returns: [encoding_1.VAL_TYPE.f64], +- binary: [ +- ...encoding_1.op.local_get(0), +- ...encoding_1.IS_NOT_ZEROISH, +- ...encoding_1.op.local_get(1), +- ...encoding_1.IS_NOT_ZEROISH, +- encoding_1.op.i32_or, +- ...encoding_1.op.i32_const(0), +- encoding_1.op.i32_ne, +- encoding_1.op.f64_convert_i32_s, +- ], +- }, +- band: { +- args: [encoding_1.VAL_TYPE.f64, encoding_1.VAL_TYPE.f64], +- returns: [encoding_1.VAL_TYPE.f64], +- binary: [ +- ...encoding_1.op.local_get(0), +- ...encoding_1.IS_NOT_ZEROISH, +- ...encoding_1.op.local_get(1), +- ...encoding_1.IS_NOT_ZEROISH, +- encoding_1.op.i32_and, +- ...encoding_1.op.i32_const(0), +- encoding_1.op.i32_ne, +- encoding_1.op.f64_convert_i32_s, +- ], +- }, +- sign: { +- args: [encoding_1.VAL_TYPE.f64], +- returns: [encoding_1.VAL_TYPE.f64], +- binary: [ +- ...encoding_1.op.f64_const(0), +- ...encoding_1.op.local_get(0), +- encoding_1.op.f64_lt, +- ...encoding_1.op.local_get(0), +- ...encoding_1.op.f64_const(0), +- encoding_1.op.f64_lt, +- encoding_1.op.i32_sub, +- encoding_1.op.f64_convert_i32_s, +- ], +- }, +- mod: { +- args: [encoding_1.VAL_TYPE.f64, encoding_1.VAL_TYPE.f64], +- returns: [encoding_1.VAL_TYPE.f64], +- localVariables: [encoding_1.VAL_TYPE.i32], +- // TODO: Simplify all this type coersion +- binary: [ +- ...encoding_1.op.local_get(1), +- encoding_1.op.i32_trunc_f64_s, +- ...encoding_1.op.local_tee(2), +- ...encoding_1.op.i32_const(0), +- encoding_1.op.i32_ne, +- ...encoding_1.op.if(encoding_1.BLOCK.f64), +- ...encoding_1.op.local_get(0), +- encoding_1.op.i32_trunc_f64_s, +- ...encoding_1.op.local_get(2), +- encoding_1.op.i32_rem_s, +- encoding_1.op.f64_convert_i32_s, +- encoding_1.op.else, +- ...encoding_1.op.f64_const(0), +- encoding_1.op.end, +- ], +- }, +- bitwiseOr: { +- args: [encoding_1.VAL_TYPE.f64, encoding_1.VAL_TYPE.f64], +- returns: [encoding_1.VAL_TYPE.f64], +- binary: [ +- ...encoding_1.op.local_get(0), +- encoding_1.op.i64_trunc_s_f64, +- ...encoding_1.op.local_get(1), +- encoding_1.op.i64_trunc_s_f64, +- encoding_1.op.i64_or, +- encoding_1.op.f64_convert_i64_s, +- ], +- }, +- bitwiseAnd: { +- args: [encoding_1.VAL_TYPE.f64, encoding_1.VAL_TYPE.f64], +- returns: [encoding_1.VAL_TYPE.f64], +- binary: [ +- ...encoding_1.op.local_get(0), +- encoding_1.op.i64_trunc_s_f64, +- ...encoding_1.op.local_get(1), +- encoding_1.op.i64_trunc_s_f64, +- encoding_1.op.i64_and, +- encoding_1.op.f64_convert_i64_s, +- ], +- }, +- div: { +- args: [encoding_1.VAL_TYPE.f64, encoding_1.VAL_TYPE.f64], +- returns: [encoding_1.VAL_TYPE.f64], +- localVariables: [encoding_1.VAL_TYPE.i32], +- binary: [ +- ...encoding_1.op.local_get(1), +- ...encoding_1.op.f64_const(0), +- encoding_1.op.f64_ne, +- ...encoding_1.op.if(encoding_1.BLOCK.f64), +- ...encoding_1.op.local_get(0), +- ...encoding_1.op.local_get(1), +- encoding_1.op.f64_div, +- encoding_1.op.else, +- ...encoding_1.op.f64_const(0), +- encoding_1.op.end, +- ], +- }, +- // Takes a float buffer index and converts it to an int. Values out of range +- // are returned as `-1`. +- // +- // NOTE: There's actually a subtle bug that exists in Milkdrop's Eel +- // implementation, which we reproduce here. +- // +- // Wasm's `trunc()` rounds towards zero. This means that for index `-1` we +- // will return zero, since: `roundTowardZero(-1 + EPSILON) == 0` +- // +- // A subsequent check handles negative indexes, so negative indexes > than +- // `-1` are not affected. +- _getBufferIndex: { +- args: [encoding_1.VAL_TYPE.f64 /* 0: $index */], +- returns: [encoding_1.VAL_TYPE.i32 /* $noramlizedIndex */], +- localVariables: [ +- encoding_1.VAL_TYPE.f64, +- encoding_1.VAL_TYPE.i32, +- ], +- binary: [ +- ...encoding_1.op.f64_const(encoding_1.EPSILON), +- ...encoding_1.op.local_get(0), +- encoding_1.op.f64_add, +- // STACK: [$i + EPSILON] +- ...encoding_1.op.local_tee(1), +- encoding_1.op.i32_trunc_f64_s, +- // TODO We could probably make this a tee and get rid of the next get if we swap the final condition +- ...encoding_1.op.local_set(2), +- // STACK: [] +- ...encoding_1.op.i32_const(-1), +- ...encoding_1.op.local_get(2), +- // STACK: [-1, $truncated] +- ...encoding_1.op.i32_const(8), +- encoding_1.op.i32_mul, +- // STACK: [-1, $truncated * 8] +- ...encoding_1.op.local_get(2), +- ...encoding_1.op.i32_const(0), +- // STACK: [-1, $truncated * 8, $truncated, 0] +- encoding_1.op.i32_lt_s, +- // STACK: [-1, $truncated * 8, ] +- ...encoding_1.op.local_get(2), +- ...encoding_1.op.i32_const(constants_1.BUFFER_SIZE - 1), +- encoding_1.op.i32_gt_s, +- // STACK: [-1, $truncated * 8, , ] +- encoding_1.op.i32_or, +- // STACK: [-1, $truncated * 8, ] +- encoding_1.op.select, +- ], +- }, +- }; +- +- return wasmFunctions; +-} +- +-var hasRequiredEmitter; +- +-function requireEmitter () { +- if (hasRequiredEmitter) return emitter; +- hasRequiredEmitter = 1; +- var __importDefault = (emitter && emitter.__importDefault) || function (mod) { +- return (mod && mod.__esModule) ? mod : { "default": mod }; +- }; +- Object.defineProperty(emitter, "__esModule", { value: true }); +- const encoding_1 = requireEncoding(); +- const shims_1 = __importDefault(requireShims()); +- const errorUtils_1 = requireErrorUtils(); +- const wasmFunctions_1 = requireWasmFunctions(); +- const utils_1 = requireUtils(); +- const constants_1 = requireConstants(); +- function emit(ast, context) { +- var _a, _b, _c; +- switch (ast.type) { +- case "SCRIPT": { +- const body = ast.body.map((statement, i) => { +- return [...emit(statement, context), encoding_1.op.drop]; +- }); +- return utils_1.flatten(body); +- } +- case "EXPRESSION_BLOCK": { +- return emitExpressionBlock(ast.body, context); +- } +- case "BINARY_EXPRESSION": { +- const left = emit(ast.left, context); +- const right = emit(ast.right, context); +- const operatorToOps = { +- "+": [encoding_1.op.f64_add], +- "-": [encoding_1.op.f64_sub], +- "*": [encoding_1.op.f64_mul], +- "/": context.resolveFunc("div"), +- "%": context.resolveFunc("mod"), +- "|": context.resolveFunc("bitwiseOr"), +- "&": context.resolveFunc("bitwiseAnd"), +- "^": context.resolveFunc("pow"), +- // Comparison operators +- "==": [encoding_1.op.f64_sub, ...encoding_1.IS_ZEROISH, encoding_1.op.f64_convert_i32_s], +- "!=": [encoding_1.op.f64_sub, ...encoding_1.IS_NOT_ZEROISH, encoding_1.op.f64_convert_i32_s], +- "<": [encoding_1.op.f64_lt, encoding_1.op.f64_convert_i32_s], +- ">": [encoding_1.op.f64_gt, encoding_1.op.f64_convert_i32_s], +- "<=": [encoding_1.op.f64_le, encoding_1.op.f64_convert_i32_s], +- ">=": [encoding_1.op.f64_ge, encoding_1.op.f64_convert_i32_s], +- }; +- const code = operatorToOps[ast.operator]; +- if (code == null) { +- throw errorUtils_1.createCompilerError(`Unknown binary expression operator ${ast.operator}`, ast.loc, context.rawSource); +- } +- return [...left, ...right, ...code]; +- } +- case "CALL_EXPRESSION": { +- const functionName = ast.callee.value; +- // Destructure this so that TypeScript knows it won't get mutated. +- const argList = ast.arguments; +- const assertArity = (arity) => { +- if (argList.length < arity) { +- throw errorUtils_1.createUserError(`Too few arguments passed to \`${functionName}()\`. Expected ${arity} but only got ${argList.length}.`, ast.loc, context.rawSource); +- } +- if (argList.length > arity) { +- throw errorUtils_1.createUserError(`Too many arguments passed to \`${functionName}()\`. Expected ${arity} but got ${argList.length}.`, argList[arity].loc, context.rawSource); +- } +- }; +- // Some functions have special behavior +- switch (functionName) { +- case "exec2": +- assertArity(2); +- return emitExpressionBlock(ast.arguments, context); +- case "exec3": +- assertArity(3); +- return emitExpressionBlock(ast.arguments, context); +- case "if": +- assertArity(3); +- const [test, consiquent, alternate] = ast.arguments; +- return emitConditional(test, consiquent, alternate, context); +- case "while": +- assertArity(1); +- return emitWhile(ast.arguments[0], context); +- case "loop": +- assertArity(2); +- return emitLoop(ast.arguments[0], ast.arguments[1], context); +- case "megabuf": +- case "gmegabuf": +- assertArity(1); +- const index = context.resolveLocal(encoding_1.VAL_TYPE.i32); +- return [ +- ...emit(ast.arguments[0], context), +- ...((_a = context.resolveFunc("_getBufferIndex")) !== null && _a !== void 0 ? _a : []), +- ...encoding_1.op.local_tee(index), +- ...encoding_1.op.i32_const(-1), +- encoding_1.op.i32_ne, +- // STACK: [in range] +- ...encoding_1.op.if(encoding_1.BLOCK.f64), +- ...encoding_1.op.local_get(index), +- ...encoding_1.op.f64_load(3, emitAddMemoryOffset(functionName)), +- encoding_1.op.else, +- ...encoding_1.op.f64_const(0), +- encoding_1.op.end, +- ]; +- case "assign": +- assertArity(2); +- const variableIdentifier = ast.arguments[0]; +- if (variableIdentifier.type != "IDENTIFIER") { +- throw errorUtils_1.createUserError("Expected the first argument of `assign()` to be an identifier.", variableIdentifier.loc, context.rawSource); +- } +- const resolvedName = context.resolveVar(variableIdentifier.value); +- return [ +- ...emit(ast.arguments[1], context), +- ...encoding_1.op.global_set(resolvedName), +- ...encoding_1.op.global_get(resolvedName), +- ]; +- } +- // Function calls which can be linlined +- const args = utils_1.flatten(ast.arguments.map(node => emit(node, context))); +- // This is just a continuation of the above switch statement, but it's for functions which all parse their args the same. +- switch (functionName) { +- case "abs": +- assertArity(1); +- return [...args, encoding_1.op.f64_abs]; +- case "sqrt": +- assertArity(1); +- return [...args, encoding_1.op.f64_abs, encoding_1.op.f64_sqrt]; +- case "int": +- assertArity(1); +- return [...args, encoding_1.op.f64_floor]; +- case "min": +- assertArity(2); +- return [...args, encoding_1.op.f64_min]; +- case "max": +- assertArity(2); +- return [...args, encoding_1.op.f64_max]; +- case "above": +- assertArity(2); +- return [...args, encoding_1.op.f64_gt, encoding_1.op.f64_convert_i32_s]; +- case "below": +- assertArity(2); +- return [...args, encoding_1.op.f64_lt, encoding_1.op.f64_convert_i32_s]; +- case "equal": +- assertArity(2); +- return [...args, encoding_1.op.f64_sub, ...encoding_1.IS_ZEROISH, encoding_1.op.f64_convert_i32_s]; +- case "bnot": +- assertArity(1); +- return [...args, ...encoding_1.IS_ZEROISH, encoding_1.op.f64_convert_i32_s]; +- case "floor": +- assertArity(1); +- return [...args, encoding_1.op.f64_floor]; +- case "ceil": +- assertArity(1); +- return [...args, encoding_1.op.f64_ceil]; +- } +- const invocation = context.resolveFunc(functionName); +- if (invocation == null || +- // Ensure this isn't a private function. This is a bit awkward becuase +- // Eel does implement some _ functions but while they are _intended_ to be +- // private, they accidentally expose them. We should find a cleaner way +- // to defining user accessible functions vs utility functions used by +- // the compiler. +- functionName.startsWith("_")) { +- throw errorUtils_1.createUserError(`"${functionName}" is not defined.`, ast.callee.loc, context.rawSource); +- } +- if (shims_1.default[functionName] != null) { +- assertArity(shims_1.default[functionName].length); +- } +- else if (wasmFunctions_1.localFuncMap[functionName] != null) { +- assertArity(wasmFunctions_1.localFuncMap[functionName].args.length); +- } +- else { +- throw errorUtils_1.createCompilerError(`Missing arity information for the function \`${functionName}()\``, ast.callee.loc, context.rawSource); +- } +- return [...args, ...invocation]; +- } +- case "ASSIGNMENT_EXPRESSION": { +- const { left } = ast; +- const rightCode = emit(ast.right, context); +- const mutationCode = getAssignmentOperatorMutation(ast, context); +- if (left.type === "IDENTIFIER") { +- const resolvedName = context.resolveVar(left.value); +- // TODO: In lots of cases we don't care about the return value. In those +- // cases we should try to find a way to omit the `get/drop` combo. +- // Peephole optimization seems to be the conventional way to do this. +- // https://en.wikipedia.org/wiki/Peephole_optimization +- const get = encoding_1.op.global_get(resolvedName); +- const set = encoding_1.op.global_set(resolvedName); +- // `=` is a special case in that it does not need the original value. +- if (mutationCode === null) { +- return [...rightCode, ...set, ...get]; +- } +- return [...get, ...rightCode, ...mutationCode, ...set, ...get]; +- } +- if (left.type !== "CALL_EXPRESSION") { +- throw errorUtils_1.createCompilerError( +- // @ts-ignore This is a guard in case the parser has an error +- `Unexpected left hand side type for assignment: ${left.type}`, ast.loc, context.rawSource); +- } +- // Special assignment case for `megabuf(n) = e` and `gmegabuf(n) = e`. +- const localIndex = context.resolveLocal(encoding_1.VAL_TYPE.i32); +- if (left.arguments.length !== 1) { +- throw errorUtils_1.createUserError(`Expected 1 argument when assinging to a buffer but got ${left.arguments.length}.`, left.arguments.length === 0 ? left.loc : left.arguments[1].loc, context.rawSource); +- } +- const bufferName = left.callee.value; +- if (bufferName !== "gmegabuf" && bufferName !== "megabuf") { +- throw errorUtils_1.createUserError("The only function calls which may be assigned to are `gmegabuf()` and `megabuf()`.", left.callee.loc, context.rawSource); +- } +- const addOffset = emitAddMemoryOffset(bufferName); +- if (mutationCode === null) { +- // TODO: Move this to wasmFunctions once we know how to call functions +- // from within functions (need to get the offset). +- const unnormalizedIndex = context.resolveLocal(encoding_1.VAL_TYPE.i32); +- const rightValue = context.resolveLocal(encoding_1.VAL_TYPE.f64); +- return [ +- // Emit the right hand side unconditionally to ensure it always runs. +- ...rightCode, +- ...encoding_1.op.local_set(rightValue), +- ...emit(left.arguments[0], context), +- ...((_b = context.resolveFunc("_getBufferIndex")) !== null && _b !== void 0 ? _b : []), +- ...encoding_1.op.local_tee(unnormalizedIndex), +- ...encoding_1.op.i32_const(0), +- encoding_1.op.i32_lt_s, +- // STACK: [is the index out of range?] +- ...encoding_1.op.if(encoding_1.BLOCK.f64), +- ...encoding_1.op.f64_const(0), +- encoding_1.op.else, +- ...encoding_1.op.local_get(unnormalizedIndex), +- ...encoding_1.op.local_tee(localIndex), +- // STACK: [buffer index] +- ...encoding_1.op.local_get(rightValue), +- // STACK: [buffer index, right] +- ...encoding_1.op.f64_store(3, addOffset), +- // STACK: [] +- ...encoding_1.op.local_get(rightValue), +- // STACK: [Right/Buffer value] +- encoding_1.op.end, +- ]; +- } +- // TODO: Move this to wasmFunctions once we know how to call functions +- // from within functions (need to get the offset). +- const index = context.resolveLocal(encoding_1.VAL_TYPE.i32); +- const inBounds = context.resolveLocal(encoding_1.VAL_TYPE.i32); +- const rightValue = context.resolveLocal(encoding_1.VAL_TYPE.f64); +- const result = context.resolveLocal(encoding_1.VAL_TYPE.f64); +- return [ +- ...rightCode, +- ...encoding_1.op.local_set(rightValue), +- ...emit(left.arguments[0], context), +- ...((_c = context.resolveFunc("_getBufferIndex")) !== null && _c !== void 0 ? _c : []), +- ...encoding_1.op.local_tee(index), +- // STACK: [index] +- ...encoding_1.op.i32_const(-1), +- encoding_1.op.i32_ne, +- ...encoding_1.op.local_tee(inBounds), +- ...encoding_1.op.if(encoding_1.BLOCK.f64), +- ...encoding_1.op.local_get(index), +- ...encoding_1.op.f64_load(3, addOffset), +- encoding_1.op.else, +- ...encoding_1.op.f64_const(0), +- encoding_1.op.end, +- // STACK: [current value from memory || 0] +- // Apply the mutation +- ...encoding_1.op.local_get(rightValue), +- ...mutationCode, +- ...encoding_1.op.local_tee(result), +- // STACK: [new value] +- ...encoding_1.op.local_get(inBounds), +- ...encoding_1.op.if(encoding_1.BLOCK.void), +- ...encoding_1.op.local_get(index), +- ...encoding_1.op.local_get(result), +- ...encoding_1.op.f64_store(3, addOffset), +- encoding_1.op.end, +- ]; +- } +- case "LOGICAL_EXPRESSION": { +- const left = emit(ast.left, context); +- const right = emit(ast.right, context); +- const behaviorMap = { +- "&&": { +- comparison: encoding_1.IS_ZEROISH, +- shortCircutValue: 0, +- }, +- "||": { +- comparison: encoding_1.IS_NOT_ZEROISH, +- shortCircutValue: 1, +- }, +- }; +- const behavior = behaviorMap[ast.operator]; +- if (behavior == null) { +- throw errorUtils_1.createCompilerError(`Unknown logical expression operator ${ast.operator}`, ast.loc, context.rawSource); +- } +- const { comparison, shortCircutValue } = behavior; +- return [ +- ...left, +- ...comparison, +- ...encoding_1.op.if(encoding_1.BLOCK.f64), +- ...encoding_1.op.f64_const(shortCircutValue), +- encoding_1.op.else, +- ...right, +- ...encoding_1.IS_NOT_ZEROISH, +- encoding_1.op.f64_convert_i32_s, +- encoding_1.op.end, +- ]; +- } +- case "UNARY_EXPRESSION": { +- const value = emit(ast.value, context); +- const operatorToCode = { +- "-": [encoding_1.op.f64_neg], +- "+": [], +- "!": [...encoding_1.IS_ZEROISH, encoding_1.op.f64_convert_i32_s], +- }; +- const code = operatorToCode[ast.operator]; +- if (code == null) { +- throw errorUtils_1.createCompilerError(`Unknown logical unary operator ${ast.operator}`, ast.loc, context.rawSource); +- } +- return [...value, ...code]; +- } +- case "IDENTIFIER": +- const variableName = ast.value; +- // TODO: It's a bit odd that not every IDENTIFIER node gets emitted. In +- // function calls and assignments we just peek at the name and never emit +- // it. +- return encoding_1.op.global_get(context.resolveVar(variableName)); +- case "NUMBER_LITERAL": +- return encoding_1.op.f64_const(ast.value); +- default: +- throw errorUtils_1.createCompilerError( +- // @ts-ignore This runtime check is here because the caller may not be type-checked +- `Unknown AST node type ${ast.type}`, +- // @ts-ignore This runtime check is here because the caller may not be type-checked +- ast.loc, context.rawSource); +- } +- } +- emitter.emit = emit; +- function emitExpressionBlock(body, context) { +- const statements = body.map((statement, i) => { +- return emit(statement, context); +- }); +- return utils_1.flatten(utils_1.arrayJoin(statements, [encoding_1.op.drop])); +- } +- function emitWhile(expression, context) { +- const body = emit(expression, context); +- const iterationCount = context.resolveLocal(encoding_1.VAL_TYPE.i32); +- return [ +- ...encoding_1.op.i32_const(0), +- ...encoding_1.op.local_set(iterationCount), +- ...encoding_1.op.loop(encoding_1.BLOCK.void), +- // Increment and check loop count +- ...encoding_1.op.local_get(iterationCount), +- ...encoding_1.op.i32_const(1), +- encoding_1.op.i32_add, +- ...encoding_1.op.local_tee(iterationCount), +- // STACK: [iteration count] +- ...encoding_1.op.i32_const(constants_1.MAX_LOOP_COUNT), +- encoding_1.op.i32_lt_u, +- // STACK: [loop in range] +- ...body, +- ...encoding_1.IS_NOT_ZEROISH, +- // STACK: [loop in range, body is truthy] +- encoding_1.op.i32_and, +- // STACK: [can continue] +- ...encoding_1.op.br_if(0), +- encoding_1.op.end, +- ...encoding_1.op.f64_const(0), +- ]; +- } +- function emitLoop(count, expression, context) { +- const body = emit(expression, context); +- const localIndex = context.resolveLocal(encoding_1.VAL_TYPE.i32); +- return [ +- ...encoding_1.op.block(encoding_1.BLOCK.void), +- // Assign the count to a variable +- ...emit(count, context), +- encoding_1.op.i32_trunc_f64_s, +- ...encoding_1.op.local_tee(localIndex), +- ...encoding_1.op.i32_const(0), +- encoding_1.op.i32_le_s, +- ...encoding_1.op.br_if(1), +- ...encoding_1.op.loop(encoding_1.BLOCK.void), +- // Run the body +- ...body, +- encoding_1.op.drop, +- // Decrement the count +- ...encoding_1.op.local_get(localIndex), +- ...encoding_1.op.i32_const(1), +- encoding_1.op.i32_sub, +- ...encoding_1.op.local_tee(localIndex), +- ...encoding_1.op.i32_const(0), +- encoding_1.op.i32_ne, +- ...encoding_1.op.br_if(0), +- encoding_1.op.end, +- encoding_1.op.end, +- ...encoding_1.op.f64_const(0), +- ]; +- } +- function emitConditional(test, consiquent, alternate, context) { +- // TODO: In some cases https://webassembly.studio/ compiles these to use `select`. +- // Is that an optimization that we might want as well? +- return [ +- ...emit(test, context), +- ...encoding_1.IS_NOT_ZEROISH, +- ...encoding_1.op.if(encoding_1.BLOCK.f64), +- ...emit(consiquent, context), +- encoding_1.op.else, +- ...emit(alternate, context), +- encoding_1.op.end, +- ]; +- } +- // There are two sections of memory. This function emits code to add the correct +- // offset to an i32 index already on the stack. +- function emitAddMemoryOffset(name) { +- switch (name) { +- case "gmegabuf": +- return constants_1.BUFFER_SIZE * 8; +- case "megabuf": +- return 0; +- } +- } +- function getAssignmentOperatorMutation(ast, context) { +- const operatorToCode = { +- "+=": [encoding_1.op.f64_add], +- "-=": [encoding_1.op.f64_sub], +- "*=": [encoding_1.op.f64_mul], +- "/=": [encoding_1.op.f64_div], +- "%=": context.resolveFunc("mod"), +- "=": null, +- }; +- const operatorCode = operatorToCode[ast.operator]; +- if (operatorCode === undefined) { +- throw errorUtils_1.createCompilerError(`Unknown assignment operator "${ast.operator}"`, ast.loc, context.rawSource); +- } +- return operatorCode; +- } +- +- return emitter; +-} +- +-var hasRequiredCompiler; +- +-function requireCompiler () { +- if (hasRequiredCompiler) return compiler; +- hasRequiredCompiler = 1; +- var __importDefault = (compiler && compiler.__importDefault) || function (mod) { +- return (mod && mod.__esModule) ? mod : { "default": mod }; +- }; +- var __importStar = (compiler && compiler.__importStar) || function (mod) { +- if (mod && mod.__esModule) return mod; +- var result = {}; +- if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; +- result["default"] = mod; +- return result; +- }; +- Object.defineProperty(compiler, "__esModule", { value: true }); +- const parser_1 = requireParser(); +- const emitter_1 = requireEmitter(); +- const encoding_1 = requireEncoding(); +- const shims_1 = __importDefault(requireShims()); +- const Utils = __importStar(requireUtils()); +- const wasmFunctions_1 = requireWasmFunctions(); +- const constants_1 = requireConstants(); +- function compileModule({ pools, functions: funcs, eelVersion = 2, preParsed = false, }) { +- if (Object.keys(pools).includes("shims")) { +- throw new Error('You may not name a pool "shims". "shims" is reserved for injected JavaScript functions.'); +- } +- // Collect all the globals that we expect to get as imports. +- const importedVars = []; +- Object.entries(pools).forEach(([poolName, pool]) => { +- pool.forEach(variableName => { +- importedVars.push([poolName, variableName]); +- }); +- }); +- // Ensure all the imported globals get the first ids. +- const varResolver = new Utils.ScopedIdMap(); +- importedVars.forEach(([poolName, variableName]) => { +- varResolver.get(poolName, variableName); +- }); +- const functionImports = Object.entries(shims_1.default).map(([name, func]) => { +- return { +- args: new Array(func.length).fill(null).map(_ => encoding_1.VAL_TYPE.f64), +- // Shims implicitly always return a number +- returns: [encoding_1.VAL_TYPE.f64], +- name, +- }; +- }); +- const localFuncOrder = []; +- const moduleFuncs = []; +- Object.entries(funcs).forEach(([name, { pool, code }]) => { +- if (pools[pool] == null) { +- const poolsList = Object.keys(pools); +- if (poolsList.length === 0) { +- throw new Error(`The function "${name}" was declared as using a variable ` + +- `pool named "${pool}" but no pools were defined.`); +- } +- throw new Error(`The function "${name}" was declared as using a variable ` + +- `pool named "${pool}" which is not among the variable ` + +- `pools defined. The defined variable pools are: ` + +- `${Utils.formatList(poolsList)}.`); +- } +- const ast = preParsed ? code : parser_1.parse(code); +- if (typeof ast === "string") { +- // TODO: Change the API so this can be enforced by types +- throw new Error("Got passed unparsed code without setting the preParsed flag"); +- } +- if (ast.type !== "SCRIPT") { +- throw new Error("Invalid AST"); +- } +- if (ast.body.length === 0) { +- return; +- } +- const localVariables = []; +- const context = { +- resolveVar: name => { +- // The `reg00`-`reg99` variables are special in that they are shared between all pools. +- if (/^reg\d\d$/.test(name)) { +- return varResolver.get(null, name); +- } +- return varResolver.get(pool, name); +- }, +- resolveLocal: type => { +- // TODO: We could provide a way for the emitter to release a local +- // variable so that we can reuse it, much in the same way a traditional +- // compiler does in register allocation. +- localVariables.push(type); +- return localVariables.length - 1; +- }, +- resolveFunc: name => { +- // If this is a shim, return the shim index. +- const shimdex = functionImports.findIndex(func => func.name === name); +- if (shimdex !== -1) { +- const call = encoding_1.op.call(shimdex); +- if (name === "rand" && eelVersion === 1) { +- return [...call, encoding_1.op.f64_floor]; +- } +- return call; +- } +- // If it's not a shim and it's not a defined function, return null. +- // The emitter will generate a nice error. +- if (wasmFunctions_1.localFuncMap[name] == null) { +- return null; +- } +- let index = localFuncOrder.indexOf(name); +- if (index === -1) { +- localFuncOrder.push(name); +- index = localFuncOrder.length - 1; +- } +- return encoding_1.op.call(index + functionImports.length); +- }, +- rawSource: code, +- }; +- const binary = emitter_1.emit(ast, context); +- moduleFuncs.push({ +- binary, +- exportName: name, +- args: [], +- returns: [], +- localVariables, +- }); +- }); +- const localFuncs = localFuncOrder.map(name => { +- const func = wasmFunctions_1.localFuncMap[name]; +- // This check is technicaly redundant since we check inside resolveLocalFunc +- // in the compiler context. It's here just to catch potential compiler bugs. +- if (func == null) { +- throw new Error(`Undefined local function "${name}"`); +- } +- return func; +- }); +- // Given a function definition, return a hashable string representation of its signature. +- const getSignatureKey = (func) => { +- return [...func.args, "|", ...func.returns].join("-"); +- }; +- // https://webassembly.github.io/spec/core/binary/modules.html#type-section +- const types = []; +- const typeIndexByKey = new Map(); +- [...functionImports, ...localFuncs, ...moduleFuncs].forEach(func => { +- const key = getSignatureKey(func); +- if (typeIndexByKey.has(key)) { +- return; +- } +- types.push([ +- encoding_1.FUNCTION_TYPE, +- ...encoding_1.encodeFlatVector(func.args), +- ...encoding_1.encodeFlatVector(func.returns), +- ]); +- typeIndexByKey.set(key, types.length - 1); +- }); +- function getTypeIndex(func) { +- const key = getSignatureKey(func); +- const typeIndex = typeIndexByKey.get(key); +- if (typeIndex == null) { +- throw new Error(`Failed to get a type index for key ${key}`); +- } +- return typeIndex; +- } +- // https://webassembly.github.io/spec/core/binary/modules.html#import-section +- const imports = [ +- ...importedVars.map(([namespace, name]) => { +- return [ +- ...encoding_1.encodeString(namespace), +- ...encoding_1.encodeString(name), +- ...[encoding_1.GLOBAL_TYPE, encoding_1.VAL_TYPE.f64, encoding_1.MUTABILITY.var], +- ]; +- }), +- ...functionImports.map((func, i) => { +- const typeIndex = getTypeIndex(func); +- return [ +- ...encoding_1.encodeString("shims"), +- ...encoding_1.encodeString(func.name), +- ...[encoding_1.TYPE_IDX, ...encoding_1.unsignedLEB128(typeIndex)], +- ]; +- }), +- ]; +- // https://webassembly.github.io/spec/core/binary/modules.html#function-section +- // +- // > Functions are referenced through function indices, starting with the smallest +- // > index not referencing a function import. +- const functions = [...localFuncs, ...moduleFuncs].map(func => { +- const typeIndex = getTypeIndex(func); +- return encoding_1.unsignedLEB128(typeIndex); +- }); +- const memories = [ +- // Only one memory +- [ +- 0x01, +- ...encoding_1.unsignedLEB128(constants_1.WASM_MEMORY_SIZE), +- ...encoding_1.unsignedLEB128(constants_1.WASM_MEMORY_SIZE), +- ], +- ]; +- // https://webassembly.github.io/spec/core/binary/modules.html#global-section +- const globalCount = varResolver.size() - importedVars.length; +- const globals = Utils.times(globalCount, () => { +- return [ +- encoding_1.VAL_TYPE.f64, +- encoding_1.MUTABILITY.var, +- ...encoding_1.op.f64_const(0), +- encoding_1.op.end, +- ]; +- }); +- // https://webassembly.github.io/spec/core/binary/modules.html#binary-exportsec +- const xports = [...moduleFuncs].map((func, i) => { +- const funcIndex = i + functionImports.length + localFuncs.length; +- return [ +- ...encoding_1.encodeString(func.exportName), +- encoding_1.EXPORT_TYPE.FUNC, +- ...encoding_1.unsignedLEB128(funcIndex), +- ]; +- }); +- /* Uncomment this to expose memory +- xports.push([ +- ...encodeString("memory"), +- EXPORT_TYPE.MEMORY, +- ...unsignedLEB128(0), +- ]); +- */ +- // https://webassembly.github.io/spec/core/binary/modules.html#code-section +- const codes = [...localFuncs, ...moduleFuncs].map(func => { +- var _a; +- // TODO: We could collapose consecutive types here, or even move to a two +- // pass approach where ids are resolved after the emitter is run. +- const localTypes = ((_a = func.localVariables) !== null && _a !== void 0 ? _a : []).map(type => { +- return [...encoding_1.unsignedLEB128(1), type]; +- }); +- // It's a bit odd that every other section is an array of arrays and this +- // one is an array of vectors. The spec says this is so that when navigating +- // the binary functions can be skipped efficiently. +- return encoding_1.encodeFlatVector([ +- ...encoding_1.encodeNestedVector(localTypes), +- ...func.binary, +- encoding_1.op.end, +- ]); +- }); +- return new Uint8Array([ +- // Magic module header +- ...encoding_1.MAGIC, +- // Version number +- ...encoding_1.WASM_VERSION, +- ...encoding_1.encodeSection(encoding_1.SECTION.TYPE, types), +- ...encoding_1.encodeSection(encoding_1.SECTION.IMPORT, imports), +- ...encoding_1.encodeSection(encoding_1.SECTION.FUNC, functions), +- ...encoding_1.encodeSection(encoding_1.SECTION.MEMORY, memories), +- ...encoding_1.encodeSection(encoding_1.SECTION.GLOBAL, globals), +- ...encoding_1.encodeSection(encoding_1.SECTION.EXPORT, xports), +- ...encoding_1.encodeSection(encoding_1.SECTION.CODE, codes), +- ]); +- } +- compiler.compileModule = compileModule; +- +- return compiler; +-} +- +-var loader = {}; +- +-var hasRequiredLoader; +- +-function requireLoader () { +- if (hasRequiredLoader) return loader; +- hasRequiredLoader = 1; +- var __importDefault = (loader && loader.__importDefault) || function (mod) { +- return (mod && mod.__esModule) ? mod : { "default": mod }; +- }; +- Object.defineProperty(loader, "__esModule", { value: true }); +- const shims_1 = __importDefault(requireShims()); +- const compiler_1 = requireCompiler(); +- async function loadModule({ pools, functions, eelVersion = 2, }) { +- let compilerPools = {}; +- Object.entries(pools).forEach(([key, globals]) => { +- compilerPools[key] = new Set(Object.keys(globals)); +- }); +- const buffer = compiler_1.compileModule({ +- pools: compilerPools, +- functions, +- eelVersion, +- }); +- const mod = await WebAssembly.compile(buffer); +- var importObject = Object.assign(Object.assign({}, pools), { shims: shims_1.default }); +- return await WebAssembly.instantiate(mod, importObject); +- } +- loader.loadModule = loadModule; +- +- return loader; +-} +- +-var hasRequiredSrc; +- +-function requireSrc () { +- if (hasRequiredSrc) return src; +- hasRequiredSrc = 1; +- var __importDefault = (src && src.__importDefault) || function (mod) { +- return (mod && mod.__esModule) ? mod : { "default": mod }; +- }; +- Object.defineProperty(src, "__esModule", { value: true }); +- const parser_1 = requireParser(); +- src.parse = parser_1.parse; +- const compiler_1 = requireCompiler(); +- src.compileModule = compiler_1.compileModule; +- const shims_1 = __importDefault(requireShims()); +- src.shims = shims_1.default; +- const loader_1 = requireLoader(); +- src.loadModule = loader_1.loadModule; +- +- return src; +-} +- +-var srcExports = requireSrc(); ++function t(t,e){let n={destCol:1,srcCol:1,srcLine:1};t.forEach(t=>{t.destCol>e||(n=t);});const r=e-n.destCol;return {column:n.srcCol+r,line:n.srcLine}}var e=function(){var t=function(t,e,n,r){for(n=n||{},r=t.length;r--;n[t[r]]=e);return n},e=[1,18],n=[1,7],r=[1,19],s=[1,20],i=[1,14],o=[1,15],a=[1,16],c=[1,33],l=[1,31],h=[1,23],u=[1,22],y=[1,24],p=[1,25],f=[1,26],g=[1,27],m=[1,28],_=[1,29],E=[1,30],d=[5,8,15,18,20,28,29,32,33,34,35,36,37,38],b=[5,15,18],w=[5,12,15,17,18,24,25,28,29,30],S=[1,57],I=[5,8,12,15,17,18,24,25,28,29,30],k=[15,18],O=[5,8,15,18,28,29,38],N=[5,8,15,18,28,29,32,33,38],v=[5,8,15,18,28,29,32,33,34,37,38],R=[5,8,15,18,28,29,32,33,34,35,36,37,38],$=[5,8,15,18],A=[5,8,15,18,20,22,28,29,32,33,34,35,36,37,38],x={trace:function(){},yy:{},symbols_:{error:2,SCRIPT:3,expression:4,EOF:5,expressionsOptionalTrailingSemi:6,separator:7,";":8,expressions:9,EXPRESSION_BLOCK:10,IDENTIFIER:11,IDENTIFIER_TOKEN:12,argument:13,arguments:14,",":15,FUNCTION_CALL:16,"(":17,")":18,LOGICAL_EXPRESSION:19,LOGICAL_OPERATOR_TOKEN:20,ASSIGNMENT:21,ASSIGNMENT_OPERATOR_TOKEN:22,number:23,DIGITS_TOKEN:24,".":25,NUMBER_LITERAL:26,UNARY_EXPRESSION:27,"-":28,"+":29,"!":30,BINARY_EXPRESSION:31,"*":32,"/":33,"%":34,"&":35,"|":36,"^":37,COMPARISON_TOKEN:38,$accept:0,$end:1},terminals_:{2:"error",5:"EOF",8:";",12:"IDENTIFIER_TOKEN",15:",",17:"(",18:")",20:"LOGICAL_OPERATOR_TOKEN",22:"ASSIGNMENT_OPERATOR_TOKEN",24:"DIGITS_TOKEN",25:".",28:"-",29:"+",30:"!",32:"*",33:"/",34:"%",35:"&",36:"|",37:"^",38:"COMPARISON_TOKEN"},productions_:[0,[3,2],[3,2],[3,1],[7,1],[7,2],[9,2],[9,3],[6,1],[6,2],[10,1],[11,1],[13,1],[13,1],[14,1],[14,3],[16,3],[16,4],[19,3],[21,3],[21,3],[23,1],[23,2],[23,3],[23,2],[23,1],[26,1],[27,2],[27,2],[27,2],[31,3],[31,3],[31,3],[31,3],[31,3],[31,3],[31,3],[31,3],[31,3],[4,1],[4,1],[4,3],[4,1],[4,1],[4,1],[4,1],[4,1],[4,3]],performAction:function(t,e,n,r,s,i,o){var a=i.length-1;switch(s){case 1:return {type:"SCRIPT",body:[i[a-1]],loc:this._$};case 2:return {type:"SCRIPT",body:i[a-1],loc:this._$};case 3:return {type:"SCRIPT",body:[],loc:this._$};case 6:this.$=[i[a-1]];break;case 7:this.$=i[a-2].concat([i[a-1]]);break;case 8:this.$=i[a];break;case 9:this.$=i[a-1].concat([i[a]]);break;case 10:this.$={type:"EXPRESSION_BLOCK",body:i[a],loc:this._$};break;case 11:this.$={type:"IDENTIFIER",value:i[a].toLowerCase(),loc:this._$};break;case 14:this.$=[i[a]];break;case 15:this.$=i[a-2].concat([i[a]]);break;case 16:this.$={type:"CALL_EXPRESSION",callee:i[a-2],arguments:[],loc:this._$};break;case 17:this.$={type:"CALL_EXPRESSION",callee:i[a-3],arguments:i[a-1],loc:this._$};break;case 18:this.$={type:"LOGICAL_EXPRESSION",left:i[a-2],right:i[a],operator:i[a-1],loc:this._$};break;case 19:case 20:this.$={type:"ASSIGNMENT_EXPRESSION",left:i[a-2],operator:i[a-1],right:i[a],loc:this._$};break;case 21:this.$=Number(i[a]);break;case 22:this.$=Number(i[a-1]);break;case 23:this.$=Number(i[a-2]+i[a-1]+i[a]);break;case 24:this.$=Number("0"+i[a-1]+i[a]);break;case 25:this.$=0;break;case 26:this.$={type:"NUMBER_LITERAL",value:i[a],loc:this._$};break;case 27:case 28:case 29:this.$={type:"UNARY_EXPRESSION",value:i[a],operator:i[a-1],loc:this._$};break;case 30:case 31:case 32:case 33:case 34:case 35:case 36:case 37:case 38:this.$={type:"BINARY_EXPRESSION",left:i[a-2],right:i[a],operator:i[a-1],loc:this._$};break;case 41:case 47:this.$=i[a-1];}},table:[{3:1,4:2,5:[1,4],6:3,9:13,11:11,12:e,16:10,17:n,19:12,21:9,23:17,24:r,25:s,26:8,27:6,28:i,29:o,30:a,31:5},{1:[3]},{5:[1,21],7:32,8:c,20:l,28:h,29:u,32:y,33:p,34:f,35:g,36:m,37:_,38:E},{5:[1,34]},{1:[2,3]},t(d,[2,39]),t(d,[2,40]),{4:35,6:37,9:13,10:36,11:11,12:e,16:10,17:n,19:12,21:9,23:17,24:r,25:s,26:8,27:6,28:i,29:o,30:a,31:5},t(d,[2,42]),t(d,[2,43]),t(d,[2,44],{22:[1,38]}),t(d,[2,45],{17:[1,40],22:[1,39]}),t(d,[2,46]),t(b,[2,8],{31:5,27:6,26:8,21:9,16:10,11:11,19:12,23:17,4:41,12:e,17:n,24:r,25:s,28:i,29:o,30:a}),{4:42,11:11,12:e,16:10,17:n,19:12,21:9,23:17,24:r,25:s,26:8,27:6,28:i,29:o,30:a,31:5},{4:43,11:11,12:e,16:10,17:n,19:12,21:9,23:17,24:r,25:s,26:8,27:6,28:i,29:o,30:a,31:5},{4:44,11:11,12:e,16:10,17:n,19:12,21:9,23:17,24:r,25:s,26:8,27:6,28:i,29:o,30:a,31:5},t(d,[2,26]),t([5,8,15,17,18,20,22,28,29,32,33,34,35,36,37,38],[2,11]),t(d,[2,21],{25:[1,45]}),t(d,[2,25],{24:[1,46]}),{1:[2,1]},{4:47,11:11,12:e,16:10,17:n,19:12,21:9,23:17,24:r,25:s,26:8,27:6,28:i,29:o,30:a,31:5},{4:48,11:11,12:e,16:10,17:n,19:12,21:9,23:17,24:r,25:s,26:8,27:6,28:i,29:o,30:a,31:5},{4:49,11:11,12:e,16:10,17:n,19:12,21:9,23:17,24:r,25:s,26:8,27:6,28:i,29:o,30:a,31:5},{4:50,11:11,12:e,16:10,17:n,19:12,21:9,23:17,24:r,25:s,26:8,27:6,28:i,29:o,30:a,31:5},{4:51,11:11,12:e,16:10,17:n,19:12,21:9,23:17,24:r,25:s,26:8,27:6,28:i,29:o,30:a,31:5},{4:52,11:11,12:e,16:10,17:n,19:12,21:9,23:17,24:r,25:s,26:8,27:6,28:i,29:o,30:a,31:5},{4:53,11:11,12:e,16:10,17:n,19:12,21:9,23:17,24:r,25:s,26:8,27:6,28:i,29:o,30:a,31:5},{4:54,11:11,12:e,16:10,17:n,19:12,21:9,23:17,24:r,25:s,26:8,27:6,28:i,29:o,30:a,31:5},{4:55,11:11,12:e,16:10,17:n,19:12,21:9,23:17,24:r,25:s,26:8,27:6,28:i,29:o,30:a,31:5},{4:56,11:11,12:e,16:10,17:n,19:12,21:9,23:17,24:r,25:s,26:8,27:6,28:i,29:o,30:a,31:5},t(w,[2,6],{8:S}),t(I,[2,4]),{1:[2,2]},{7:32,8:c,18:[1,58],20:l,28:h,29:u,32:y,33:p,34:f,35:g,36:m,37:_,38:E},{18:[1,59]},t(k,[2,10]),{4:60,11:11,12:e,16:10,17:n,19:12,21:9,23:17,24:r,25:s,26:8,27:6,28:i,29:o,30:a,31:5},{4:61,11:11,12:e,16:10,17:n,19:12,21:9,23:17,24:r,25:s,26:8,27:6,28:i,29:o,30:a,31:5},{4:65,6:37,9:13,10:66,11:11,12:e,13:64,14:63,16:10,17:n,18:[1,62],19:12,21:9,23:17,24:r,25:s,26:8,27:6,28:i,29:o,30:a,31:5},t(b,[2,9],{7:67,8:c,20:l,28:h,29:u,32:y,33:p,34:f,35:g,36:m,37:_,38:E}),t(O,[2,27],{20:l,32:y,33:p,34:f,35:g,36:m,37:_}),t(O,[2,28],{20:l,32:y,33:p,34:f,35:g,36:m,37:_}),t(O,[2,29],{20:l,32:y,33:p,34:f,35:g,36:m,37:_}),t(d,[2,22],{24:[1,68]}),t(d,[2,24]),t(O,[2,30],{20:l,32:y,33:p,34:f,35:g,36:m,37:_}),t(O,[2,31],{20:l,32:y,33:p,34:f,35:g,36:m,37:_}),t(N,[2,32],{20:l,34:f,35:g,36:m,37:_}),t(N,[2,33],{20:l,34:f,35:g,36:m,37:_}),t(v,[2,34],{20:l,35:g,36:m}),t(R,[2,35],{20:l}),t(R,[2,36],{20:l}),t(v,[2,37],{20:l,35:g,36:m}),t($,[2,38],{20:l,28:h,29:u,32:y,33:p,34:f,35:g,36:m,37:_,38:E}),t(d,[2,18]),t(I,[2,5]),t(d,[2,41]),t(d,[2,47]),t($,[2,20],{20:l,28:h,29:u,32:y,33:p,34:f,35:g,36:m,37:_,38:E}),t($,[2,19],{20:l,28:h,29:u,32:y,33:p,34:f,35:g,36:m,37:_,38:E}),t(A,[2,16]),{15:[1,70],18:[1,69]},t(k,[2,14]),t(k,[2,12],{7:32,8:c,20:l,28:h,29:u,32:y,33:p,34:f,35:g,36:m,37:_,38:E}),t(k,[2,13]),t(w,[2,7],{8:S}),t(d,[2,23]),t(A,[2,17]),{4:65,6:37,9:13,10:66,11:11,12:e,13:71,16:10,17:n,19:12,21:9,23:17,24:r,25:s,26:8,27:6,28:i,29:o,30:a,31:5},t(k,[2,15])],defaultActions:{4:[2,3],21:[2,1],34:[2,2]},parseError:function(t,e){if(!e.recoverable){var n=new Error(t);throw n.hash=e,n}this.trace(t);},parse:function(t){var e=this,n=[0],r=[null],s=[],i=this.table,o="",a=0,c=0,l=s.slice.call(arguments,1),h=Object.create(this.lexer),u={yy:{}};for(var y in this.yy)Object.prototype.hasOwnProperty.call(this.yy,y)&&(u.yy[y]=this.yy[y]);h.setInput(t,u.yy),u.yy.lexer=h,u.yy.parser=this,void 0===h.yylloc&&(h.yylloc={});var p=h.yylloc;s.push(p);var f=h.options&&h.options.ranges;"function"==typeof u.yy.parseError?this.parseError=u.yy.parseError:this.parseError=Object.getPrototypeOf(this).parseError;for(var g,m,_,E,d,b,w,S,I=function(){var t;return "number"!=typeof(t=h.lex()||1)&&(t=e.symbols_[t]||t),t},k={};;){if(m=n[n.length-1],this.defaultActions[m]?_=this.defaultActions[m]:(null==g&&(g=I()),_=i[m]&&i[m][g]),void 0===_||!_.length||!_[0]){var O="";for(d in S=[],i[m])this.terminals_[d]&&d>2&&S.push("'"+this.terminals_[d]+"'");O=h.showPosition?"Parse error on line "+(a+1)+":\n"+h.showPosition()+"\nExpecting "+S.join(", ")+", got '"+(this.terminals_[g]||g)+"'":"Parse error on line "+(a+1)+": Unexpected "+(1==g?"end of input":"'"+(this.terminals_[g]||g)+"'"),this.parseError(O,{text:h.match,token:this.terminals_[g]||g,line:h.yylineno,loc:p,expected:S});}if(_[0]instanceof Array&&_.length>1)throw new Error("Parse Error: multiple actions possible at state: "+m+", token: "+g);switch(_[0]){case 1:n.push(g),r.push(h.yytext),s.push(h.yylloc),n.push(_[1]),g=null,c=h.yyleng,o=h.yytext,a=h.yylineno,p=h.yylloc;break;case 2:if(b=this.productions_[_[1]][1],k.$=r[r.length-b],k._$={first_line:s[s.length-(b||1)].first_line,last_line:s[s.length-1].last_line,first_column:s[s.length-(b||1)].first_column,last_column:s[s.length-1].last_column},f&&(k._$.range=[s[s.length-(b||1)].range[0],s[s.length-1].range[1]]),void 0!==(E=this.performAction.apply(k,[o,c,a,u.yy,_[1],r,s].concat(l))))return E;b&&(n=n.slice(0,-1*b*2),r=r.slice(0,-1*b),s=s.slice(0,-1*b)),n.push(this.productions_[_[1]][0]),r.push(k.$),s.push(k._$),w=i[n[n.length-2]][n[n.length-1]],n.push(w);break;case 3:return true}}return true}},L={EOF:1,parseError:function(t,e){if(!this.yy.parser)throw new Error(t);this.yy.parser.parseError(t,e);},setInput:function(t,e){return this.yy=e||this.yy||{},this._input=t,this._more=this._backtrack=this.done=false,this.yylineno=this.yyleng=0,this.yytext=this.matched=this.match="",this.conditionStack=["INITIAL"],this.yylloc={first_line:1,first_column:0,last_line:1,last_column:0},this.options.ranges&&(this.yylloc.range=[0,0]),this.offset=0,this},input:function(){var t=this._input[0];return this.yytext+=t,this.yyleng++,this.offset++,this.match+=t,this.matched+=t,t.match(/(?:\r\n?|\n).*/g)?(this.yylineno++,this.yylloc.last_line++):this.yylloc.last_column++,this.options.ranges&&this.yylloc.range[1]++,this._input=this._input.slice(1),t},unput:function(t){var e=t.length,n=t.split(/(?:\r\n?|\n)/g);this._input=t+this._input,this.yytext=this.yytext.substr(0,this.yytext.length-e),this.offset-=e;var r=this.match.split(/(?:\r\n?|\n)/g);this.match=this.match.substr(0,this.match.length-1),this.matched=this.matched.substr(0,this.matched.length-1),n.length-1&&(this.yylineno-=n.length-1);var s=this.yylloc.range;return this.yylloc={first_line:this.yylloc.first_line,last_line:this.yylineno+1,first_column:this.yylloc.first_column,last_column:n?(n.length===r.length?this.yylloc.first_column:0)+r[r.length-n.length].length-n[0].length:this.yylloc.first_column-e},this.options.ranges&&(this.yylloc.range=[s[0],s[0]+this.yyleng-e]),this.yyleng=this.yytext.length,this},more:function(){return this._more=true,this},reject:function(){return this.options.backtrack_lexer?(this._backtrack=true,this):this.parseError("Lexical error on line "+(this.yylineno+1)+". You can only invoke reject() in the lexer when the lexer is of the backtracking persuasion (options.backtrack_lexer = true).\n"+this.showPosition(),{text:"",token:null,line:this.yylineno})},less:function(t){this.unput(this.match.slice(t));},pastInput:function(){var t=this.matched.substr(0,this.matched.length-this.match.length);return (t.length>20?"...":"")+t.substr(-20).replace(/\n/g,"")},upcomingInput:function(){var t=this.match;return t.length<20&&(t+=this._input.substr(0,20-t.length)),(t.substr(0,20)+(t.length>20?"...":"")).replace(/\n/g,"")},showPosition:function(){var t=this.pastInput(),e=new Array(t.length+1).join("-");return t+this.upcomingInput()+"\n"+e+"^"},test_match:function(t,e){var n,r,s;if(this.options.backtrack_lexer&&(s={yylineno:this.yylineno,yylloc:{first_line:this.yylloc.first_line,last_line:this.last_line,first_column:this.yylloc.first_column,last_column:this.yylloc.last_column},yytext:this.yytext,match:this.match,matches:this.matches,matched:this.matched,yyleng:this.yyleng,offset:this.offset,_more:this._more,_input:this._input,yy:this.yy,conditionStack:this.conditionStack.slice(0),done:this.done},this.options.ranges&&(s.yylloc.range=this.yylloc.range.slice(0))),(r=t[0].match(/(?:\r\n?|\n).*/g))&&(this.yylineno+=r.length),this.yylloc={first_line:this.yylloc.last_line,last_line:this.yylineno+1,first_column:this.yylloc.last_column,last_column:r?r[r.length-1].length-r[r.length-1].match(/\r?\n?/)[0].length:this.yylloc.last_column+t[0].length},this.yytext+=t[0],this.match+=t[0],this.matches=t,this.yyleng=this.yytext.length,this.options.ranges&&(this.yylloc.range=[this.offset,this.offset+=this.yyleng]),this._more=false,this._backtrack=false,this._input=this._input.slice(t[0].length),this.matched+=t[0],n=this.performAction.call(this,this.yy,this,e,this.conditionStack[this.conditionStack.length-1]),this.done&&this._input&&(this.done=false),n)return n;if(this._backtrack){for(var i in s)this[i]=s[i];return false}return false},next:function(){if(this.done)return this.EOF;var t,e,n,r;this._input||(this.done=true),this._more||(this.yytext="",this.match="");for(var s=this._currentRules(),i=0;ie[0].length)){if(e=n,r=i,this.options.backtrack_lexer){if(false!==(t=this.test_match(n,s[i])))return t;if(this._backtrack){e=false;continue}return false}if(!this.options.flex)break}return e?false!==(t=this.test_match(e,s[r]))&&t:""===this._input?this.EOF:this.parseError("Lexical error on line "+(this.yylineno+1)+". Unrecognized text.\n"+this.showPosition(),{text:"",token:null,line:this.yylineno})},lex:function(){var t=this.next();return t||this.lex()},begin:function(t){this.conditionStack.push(t);},popState:function(){return this.conditionStack.length-1>0?this.conditionStack.pop():this.conditionStack[0]},_currentRules:function(){return this.conditionStack.length&&this.conditionStack[this.conditionStack.length-1]?this.conditions[this.conditionStack[this.conditionStack.length-1]].rules:this.conditions.INITIAL.rules},topState:function(t){return (t=this.conditionStack.length-1-Math.abs(t||0))>=0?this.conditionStack[t]:"INITIAL"},pushState:function(t){this.begin(t);},stateStackSize:function(){return this.conditionStack.length},options:{},performAction:function(t,e,n,r){switch(n){case 0:break;case 1:return 24;case 2:return 38;case 3:return 22;case 4:return 20;case 5:return 12;case 6:return 5;case 7:return e.yytext[0]}},rules:[/^(?:\s+)/,/^(?:[0-9]+)/,/^(?:(==|!=|<=|>=|<|>))/,/^(?:[+\-*/%]?=)/,/^(?:(\&\&)|\|\|)/,/^(?:[a-zA-Z_][a-zA-Z0-9._]*)/,/^(?:$)/,/^(?:.)/],conditions:{INITIAL:{rules:[0,1,2,3,4,5,6,7],inclusive:true}}};function T(){this.yy={};}return x.lexer=L,T.prototype=x,x.Parser=T,new T}();const n={ASSIGNMENT_EXPRESSION:[{type:"NODE",key:"right"}],SCRIPT:[{type:"ARRAY",key:"body"}],EXPRESSION_BLOCK:[{type:"ARRAY",key:"body"}],UNARY_EXPRESSION:[{type:"NODE",key:"value"}],NUMBER_LITERAL:[],IDENTIFIER:[],CALL_EXPRESSION:[{type:"ARRAY",key:"arguments"},{type:"NODE",key:"callee"}],BINARY_EXPRESSION:[{type:"NODE",key:"left"},{type:"NODE",key:"right"}],LOGICAL_EXPRESSION:[{type:"NODE",key:"left"},{type:"NODE",key:"right"}]};function r(t,e){const s=n[t.type];let i=t;if(null==s)throw new Error(`Unknown children definition for ${t.type}`);return s.forEach(n=>{if("NODE"===n.type){const s=t[n.key],o=r(s,e);o!==s&&(i={...i,[n.key]:o});}else if("ARRAY"===n.type){const s=t[n.key],o=s.map(t=>r(t,e)),a=s.some((t,e)=>t!==o[e]);a&&(i={...i,[n.key]:o});}}),e(i)}function s(t){return [].concat.apply([],t)}function i(t,e){return new Array(t).fill(e).join("")}class o{constructor(){this._map=new Map;}get(t,e){const n=null==t?e:`${t}::${e}`;return this._map.has(n)||this._map.set(n,this._map.size),this._map.get(n)}size(){return this._map.size}}class a extends Error{constructor(t,e,n){super(t),this.sourceContext=function(t,e,n=1){const r=Math.max(t.first_line-1-n,0),s=t.last_line+n,o=e.split("\n").slice(r,s).map((e,n)=>{const s=n+r+1;return `${s>=t.first_line&&s<=t.last_line?">":" "} ${s} | ${e}`});if(t.first_line===t.last_line){const e=i(t.first_column," "),n=i(t.last_column-t.first_column,"^"),s=t.first_line-r;o.splice(s,0,` | ${e}${n}`);}return o.join("\n")}(e,n),this.loc=e;}}class c extends a{}function l(t,e,n){return new c(t,e,n)}function h(t,e,n){return new a(t,e,n)}function u(e,n){const r=t(n,e.first_column),s=t(n,e.last_column);return {first_column:r.column,last_column:s.column,first_line:r.line,last_line:s.line}}function y(t){const[n,s]=function(t){const e=[];let n=1,r="",s=0,i=false,o=false,a=false;for(let c=0;c{if(1!==e.loc.first_line||1!=e.loc.last_line)throw h("Unexpected multiline",e.loc,t);return Object.assign(Object.assign({},e),{loc:u(e.loc,s)})})}catch(e){if(null==e.hash)throw e;throw l(`Parse Error: ${e.message.split("\n")[3]}`,u(e.hash.loc,s),t)}}const p=[0,97,115,109],f=[1,0,0,0],g=1e-5,m=1,_=2,E=3,d=5,b=6,w=7,S=10,I=0,k=t=>[2,t],O=t=>[3,t],N=t=>[4,t],v=5,R=11,$=t=>[13,...Pt(t)],A=t=>[16,...Pt(t)],x=26,L=27,T=t=>[32,...Pt(t)],P=t=>[33,...Pt(t)],C=t=>[34,...Pt(t)],M=t=>[35,...Pt(t)],F=t=>[36,...Pt(t)],X=(t,e)=>[43,...Pt(t),...Pt(e)],j=(t,e)=>[57,...Pt(t),...Pt(e)],U=t=>[65,...Ct(t)],D=t=>[66,...Ct(t)],B=t=>[68,...Lt(t)],V=71,G=72,K=73,Y=74,z=76,q=82,W=98,Z=99,J=100,H=101,Q=102,tt=106,et=107,nt=108,rt=113,st=114,it=129,ot=131,at=132,ct=153,lt=154,ht=155,ut=156,yt=159,pt=160,ft=161,gt=162,mt=163,_t=164,Et=165,dt=170,bt=176,wt=185,St=183,It=176,kt=127,Ot=126,Nt=124,vt=1,Rt=64,$t=124,At=[ct,...B(g),Z],xt=[ct,...B(g),J];function Lt(t){const e=new Uint8Array(8);return function(t,e){const n=new ArrayBuffer(8);new DataView(n).setFloat64(0,e);const r=new Uint8Array(n).reverse();t.set(r,0);}(e,t),e}const Tt=t=>[t.length].concat(t.split("").map(t=>t.charCodeAt(0)));function Pt(t){const e=[];do{let n=127&t;0!==(t>>>=7)&&(n|=128),e.push(n);}while(0!==t);return e}function Ct(t){let e=[],n=0,r=Math.ceil(Math.log2(Math.abs(t))),s=t<0,i=true;for(;i;)n=127&t,t>>=7,s&&(t|=-(1<Pt(t.length).concat(t),Ft=t=>Pt(t.length).concat(s(t));function Xt(t,e){if(0===e.length)return [];const n=Mt(Ft(e));return n.unshift(t),n}const jt=(t,e,n)=>{const r=[...Tt(e),...n];return [t,...Mt(r)]},Ut={sin:Math.sin,cos:Math.cos,tan:Math.tan,asin:Math.asin,acos:Math.acos,atan:Math.atan,atan2:Math.atan2,rand:t=>Math.random()*t,pow:Math.pow,log:Math.log,log10:Math.log10,exp:Math.exp,sigmoid:function(t,e){const n=1+Math.exp(-t*e);return Math.abs(n)>1e-5?1/n:0}},Dt=1048576,Bt=Math.ceil(2048),Vt=B(Math.pow(2,31)),Gt=B(Math.pow(2,31));function Kt(t){return [...T(t),...Vt,Z,...N($t),...T(t),ct,v,...Gt,R,It]}const Yt={sqr:{args:[Nt],returns:[Nt],binary:[...T(0),...T(0),gt]},bor:{args:[Nt,Nt],returns:[Nt],binary:[...T(0),...xt,...T(1),...xt,st,...U(0),V,St]},band:{args:[Nt,Nt],returns:[Nt],binary:[...T(0),...xt,...T(1),...xt,rt,...U(0),V,St]},sign:{args:[Nt],returns:[Nt],binary:[...B(0),...T(0),Z,...T(0),...B(0),Z,et,St]},mod:{args:[Nt,Nt],returns:[Nt],localVariables:[Ot],binary:[...Kt(1),...C(2),...D(0),q,...N($t),...Kt(0),...T(2),it,wt,v,...B(0),R]},bitwiseOr:{args:[Nt,Nt],returns:[Nt],binary:[...T(0),bt,...T(1),bt,at,wt]},bitwiseAnd:{args:[Nt,Nt],returns:[Nt],binary:[...T(0),bt,...T(1),bt,ot,wt]},div:{args:[Nt,Nt],returns:[Nt],localVariables:[kt],binary:[...T(1),...B(0),W,...N($t),...T(0),...T(1),mt,v,...B(0),R]},_getBufferIndex:{args:[Nt],returns:[kt],localVariables:[Nt,kt],binary:[...B(g),...T(0),pt,...C(1),dt,...P(2),...U(-1),...T(2),...U(8),nt,...T(2),...U(0),G,...T(2),...U(8388607),Y,st,L]}};function zt(t,e){var n,r,i;switch(t.type){case "SCRIPT":return s(t.body.map((t,n)=>[...zt(t,e),x]));case "EXPRESSION_BLOCK":return qt(t.body,e);case "BINARY_EXPRESSION":{const n=zt(t.left,e),r=zt(t.right,e),s={"+":[pt],"-":[ft],"*":[gt],"/":e.resolveFunc("div"),"%":e.resolveFunc("mod"),"|":e.resolveFunc("bitwiseOr"),"&":e.resolveFunc("bitwiseAnd"),"^":e.resolveFunc("pow"),"==":[ft,...At,St],"!=":[ft,...xt,St],"<":[Z,St],">":[J,St],"<=":[H,St],">=":[Q,St]}[t.operator];if(null==s)throw h(`Unknown binary expression operator ${t.operator}`,t.loc,e.rawSource);return [...n,...r,...s]}case "CALL_EXPRESSION":{const r=t.callee.value,i=t.arguments,o=n=>{if(i.lengthn)throw l(`Too many arguments passed to \`${r}()\`. Expected ${n} but got ${i.length}.`,i[n].loc,e.rawSource)};switch(r){case "exec2":return o(2),qt(t.arguments,e);case "exec3":return o(3),qt(t.arguments,e);case "if":o(3);const[s,i,a]=t.arguments;return function(t,e,n,r){return [...zt(t,r),...xt,...N($t),...zt(e,r),v,...zt(n,r),R]}(s,i,a,e);case "while":return o(1),function(t,e){const n=zt(t,e),r=e.resolveLocal(kt);return [...U(0),...P(r),...O(Rt),...T(r),...U(1),tt,...C(r),...U(Dt),K,...n,...xt,rt,...$(0),R,...B(0)]}(t.arguments[0],e);case "loop":return o(2),function(t,e,n){const r=zt(e,n),s=n.resolveLocal(kt);return [...k(Rt),...zt(t,n),dt,...C(s),...U(0),z,...$(1),...O(Rt),...r,x,...T(s),...U(1),et,...C(s),...U(0),V,...$(0),R,R,...B(0)]}(t.arguments[0],t.arguments[1],e);case "megabuf":case "gmegabuf":o(1);const c=e.resolveLocal(kt);return [...zt(t.arguments[0],e),...null!==(n=e.resolveFunc("_getBufferIndex"))&&void 0!==n?n:[],...C(c),...U(-1),V,...N($t),...T(c),...X(3,Wt(r)),v,...B(0),R];case "assign":o(2);const h=t.arguments[0];if("IDENTIFIER"!=h.type)throw l("Expected the first argument of `assign()` to be an identifier.",h.loc,e.rawSource);const u=e.resolveVar(h.value);return [...zt(t.arguments[1],e),...F(u),...M(u)]}const a=s(t.arguments.map(t=>zt(t,e)));switch(r){case "abs":return o(1),[...a,ct];case "sqrt":return o(1),[...a,ct,yt];case "int":case "floor":return o(1),[...a,ut];case "min":return o(2),[...a,_t];case "max":return o(2),[...a,Et];case "above":return o(2),[...a,J,St];case "below":return o(2),[...a,Z,St];case "equal":return o(2),[...a,ft,...At,St];case "bnot":return o(1),[...a,...At,St];case "ceil":return o(1),[...a,ht]}const c=e.resolveFunc(r);if(null==c||r.startsWith("_"))throw l(`"${r}" is not defined.`,t.callee.loc,e.rawSource);if(null!=Ut[r])o(Ut[r].length);else {if(null==Yt[r])throw h(`Missing arity information for the function \`${r}()\``,t.callee.loc,e.rawSource);o(Yt[r].args.length);}return [...a,...c]}case "ASSIGNMENT_EXPRESSION":{const{left:n}=t,s=zt(t.right,e),o=function(t,e){const n={"+=":[pt],"-=":[ft],"*=":[gt],"/=":[mt],"%=":e.resolveFunc("mod"),"=":null},r=n[t.operator];if(void 0===r)throw h(`Unknown assignment operator "${t.operator}"`,t.loc,e.rawSource);return r}(t,e);if("IDENTIFIER"===n.type){const t=e.resolveVar(n.value),r=M(t),i=F(t);return null===o?[...s,...i,...r]:[...r,...s,...o,...i,...r]}if("CALL_EXPRESSION"!==n.type)throw h(`Unexpected left hand side type for assignment: ${n.type}`,t.loc,e.rawSource);const a=e.resolveLocal(kt);if(1!==n.arguments.length)throw l(`Expected 1 argument when assigning to a buffer but got ${n.arguments.length}.`,0===n.arguments.length?n.loc:n.arguments[1].loc,e.rawSource);const c=n.callee.value;if("gmegabuf"!==c&&"megabuf"!==c)throw l("The only function calls which may be assigned to are `gmegabuf()` and `megabuf()`.",n.callee.loc,e.rawSource);const u=Wt(c);if(null===o){const t=e.resolveLocal(kt),i=e.resolveLocal(Nt);return [...s,...P(i),...zt(n.arguments[0],e),...null!==(r=e.resolveFunc("_getBufferIndex"))&&void 0!==r?r:[],...C(t),...U(0),G,...N($t),...B(0),v,...T(t),...C(a),...T(i),...j(3,u),...T(i),R]}const y=e.resolveLocal(kt),p=e.resolveLocal(kt),f=e.resolveLocal(Nt),g=e.resolveLocal(Nt);return [...s,...P(f),...zt(n.arguments[0],e),...null!==(i=e.resolveFunc("_getBufferIndex"))&&void 0!==i?i:[],...C(y),...U(-1),V,...C(p),...N($t),...T(y),...X(3,u),v,...B(0),R,...T(f),...o,...C(g),...T(p),...N(Rt),...T(y),...T(g),...j(3,u),R]}case "LOGICAL_EXPRESSION":{const n=zt(t.left,e),r=zt(t.right,e),s={"&&":{comparison:At,shortCircuitValue:0},"||":{comparison:xt,shortCircuitValue:1}}[t.operator];if(null==s)throw h(`Unknown logical expression operator ${t.operator}`,t.loc,e.rawSource);const{comparison:i,shortCircuitValue:o}=s;return [...n,...i,...N($t),...B(o),v,...r,...xt,St,R]}case "UNARY_EXPRESSION":{const n=zt(t.value,e),r={"-":[lt],"+":[],"!":[...At,St]}[t.operator];if(null==r)throw h(`Unknown logical unary operator ${t.operator}`,t.loc,e.rawSource);return [...n,...r]}case "IDENTIFIER":const o=t.value;return M(e.resolveVar(o));case "NUMBER_LITERAL":return B(t.value);default:throw h(`Unknown AST node type ${t.type}`,t.loc,e.rawSource)}}function qt(t,e){return s(function(t,e){const n=[];for(let r=0;rzt(t,e)),[x]))}function Wt(t){switch(t){case "gmegabuf":return 67108864;case "megabuf":return 0}}function Zt({pools:t,functions:e,eelVersion:n=2,preParsed:r=false}){if(Object.keys(t).includes("shims"))throw new Error('You may not name a pool "shims". "shims" is reserved for injected JavaScript functions.');const s=[];Object.entries(t).forEach(([t,e])=>{e.forEach(e=>{s.push([t,e]);});});const i=new o;s.forEach(([t,e])=>{i.get(t,e);});const a=Object.entries(Ut).map(([t,e])=>({args:new Array(e.length).fill(null).map(t=>Nt),returns:[Nt],name:t})),c=[],l=[];Object.entries(e).forEach(([e,{pool:s,code:o}])=>{if(null==t[s]){const n=Object.keys(t);if(0===n.length)throw new Error(`The function "${e}" was declared as using a variable pool named "${s}" but no pools were defined.`);throw new Error(`The function "${e}" was declared as using a variable pool named "${s}" which is not among the variable pools defined. The defined variable pools are: ${function(t){if(0===t.length)throw new Error("Cannot format an empty list");if(1===t.length)return t[0];const e=t.map(t=>`"${t}"`),n=e.pop();return e.join(", ")+` and ${n}`}(n)}.`)}const h=r?o:y(o);if("string"==typeof h)throw new Error("Got passed unparsed code without setting the preParsed flag");if("SCRIPT"!==h.type)throw new Error("Invalid AST");if(0===h.body.length)return;const u=[],p={resolveVar:t=>/^reg\d\d$/.test(t)?i.get(null,t):i.get(s,t),resolveLocal:t=>(u.push(t),u.length-1),resolveFunc:t=>{const e=a.findIndex(e=>e.name===t);if(-1!==e){const r=A(e);return "rand"===t&&1===n?[...r,ut]:r}if(null==Yt[t])return null;let r=c.indexOf(t);return -1===r&&(c.push(t),r=c.length-1),A(r+a.length)},rawSource:o},f=zt(h,p);l.push({binary:f,exportName:e,args:[],returns:[],localVariables:u});});const h=c.map(t=>{const e=Yt[t];if(null==e)throw new Error(`Undefined local function "${t}"`);return e}),u=t=>[...t.args,"|",...t.returns].join("-"),g=[],k=new Map;function O(t){const e=u(t),n=k.get(e);if(null==n)throw new Error(`Failed to get a type index for key ${e}`);return n}[...a,...h,...l].forEach(t=>{const e=u(t);k.has(e)||(g.push([96,...Mt(t.args),...Mt(t.returns)]),k.set(e,g.length-1));});const N=[...s.map(([t,e])=>[...Tt(t),...Tt(e),3,Nt,vt]),...a.map((t,e)=>{const n=O(t);return [...Tt("shims"),...Tt(t.name),0,...Pt(n)]})],v=[...h,...l].map(t=>Pt(O(t))),$=[[1,...Pt(Bt),...Pt(Bt)]],x=i.size()-s.length,L=(T=()=>[Nt,vt,...B(0),R],new Array(x).fill(null).map((t,e)=>T(e)));var T;const P=[...l].map((t,e)=>{const n=e+a.length+h.length;return [...Tt(t.exportName),I,...Pt(n)]}),C=[...h,...l].map(t=>{var e;const n=(null!==(e=t.localVariables)&&void 0!==e?e:[]).map(t=>[...Pt(1),t]);return Mt([...Ft(n),...t.binary,R])}),M=[...a.map(t=>t.name),...c,...l.map(t=>t.exportName)].map((t,e)=>[...Pt(e),...Tt(t)]),F=[1,...Mt(Ft(M))];return new Uint8Array([...p,...f,...Xt(m,g),...Xt(_,N),...Xt(E,v),...Xt(d,$),...Xt(b,L),...Xt(w,P),...Xt(S,C),...jt(0,"name",F)])}async function Jt({pools:t,functions:e,eelVersion:n=2}){let r={};Object.entries(t).forEach(([t,e])=>{r[t]=new Set(Object.keys(e));});const s=Zt({pools:r,functions:e,eelVersion:n}),i=await WebAssembly.compile(s);var o=Object.assign(Object.assign({},t),{shims:Ut});return await WebAssembly.instantiate(i,o)} + + // Runtime header offsets + const ID_OFFSET = -8; +@@ -12630,7 +10304,7 @@ class Visualizer { + } + } + +- const mod = await srcExports.loadModule({ ++ const mod = await Jt({ + pools: wasmVarPools, + functions: wasmFunctions, + eelVersion: preset.version || 2, +diff --git a/dist/butterchurn.min.js b/dist/butterchurn.min.js +index 4842342a245e4ac06c83db66becd53737cf26e84..2cc9f8ea71e5956bf9e0afe80d71cd1a7f080ca2 100644 +--- a/dist/butterchurn.min.js ++++ b/dist/butterchurn.min.js +@@ -1,2 +1,2 @@ +-{const t=(t,e)=>{var i="function"==typeof e,s="function"==typeof e,r="function"==typeof e;Object.defineProperty(Math,t,{configurable:i,enumerable:r,writable:s,value:e})};t("DEG_PER_RAD",Math.PI/180),t("RAD_PER_DEG",180/Math.PI);const e=new Float32Array(1);t("scale",function(t,e,i,s,r){return 0===arguments.length||Number.isNaN(t)||Number.isNaN(e)||Number.isNaN(i)||Number.isNaN(s)||Number.isNaN(r)?NaN:t===1/0||t===-1/0?t:(t-e)*(r-s)/(i-e)+s}),t("fscale",function(t,i,s,r,a){return e[0]=Math.scale(t,i,s,r,a),e[0]}),t("clamp",function(t,e,i){return Math.min(i,Math.max(e,t))}),t("radians",function(t){return t*Math.DEG_PER_RAD}),t("degrees",function(t){return t*Math.RAD_PER_DEG})}var t=1e-5;window.sqr=function(t){return t*t},window.sqrt=function(t){return Math.sqrt(Math.abs(t))},window.log10=function(t){return Math.log(t)*Math.LOG10E},window.sign=function(t){return t>0?1:t<0?-1:0},window.rand=function(t){var e=Math.floor(t);return e<1?Math.random():Math.random()*e},window.randint=function(t){return Math.floor(rand(t))},window.bnot=function(e){return Math.abs(e)t?1/s:0},window.bor=function(e,i){return Math.abs(e)>t||Math.abs(i)>t?1:0},window.band=function(e,i){return Math.abs(e)>t&&Math.abs(i)>t?1:0},window.equal=function(e,i){return Math.abs(e-i)e?1:0},window.below=function(t,e){return tt?i:s},window.memcpy=function(t,e,i,s){let r=e,a=i,o=s;return a<0&&(o+=a,r-=a,a=0),r<0&&(o+=r,a-=r,r=0),o>0&&t.copyWithin(r,a,o),e};var e,i={},s={},r={};function a(t){throw new Error('Could not dynamically require "'+t+'". Please configure the dynamicRequireTargets or/and ignoreDynamicRequires option of @rollup/plugin-commonjs appropriately for this require call to work.')}var o,h,A,n={};function l(){return o||(o=1,function(t){var e=function(){var t=function(t,e,i,s){for(i=i||{},s=t.length;s--;i[t[s]]=e);return i},e=[1,18],i=[1,7],s=[1,19],r=[1,20],a=[1,14],o=[1,15],h=[1,16],A=[1,33],n=[1,31],l=[1,23],c=[1,22],g=[1,24],m=[1,25],u=[1,26],f=[1,27],d=[1,28],p=[1,29],_=[1,30],E=[5,8,15,18,20,28,29,32,33,34,35,36,37,38],b=[5,15,18],x=[5,12,15,17,18,24,25,28,29,30],v=[1,57],T=[5,8,12,15,17,18,24,25,28,29,30],S=[15,18],P=[5,8,15,18,28,29,38],w=[5,8,15,18,28,29,32,33,38],I=[5,8,15,18,28,29,32,33,34,37,38],R=[5,8,15,18,28,29,32,33,34,35,36,37,38],y=[5,8,15,18],B=[5,8,15,18,20,22,28,29,32,33,34,35,36,37,38],L={trace:function(){},yy:{},symbols_:{error:2,SCRIPT:3,expression:4,EOF:5,expressionsOptionalTrailingSemi:6,separator:7,";":8,expressions:9,EXPRESSION_BLOCK:10,IDENTIFIER:11,IDENTIFIER_TOKEN:12,argument:13,arguments:14,",":15,FUNCTION_CALL:16,"(":17,")":18,LOGICAL_EXPRESSION:19,LOGICAL_OPERATOR_TOKEN:20,ASSIGNMENT:21,ASSIGNMENT_OPERATOR_TOKEN:22,number:23,DIGITS_TOKEN:24,".":25,NUMBER_LITERAL:26,UNARY_EXPRESSION:27,"-":28,"+":29,"!":30,BINARY_EXPRESSION:31,"*":32,"/":33,"%":34,"&":35,"|":36,"^":37,COMPARISON_TOKEN:38,$accept:0,$end:1},terminals_:{2:"error",5:"EOF",8:";",12:"IDENTIFIER_TOKEN",15:",",17:"(",18:")",20:"LOGICAL_OPERATOR_TOKEN",22:"ASSIGNMENT_OPERATOR_TOKEN",24:"DIGITS_TOKEN",25:".",28:"-",29:"+",30:"!",32:"*",33:"/",34:"%",35:"&",36:"|",37:"^",38:"COMPARISON_TOKEN"},productions_:[0,[3,2],[3,2],[3,1],[7,1],[7,2],[9,2],[9,3],[6,1],[6,2],[10,1],[11,1],[13,1],[13,1],[14,1],[14,3],[16,3],[16,4],[19,3],[21,3],[21,3],[23,1],[23,2],[23,3],[23,2],[23,1],[26,1],[27,2],[27,2],[27,2],[31,3],[31,3],[31,3],[31,3],[31,3],[31,3],[31,3],[31,3],[31,3],[4,1],[4,1],[4,3],[4,1],[4,1],[4,1],[4,1],[4,1],[4,3]],performAction:function(t,e,i,s,r,a,o){var h=a.length-1;switch(r){case 1:return{type:"SCRIPT",body:[a[h-1]],loc:this._$};case 2:return{type:"SCRIPT",body:a[h-1],loc:this._$};case 3:return{type:"SCRIPT",body:[],loc:this._$};case 6:this.$=[a[h-1]];break;case 7:this.$=a[h-2].concat([a[h-1]]);break;case 8:this.$=a[h];break;case 9:this.$=a[h-1].concat([a[h]]);break;case 10:this.$={type:"EXPRESSION_BLOCK",body:a[h],loc:this._$};break;case 11:this.$={type:"IDENTIFIER",value:a[h].toLowerCase(),loc:this._$};break;case 14:this.$=[a[h]];break;case 15:this.$=a[h-2].concat([a[h]]);break;case 16:this.$={type:"CALL_EXPRESSION",callee:a[h-2],arguments:[],loc:this._$};break;case 17:this.$={type:"CALL_EXPRESSION",callee:a[h-3],arguments:a[h-1],loc:this._$};break;case 18:this.$={type:"LOGICAL_EXPRESSION",left:a[h-2],right:a[h],operator:a[h-1],loc:this._$};break;case 19:case 20:this.$={type:"ASSIGNMENT_EXPRESSION",left:a[h-2],operator:a[h-1],right:a[h],loc:this._$};break;case 21:this.$=Number(a[h]);break;case 22:this.$=Number(a[h-1]);break;case 23:this.$=Number(a[h-2]+a[h-1]+a[h]);break;case 24:this.$=Number("0"+a[h-1]+a[h]);break;case 25:this.$=0;break;case 26:this.$={type:"NUMBER_LITERAL",value:a[h],loc:this._$};break;case 27:case 28:case 29:this.$={type:"UNARY_EXPRESSION",value:a[h],operator:a[h-1],loc:this._$};break;case 30:case 31:case 32:case 33:case 34:case 35:case 36:case 37:case 38:this.$={type:"BINARY_EXPRESSION",left:a[h-2],right:a[h],operator:a[h-1],loc:this._$};break;case 41:case 47:this.$=a[h-1]}},table:[{3:1,4:2,5:[1,4],6:3,9:13,11:11,12:e,16:10,17:i,19:12,21:9,23:17,24:s,25:r,26:8,27:6,28:a,29:o,30:h,31:5},{1:[3]},{5:[1,21],7:32,8:A,20:n,28:l,29:c,32:g,33:m,34:u,35:f,36:d,37:p,38:_},{5:[1,34]},{1:[2,3]},t(E,[2,39]),t(E,[2,40]),{4:35,6:37,9:13,10:36,11:11,12:e,16:10,17:i,19:12,21:9,23:17,24:s,25:r,26:8,27:6,28:a,29:o,30:h,31:5},t(E,[2,42]),t(E,[2,43]),t(E,[2,44],{22:[1,38]}),t(E,[2,45],{17:[1,40],22:[1,39]}),t(E,[2,46]),t(b,[2,8],{31:5,27:6,26:8,21:9,16:10,11:11,19:12,23:17,4:41,12:e,17:i,24:s,25:r,28:a,29:o,30:h}),{4:42,11:11,12:e,16:10,17:i,19:12,21:9,23:17,24:s,25:r,26:8,27:6,28:a,29:o,30:h,31:5},{4:43,11:11,12:e,16:10,17:i,19:12,21:9,23:17,24:s,25:r,26:8,27:6,28:a,29:o,30:h,31:5},{4:44,11:11,12:e,16:10,17:i,19:12,21:9,23:17,24:s,25:r,26:8,27:6,28:a,29:o,30:h,31:5},t(E,[2,26]),t([5,8,15,17,18,20,22,28,29,32,33,34,35,36,37,38],[2,11]),t(E,[2,21],{25:[1,45]}),t(E,[2,25],{24:[1,46]}),{1:[2,1]},{4:47,11:11,12:e,16:10,17:i,19:12,21:9,23:17,24:s,25:r,26:8,27:6,28:a,29:o,30:h,31:5},{4:48,11:11,12:e,16:10,17:i,19:12,21:9,23:17,24:s,25:r,26:8,27:6,28:a,29:o,30:h,31:5},{4:49,11:11,12:e,16:10,17:i,19:12,21:9,23:17,24:s,25:r,26:8,27:6,28:a,29:o,30:h,31:5},{4:50,11:11,12:e,16:10,17:i,19:12,21:9,23:17,24:s,25:r,26:8,27:6,28:a,29:o,30:h,31:5},{4:51,11:11,12:e,16:10,17:i,19:12,21:9,23:17,24:s,25:r,26:8,27:6,28:a,29:o,30:h,31:5},{4:52,11:11,12:e,16:10,17:i,19:12,21:9,23:17,24:s,25:r,26:8,27:6,28:a,29:o,30:h,31:5},{4:53,11:11,12:e,16:10,17:i,19:12,21:9,23:17,24:s,25:r,26:8,27:6,28:a,29:o,30:h,31:5},{4:54,11:11,12:e,16:10,17:i,19:12,21:9,23:17,24:s,25:r,26:8,27:6,28:a,29:o,30:h,31:5},{4:55,11:11,12:e,16:10,17:i,19:12,21:9,23:17,24:s,25:r,26:8,27:6,28:a,29:o,30:h,31:5},{4:56,11:11,12:e,16:10,17:i,19:12,21:9,23:17,24:s,25:r,26:8,27:6,28:a,29:o,30:h,31:5},t(x,[2,6],{8:v}),t(T,[2,4]),{1:[2,2]},{7:32,8:A,18:[1,58],20:n,28:l,29:c,32:g,33:m,34:u,35:f,36:d,37:p,38:_},{18:[1,59]},t(S,[2,10]),{4:60,11:11,12:e,16:10,17:i,19:12,21:9,23:17,24:s,25:r,26:8,27:6,28:a,29:o,30:h,31:5},{4:61,11:11,12:e,16:10,17:i,19:12,21:9,23:17,24:s,25:r,26:8,27:6,28:a,29:o,30:h,31:5},{4:65,6:37,9:13,10:66,11:11,12:e,13:64,14:63,16:10,17:i,18:[1,62],19:12,21:9,23:17,24:s,25:r,26:8,27:6,28:a,29:o,30:h,31:5},t(b,[2,9],{7:67,8:A,20:n,28:l,29:c,32:g,33:m,34:u,35:f,36:d,37:p,38:_}),t(P,[2,27],{20:n,32:g,33:m,34:u,35:f,36:d,37:p}),t(P,[2,28],{20:n,32:g,33:m,34:u,35:f,36:d,37:p}),t(P,[2,29],{20:n,32:g,33:m,34:u,35:f,36:d,37:p}),t(E,[2,22],{24:[1,68]}),t(E,[2,24]),t(P,[2,30],{20:n,32:g,33:m,34:u,35:f,36:d,37:p}),t(P,[2,31],{20:n,32:g,33:m,34:u,35:f,36:d,37:p}),t(w,[2,32],{20:n,34:u,35:f,36:d,37:p}),t(w,[2,33],{20:n,34:u,35:f,36:d,37:p}),t(I,[2,34],{20:n,35:f,36:d}),t(R,[2,35],{20:n}),t(R,[2,36],{20:n}),t(I,[2,37],{20:n,35:f,36:d}),t(y,[2,38],{20:n,28:l,29:c,32:g,33:m,34:u,35:f,36:d,37:p,38:_}),t(E,[2,18]),t(T,[2,5]),t(E,[2,41]),t(E,[2,47]),t(y,[2,20],{20:n,28:l,29:c,32:g,33:m,34:u,35:f,36:d,37:p,38:_}),t(y,[2,19],{20:n,28:l,29:c,32:g,33:m,34:u,35:f,36:d,37:p,38:_}),t(B,[2,16]),{15:[1,70],18:[1,69]},t(S,[2,14]),t(S,[2,12],{7:32,8:A,20:n,28:l,29:c,32:g,33:m,34:u,35:f,36:d,37:p,38:_}),t(S,[2,13]),t(x,[2,7],{8:v}),t(E,[2,23]),t(B,[2,17]),{4:65,6:37,9:13,10:66,11:11,12:e,13:71,16:10,17:i,19:12,21:9,23:17,24:s,25:r,26:8,27:6,28:a,29:o,30:h,31:5},t(S,[2,15])],defaultActions:{4:[2,3],21:[2,1],34:[2,2]},parseError:function(t,e){if(!e.recoverable){var i=new Error(t);throw i.hash=e,i}this.trace(t)},parse:function(t){var e=this,i=[0],s=[null],r=[],a=this.table,o="",h=0,A=0,n=r.slice.call(arguments,1),l=Object.create(this.lexer),c={yy:{}};for(var g in this.yy)Object.prototype.hasOwnProperty.call(this.yy,g)&&(c.yy[g]=this.yy[g]);l.setInput(t,c.yy),c.yy.lexer=l,c.yy.parser=this,void 0===l.yylloc&&(l.yylloc={});var m=l.yylloc;r.push(m);var u=l.options&&l.options.ranges;"function"==typeof c.yy.parseError?this.parseError=c.yy.parseError:this.parseError=Object.getPrototypeOf(this).parseError;for(var f,d,p,_,E,b,x,v,T=function(){var t;return"number"!=typeof(t=l.lex()||1)&&(t=e.symbols_[t]||t),t},S={};;){if(d=i[i.length-1],this.defaultActions[d]?p=this.defaultActions[d]:(null==f&&(f=T()),p=a[d]&&a[d][f]),void 0===p||!p.length||!p[0]){var P="";for(E in v=[],a[d])this.terminals_[E]&&E>2&&v.push("'"+this.terminals_[E]+"'");P=l.showPosition?"Parse error on line "+(h+1)+":\n"+l.showPosition()+"\nExpecting "+v.join(", ")+", got '"+(this.terminals_[f]||f)+"'":"Parse error on line "+(h+1)+": Unexpected "+(1==f?"end of input":"'"+(this.terminals_[f]||f)+"'"),this.parseError(P,{text:l.match,token:this.terminals_[f]||f,line:l.yylineno,loc:m,expected:v})}if(p[0]instanceof Array&&p.length>1)throw new Error("Parse Error: multiple actions possible at state: "+d+", token: "+f);switch(p[0]){case 1:i.push(f),s.push(l.yytext),r.push(l.yylloc),i.push(p[1]),f=null,A=l.yyleng,o=l.yytext,h=l.yylineno,m=l.yylloc;break;case 2:if(b=this.productions_[p[1]][1],S.$=s[s.length-b],S._$={first_line:r[r.length-(b||1)].first_line,last_line:r[r.length-1].last_line,first_column:r[r.length-(b||1)].first_column,last_column:r[r.length-1].last_column},u&&(S._$.range=[r[r.length-(b||1)].range[0],r[r.length-1].range[1]]),void 0!==(_=this.performAction.apply(S,[o,A,h,c.yy,p[1],s,r].concat(n))))return _;b&&(i=i.slice(0,-1*b*2),s=s.slice(0,-1*b),r=r.slice(0,-1*b)),i.push(this.productions_[p[1]][0]),s.push(S.$),r.push(S._$),x=a[i[i.length-2]][i[i.length-1]],i.push(x);break;case 3:return!0}}return!0}},C={EOF:1,parseError:function(t,e){if(!this.yy.parser)throw new Error(t);this.yy.parser.parseError(t,e)},setInput:function(t,e){return this.yy=e||this.yy||{},this._input=t,this._more=this._backtrack=this.done=!1,this.yylineno=this.yyleng=0,this.yytext=this.matched=this.match="",this.conditionStack=["INITIAL"],this.yylloc={first_line:1,first_column:0,last_line:1,last_column:0},this.options.ranges&&(this.yylloc.range=[0,0]),this.offset=0,this},input:function(){var t=this._input[0];return this.yytext+=t,this.yyleng++,this.offset++,this.match+=t,this.matched+=t,t.match(/(?:\r\n?|\n).*/g)?(this.yylineno++,this.yylloc.last_line++):this.yylloc.last_column++,this.options.ranges&&this.yylloc.range[1]++,this._input=this._input.slice(1),t},unput:function(t){var e=t.length,i=t.split(/(?:\r\n?|\n)/g);this._input=t+this._input,this.yytext=this.yytext.substr(0,this.yytext.length-e),this.offset-=e;var s=this.match.split(/(?:\r\n?|\n)/g);this.match=this.match.substr(0,this.match.length-1),this.matched=this.matched.substr(0,this.matched.length-1),i.length-1&&(this.yylineno-=i.length-1);var r=this.yylloc.range;return this.yylloc={first_line:this.yylloc.first_line,last_line:this.yylineno+1,first_column:this.yylloc.first_column,last_column:i?(i.length===s.length?this.yylloc.first_column:0)+s[s.length-i.length].length-i[0].length:this.yylloc.first_column-e},this.options.ranges&&(this.yylloc.range=[r[0],r[0]+this.yyleng-e]),this.yyleng=this.yytext.length,this},more:function(){return this._more=!0,this},reject:function(){return this.options.backtrack_lexer?(this._backtrack=!0,this):this.parseError("Lexical error on line "+(this.yylineno+1)+". You can only invoke reject() in the lexer when the lexer is of the backtracking persuasion (options.backtrack_lexer = true).\n"+this.showPosition(),{text:"",token:null,line:this.yylineno})},less:function(t){this.unput(this.match.slice(t))},pastInput:function(){var t=this.matched.substr(0,this.matched.length-this.match.length);return(t.length>20?"...":"")+t.substr(-20).replace(/\n/g,"")},upcomingInput:function(){var t=this.match;return t.length<20&&(t+=this._input.substr(0,20-t.length)),(t.substr(0,20)+(t.length>20?"...":"")).replace(/\n/g,"")},showPosition:function(){var t=this.pastInput(),e=new Array(t.length+1).join("-");return t+this.upcomingInput()+"\n"+e+"^"},test_match:function(t,e){var i,s,r;if(this.options.backtrack_lexer&&(r={yylineno:this.yylineno,yylloc:{first_line:this.yylloc.first_line,last_line:this.last_line,first_column:this.yylloc.first_column,last_column:this.yylloc.last_column},yytext:this.yytext,match:this.match,matches:this.matches,matched:this.matched,yyleng:this.yyleng,offset:this.offset,_more:this._more,_input:this._input,yy:this.yy,conditionStack:this.conditionStack.slice(0),done:this.done},this.options.ranges&&(r.yylloc.range=this.yylloc.range.slice(0))),(s=t[0].match(/(?:\r\n?|\n).*/g))&&(this.yylineno+=s.length),this.yylloc={first_line:this.yylloc.last_line,last_line:this.yylineno+1,first_column:this.yylloc.last_column,last_column:s?s[s.length-1].length-s[s.length-1].match(/\r?\n?/)[0].length:this.yylloc.last_column+t[0].length},this.yytext+=t[0],this.match+=t[0],this.matches=t,this.yyleng=this.yytext.length,this.options.ranges&&(this.yylloc.range=[this.offset,this.offset+=this.yyleng]),this._more=!1,this._backtrack=!1,this._input=this._input.slice(t[0].length),this.matched+=t[0],i=this.performAction.call(this,this.yy,this,e,this.conditionStack[this.conditionStack.length-1]),this.done&&this._input&&(this.done=!1),i)return i;if(this._backtrack){for(var a in r)this[a]=r[a];return!1}return!1},next:function(){if(this.done)return this.EOF;var t,e,i,s;this._input||(this.done=!0),this._more||(this.yytext="",this.match="");for(var r=this._currentRules(),a=0;ae[0].length)){if(e=i,s=a,this.options.backtrack_lexer){if(!1!==(t=this.test_match(i,r[a])))return t;if(this._backtrack){e=!1;continue}return!1}if(!this.options.flex)break}return e?!1!==(t=this.test_match(e,r[s]))&&t:""===this._input?this.EOF:this.parseError("Lexical error on line "+(this.yylineno+1)+". Unrecognized text.\n"+this.showPosition(),{text:"",token:null,line:this.yylineno})},lex:function(){var t=this.next();return t||this.lex()},begin:function(t){this.conditionStack.push(t)},popState:function(){return this.conditionStack.length-1>0?this.conditionStack.pop():this.conditionStack[0]},_currentRules:function(){return this.conditionStack.length&&this.conditionStack[this.conditionStack.length-1]?this.conditions[this.conditionStack[this.conditionStack.length-1]].rules:this.conditions.INITIAL.rules},topState:function(t){return(t=this.conditionStack.length-1-Math.abs(t||0))>=0?this.conditionStack[t]:"INITIAL"},pushState:function(t){this.begin(t)},stateStackSize:function(){return this.conditionStack.length},options:{},performAction:function(t,e,i,s){switch(i){case 0:break;case 1:return 24;case 2:return 38;case 3:return 22;case 4:return 20;case 5:return 12;case 6:return 5;case 7:return e.yytext[0]}},rules:[/^(?:\s+)/,/^(?:[0-9]+)/,/^(?:(==|!=|<=|>=|<|>))/,/^(?:[+\-*/%]?=)/,/^(?:(\&\&)|\|\|)/,/^(?:[a-zA-Z_][a-zA-Z0-9._]*)/,/^(?:$)/,/^(?:.)/],conditions:{INITIAL:{rules:[0,1,2,3,4,5,6,7],inclusive:!0}}};function U(){this.yy={}}return L.lexer=C,U.prototype=L,L.Parser=U,new U}();void 0!==a&&(t.parser=e,t.Parser=e.Parser,t.parse=function(){return e.parse.apply(e,arguments)},t.main=()=>{})}(n)),n}var c,g={};var m,u,f,d={},p={};function _(){if(m)return p;m=1,Object.defineProperty(p,"__esModule",{value:!0}),p.arrayJoin=function(t,e){const i=[];for(let s=0;se(i))},p.repeat=function(t,e){return new Array(t).fill(e).join("")};return p.ScopedIdMap=class{constructor(){this._map=new Map}get(t,e){const i=null==t?e:`${t}::${e}`;return this._map.has(i)||this._map.set(i,this._map.size),this._map.get(i)}size(){return this._map.size}},p.formatList=function(t){if(0===t.length)throw new Error("Cannot format an empty list");if(1===t.length)return t[0];const e=t.map(t=>`"${t}"`),i=e.pop();return e.join(", ")+` and ${i}`},p}function E(){if(u)return d;u=1,Object.defineProperty(d,"__esModule",{value:!0});const t=_();function e(e,i,s=1){const r=Math.max(e.first_line-1-s,0),a=e.last_line+s,o=i.split("\n").slice(r,a).map((t,i)=>{const s=i+r+1;return`${s>=e.first_line&&s<=e.last_line?">":" "} ${s} | ${t}`});if(e.first_line===e.last_line){const i=t.repeat(e.first_column," "),s=t.repeat(e.last_column-e.first_column,"^"),a=e.first_line-r;o.splice(a,0,` | ${i}${s}`)}return o.join("\n")}d.printLoc=e;class i extends Error{constructor(t,i,s){super(t),this.sourceContext=e(i,s),this.loc=i}}class s extends i{}return d.createUserError=function(t,e,i){return new s(t,e,i)},d.createCompilerError=function(t,e,s){return new i(t,e,s)},d}function b(){if(f)return s;f=1,Object.defineProperty(s,"__esModule",{value:!0});const t=(e||(e=1,Object.defineProperty(r,"__esModule",{value:!0}),r.getLoc=function(t,e){let i={destCol:1,srcCol:1,srcLine:1};t.forEach(t=>{t.destCol>e||(i=t)});const s=e-i.destCol;return{column:i.srcCol+s,line:i.srcLine}},r.preProcess=function(t){const e=[];let i=1,s="",r=0,a=!1,o=!1,h=!1;for(let A=0;A{if("NODE"===t.type){const r=i[t.key],o=e(r,s);o!==r&&(a=Object.assign(Object.assign({},a),{[t.key]:o}))}else if("ARRAY"===t.type){const r=i[t.key],o=r.map(t=>e(t,s)),h=r.some((t,e)=>t!==o[e]);h&&(a=Object.assign(Object.assign({},a),{[t.key]:o}))}}),s(a)},g}(),o=E();function n(e,i){const s=t.getLoc(i,e.first_column),r=t.getLoc(i,e.last_column);return{first_column:s.column,last_column:r.column,first_line:s.line,last_line:r.line}}return s.parse=function(e){const[s,r]=t.preProcess(e);try{const t=i.parse(s);return a.mapAst(t,t=>{if(1!==t.loc.first_line||1!=t.loc.last_line)throw o.createCompilerError("Unexpected multiline",t.loc,e);return Object.assign(Object.assign({},t),{loc:n(t.loc,r)})})}catch(t){if(null==t.hash)throw t;throw o.createUserError(`Parse Error: ${t.message.split("\n")[3]}`,n(t.hash.loc,r),e)}},s}var x,v,T={},S={},P={},w={};function I(){if(x)return w;return x=1,Object.defineProperty(w,"__esModule",{value:!0}),w.write=function(t,e){let i=52;var s,r,a,o=64-i-1,h=(1<>1,n=23===i?Math.pow(2,-24)-Math.pow(2,-77):0,l=0,c=e<0||0===e&&1/e<0?1:0;for(e=Math.abs(e),isNaN(e)||e===1/0?(r=isNaN(e)?1:0,s=h):(s=Math.floor(Math.log(e)/Math.LN2),e*(a=Math.pow(2,-s))<1&&(s--,a*=2),(e+=s+A>=1?n/a:n*Math.pow(2,1-A))*a>=2&&(s++,a/=2),s+A>=h?(r=0,s=h):s+A>=1?(r=(e*a-1)*Math.pow(2,i),s+=A):(r=e*Math.pow(2,A-1)*Math.pow(2,i),s=0));i>=8;t[0+l]=255&r,l+=1,r/=256,i-=8);for(s=s<0;t[0+l]=255&s,l+=1,s/=256,o-=8);t[0+l-1]|=128*c},w}function R(){return v||(v=1,function(t){var e=P&&P.__importStar||function(t){if(t&&t.__esModule)return t;var e={};if(null!=t)for(var i in t)Object.hasOwnProperty.call(t,i)&&(e[i]=t[i]);return e.default=t,e};Object.defineProperty(t,"__esModule",{value:!0});const i=e(I()),s=_();function r(t){const e=new Uint8Array(8);return i.write(e,t),e}function a(t){const e=[];do{let i=127&t;0!==(t>>>=7)&&(i|=128),e.push(i)}while(0!==t);return e}function o(t){let e=[],i=0,s=Math.ceil(Math.log2(Math.abs(t))),r=t<0,a=!0;for(;a;)i=127&t,t>>=7,r&&(t|=-(1<[2,t],loop:t=>[3,t],if:t=>[4,t],else:5,end:11,br_if:t=>[13,...a(t)],call:t=>[16,...a(t)],drop:26,select:27,local_get:t=>[32,...a(t)],local_set:t=>[33,...a(t)],local_tee:t=>[34,...a(t)],global_get:t=>[35,...a(t)],global_set:t=>[36,...a(t)],f64_load:(t,e)=>[43,...a(t),...a(e)],f64_store:(t,e)=>[57,...a(t),...a(e)],i32_const:t=>[65,...o(t)],f64_const:t=>[68,...r(t)],i32_eqz:69,i32_ne:71,i32_lt_s:72,i32_lt_u:73,i32_gt_s:74,i32_le_s:76,i32_le_u:77,i32_ge_s:78,f64_eq:97,f64_ne:98,f64_lt:99,f64_gt:100,f64_le:101,f64_ge:102,i32_add:106,i32_sub:107,i32_mul:108,i32_rem_s:111,i32_and:113,i32_or:114,i64_rem_s:129,i64_and:131,i64_or:132,f64_abs:153,f64_neg:154,f64_ceil:155,f64_floor:156,f64_sqrt:159,f64_add:160,f64_sub:161,f64_mul:162,f64_div:163,f64_min:164,f64_max:165,i32_trunc_f64_s:170,i32_trunc_f64_u:171,i64_trunc_s_f64:176,f64_convert_i64_s:185,f64_convert_i32_s:183},t.VAL_TYPE={i32:127,i64:126,f32:125,f64:124},t.MUTABILITY={const:0,var:1},t.BLOCK={void:64,i32:127,i64:126,f32:125,f64:124},t.FUNCTION_TYPE=96,t.MEMORY_IDX=2,t.GLOBAL_TYPE=3,t.TYPE_IDX=0,t.IS_ZEROISH=[t.op.f64_abs,...t.op.f64_const(t.EPSILON),t.op.f64_lt],t.IS_NOT_ZEROISH=[t.op.f64_abs,...t.op.f64_const(t.EPSILON),t.op.f64_gt],t.encodef64=r,t.encodeString=t=>[t.length].concat(t.split("").map(t=>t.charCodeAt(0))),t.unsignedLEB128=a,t.signedLEB128=o,t.encodeFlatVector=t=>a(t.length).concat(t),t.encodeNestedVector=t=>a(t.length).concat(s.flatten(t)),t.encodeSection=function(e,i){if(0===i.length)return[];const s=t.encodeFlatVector(t.encodeNestedVector(i));return s.unshift(e),s}}(P)),P}var y,B={};function L(){if(y)return B;y=1,Object.defineProperty(B,"__esModule",{value:!0});const t={sin:Math.sin,cos:Math.cos,tan:Math.tan,asin:Math.asin,acos:Math.acos,atan:Math.atan,atan2:Math.atan2,rand:t=>Math.random()*t,pow:Math.pow,log:Math.log,log10:Math.log10,exp:Math.exp,sigmoid:function(t,e){const i=1+Math.exp(-t*e);return Math.abs(i)>1e-5?1/i:0}};return B.default=t,B}var C,U,M,F,Q={},V={};function D(){return C||(C=1,function(t){Object.defineProperty(t,"__esModule",{value:!0});t.MAX_LOOP_COUNT=1048576,t.BUFFER_SIZE=8388608,t.WASM_MEMORY_SIZE=Math.ceil(8*t.BUFFER_SIZE*2/65536)}(V)),V}function q(){if(U)return Q;U=1,Object.defineProperty(Q,"__esModule",{value:!0});const t=R(),e=D();return Q.localFuncMap={sqr:{args:[t.VAL_TYPE.f64],returns:[t.VAL_TYPE.f64],binary:[...t.op.local_get(0),...t.op.local_get(0),t.op.f64_mul]},bor:{args:[t.VAL_TYPE.f64,t.VAL_TYPE.f64],returns:[t.VAL_TYPE.f64],binary:[...t.op.local_get(0),...t.IS_NOT_ZEROISH,...t.op.local_get(1),...t.IS_NOT_ZEROISH,t.op.i32_or,...t.op.i32_const(0),t.op.i32_ne,t.op.f64_convert_i32_s]},band:{args:[t.VAL_TYPE.f64,t.VAL_TYPE.f64],returns:[t.VAL_TYPE.f64],binary:[...t.op.local_get(0),...t.IS_NOT_ZEROISH,...t.op.local_get(1),...t.IS_NOT_ZEROISH,t.op.i32_and,...t.op.i32_const(0),t.op.i32_ne,t.op.f64_convert_i32_s]},sign:{args:[t.VAL_TYPE.f64],returns:[t.VAL_TYPE.f64],binary:[...t.op.f64_const(0),...t.op.local_get(0),t.op.f64_lt,...t.op.local_get(0),...t.op.f64_const(0),t.op.f64_lt,t.op.i32_sub,t.op.f64_convert_i32_s]},mod:{args:[t.VAL_TYPE.f64,t.VAL_TYPE.f64],returns:[t.VAL_TYPE.f64],localVariables:[t.VAL_TYPE.i32],binary:[...t.op.local_get(1),t.op.i32_trunc_f64_s,...t.op.local_tee(2),...t.op.i32_const(0),t.op.i32_ne,...t.op.if(t.BLOCK.f64),...t.op.local_get(0),t.op.i32_trunc_f64_s,...t.op.local_get(2),t.op.i32_rem_s,t.op.f64_convert_i32_s,t.op.else,...t.op.f64_const(0),t.op.end]},bitwiseOr:{args:[t.VAL_TYPE.f64,t.VAL_TYPE.f64],returns:[t.VAL_TYPE.f64],binary:[...t.op.local_get(0),t.op.i64_trunc_s_f64,...t.op.local_get(1),t.op.i64_trunc_s_f64,t.op.i64_or,t.op.f64_convert_i64_s]},bitwiseAnd:{args:[t.VAL_TYPE.f64,t.VAL_TYPE.f64],returns:[t.VAL_TYPE.f64],binary:[...t.op.local_get(0),t.op.i64_trunc_s_f64,...t.op.local_get(1),t.op.i64_trunc_s_f64,t.op.i64_and,t.op.f64_convert_i64_s]},div:{args:[t.VAL_TYPE.f64,t.VAL_TYPE.f64],returns:[t.VAL_TYPE.f64],localVariables:[t.VAL_TYPE.i32],binary:[...t.op.local_get(1),...t.op.f64_const(0),t.op.f64_ne,...t.op.if(t.BLOCK.f64),...t.op.local_get(0),...t.op.local_get(1),t.op.f64_div,t.op.else,...t.op.f64_const(0),t.op.end]},_getBufferIndex:{args:[t.VAL_TYPE.f64],returns:[t.VAL_TYPE.i32],localVariables:[t.VAL_TYPE.f64,t.VAL_TYPE.i32],binary:[...t.op.f64_const(t.EPSILON),...t.op.local_get(0),t.op.f64_add,...t.op.local_tee(1),t.op.i32_trunc_f64_s,...t.op.local_set(2),...t.op.i32_const(-1),...t.op.local_get(2),...t.op.i32_const(8),t.op.i32_mul,...t.op.local_get(2),...t.op.i32_const(0),t.op.i32_lt_s,...t.op.local_get(2),...t.op.i32_const(e.BUFFER_SIZE-1),t.op.i32_gt_s,t.op.i32_or,t.op.select]}},Q}function z(){if(M)return S;M=1;var t=S&&S.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(S,"__esModule",{value:!0});const e=R(),i=t(L()),s=E(),r=q(),a=_(),o=D();function h(t,l){var c,g,m;switch(t.type){case"SCRIPT":{const i=t.body.map((t,i)=>[...h(t,l),e.op.drop]);return a.flatten(i)}case"EXPRESSION_BLOCK":return A(t.body,l);case"BINARY_EXPRESSION":{const i=h(t.left,l),r=h(t.right,l),a={"+":[e.op.f64_add],"-":[e.op.f64_sub],"*":[e.op.f64_mul],"/":l.resolveFunc("div"),"%":l.resolveFunc("mod"),"|":l.resolveFunc("bitwiseOr"),"&":l.resolveFunc("bitwiseAnd"),"^":l.resolveFunc("pow"),"==":[e.op.f64_sub,...e.IS_ZEROISH,e.op.f64_convert_i32_s],"!=":[e.op.f64_sub,...e.IS_NOT_ZEROISH,e.op.f64_convert_i32_s],"<":[e.op.f64_lt,e.op.f64_convert_i32_s],">":[e.op.f64_gt,e.op.f64_convert_i32_s],"<=":[e.op.f64_le,e.op.f64_convert_i32_s],">=":[e.op.f64_ge,e.op.f64_convert_i32_s]}[t.operator];if(null==a)throw s.createCompilerError(`Unknown binary expression operator ${t.operator}`,t.loc,l.rawSource);return[...i,...r,...a]}case"CALL_EXPRESSION":{const g=t.callee.value,m=t.arguments,u=e=>{if(m.lengthe)throw s.createUserError(`Too many arguments passed to \`${g}()\`. Expected ${e} but got ${m.length}.`,m[e].loc,l.rawSource)};switch(g){case"exec2":return u(2),A(t.arguments,l);case"exec3":return u(3),A(t.arguments,l);case"if":u(3);const[i,r,a]=t.arguments;return function(t,i,s,r){return[...h(t,r),...e.IS_NOT_ZEROISH,...e.op.if(e.BLOCK.f64),...h(i,r),e.op.else,...h(s,r),e.op.end]}(i,r,a,l);case"while":return u(1),function(t,i){const s=h(t,i),r=i.resolveLocal(e.VAL_TYPE.i32);return[...e.op.i32_const(0),...e.op.local_set(r),...e.op.loop(e.BLOCK.void),...e.op.local_get(r),...e.op.i32_const(1),e.op.i32_add,...e.op.local_tee(r),...e.op.i32_const(o.MAX_LOOP_COUNT),e.op.i32_lt_u,...s,...e.IS_NOT_ZEROISH,e.op.i32_and,...e.op.br_if(0),e.op.end,...e.op.f64_const(0)]}(t.arguments[0],l);case"loop":return u(2),function(t,i,s){const r=h(i,s),a=s.resolveLocal(e.VAL_TYPE.i32);return[...e.op.block(e.BLOCK.void),...h(t,s),e.op.i32_trunc_f64_s,...e.op.local_tee(a),...e.op.i32_const(0),e.op.i32_le_s,...e.op.br_if(1),...e.op.loop(e.BLOCK.void),...r,e.op.drop,...e.op.local_get(a),...e.op.i32_const(1),e.op.i32_sub,...e.op.local_tee(a),...e.op.i32_const(0),e.op.i32_ne,...e.op.br_if(0),e.op.end,e.op.end,...e.op.f64_const(0)]}(t.arguments[0],t.arguments[1],l);case"megabuf":case"gmegabuf":u(1);const m=l.resolveLocal(e.VAL_TYPE.i32);return[...h(t.arguments[0],l),...null!==(c=l.resolveFunc("_getBufferIndex"))&&void 0!==c?c:[],...e.op.local_tee(m),...e.op.i32_const(-1),e.op.i32_ne,...e.op.if(e.BLOCK.f64),...e.op.local_get(m),...e.op.f64_load(3,n(g)),e.op.else,...e.op.f64_const(0),e.op.end];case"assign":u(2);const f=t.arguments[0];if("IDENTIFIER"!=f.type)throw s.createUserError("Expected the first argument of `assign()` to be an identifier.",f.loc,l.rawSource);const d=l.resolveVar(f.value);return[...h(t.arguments[1],l),...e.op.global_set(d),...e.op.global_get(d)]}const f=a.flatten(t.arguments.map(t=>h(t,l)));switch(g){case"abs":return u(1),[...f,e.op.f64_abs];case"sqrt":return u(1),[...f,e.op.f64_abs,e.op.f64_sqrt];case"int":case"floor":return u(1),[...f,e.op.f64_floor];case"min":return u(2),[...f,e.op.f64_min];case"max":return u(2),[...f,e.op.f64_max];case"above":return u(2),[...f,e.op.f64_gt,e.op.f64_convert_i32_s];case"below":return u(2),[...f,e.op.f64_lt,e.op.f64_convert_i32_s];case"equal":return u(2),[...f,e.op.f64_sub,...e.IS_ZEROISH,e.op.f64_convert_i32_s];case"bnot":return u(1),[...f,...e.IS_ZEROISH,e.op.f64_convert_i32_s];case"ceil":return u(1),[...f,e.op.f64_ceil]}const d=l.resolveFunc(g);if(null==d||g.startsWith("_"))throw s.createUserError(`"${g}" is not defined.`,t.callee.loc,l.rawSource);if(null!=i.default[g])u(i.default[g].length);else{if(null==r.localFuncMap[g])throw s.createCompilerError(`Missing arity information for the function \`${g}()\``,t.callee.loc,l.rawSource);u(r.localFuncMap[g].args.length)}return[...f,...d]}case"ASSIGNMENT_EXPRESSION":{const{left:i}=t,r=h(t.right,l),a=function(t,i){const r={"+=":[e.op.f64_add],"-=":[e.op.f64_sub],"*=":[e.op.f64_mul],"/=":[e.op.f64_div],"%=":i.resolveFunc("mod"),"=":null},a=r[t.operator];if(void 0===a)throw s.createCompilerError(`Unknown assignment operator "${t.operator}"`,t.loc,i.rawSource);return a}(t,l);if("IDENTIFIER"===i.type){const t=l.resolveVar(i.value),s=e.op.global_get(t),o=e.op.global_set(t);return null===a?[...r,...o,...s]:[...s,...r,...a,...o,...s]}if("CALL_EXPRESSION"!==i.type)throw s.createCompilerError(`Unexpected left hand side type for assignment: ${i.type}`,t.loc,l.rawSource);const o=l.resolveLocal(e.VAL_TYPE.i32);if(1!==i.arguments.length)throw s.createUserError(`Expected 1 argument when assinging to a buffer but got ${i.arguments.length}.`,0===i.arguments.length?i.loc:i.arguments[1].loc,l.rawSource);const A=i.callee.value;if("gmegabuf"!==A&&"megabuf"!==A)throw s.createUserError("The only function calls which may be assigned to are `gmegabuf()` and `megabuf()`.",i.callee.loc,l.rawSource);const c=n(A);if(null===a){const t=l.resolveLocal(e.VAL_TYPE.i32),s=l.resolveLocal(e.VAL_TYPE.f64);return[...r,...e.op.local_set(s),...h(i.arguments[0],l),...null!==(g=l.resolveFunc("_getBufferIndex"))&&void 0!==g?g:[],...e.op.local_tee(t),...e.op.i32_const(0),e.op.i32_lt_s,...e.op.if(e.BLOCK.f64),...e.op.f64_const(0),e.op.else,...e.op.local_get(t),...e.op.local_tee(o),...e.op.local_get(s),...e.op.f64_store(3,c),...e.op.local_get(s),e.op.end]}const u=l.resolveLocal(e.VAL_TYPE.i32),f=l.resolveLocal(e.VAL_TYPE.i32),d=l.resolveLocal(e.VAL_TYPE.f64),p=l.resolveLocal(e.VAL_TYPE.f64);return[...r,...e.op.local_set(d),...h(i.arguments[0],l),...null!==(m=l.resolveFunc("_getBufferIndex"))&&void 0!==m?m:[],...e.op.local_tee(u),...e.op.i32_const(-1),e.op.i32_ne,...e.op.local_tee(f),...e.op.if(e.BLOCK.f64),...e.op.local_get(u),...e.op.f64_load(3,c),e.op.else,...e.op.f64_const(0),e.op.end,...e.op.local_get(d),...a,...e.op.local_tee(p),...e.op.local_get(f),...e.op.if(e.BLOCK.void),...e.op.local_get(u),...e.op.local_get(p),...e.op.f64_store(3,c),e.op.end]}case"LOGICAL_EXPRESSION":{const i=h(t.left,l),r=h(t.right,l),a={"&&":{comparison:e.IS_ZEROISH,shortCircutValue:0},"||":{comparison:e.IS_NOT_ZEROISH,shortCircutValue:1}}[t.operator];if(null==a)throw s.createCompilerError(`Unknown logical expression operator ${t.operator}`,t.loc,l.rawSource);const{comparison:o,shortCircutValue:A}=a;return[...i,...o,...e.op.if(e.BLOCK.f64),...e.op.f64_const(A),e.op.else,...r,...e.IS_NOT_ZEROISH,e.op.f64_convert_i32_s,e.op.end]}case"UNARY_EXPRESSION":{const i=h(t.value,l),r={"-":[e.op.f64_neg],"+":[],"!":[...e.IS_ZEROISH,e.op.f64_convert_i32_s]}[t.operator];if(null==r)throw s.createCompilerError(`Unknown logical unary operator ${t.operator}`,t.loc,l.rawSource);return[...i,...r]}case"IDENTIFIER":const u=t.value;return e.op.global_get(l.resolveVar(u));case"NUMBER_LITERAL":return e.op.f64_const(t.value);default:throw s.createCompilerError(`Unknown AST node type ${t.type}`,t.loc,l.rawSource)}}function A(t,i){const s=t.map((t,e)=>h(t,i));return a.flatten(a.arrayJoin(s,[e.op.drop]))}function n(t){switch(t){case"gmegabuf":return 8*o.BUFFER_SIZE;case"megabuf":return 0}}return S.emit=h,S}function N(){if(F)return T;F=1;var t=T&&T.__importDefault||function(t){return t&&t.__esModule?t:{default:t}},e=T&&T.__importStar||function(t){if(t&&t.__esModule)return t;var e={};if(null!=t)for(var i in t)Object.hasOwnProperty.call(t,i)&&(e[i]=t[i]);return e.default=t,e};Object.defineProperty(T,"__esModule",{value:!0});const i=b(),s=z(),r=R(),a=t(L()),o=e(_()),h=q(),A=D();return T.compileModule=function({pools:t,functions:e,eelVersion:n=2,preParsed:l=!1}){if(Object.keys(t).includes("shims"))throw new Error('You may not name a pool "shims". "shims" is reserved for injected JavaScript functions.');const c=[];Object.entries(t).forEach(([t,e])=>{e.forEach(e=>{c.push([t,e])})});const g=new o.ScopedIdMap;c.forEach(([t,e])=>{g.get(t,e)});const m=Object.entries(a.default).map(([t,e])=>({args:new Array(e.length).fill(null).map(t=>r.VAL_TYPE.f64),returns:[r.VAL_TYPE.f64],name:t})),u=[],f=[];Object.entries(e).forEach(([e,{pool:a,code:A}])=>{if(null==t[a]){const i=Object.keys(t);if(0===i.length)throw new Error(`The function "${e}" was declared as using a variable pool named "${a}" but no pools were defined.`);throw new Error(`The function "${e}" was declared as using a variable pool named "${a}" which is not among the variable pools defined. The defined variable pools are: ${o.formatList(i)}.`)}const c=l?A:i.parse(A);if("string"==typeof c)throw new Error("Got passed unparsed code without setting the preParsed flag");if("SCRIPT"!==c.type)throw new Error("Invalid AST");if(0===c.body.length)return;const d=[],p={resolveVar:t=>/^reg\d\d$/.test(t)?g.get(null,t):g.get(a,t),resolveLocal:t=>(d.push(t),d.length-1),resolveFunc:t=>{const e=m.findIndex(e=>e.name===t);if(-1!==e){const i=r.op.call(e);return"rand"===t&&1===n?[...i,r.op.f64_floor]:i}if(null==h.localFuncMap[t])return null;let i=u.indexOf(t);return-1===i&&(u.push(t),i=u.length-1),r.op.call(i+m.length)},rawSource:A},_=s.emit(c,p);f.push({binary:_,exportName:e,args:[],returns:[],localVariables:d})});const d=u.map(t=>{const e=h.localFuncMap[t];if(null==e)throw new Error(`Undefined local function "${t}"`);return e}),p=t=>[...t.args,"|",...t.returns].join("-"),_=[],E=new Map;function b(t){const e=p(t),i=E.get(e);if(null==i)throw new Error(`Failed to get a type index for key ${e}`);return i}[...m,...d,...f].forEach(t=>{const e=p(t);E.has(e)||(_.push([r.FUNCTION_TYPE,...r.encodeFlatVector(t.args),...r.encodeFlatVector(t.returns)]),E.set(e,_.length-1))});const x=[...c.map(([t,e])=>[...r.encodeString(t),...r.encodeString(e),r.GLOBAL_TYPE,r.VAL_TYPE.f64,r.MUTABILITY.var]),...m.map((t,e)=>{const i=b(t);return[...r.encodeString("shims"),...r.encodeString(t.name),r.TYPE_IDX,...r.unsignedLEB128(i)]})],v=[...d,...f].map(t=>{const e=b(t);return r.unsignedLEB128(e)}),T=[[1,...r.unsignedLEB128(A.WASM_MEMORY_SIZE),...r.unsignedLEB128(A.WASM_MEMORY_SIZE)]],S=g.size()-c.length,P=o.times(S,()=>[r.VAL_TYPE.f64,r.MUTABILITY.var,...r.op.f64_const(0),r.op.end]),w=[...f].map((t,e)=>{const i=e+m.length+d.length;return[...r.encodeString(t.exportName),r.EXPORT_TYPE.FUNC,...r.unsignedLEB128(i)]}),I=[...d,...f].map(t=>{var e;const i=(null!==(e=t.localVariables)&&void 0!==e?e:[]).map(t=>[...r.unsignedLEB128(1),t]);return r.encodeFlatVector([...r.encodeNestedVector(i),...t.binary,r.op.end])});return new Uint8Array([...r.MAGIC,...r.WASM_VERSION,...r.encodeSection(r.SECTION.TYPE,_),...r.encodeSection(r.SECTION.IMPORT,x),...r.encodeSection(r.SECTION.FUNC,v),...r.encodeSection(r.SECTION.MEMORY,T),...r.encodeSection(r.SECTION.GLOBAL,P),...r.encodeSection(r.SECTION.EXPORT,w),...r.encodeSection(r.SECTION.CODE,I)])},T}var X,O,k={};var G=function(){if(O)return i;O=1;var t=i&&i.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(i,"__esModule",{value:!0});const e=b();i.parse=e.parse;const s=N();i.compileModule=s.compileModule;const r=t(L());i.shims=r.default;const a=function(){if(X)return k;X=1;var t=k&&k.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(k,"__esModule",{value:!0});const e=t(L()),i=N();return k.loadModule=async function({pools:t,functions:s,eelVersion:r=2}){let a={};Object.entries(t).forEach(([t,e])=>{a[t]=new Set(Object.keys(e))});const o=i.compileModule({pools:a,functions:s,eelVersion:r}),h=await WebAssembly.compile(o);var A=Object.assign(Object.assign({},t),{shims:e.default});return await WebAssembly.instantiate(h,A)},k}();return i.loadModule=a.loadModule,i}();const Y="undefined"!=typeof BigUint64Array,W=Symbol(),J=new TextDecoder("utf-16le");function K(t,e){const i=new Uint32Array(t)[e+-4>>>2]>>>1,s=new Uint16Array(t,e,i);return i<=32?String.fromCharCode.apply(String,s):J.decode(s)}function j(t){const e={};function i(t,e){return t?K(t.buffer,e):""}const s=t.env=t.env||{};return s.abort=s.abort||function(t,r,a,o){const h=e.memory||s.memory;throw Error(`abort: ${i(h,t)} at ${i(h,r)}:${a}:${o}`)},s.trace=s.trace||function(t,r,...a){const o=e.memory||s.memory;console.log(`trace: ${i(o,t)}${r?" ":""}${a.slice(0,r).join(", ")}`)},s.seed=s.seed||Date.now,t.Math=t.Math||Math,t.Date=t.Date||Date,e}function H(t,e){const i=e.exports,s=i.memory,r=i.table,a=i.__new,o=i.__retain,h=i.__rtti_base||-1;function A(t){const e=function(t){const e=new Uint32Array(s.buffer);if((t>>>=0)>=e[h>>>2])throw Error(`invalid id: ${t}`);return e[(h+4>>>2)+2*t]}(t);if(!(7&e))throw Error(`not an array: ${t}, flags=${e}`);return e}function n(t){const e=new Uint32Array(s.buffer);if((t>>>=0)>=e[h>>>2])throw Error(`invalid id: ${t}`);return e[(h+4>>>2)+2*t+1]}function l(t){return 31-Math.clz32(t>>>6&31)}function c(t,e,i){const r=s.buffer;if(i)switch(t){case 2:return new Float32Array(r);case 3:return new Float64Array(r)}else switch(t){case 0:return new(e?Int8Array:Uint8Array)(r);case 1:return new(e?Int16Array:Uint16Array)(r);case 2:return new(e?Int32Array:Uint32Array)(r);case 3:return new(e?BigInt64Array:BigUint64Array)(r)}throw Error(`unsupported align: ${t}`)}function g(t){const e=new Uint32Array(s.buffer),i=A(e[t+-8>>>2]),r=l(i);let a=4&i?t:e[t+4>>>2];const o=2&i?e[t+12>>>2]:e[a+-4>>>2]>>>r;return c(r,2048&i,4096&i).subarray(a>>>=r,a+o)}function m(t,e,i){return new t(u(t,e,i))}function u(t,e,i){const r=s.buffer,a=new Uint32Array(r),o=a[i+4>>>2];return new t(r,o,a[o+-4>>>2]>>>e)}function f(e,i,s){t[`__get${i}`]=m.bind(null,e,s),t[`__get${i}View`]=u.bind(null,e,s)}return t.__newString=function(t){if(null==t)return 0;const e=t.length,i=a(e<<1,1),r=new Uint16Array(s.buffer);for(var o=0,h=i>>>1;o>>2])throw Error(`not a string: ${t}`);return K(e,t)},t.__newArray=function(t,e){const i=A(t),r=l(i),h=e.length,n=a(h<>>2]=o(n),A[e+4>>>2]=n,A[e+8>>>2]=h<>>2]=h),g=e}const m=c(r,2048&i,4096&i);if(16384&i)for(let t=0;t>>r)+t]=o(e[t]);else m.set(e,n>>>r);return g},t.__getArrayView=g,t.__getArray=function(t){const e=g(t),i=e.length,s=new Array(i);for(let t=0;t>>2];return e.slice(t,t+i)},[Int8Array,Uint8Array,Uint8ClampedArray,Int16Array,Uint16Array,Int32Array,Uint32Array,Float32Array,Float64Array].forEach(t=>{f(t,t.name,31-Math.clz32(t.BYTES_PER_ELEMENT))}),Y&&[BigUint64Array,BigInt64Array].forEach(t=>{f(t,t.name.slice(3),3)}),t.__instanceof=function(t,e){const i=new Uint32Array(s.buffer);let r=i[t+-8>>>2];if(r<=i[h>>>2])do{if(r==e)return!0;r=n(r)}while(r);return!1},t.memory=t.memory||s,t.table=t.table||r,it(i,t)}function Z(t){return"undefined"!=typeof Response&&t instanceof Response}function $(t){return t instanceof WebAssembly.Module}async function tt(t,e={}){if(Z(t=await t))return et(t,e);const i=$(t)?t:await WebAssembly.compile(t),s=j(e),r=await WebAssembly.instantiate(i,e);return{module:i,instance:r,exports:H(s,r)}}async function et(t,e={}){if(!WebAssembly.instantiateStreaming)return tt(Z(t=await t)?t.arrayBuffer():t,e);const i=j(e),s=await WebAssembly.instantiateStreaming(t,e),r=H(i,s.instance);return{...s,exports:r}}function it(t,e={}){const i=t.__argumentsLength?e=>{t.__argumentsLength.value=e}:t.__setArgumentsLength||t.__setargc||(()=>{});for(let s in t){if(!Object.prototype.hasOwnProperty.call(t,s))continue;const r=t[s];let a=s.split("."),o=e;for(;a.length>1;){let t=a.shift();Object.prototype.hasOwnProperty.call(o,t)||(o[t]={}),o=o[t]}let h=a[0],A=h.indexOf("#");if(A>=0){const e=h.substring(0,A),a=o[e];if(void 0===a||!a.prototype){const t=function(...e){return t.wrap(t.prototype.constructor(0,...e))};t.prototype={valueOf(){return this[W]}},t.wrap=function(e){return Object.create(t.prototype,{[W]:{value:e,writable:!1}})},a&&Object.getOwnPropertyNames(a).forEach(e=>Object.defineProperty(t,e,Object.getOwnPropertyDescriptor(a,e))),o[e]=t}if(h=h.substring(A+1),o=o[e].prototype,/^(get|set):/.test(h)){if(!Object.prototype.hasOwnProperty.call(o,h=h.substring(4))){let e=t[s.replace("set:","get:")],i=t[s.replace("get:","set:")];Object.defineProperty(o,h,{get(){return e(this[W])},set(t){i(this[W],t)},enumerable:!0})}}else"constructor"===h?(o[h]=(...t)=>(i(t.length),r(...t))).original=r:(o[h]=function(...t){return i(t.length),r(this[W],...t)}).original=r}else/^(get|set):/.test(h)?Object.prototype.hasOwnProperty.call(o,h=h.substring(4))||Object.defineProperty(o,h,{get:t[s.replace("set:","get:")],set:t[s.replace("get:","set:")],enumerable:!0}):"function"==typeof r&&r!==i?(o[h]=(...t)=>(i(t.length),r(...t))).original=r:o[h]=r}return e}var st={instantiate:tt,instantiateSync:function(t,e={}){const i=$(t)?t:new WebAssembly.Module(t),s=j(e),r=new WebAssembly.Instance(i,e);return{module:i,instance:r,exports:H(s,r)}},instantiateStreaming:et,demangle:it};class rt{constructor(t,e,i=!1){this.samplesIn=t,this.samplesOut=e,this.equalize=i,this.NFREQ=2*e,this.equalize&&this.initEqualizeTable(),this.initBitRevTable(),this.initCosSinTable()}initEqualizeTable(){this.equalizeArr=new Float32Array(this.samplesOut);const t=1/this.samplesOut;for(let e=0;ee){const i=this.bitrevtable[e];this.bitrevtable[e]=this.bitrevtable[t],this.bitrevtable[t]=i}let i=this.NFREQ>>1;for(;i>=1&&t>=i;)t-=i,i>>=1;t+=i}}initCosSinTable(){let t=2,e=0;for(;t<=this.NFREQ;)e+=1,t<<=1;this.cossintable=[new Float32Array(e),new Float32Array(e)],t=2;let i=0;for(;t<=this.NFREQ;){const e=-2*Math.PI/t;this.cossintable[0][i]=Math.cos(e),this.cossintable[1][i]=Math.sin(e),i+=1,t<<=1}}timeToFrequencyDomain(t){const e=new Float32Array(this.NFREQ),i=new Float32Array(this.NFREQ);for(let s=0;s>1;for(let r=0;r0){let i=t;!ot.isFiniteNumber(i)||i<15?i=15:i>144&&(i=144),this.imm.fill(0);for(let t=0;t<3;t++)for(let e=this.starts[t];ethis.avg[t]?.2:.5,s=ot.adjustRateToFPS(s,30,i),this.avg[t]=this.avg[t]*s+this.imm[t]*(1-s),s=e<50?.9:.992,s=ot.adjustRateToFPS(s,30,i),this.longAvg[t]=this.longAvg[t]*s+this.imm[t]*(1-s),this.longAvg[t]<.001?(this.val[t]=1,this.att[t]=1):(this.val[t]=this.imm[t]/this.longAvg[t],this.att[t]=this.avg[t]/this.longAvg[t])}}}}const ht={baseVals:{gammaadj:1.25,wave_g:.5,mv_x:12,warpscale:1,brighten:0,mv_y:9,wave_scale:1,echo_alpha:0,additivewave:0,sx:1,sy:1,warp:.01,red_blue:0,wave_mode:0,wave_brighten:0,wrap:0,zoomexp:1,fshader:0,wave_r:.5,echo_zoom:1,wave_smoothing:.75,warpanimspeed:1,wave_dots:0,wave_x:.5,wave_y:.5,zoom:1,solarize:0,modwavealphabyvolume:0,dx:0,cx:.5,dy:0,darken_center:0,cy:.5,invert:0,bmotionvectorson:0,rot:0,modwavealphaend:.95,wave_mystery:-.2,decay:.9,wave_a:1,wave_b:.5,rating:5,modwavealphastart:.75,darken:0,echo_orient:0,ib_r:.5,ib_g:.5,ib_b:.5,ib_a:0,ib_size:0,ob_r:.5,ob_g:.5,ob_b:.5,ob_a:0,ob_size:0,mv_dx:0,mv_dy:0,mv_a:0,mv_r:.5,mv_g:.5,mv_b:.5,mv_l:0},init_eqs:function(){return{}},frame_eqs:function(t){return t.rkeys=["warp"],t.zoom=1.01+.02*t.treb_att,t.warp=.15+.25*t.bass_att,t},pixel_eqs:function(t){return t.warp=t.warp+.15*t.rad,t},waves:[{baseVals:{a:1,enabled:0,b:1,g:1,scaling:1,samples:512,additive:0,usedots:0,spectrum:0,r:1,smoothing:.5,thick:0,sep:0},init_eqs:function(t){return t.rkeys=[],t},frame_eqs:function(t){return t},point_eqs:""},{baseVals:{a:1,enabled:0,b:1,g:1,scaling:1,samples:512,additive:0,usedots:0,spectrum:0,r:1,smoothing:.5,thick:0,sep:0},init_eqs:function(t){return t.rkeys=[],t},frame_eqs:function(t){return t},point_eqs:""},{baseVals:{a:1,enabled:0,b:1,g:1,scaling:1,samples:512,additive:0,usedots:0,spectrum:0,r:1,smoothing:.5,thick:0,sep:0},init_eqs:function(t){return t.rkeys=[],t},frame_eqs:function(t){return t},point_eqs:""},{baseVals:{a:1,enabled:0,b:1,g:1,scaling:1,samples:512,additive:0,usedots:0,spectrum:0,r:1,smoothing:.5,thick:0,sep:0},init_eqs:function(t){return t.rkeys=[],t},frame_eqs:function(t){return t},point_eqs:""}],shapes:[{baseVals:{r2:0,a:1,enabled:0,b:0,tex_ang:0,thickoutline:0,g:0,textured:0,g2:1,tex_zoom:1,additive:0,border_a:.1,border_b:1,b2:0,a2:0,r:1,border_g:1,rad:.1,x:.5,y:.5,ang:0,sides:4,border_r:1},init_eqs:function(t){return t.rkeys=[],t},frame_eqs:function(t){return t}},{baseVals:{r2:0,a:1,enabled:0,b:0,tex_ang:0,thickoutline:0,g:0,textured:0,g2:1,tex_zoom:1,additive:0,border_a:.1,border_b:1,b2:0,a2:0,r:1,border_g:1,rad:.1,x:.5,y:.5,ang:0,sides:4,border_r:1},init_eqs:function(t){return t.rkeys=[],t},frame_eqs:function(t){return t}},{baseVals:{r2:0,a:1,enabled:0,b:0,tex_ang:0,thickoutline:0,g:0,textured:0,g2:1,tex_zoom:1,additive:0,border_a:.1,border_b:1,b2:0,a2:0,r:1,border_g:1,rad:.1,x:.5,y:.5,ang:0,sides:4,border_r:1},init_eqs:function(t){return t.rkeys=[],t},frame_eqs:function(t){return t}},{baseVals:{r2:0,a:1,enabled:0,b:0,tex_ang:0,thickoutline:0,g:0,textured:0,g2:1,tex_zoom:1,additive:0,border_a:.1,border_b:1,b2:0,a2:0,r:1,border_g:1,rad:.1,x:.5,y:.5,ang:0,sides:4,border_r:1},init_eqs:function(t){return t.rkeys=[],t},frame_eqs:function(t){return t}}],warp:"shader_body {\nret = texture2D(sampler_main, uv).rgb;\nret -= 0.004;\n}\n",comp:"shader_body {\nret = texture2D(sampler_main, uv).rgb;\nret *= hue_shader;\n}\n"};class At{static atan2(t,e){let i=Math.atan2(t,e);return i<0&&(i+=2*Math.PI),i}static cloneVars(t){return Object.assign({},t)}static range(t,e){return void 0===e?[...Array(t).keys()]:Array.from({length:e-t},(e,i)=>i+t)}static pick(t,e){const i={};for(let s=0;s>>8,this.state[0]=t^e^e>>>19,(this.state[0]>>>0)/4294967296}nextInt(t){return Math.floor(this.next()*t)}rand(t){return t<1?this.next():Math.floor(this.next()*Math.floor(t))}reset(t){nt.initializeState(this.state,t),this.warmUp()}}function lt(){return{random:Math.random,rand:t=>t<1?Math.random():Math.random()*Math.floor(t),randint:t=>Math.floor((t<1?Math.random():Math.random()*Math.floor(t))+1),getRNG:()=>null,reset:()=>{}}}let ct=null;function gt(t={}){return ct=t.deterministic||t.testMode?function(t=1){const e=new nt(t);return{random:()=>e.next(),rand:t=>e.rand(t),randint:t=>Math.floor(e.rand(t)+1),getRNG:()=>e,reset:i=>{void 0!==i?e.reset(i):e.reset(t)}}}(t.seed||12345):lt(),(t.deterministic||t.testMode)&&(window.rand=t=>ct.rand(t),window.randint=t=>ct.randint(t),Math.random=()=>ct.random()),ct}function mt(){return ct||(ct=lt()),ct}class ut{constructor(t,e,i){this.rng=mt(),this.preset=t,this.texsizeX=i.texsizeX,this.texsizeY=i.texsizeY,this.mesh_width=i.mesh_width,this.mesh_height=i.mesh_height,this.aspectx=i.aspectx,this.aspecty=i.aspecty,this.invAspectx=1/this.aspectx,this.invAspecty=1/this.aspecty,this.qs=At.range(1,33).map(t=>`q${t}`),this.ts=At.range(1,9).map(t=>`t${t}`),this.regs=At.range(100).map(t=>t<10?`reg0${t}`:`reg${t}`),this.initializeEquations(e)}initializeEquations(t){this.runVertEQs=""!==this.preset.pixel_eqs,this.mdVSQInit=null,this.mdVSRegs=null,this.mdVSFrame=null,this.mdVSUserKeys=null,this.mdVSFrameMap=null,this.mdVSShapes=null,this.mdVSUserKeysShapes=null,this.mdVSFrameMapShapes=null,this.mdVSWaves=null,this.mdVSUserKeysWaves=null,this.mdVSFrameMapWaves=null,this.mdVSQAfterFrame=null,this.gmegabuf=new Array(1048576).fill(0);const e={frame:t.frame,time:t.time,fps:t.fps,bass:t.bass,bass_att:t.bass_att,mid:t.mid,mid_att:t.mid_att,treb:t.treb,treb_att:t.treb_att,meshx:this.mesh_width,meshy:this.mesh_height,aspectx:this.invAspectx,aspecty:this.invAspecty,pixelsx:this.texsizeX,pixelsy:this.texsizeY,gmegabuf:this.gmegabuf};this.mdVS=Object.assign({},this.preset.baseVals,e),this.mdVS.megabuf=new Array(1048576).fill(0),this.mdVS.rand_start=new Float32Array([this.rng.random(),this.rng.random(),this.rng.random(),this.rng.random()]),this.mdVS.rand_preset=new Float32Array([this.rng.random(),this.rng.random(),this.rng.random(),this.rng.random()]);const i=this.qs.concat(this.regs,Object.keys(this.mdVS)),s=this.preset.init_eqs(At.cloneVars(this.mdVS));this.mdVSQInit=At.pick(s,this.qs),this.mdVSRegs=At.pick(s,this.regs);const r=At.pick(s,Object.keys(At.omit(s,i)));if(r.megabuf=s.megabuf,r.gmegabuf=s.gmegabuf,this.mdVSFrame=this.preset.frame_eqs(Object.assign({},this.mdVS,this.mdVSQInit,this.mdVSRegs,r)),this.mdVSUserKeys=Object.keys(At.omit(this.mdVSFrame,i)),this.mdVSFrameMap=At.pick(this.mdVSFrame,this.mdVSUserKeys),this.mdVSQAfterFrame=At.pick(this.mdVSFrame,this.qs),this.mdVSRegs=At.pick(this.mdVSFrame,this.regs),this.mdVSWaves=[],this.mdVSTWaveInits=[],this.mdVSUserKeysWaves=[],this.mdVSFrameMapWaves=[],this.preset.waves&&this.preset.waves.length>0)for(let t=0;t0)for(let t=0;t`q${t}`),this.ts=At.range(1,9).map(t=>`t${t}`),this.regs=At.range(100).map(t=>t<10?`reg0${t}`:`reg${t}`),this.globalKeys=["frame","time","fps","bass","bass_att","mid","mid_att","treb","treb_att","meshx","meshy","aspectx","aspecty","pixelsx","pixelsy"],this.frameKeys=["decay","wave_a","wave_r","wave_g","wave_b","wave_x","wave_y","wave_scale","wave_smoothing","wave_mode","old_wave_mode","wave_mystery","ob_size","ob_r","ob_g","ob_b","ob_a","ib_size","ib_r","ib_g","ib_b","ib_a","mv_x","mv_y","mv_dx","mv_dy","mv_l","mv_r","mv_g","mv_b","mv_a","echo_zoom","echo_alpha","echo_orient","wave_dots","wave_thick","additivewave","wave_brighten","modwavealphabyvolume","modwavealphastart","modwavealphaend","darken_center","gammaadj","warp","warpanimspeed","warpscale","zoom","zoomexp","rot","cx","cy","dx","dy","sx","sy","fshader","wrap","invert","brighten","darken","solarize","bmotionvectorson","b1n","b2n","b3n","b1x","b2x","b3x","b1ed"],this.waveFrameKeys=["samples","sep","scaling","spectrum","smoothing","r","g","b","a"],this.waveFrameInputKeys=["samples","r","g","b","a"],this.initializeEquations(e)}getQVars(t){return At.pickWasm(this.preset.globalPools[t],this.qs)}getTVars(t){return At.pickWasm(this.preset.globalPools[t],this.ts)}initializeEquations(t){this.runVertEQs=!!this.preset.pixel_eqs,this.mdVSQInit=null,this.mdVSQAfterFrame=null;const e={frame:t.frame,time:t.time,fps:t.fps,bass:t.bass,bass_att:t.bass_att,mid:t.mid,mid_att:t.mid_att,treb:t.treb,treb_att:t.treb_att,meshx:this.mesh_width,meshy:this.mesh_height,aspectx:this.invAspectx,aspecty:this.invAspecty,pixelsx:this.texsizeX,pixelsy:this.texsizeY};if(this.mdVS=Object.assign({},this.preset.baseVals,e),At.setWasm(this.preset.globalPools.perFrame,this.mdVS,Object.keys(this.mdVS)),this.rand_start=new Float32Array([this.rng.random(),this.rng.random(),this.rng.random(),this.rng.random()]),this.rand_preset=new Float32Array([this.rng.random(),this.rng.random(),this.rng.random(),this.rng.random()]),this.preset.init_eqs(),this.mdVSQInit=this.getQVars("perFrame"),this.preset.frame_eqs(),this.mdVSQAfterFrame=this.getQVars("perFrame"),this.mdVSTWaveInits=[],this.preset.waves&&this.preset.waves.length>0)for(let t=0;t0)for(let t=0;t-1){const i=t.substring(0,e),s=t.substring(e),r=s.indexOf("{"),a=s.lastIndexOf("}");return[i,s.substring(r+1,a)]}return["",t]}static getFragmentFloatPrecision(t){return t.getShaderPrecisionFormat(t.FRAGMENT_SHADER,t.HIGH_FLOAT).precision>0?"highp":t.getShaderPrecisionFormat(t.FRAGMENT_SHADER,t.MEDIUM_FLOAT).precision>0?"mediump":"lowp"}static getUserSamplers(t){const e=[],i=t.match(dt);if(i&&i.length>0)for(let t=0;t0){const t=s[1];e.push({sampler:t})}}return e}}class Et{static smoothWave(t,e,i,s=!1){const r=-.15,a=1.15,o=1.15,h=-.15;let A,n=0,l=0,c=1;for(let g=0;g-.01&&a>.001&&i.length>0){const h=bt.processWaveform(i,r),A=bt.processWaveform(s,r),n=Math.floor(r.wave_mode)%8,l=Math.floor(r.old_wave_mode)%8,c=2*r.wave_x-1,g=2*r.wave_y-1;this.numVert=0,this.oldNumVert=0;const m=t&&n!==l?2:1;for(let t=0;t1)||(u=.5*u+.5,u-=Math.floor(u),u=Math.abs(u),u=2*u-1),0===t?(s=this.positions,m=this.positions2):(s=this.oldPositions,m=this.oldPositions2),a=r.wave_a,0===e){if(r.modwavealphabyvolume>0){const t=r.modwavealphaend-r.modwavealphastart;a*=(o-r.modwavealphastart)/t}a=Math.clamp(a,0,1),i=Math.floor(h.length/2)+1;const t=1/(i-1),e=Math.floor((h.length-i)/2);for(let a=0;a0){const t=r.modwavealphaend-r.modwavealphastart;a*=(o-r.modwavealphastart)/t}a=Math.clamp(a,0,1),i=Math.floor(h.length/2);for(let t=0;t=1024&&this.texsizeX<2048?a*=.11:a*=.13,r.modwavealphabyvolume>0){const t=r.modwavealphaend-r.modwavealphastart;a*=(o-r.modwavealphastart)/t}a=Math.clamp(a,0,1),i=h.length;for(let t=0;t=1024&&this.texsizeX<2048?a*=.22:a*=.33,a*=1.3,a*=r.treb*r.treb,r.modwavealphabyvolume>0){const t=r.modwavealphaend-r.modwavealphastart;a*=(o-r.modwavealphastart)/t}a=Math.clamp(a,0,1),i=h.length;for(let t=0;t0){const t=r.modwavealphaend-r.modwavealphastart;a*=(o-r.modwavealphastart)/t}a=Math.clamp(a,0,1),i=h.length,i>this.texsizeX/3&&(i=Math.floor(this.texsizeX/3));const t=1/i,e=Math.floor((h.length-i)/2),n=.45+.5*(.5*u+.5),l=1-n;for(let r=0;r1&&(i=i*l+n*(2*s[3*(r-1)+0]-s[3*(r-2)+0]),a=a*l+n*(2*s[3*(r-1)+1]-s[3*(r-2)+1])),s[3*r+0]=i,s[3*r+1]=a,s[3*r+2]=0}}else if(5===e){if(this.texsizeX<1024?a*=.09:this.texsizeX>=1024&&this.texsizeX<2048?a*=.11:a*=.13,r.modwavealphabyvolume>0){const t=r.modwavealphaend-r.modwavealphastart;a*=(o-r.modwavealphastart)/t}a=Math.clamp(a,0,1);const t=Math.cos(.3*r.time),e=Math.sin(.3*r.time);i=h.length;for(let i=0;i0){const t=r.modwavealphaend-r.modwavealphastart;a*=(o-r.modwavealphastart)/t}a=Math.clamp(a,0,1),i=Math.floor(h.length/2),i>this.texsizeX/3&&(i=Math.floor(this.texsizeX/3));const t=Math.floor((h.length-i)/2),n=.5*Math.PI*u;let l=Math.cos(n),f=Math.sin(n);const d=[c*Math.cos(n+.5*Math.PI)-3*l,c*Math.cos(n+.5*Math.PI)+3*l],p=[c*Math.sin(n+.5*Math.PI)-3*f,c*Math.sin(n+.5*Math.PI)+3*f];for(let t=0;t<2;t++)for(let e=0;e<4;e++){let i,s=!1;switch(e){case 0:d[t]>1.1&&(i=(1.1-d[1-t])/(d[t]-d[1-t]),s=!0);break;case 1:d[t]<-1.1&&(i=(-1.1-d[1-t])/(d[t]-d[1-t]),s=!0);break;case 2:p[t]>1.1&&(i=(1.1-p[1-t])/(p[t]-p[1-t]),s=!0);break;case 3:p[t]<-1.1&&(i=(-1.1-p[1-t])/(p[t]-p[1-t]),s=!0)}if(s){const e=d[t]-d[1-t],s=p[t]-p[1-t];d[t]=d[1-t]+e*i,p[t]=p[1-t]+s*i}}l=(d[1]-d[0])/i,f=(p[1]-p[0])/i;const _=Math.atan2(f,l),E=Math.cos(_+.5*Math.PI),b=Math.sin(_+.5*Math.PI);if(6===e)for(let e=0;e0&&(a=u*this.alpha+f*this.oldAlpha);let d=Math.clamp(r.wave_r,0,1),p=Math.clamp(r.wave_g,0,1),_=Math.clamp(r.wave_b,0,1);if(0!==r.wave_brighten){const t=Math.max(d,p,_);t>.01&&(d/=t,p/=t,_/=t)}if(this.color=[d,p,_,a],this.oldNumVert>0)if(7===n){const t=(this.oldNumVert-1)/(2*this.numVert);for(let e=0;e0){let A;if(a.preset.useWASM)A=a.runWaveFrameEquations(this.index,r);else{const t=Object.assign({},a.mdVSWaves[this.index],a.mdVSFrameMapWaves[this.index],a.mdVSQAfterFrame,a.mdVSTWaveInits[this.index],r);A=a.runWaveFrameEquations(this.index,t)}const n=512;Object.prototype.hasOwnProperty.call(A,"samples")?this.samples=A.samples:this.samples=n,this.samples>n&&(this.samples=n),this.samples=Math.floor(this.samples);const l=a.preset.waves[this.index].baseVals,c=Math.floor(A.sep),g=A.scaling,m=A.spectrum,u=A.smoothing,f=l.usedots,d=A.r,p=A.g,_=A.b,E=A.a,b=a.preset.baseVals.wave_scale;if(this.samples-=c,this.samples>=2||0!==f&&this.samples>=1){const r=0!==m,x=(r?.15:.004)*g*b,v=r?i:t,T=r?s:e,S=r?0:Math.floor((n-this.samples)/2-c/2),P=r?0:Math.floor((n-this.samples)/2+c/2),w=r?(n-c)/this.samples:1,I=(.98*u)**.5,R=1-I;this.pointsData[0][0]=v[S],this.pointsData[1][0]=T[P];for(let t=1;t=0;t--)this.pointsData[0][t]=this.pointsData[0][t]*R+this.pointsData[0][t+1]*I,this.pointsData[1][t]=this.pointsData[1][t]*R+this.pointsData[1][t+1]*I;for(let t=0;t=1024?1:0)):this.gl.uniform1f(this.sizeLoc,1+(this.texsizeX>=1024?1:0)):(this.gl.uniform1f(this.sizeLoc,1),e&&(o=4)),i?this.gl.blendFunc(this.gl.SRC_ALPHA,this.gl.ONE):this.gl.blendFunc(this.gl.SRC_ALPHA,this.gl.ONE_MINUS_SRC_ALPHA);const h=t?this.gl.POINTS:this.gl.LINE_STRIP;for(let t=0;t0,R=Math.abs(T)>=1,y=Math.abs(v)>=1,B=Math.abs(w)>=1;this.positions[0]=A,this.positions[1]=n,this.positions[2]=0,this.colors[0]=l,this.colors[1]=c,this.colors[2]=g,this.colors[3]=m*t,R&&(this.uvs[0]=.5,this.uvs[1]=.5);const L=.25*Math.PI;for(let e=1;e<=s+1;e++){const i=2*((e-1)/s)*Math.PI,r=i+h+L;if(this.positions[3*e+0]=A+o*Math.cos(r)*this.aspecty,this.positions[3*e+1]=n+o*Math.sin(r),this.positions[3*e+2]=0,this.colors[4*e+0]=u,this.colors[4*e+1]=f,this.colors[4*e+2]=d,this.colors[4*e+3]=p*t,R){const t=i+P+L;this.uvs[2*e+0]=.5+.5*Math.cos(t)/S*this.aspecty,this.uvs[2*e+1]=.5+.5*Math.sin(t)/S}I&&(this.borderPositions[3*(e-1)+0]=this.positions[3*e+0],this.borderPositions[3*(e-1)+1]=this.positions[3*e+1],this.borderPositions[3*(e-1)+2]=this.positions[3*e+2])}this.drawCustomShapeInstance(r,s,R,I,y,B)}}else{this.setupShapeBuffers(i.mdVSFrame.wrap);let s=Object.assign({},i.mdVSShapes[this.index],i.mdVSFrameMapShapes[this.index],e);""===i.preset.shapes[this.index].frame_eqs_str&&(s=Object.assign(s,i.mdVSQAfterFrame,i.mdVSTShapeInits[this.index]));const a=i.preset.shapes[this.index].baseVals,o=Math.clamp(a.num_inst,1,1024);for(let e=0;e0,B=Math.abs(P)>=1,L=Math.abs(S)>=1,C=Math.abs(R)>=1;this.positions[0]=l,this.positions[1]=c,this.positions[2]=0,this.colors[0]=g,this.colors[1]=m,this.colors[2]=u,this.colors[3]=f*t,B&&(this.uvs[0]=.5,this.uvs[1]=.5);const U=.25*Math.PI;for(let e=1;e<=h+1;e++){const i=2*((e-1)/h)*Math.PI,s=i+n+U;if(this.positions[3*e+0]=l+A*Math.cos(s)*this.aspecty,this.positions[3*e+1]=c+A*Math.sin(s),this.positions[3*e+2]=0,this.colors[4*e+0]=d,this.colors[4*e+1]=p,this.colors[4*e+2]=_,this.colors[4*e+3]=E*t,B){const t=i+I+U;this.uvs[2*e+0]=.5+.5*Math.cos(t)/w*this.aspecty,this.uvs[2*e+1]=.5+.5*Math.sin(t)/w}y&&(this.borderPositions[3*(e-1)+0]=this.positions[3*e+0],this.borderPositions[3*(e-1)+1]=this.positions[3*e+1],this.borderPositions[3*(e-1)+2]=this.positions[3*e+2])}this.mdVSShapeFrame=o,this.drawCustomShapeInstance(r,h,B,y,L,C)}const h=i.mdVSUserKeysShapes[this.index],A=At.pick(this.mdVSShapeFrame,h);i.mdVSFrameMapShapes[this.index]=A}}setupShapeBuffers(t){this.gl.bindBuffer(this.gl.ARRAY_BUFFER,this.positionVertexBuf),this.gl.bufferData(this.gl.ARRAY_BUFFER,this.positions,this.gl.DYNAMIC_DRAW),this.gl.vertexAttribPointer(this.aPosLocation,3,this.gl.FLOAT,!1,0,0),this.gl.enableVertexAttribArray(this.aPosLocation),this.gl.bindBuffer(this.gl.ARRAY_BUFFER,this.colorVertexBuf),this.gl.bufferData(this.gl.ARRAY_BUFFER,this.colors,this.gl.DYNAMIC_DRAW),this.gl.vertexAttribPointer(this.aColorLocation,4,this.gl.FLOAT,!1,0,0),this.gl.enableVertexAttribArray(this.aColorLocation),this.gl.bindBuffer(this.gl.ARRAY_BUFFER,this.uvVertexBuf),this.gl.bufferData(this.gl.ARRAY_BUFFER,this.uvs,this.gl.DYNAMIC_DRAW),this.gl.vertexAttribPointer(this.aUvLocation,2,this.gl.FLOAT,!1,0,0),this.gl.enableVertexAttribArray(this.aUvLocation),this.gl.bindBuffer(this.gl.ARRAY_BUFFER,this.borderPositionVertexBuf),this.gl.bufferData(this.gl.ARRAY_BUFFER,this.borderPositions,this.gl.DYNAMIC_DRAW),this.gl.vertexAttribPointer(this.aBorderPosLoc,3,this.gl.FLOAT,!1,0,0),this.gl.enableVertexAttribArray(this.aBorderPosLoc);const e=0!==t?this.gl.REPEAT:this.gl.CLAMP_TO_EDGE;this.gl.samplerParameteri(this.mainSampler,this.gl.TEXTURE_WRAP_S,e),this.gl.samplerParameteri(this.mainSampler,this.gl.TEXTURE_WRAP_T,e)}drawCustomShapeInstance(t,e,i,s,r,a){if(this.gl.useProgram(this.shaderProgram),this.gl.bindBuffer(this.gl.ARRAY_BUFFER,this.positionVertexBuf),this.gl.bufferSubData(this.gl.ARRAY_BUFFER,0,this.positions,0,3*(e+2)),this.gl.vertexAttribPointer(this.aPosLocation,3,this.gl.FLOAT,!1,0,0),this.gl.enableVertexAttribArray(this.aPosLocation),this.gl.bindBuffer(this.gl.ARRAY_BUFFER,this.colorVertexBuf),this.gl.bufferSubData(this.gl.ARRAY_BUFFER,0,this.colors,0,4*(e+2)),this.gl.vertexAttribPointer(this.aColorLocation,4,this.gl.FLOAT,!1,0,0),this.gl.enableVertexAttribArray(this.aColorLocation),i&&(this.gl.bindBuffer(this.gl.ARRAY_BUFFER,this.uvVertexBuf),this.gl.bufferSubData(this.gl.ARRAY_BUFFER,0,this.uvs,0,2*(e+2)),this.gl.vertexAttribPointer(this.aUvLocation,2,this.gl.FLOAT,!1,0,0),this.gl.enableVertexAttribArray(this.aUvLocation)),this.gl.uniform1f(this.texturedLoc,i?1:0),this.gl.activeTexture(this.gl.TEXTURE0),this.gl.bindTexture(this.gl.TEXTURE_2D,t),this.gl.bindSampler(0,this.mainSampler),this.gl.uniform1i(this.textureLoc,0),a?this.gl.blendFunc(this.gl.SRC_ALPHA,this.gl.ONE):this.gl.blendFunc(this.gl.SRC_ALPHA,this.gl.ONE_MINUS_SRC_ALPHA),this.gl.drawArrays(this.gl.TRIANGLE_FAN,0,e+2),s){this.gl.useProgram(this.borderShaderProgram),this.gl.bindBuffer(this.gl.ARRAY_BUFFER,this.borderPositionVertexBuf),this.gl.bufferSubData(this.gl.ARRAY_BUFFER,0,this.borderPositions,0,3*(e+1)),this.gl.vertexAttribPointer(this.aBorderPosLoc,3,this.gl.FLOAT,!1,0,0),this.gl.enableVertexAttribArray(this.aBorderPosLoc),this.gl.uniform4fv(this.uBorderColorLoc,this.borderColor);const t=r?4:1;for(let i=0;i0&&t[3]>0){const t=2,s=2,r=t/2,a=s/2,o=i/2,h=e/2+o,A=o*t,n=o*s,l=h*t,c=h*s;let g=[-r+A,-a+c,0],m=[-r+A,a-c,0],u=[-r+l,a-c,0],f=[-r+l,-a+c,0];return this.addTriangle(0,f,m,g),this.addTriangle(9,f,u,m),g=[r-A,-a+c,0],m=[r-A,a-c,0],u=[r-l,a-c,0],f=[r-l,-a+c,0],this.addTriangle(18,g,m,f),this.addTriangle(27,m,u,f),g=[-r+A,-a+n,0],m=[-r+A,c-a,0],u=[r-A,c-a,0],f=[r-A,-a+n,0],this.addTriangle(36,f,m,g),this.addTriangle(45,f,u,m),g=[-r+A,a-n,0],m=[-r+A,a-c,0],u=[r-A,a-c,0],f=[r-A,a-n,0],this.addTriangle(54,g,m,f),this.addTriangle(63,m,u,f),!0}return!1}drawBorder(t,e,i){this.generateBorder(t,e,i)&&(this.gl.useProgram(this.shaderProgram),this.gl.bindBuffer(this.gl.ARRAY_BUFFER,this.vertexBuf),this.gl.bufferData(this.gl.ARRAY_BUFFER,this.positions,this.gl.STATIC_DRAW),this.gl.vertexAttribPointer(this.aPosLoc,3,this.gl.FLOAT,!1,0,0),this.gl.enableVertexAttribArray(this.aPosLoc),this.gl.uniform4fv(this.colorLoc,t),this.gl.blendFunc(this.gl.SRC_ALPHA,this.gl.ONE_MINUS_SRC_ALPHA),this.gl.drawArrays(this.gl.TRIANGLES,0,this.positions.length/3))}}class St{constructor(t,e){this.gl=t,this.aspectx=e.aspectx,this.aspecty=e.aspecty,this.invAspectx=1/this.aspectx,this.invAspecty=1/this.aspecty,this.generatePositions(),this.colors=new Float32Array([0,0,0,3/32,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]),this.positionVertexBuf=this.gl.createBuffer(),this.colorVertexBuf=this.gl.createBuffer(),this.floatPrecision=_t.getFragmentFloatPrecision(this.gl),this.createShader()}updateGlobals(t){this.aspectx=t.aspectx,this.aspecty=t.aspecty,this.invAspectx=1/this.aspectx,this.invAspecty=1/this.aspecty,this.generatePositions()}generatePositions(){const t=.05;this.positions=new Float32Array([0,0,0,-.05*this.aspecty,0,0,0,-.05,0,t*this.aspecty,0,0,0,t,0,-.05*this.aspecty,0,0])}createShader(){this.shaderProgram=this.gl.createProgram();const t=this.gl.createShader(this.gl.VERTEX_SHADER);this.gl.shaderSource(t,"\n #version 300 es\n in vec3 aPos;\n in vec4 aColor;\n out vec4 vColor;\n void main(void) {\n vColor = aColor;\n gl_Position = vec4(aPos, 1.0);\n }\n ".trim()),this.gl.compileShader(t);const e=this.gl.createShader(this.gl.FRAGMENT_SHADER);this.gl.shaderSource(e,`\n #version 300 es\n precision ${this.floatPrecision} float;\n precision highp int;\n precision mediump sampler2D;\n in vec4 vColor;\n out vec4 fragColor;\n void main(void) {\n fragColor = vColor;\n }\n `.trim()),this.gl.compileShader(e),this.gl.attachShader(this.shaderProgram,t),this.gl.attachShader(this.shaderProgram,e),this.gl.linkProgram(this.shaderProgram),this.aPosLocation=this.gl.getAttribLocation(this.shaderProgram,"aPos"),this.aColorLocation=this.gl.getAttribLocation(this.shaderProgram,"aColor")}drawDarkenCenter(t){0!==t.darken_center&&(this.gl.useProgram(this.shaderProgram),this.gl.bindBuffer(this.gl.ARRAY_BUFFER,this.positionVertexBuf),this.gl.bufferData(this.gl.ARRAY_BUFFER,this.positions,this.gl.STATIC_DRAW),this.gl.vertexAttribPointer(this.aPosLocation,3,this.gl.FLOAT,!1,0,0),this.gl.enableVertexAttribArray(this.aPosLocation),this.gl.bindBuffer(this.gl.ARRAY_BUFFER,this.colorVertexBuf),this.gl.bufferData(this.gl.ARRAY_BUFFER,this.colors,this.gl.STATIC_DRAW),this.gl.vertexAttribPointer(this.aColorLocation,4,this.gl.FLOAT,!1,0,0),this.gl.enableVertexAttribArray(this.aColorLocation),this.gl.blendFunc(this.gl.SRC_ALPHA,this.gl.ONE_MINUS_SRC_ALPHA),this.gl.drawArrays(this.gl.TRIANGLE_FAN,0,this.positions.length/3))}}class Pt{constructor(t,e){this.gl=t,this.maxX=64,this.maxY=48,this.positions=new Float32Array(this.maxX*this.maxY*2*3),this.texsizeX=e.texsizeX,this.texsizeY=e.texsizeY,this.mesh_width=e.mesh_width,this.mesh_height=e.mesh_height,this.positionVertexBuf=this.gl.createBuffer(),this.floatPrecision=_t.getFragmentFloatPrecision(this.gl),this.createShader()}updateGlobals(t){this.texsizeX=t.texsizeX,this.texsizeY=t.texsizeY,this.mesh_width=t.mesh_width,this.mesh_height=t.mesh_height}createShader(){this.shaderProgram=this.gl.createProgram();const t=this.gl.createShader(this.gl.VERTEX_SHADER);this.gl.shaderSource(t,"\n #version 300 es\n in vec3 aPos;\n void main(void) {\n gl_Position = vec4(aPos, 1.0);\n }\n ".trim()),this.gl.compileShader(t);const e=this.gl.createShader(this.gl.FRAGMENT_SHADER);this.gl.shaderSource(e,`\n #version 300 es\n precision ${this.floatPrecision} float;\n precision highp int;\n precision mediump sampler2D;\n out vec4 fragColor;\n uniform vec4 u_color;\n void main(void) {\n fragColor = u_color;\n }\n `.trim()),this.gl.compileShader(e),this.gl.attachShader(this.shaderProgram,t),this.gl.attachShader(this.shaderProgram,e),this.gl.linkProgram(this.shaderProgram),this.aPosLoc=this.gl.getAttribLocation(this.shaderProgram,"aPos"),this.colorLoc=this.gl.getUniformLocation(this.shaderProgram,"u_color")}getMotionDir(t,e,i){const s=Math.floor(i*this.mesh_height),r=i*this.mesh_height-s,a=Math.floor(e*this.mesh_width),o=e*this.mesh_width-a,h=a+1,A=s+1,n=this.mesh_width+1;let l,c;return l=t[2*(s*n+a)+0]*(1-o)*(1-r),c=t[2*(s*n+a)+1]*(1-o)*(1-r),l+=t[2*(s*n+h)+0]*o*(1-r),c+=t[2*(s*n+h)+1]*o*(1-r),l+=t[2*(A*n+a)+0]*(1-o)*r,c+=t[2*(A*n+a)+1]*(1-o)*r,l+=t[2*(A*n+h)+0]*o*r,c+=t[2*(A*n+h)+1]*o*r,[l,1-c]}generateMotionVectors(t,e){const i=0===t.bmotionvectorson?0:t.mv_a;let s=Math.floor(t.mv_x),r=Math.floor(t.mv_y);if(i>.001&&s>0&&r>0){let a=t.mv_x-s,o=t.mv_y-r;s>this.maxX&&(s=this.maxX,a=0),r>this.maxY&&(r=this.maxY,o=0);const h=t.mv_dx,A=t.mv_dy,n=t.mv_l,l=1/this.texsizeX;this.numVecVerts=0;for(let t=0;t1e-4&&i<.9999)for(let t=0;t1e-4&&r<.9999){const t=this.getMotionDir(e,r,i);let s=t[0],a=t[1],o=s-r,h=a-i;o*=n,h*=n;let A=Math.sqrt(o*o+h*h);A1e-8?(A=l/A,o*=A,h*=A):(o=l,o=l),s=r+o,a=i+h;const c=2*r-1,g=2*i-1,m=2*s-1,u=2*a-1;this.positions[3*this.numVecVerts+0]=c,this.positions[3*this.numVecVerts+1]=g,this.positions[3*this.numVecVerts+2]=0,this.positions[3*(this.numVecVerts+1)+0]=m,this.positions[3*(this.numVecVerts+1)+1]=u,this.positions[3*(this.numVecVerts+1)+2]=0,this.numVecVerts+=2}}}if(this.numVecVerts>0)return this.color=[t.mv_r,t.mv_g,t.mv_b,i],!0}return!1}drawMotionVectors(t,e){this.generateMotionVectors(t,e)&&(this.gl.useProgram(this.shaderProgram),this.gl.bindBuffer(this.gl.ARRAY_BUFFER,this.positionVertexBuf),this.gl.bufferData(this.gl.ARRAY_BUFFER,this.positions,this.gl.STATIC_DRAW),this.gl.vertexAttribPointer(this.aPosLoc,3,this.gl.FLOAT,!1,0,0),this.gl.enableVertexAttribArray(this.aPosLoc),this.gl.uniform4fv(this.colorLoc,this.color),this.gl.lineWidth(1),this.gl.blendFunc(this.gl.SRC_ALPHA,this.gl.ONE_MINUS_SRC_ALPHA),this.gl.drawArrays(this.gl.LINES,0,this.numVecVerts))}}class wt{constructor(t,e,i,s={}){this.gl=t,this.noise=e,this.image=i,this.rng=mt(),this.texsizeX=s.texsizeX,this.texsizeY=s.texsizeY,this.mesh_width=s.mesh_width,this.mesh_height=s.mesh_height,this.aspectx=s.aspectx,this.aspecty=s.aspecty,this.invAspectx=1/this.aspectx,this.invAspecty=1/this.aspecty,this.buildPositions(),this.indexBuf=t.createBuffer(),this.positionVertexBuf=this.gl.createBuffer(),this.warpUvVertexBuf=this.gl.createBuffer(),this.warpColorVertexBuf=this.gl.createBuffer(),this.floatPrecision=_t.getFragmentFloatPrecision(this.gl),this.createShader(),this.mainSampler=this.gl.createSampler(),this.mainSamplerFW=this.gl.createSampler(),this.mainSamplerFC=this.gl.createSampler(),this.mainSamplerPW=this.gl.createSampler(),this.mainSamplerPC=this.gl.createSampler(),t.samplerParameteri(this.mainSampler,t.TEXTURE_MIN_FILTER,t.LINEAR_MIPMAP_LINEAR),t.samplerParameteri(this.mainSampler,t.TEXTURE_MAG_FILTER,t.LINEAR),t.samplerParameteri(this.mainSampler,t.TEXTURE_WRAP_S,t.REPEAT),t.samplerParameteri(this.mainSampler,t.TEXTURE_WRAP_T,t.REPEAT),t.samplerParameteri(this.mainSamplerFW,t.TEXTURE_MIN_FILTER,t.LINEAR_MIPMAP_LINEAR),t.samplerParameteri(this.mainSamplerFW,t.TEXTURE_MAG_FILTER,t.LINEAR),t.samplerParameteri(this.mainSamplerFW,t.TEXTURE_WRAP_S,t.REPEAT),t.samplerParameteri(this.mainSamplerFW,t.TEXTURE_WRAP_T,t.REPEAT),t.samplerParameteri(this.mainSamplerFC,t.TEXTURE_MIN_FILTER,t.LINEAR_MIPMAP_LINEAR),t.samplerParameteri(this.mainSamplerFC,t.TEXTURE_MAG_FILTER,t.LINEAR),t.samplerParameteri(this.mainSamplerFC,t.TEXTURE_WRAP_S,t.CLAMP_TO_EDGE),t.samplerParameteri(this.mainSamplerFC,t.TEXTURE_WRAP_T,t.CLAMP_TO_EDGE),t.samplerParameteri(this.mainSamplerPW,t.TEXTURE_MIN_FILTER,t.NEAREST_MIPMAP_NEAREST),t.samplerParameteri(this.mainSamplerPW,t.TEXTURE_MAG_FILTER,t.NEAREST),t.samplerParameteri(this.mainSamplerPW,t.TEXTURE_WRAP_S,t.REPEAT),t.samplerParameteri(this.mainSamplerPW,t.TEXTURE_WRAP_T,t.REPEAT),t.samplerParameteri(this.mainSamplerPC,t.TEXTURE_MIN_FILTER,t.NEAREST_MIPMAP_NEAREST),t.samplerParameteri(this.mainSamplerPC,t.TEXTURE_MAG_FILTER,t.NEAREST),t.samplerParameteri(this.mainSamplerPC,t.TEXTURE_WRAP_S,t.CLAMP_TO_EDGE),t.samplerParameteri(this.mainSamplerPC,t.TEXTURE_WRAP_T,t.CLAMP_TO_EDGE)}buildPositions(){const t=this.mesh_width,e=this.mesh_height,i=t+1,s=e+1,r=2/t,a=2/e,o=[];for(let t=0;t= 2.0) ? -1.0 : 1.0;\n vec2 uv_echo = ((uv - 0.5) *\n (1.0 / echo_zoom) *\n vec2(orient_x, orient_y)) + 0.5;\n\n ret = mix(texture(sampler_main, uv).rgb,\n texture(sampler_main, uv_echo).rgb,\n echo_alpha);\n\n ret *= gammaAdj;\n\n if(fShader >= 1.0) {\n ret *= hue_shader;\n } else if(fShader > 0.001) {\n ret *= (1.0 - fShader) + (fShader * hue_shader);\n }\n\n if(brighten != 0) ret = sqrt(ret);\n if(darken != 0) ret = ret*ret;\n if(solarize != 0) ret = ret * (1.0 - ret) * 4.0;\n if(invert != 0) ret = 1.0 - ret;",i="";else{const s=_t.getShaderParts(t);i=s[0],e=s[1]}e=e.replace(/texture2D/g,"texture"),e=e.replace(/texture3D/g,"texture"),this.userTextures=_t.getUserSamplers(i),this.shaderProgram=this.gl.createProgram();const s=this.gl.createShader(this.gl.VERTEX_SHADER);this.gl.shaderSource(s,"\n #version 300 es\n const vec2 halfmad = vec2(0.5);\n in vec2 aPos;\n in vec4 aCompColor;\n out vec2 vUv;\n out vec4 vColor;\n void main(void) {\n gl_Position = vec4(aPos, 0.0, 1.0);\n vUv = aPos * halfmad + halfmad;\n vColor = aCompColor;\n }\n ".trim()),this.gl.compileShader(s);const r=this.gl.createShader(this.gl.FRAGMENT_SHADER);this.gl.shaderSource(r,`\n #version 300 es\n precision ${this.floatPrecision} float;\n precision highp int;\n precision mediump sampler2D;\n precision mediump sampler3D;\n\n vec3 lum(vec3 v){\n return vec3(dot(v, vec3(0.32,0.49,0.29)));\n }\n\n in vec2 vUv;\n in vec4 vColor;\n out vec4 fragColor;\n uniform sampler2D sampler_main;\n uniform sampler2D sampler_fw_main;\n uniform sampler2D sampler_fc_main;\n uniform sampler2D sampler_pw_main;\n uniform sampler2D sampler_pc_main;\n uniform sampler2D sampler_blur1;\n uniform sampler2D sampler_blur2;\n uniform sampler2D sampler_blur3;\n uniform sampler2D sampler_noise_lq;\n uniform sampler2D sampler_noise_lq_lite;\n uniform sampler2D sampler_noise_mq;\n uniform sampler2D sampler_noise_hq;\n uniform sampler2D sampler_pw_noise_lq;\n uniform sampler3D sampler_noisevol_lq;\n uniform sampler3D sampler_noisevol_hq;\n\n uniform float time;\n uniform float gammaAdj;\n uniform float echo_zoom;\n uniform float echo_alpha;\n uniform float echo_orientation;\n uniform int invert;\n uniform int brighten;\n uniform int darken;\n uniform int solarize;\n uniform vec2 resolution;\n uniform vec4 aspect;\n uniform vec4 texsize;\n uniform vec4 texsize_noise_lq;\n uniform vec4 texsize_noise_mq;\n uniform vec4 texsize_noise_hq;\n uniform vec4 texsize_noise_lq_lite;\n uniform vec4 texsize_noisevol_lq;\n uniform vec4 texsize_noisevol_hq;\n\n uniform float bass;\n uniform float mid;\n uniform float treb;\n uniform float vol;\n uniform float bass_att;\n uniform float mid_att;\n uniform float treb_att;\n uniform float vol_att;\n\n uniform float frame;\n uniform float fps;\n\n uniform vec4 _qa;\n uniform vec4 _qb;\n uniform vec4 _qc;\n uniform vec4 _qd;\n uniform vec4 _qe;\n uniform vec4 _qf;\n uniform vec4 _qg;\n uniform vec4 _qh;\n\n #define q1 _qa.x\n #define q2 _qa.y\n #define q3 _qa.z\n #define q4 _qa.w\n #define q5 _qb.x\n #define q6 _qb.y\n #define q7 _qb.z\n #define q8 _qb.w\n #define q9 _qc.x\n #define q10 _qc.y\n #define q11 _qc.z\n #define q12 _qc.w\n #define q13 _qd.x\n #define q14 _qd.y\n #define q15 _qd.z\n #define q16 _qd.w\n #define q17 _qe.x\n #define q18 _qe.y\n #define q19 _qe.z\n #define q20 _qe.w\n #define q21 _qf.x\n #define q22 _qf.y\n #define q23 _qf.z\n #define q24 _qf.w\n #define q25 _qg.x\n #define q26 _qg.y\n #define q27 _qg.z\n #define q28 _qg.w\n #define q29 _qh.x\n #define q30 _qh.y\n #define q31 _qh.z\n #define q32 _qh.w\n\n uniform vec4 slow_roam_cos;\n uniform vec4 roam_cos;\n uniform vec4 slow_roam_sin;\n uniform vec4 roam_sin;\n\n uniform float blur1_min;\n uniform float blur1_max;\n uniform float blur2_min;\n uniform float blur2_max;\n uniform float blur3_min;\n uniform float blur3_max;\n\n uniform float scale1;\n uniform float scale2;\n uniform float scale3;\n uniform float bias1;\n uniform float bias2;\n uniform float bias3;\n\n uniform vec4 rand_frame;\n uniform vec4 rand_preset;\n\n uniform float fShader;\n\n float PI = ${Math.PI};\n\n ${i}\n\n void main(void) {\n vec3 ret;\n vec2 uv = vUv;\n vec2 uv_orig = vUv;\n uv.y = 1.0 - uv.y;\n uv_orig.y = 1.0 - uv_orig.y;\n float rad = length(uv - 0.5);\n float ang = atan(uv.x - 0.5, uv.y - 0.5);\n vec3 hue_shader = vColor.rgb;\n\n ${e}\n\n fragColor = vec4(ret, vColor.a);\n }\n `.trim()),this.gl.compileShader(r),this.gl.attachShader(this.shaderProgram,s),this.gl.attachShader(this.shaderProgram,r),this.gl.linkProgram(this.shaderProgram),this.positionLocation=this.gl.getAttribLocation(this.shaderProgram,"aPos"),this.compColorLocation=this.gl.getAttribLocation(this.shaderProgram,"aCompColor"),this.textureLoc=this.gl.getUniformLocation(this.shaderProgram,"sampler_main"),this.textureFWLoc=this.gl.getUniformLocation(this.shaderProgram,"sampler_fw_main"),this.textureFCLoc=this.gl.getUniformLocation(this.shaderProgram,"sampler_fc_main"),this.texturePWLoc=this.gl.getUniformLocation(this.shaderProgram,"sampler_pw_main"),this.texturePCLoc=this.gl.getUniformLocation(this.shaderProgram,"sampler_pc_main"),this.blurTexture1Loc=this.gl.getUniformLocation(this.shaderProgram,"sampler_blur1"),this.blurTexture2Loc=this.gl.getUniformLocation(this.shaderProgram,"sampler_blur2"),this.blurTexture3Loc=this.gl.getUniformLocation(this.shaderProgram,"sampler_blur3"),this.noiseLQLoc=this.gl.getUniformLocation(this.shaderProgram,"sampler_noise_lq"),this.noiseMQLoc=this.gl.getUniformLocation(this.shaderProgram,"sampler_noise_mq"),this.noiseHQLoc=this.gl.getUniformLocation(this.shaderProgram,"sampler_noise_hq"),this.noiseLQLiteLoc=this.gl.getUniformLocation(this.shaderProgram,"sampler_noise_lq_lite"),this.noisePointLQLoc=this.gl.getUniformLocation(this.shaderProgram,"sampler_pw_noise_lq"),this.noiseVolLQLoc=this.gl.getUniformLocation(this.shaderProgram,"sampler_noisevol_lq"),this.noiseVolHQLoc=this.gl.getUniformLocation(this.shaderProgram,"sampler_noisevol_hq"),this.timeLoc=this.gl.getUniformLocation(this.shaderProgram,"time"),this.gammaAdjLoc=this.gl.getUniformLocation(this.shaderProgram,"gammaAdj"),this.echoZoomLoc=this.gl.getUniformLocation(this.shaderProgram,"echo_zoom"),this.echoAlphaLoc=this.gl.getUniformLocation(this.shaderProgram,"echo_alpha"),this.echoOrientationLoc=this.gl.getUniformLocation(this.shaderProgram,"echo_orientation"),this.invertLoc=this.gl.getUniformLocation(this.shaderProgram,"invert"),this.brightenLoc=this.gl.getUniformLocation(this.shaderProgram,"brighten"),this.darkenLoc=this.gl.getUniformLocation(this.shaderProgram,"darken"),this.solarizeLoc=this.gl.getUniformLocation(this.shaderProgram,"solarize"),this.texsizeLoc=this.gl.getUniformLocation(this.shaderProgram,"texsize"),this.texsizeNoiseLQLoc=this.gl.getUniformLocation(this.shaderProgram,"texsize_noise_lq"),this.texsizeNoiseMQLoc=this.gl.getUniformLocation(this.shaderProgram,"texsize_noise_mq"),this.texsizeNoiseHQLoc=this.gl.getUniformLocation(this.shaderProgram,"texsize_noise_hq"),this.texsizeNoiseLQLiteLoc=this.gl.getUniformLocation(this.shaderProgram,"texsize_noise_lq_lite"),this.texsizeNoiseVolLQLoc=this.gl.getUniformLocation(this.shaderProgram,"texsize_noisevol_lq"),this.texsizeNoiseVolHQLoc=this.gl.getUniformLocation(this.shaderProgram,"texsize_noisevol_hq"),this.resolutionLoc=this.gl.getUniformLocation(this.shaderProgram,"resolution"),this.aspectLoc=this.gl.getUniformLocation(this.shaderProgram,"aspect"),this.bassLoc=this.gl.getUniformLocation(this.shaderProgram,"bass"),this.midLoc=this.gl.getUniformLocation(this.shaderProgram,"mid"),this.trebLoc=this.gl.getUniformLocation(this.shaderProgram,"treb"),this.volLoc=this.gl.getUniformLocation(this.shaderProgram,"vol"),this.bassAttLoc=this.gl.getUniformLocation(this.shaderProgram,"bass_att"),this.midAttLoc=this.gl.getUniformLocation(this.shaderProgram,"mid_att"),this.trebAttLoc=this.gl.getUniformLocation(this.shaderProgram,"treb_att"),this.volAttLoc=this.gl.getUniformLocation(this.shaderProgram,"vol_att"),this.frameLoc=this.gl.getUniformLocation(this.shaderProgram,"frame"),this.fpsLoc=this.gl.getUniformLocation(this.shaderProgram,"fps"),this.blur1MinLoc=this.gl.getUniformLocation(this.shaderProgram,"blur1_min"),this.blur1MaxLoc=this.gl.getUniformLocation(this.shaderProgram,"blur1_max"),this.blur2MinLoc=this.gl.getUniformLocation(this.shaderProgram,"blur2_min"),this.blur2MaxLoc=this.gl.getUniformLocation(this.shaderProgram,"blur2_max"),this.blur3MinLoc=this.gl.getUniformLocation(this.shaderProgram,"blur3_min"),this.blur3MaxLoc=this.gl.getUniformLocation(this.shaderProgram,"blur3_max"),this.scale1Loc=this.gl.getUniformLocation(this.shaderProgram,"scale1"),this.scale2Loc=this.gl.getUniformLocation(this.shaderProgram,"scale2"),this.scale3Loc=this.gl.getUniformLocation(this.shaderProgram,"scale3"),this.bias1Loc=this.gl.getUniformLocation(this.shaderProgram,"bias1"),this.bias2Loc=this.gl.getUniformLocation(this.shaderProgram,"bias2"),this.bias3Loc=this.gl.getUniformLocation(this.shaderProgram,"bias3"),this.randPresetLoc=this.gl.getUniformLocation(this.shaderProgram,"rand_preset"),this.randFrameLoc=this.gl.getUniformLocation(this.shaderProgram,"rand_frame"),this.fShaderLoc=this.gl.getUniformLocation(this.shaderProgram,"fShader"),this.qaLoc=this.gl.getUniformLocation(this.shaderProgram,"_qa"),this.qbLoc=this.gl.getUniformLocation(this.shaderProgram,"_qb"),this.qcLoc=this.gl.getUniformLocation(this.shaderProgram,"_qc"),this.qdLoc=this.gl.getUniformLocation(this.shaderProgram,"_qd"),this.qeLoc=this.gl.getUniformLocation(this.shaderProgram,"_qe"),this.qfLoc=this.gl.getUniformLocation(this.shaderProgram,"_qf"),this.qgLoc=this.gl.getUniformLocation(this.shaderProgram,"_qg"),this.qhLoc=this.gl.getUniformLocation(this.shaderProgram,"_qh"),this.slowRoamCosLoc=this.gl.getUniformLocation(this.shaderProgram,"slow_roam_cos"),this.roamCosLoc=this.gl.getUniformLocation(this.shaderProgram,"roam_cos"),this.slowRoamSinLoc=this.gl.getUniformLocation(this.shaderProgram,"slow_roam_sin"),this.roamSinLoc=this.gl.getUniformLocation(this.shaderProgram,"roam_sin");for(let t=0;t lumaMax))\n color = vec4(rgbA, 1.0);\n else\n color = vec4(rgbB, 1.0);\n\n fragColor = color;\n }`),this.gl.compileShader(e),this.gl.attachShader(this.shaderProgram,t),this.gl.attachShader(this.shaderProgram,e),this.gl.linkProgram(this.shaderProgram),this.positionLocation=this.gl.getAttribLocation(this.shaderProgram,"aPos"),this.textureLoc=this.gl.getUniformLocation(this.shaderProgram,"uTexture"),this.texsizeLoc=this.gl.getUniformLocation(this.shaderProgram,"texsize")}createShader(){this.shaderProgram=this.gl.createProgram();const t=this.gl.createShader(this.gl.VERTEX_SHADER);this.gl.shaderSource(t,"#version 300 es\n const vec2 halfmad = vec2(0.5);\n in vec2 aPos;\n out vec2 uv;\n void main(void) {\n gl_Position = vec4(aPos, 0.0, 1.0);\n uv = aPos * halfmad + halfmad;\n }"),this.gl.compileShader(t);const e=this.gl.createShader(this.gl.FRAGMENT_SHADER);this.gl.shaderSource(e,`#version 300 es\n precision ${this.floatPrecision} float;\n precision highp int;\n precision mediump sampler2D;\n\n in vec2 uv;\n out vec4 fragColor;\n uniform sampler2D uTexture;\n\n void main(void) {\n fragColor = vec4(texture(uTexture, uv).rgb, 1.0);\n }`),this.gl.compileShader(e),this.gl.attachShader(this.shaderProgram,t),this.gl.attachShader(this.shaderProgram,e),this.gl.linkProgram(this.shaderProgram),this.positionLocation=this.gl.getAttribLocation(this.shaderProgram,"aPos"),this.textureLoc=this.gl.getUniformLocation(this.shaderProgram,"uTexture")}renderQuadTexture(t){this.gl.useProgram(this.shaderProgram),this.gl.bindBuffer(this.gl.ARRAY_BUFFER,this.vertexBuf),this.gl.bufferData(this.gl.ARRAY_BUFFER,this.positions,this.gl.STATIC_DRAW),this.gl.vertexAttribPointer(this.positionLocation,2,this.gl.FLOAT,!1,0,0),this.gl.enableVertexAttribArray(this.positionLocation),this.gl.activeTexture(this.gl.TEXTURE0),this.gl.bindTexture(this.gl.TEXTURE_2D,t),this.gl.uniform1i(this.textureLoc,0),this.useFXAA()&&this.gl.uniform4fv(this.texsizeLoc,new Float32Array([this.texsizeX,this.texsizeY,1/this.texsizeX,1/this.texsizeY])),this.gl.blendFunc(this.gl.SRC_ALPHA,this.gl.ONE_MINUS_SRC_ALPHA),this.gl.drawArrays(this.gl.TRIANGLE_STRIP,0,4)}}class yt{constructor(t){this.gl=t,this.positions=new Float32Array([-1,-1,1,-1,-1,1,1,1]),this.vertexBuf=this.gl.createBuffer(),this.floatPrecision=_t.getFragmentFloatPrecision(this.gl),this.createShader()}createShader(){this.shaderProgram=this.gl.createProgram();const t=this.gl.createShader(this.gl.VERTEX_SHADER);this.gl.shaderSource(t,"#version 300 es\n const vec2 halfmad = vec2(0.5);\n in vec2 aPos;\n out vec2 uv;\n void main(void) {\n gl_Position = vec4(aPos, 0.0, 1.0);\n uv = aPos * halfmad + halfmad;\n }"),this.gl.compileShader(t);const e=this.gl.createShader(this.gl.FRAGMENT_SHADER);this.gl.shaderSource(e,`#version 300 es\n precision ${this.floatPrecision} float;\n precision highp int;\n precision mediump sampler2D;\n\n in vec2 uv;\n out vec4 fragColor;\n uniform sampler2D uTexture;\n\n void main(void) {\n fragColor = vec4(texture(uTexture, uv).rgb, 1.0);\n }`),this.gl.compileShader(e),this.gl.attachShader(this.shaderProgram,t),this.gl.attachShader(this.shaderProgram,e),this.gl.linkProgram(this.shaderProgram),this.positionLocation=this.gl.getAttribLocation(this.shaderProgram,"aPos"),this.textureLoc=this.gl.getUniformLocation(this.shaderProgram,"uTexture")}renderQuadTexture(t){this.gl.useProgram(this.shaderProgram),this.gl.bindBuffer(this.gl.ARRAY_BUFFER,this.vertexBuf),this.gl.bufferData(this.gl.ARRAY_BUFFER,this.positions,this.gl.STATIC_DRAW),this.gl.vertexAttribPointer(this.positionLocation,2,this.gl.FLOAT,!1,0,0),this.gl.enableVertexAttribArray(this.positionLocation),this.gl.activeTexture(this.gl.TEXTURE0),this.gl.bindTexture(this.gl.TEXTURE_2D,t),this.gl.generateMipmap(this.gl.TEXTURE_2D),this.gl.uniform1i(this.textureLoc,0),this.gl.blendFunc(this.gl.SRC_ALPHA,this.gl.ONE_MINUS_SRC_ALPHA),this.gl.drawArrays(this.gl.TRIANGLE_STRIP,0,4)}}class Bt{constructor(t,e){this.gl=t,this.blurLevel=e;const i=[4,3.8,3.5,2.9,1.9,1.2,.7,.3],s=i[0]+i[1]+i[2]+i[3],r=i[4]+i[5]+i[6]+i[7],a=0+(i[2]+i[3])/s*2,o=2+(i[6]+i[7])/r*2;this.wds=new Float32Array([s,r,a,o]),this.wDiv=1/(2*(s+r)),this.positions=new Float32Array([-1,-1,1,-1,-1,1,1,1]),this.vertexBuf=this.gl.createBuffer(),this.floatPrecision=_t.getFragmentFloatPrecision(this.gl),this.createShader()}createShader(){this.shaderProgram=this.gl.createProgram();const t=this.gl.createShader(this.gl.VERTEX_SHADER);this.gl.shaderSource(t,"\n #version 300 es\n const vec2 halfmad = vec2(0.5);\n in vec2 aPos;\n out vec2 uv;\n void main(void) {\n gl_Position = vec4(aPos, 0.0, 1.0);\n uv = aPos * halfmad + halfmad;\n }\n ".trim()),this.gl.compileShader(t);const e=this.gl.createShader(this.gl.FRAGMENT_SHADER);this.gl.shaderSource(e,`#version 300 es\n precision ${this.floatPrecision} float;\n precision highp int;\n precision mediump sampler2D;\n\n in vec2 uv;\n out vec4 fragColor;\n uniform sampler2D uTexture;\n uniform vec4 texsize;\n uniform float ed1;\n uniform float ed2;\n uniform float ed3;\n uniform vec4 wds;\n uniform float wdiv;\n\n void main(void) {\n float w1 = wds[0];\n float w2 = wds[1];\n float d1 = wds[2];\n float d2 = wds[3];\n\n vec2 uv2 = uv.xy;\n\n vec3 blur =\n ( texture(uTexture, uv2 + vec2(0.0, d1 * texsize.w) ).xyz\n + texture(uTexture, uv2 + vec2(0.0,-d1 * texsize.w) ).xyz) * w1 +\n ( texture(uTexture, uv2 + vec2(0.0, d2 * texsize.w) ).xyz\n + texture(uTexture, uv2 + vec2(0.0,-d2 * texsize.w) ).xyz) * w2;\n\n blur.xyz *= wdiv;\n\n float t = min(min(uv.x, uv.y), 1.0 - max(uv.x, uv.y));\n t = sqrt(t);\n t = ed1 + ed2 * clamp(t * ed3, 0.0, 1.0);\n blur.xyz *= t;\n\n fragColor = vec4(blur, 1.0);\n }`),this.gl.compileShader(e),this.gl.attachShader(this.shaderProgram,t),this.gl.attachShader(this.shaderProgram,e),this.gl.linkProgram(this.shaderProgram),this.positionLocation=this.gl.getAttribLocation(this.shaderProgram,"aPos"),this.textureLoc=this.gl.getUniformLocation(this.shaderProgram,"uTexture"),this.texsizeLocation=this.gl.getUniformLocation(this.shaderProgram,"texsize"),this.ed1Loc=this.gl.getUniformLocation(this.shaderProgram,"ed1"),this.ed2Loc=this.gl.getUniformLocation(this.shaderProgram,"ed2"),this.ed3Loc=this.gl.getUniformLocation(this.shaderProgram,"ed3"),this.wdsLocation=this.gl.getUniformLocation(this.shaderProgram,"wds"),this.wdivLoc=this.gl.getUniformLocation(this.shaderProgram,"wdiv")}renderQuadTexture(t,e,i){this.gl.useProgram(this.shaderProgram),this.gl.bindBuffer(this.gl.ARRAY_BUFFER,this.vertexBuf),this.gl.bufferData(this.gl.ARRAY_BUFFER,this.positions,this.gl.STATIC_DRAW),this.gl.vertexAttribPointer(this.positionLocation,2,this.gl.FLOAT,!1,0,0),this.gl.enableVertexAttribArray(this.positionLocation),this.gl.activeTexture(this.gl.TEXTURE0),this.gl.bindTexture(this.gl.TEXTURE_2D,t),this.gl.uniform1i(this.textureLoc,0);const s=0===this.blurLevel?e.b1ed:0;this.gl.uniform4fv(this.texsizeLocation,[i[0],i[1],1/i[0],1/i[1]]),this.gl.uniform1f(this.ed1Loc,1-s),this.gl.uniform1f(this.ed2Loc,s),this.gl.uniform1f(this.ed3Loc,5),this.gl.uniform4fv(this.wdsLocation,this.wds),this.gl.uniform1f(this.wdivLoc,this.wDiv),this.gl.blendFunc(this.gl.SRC_ALPHA,this.gl.ONE_MINUS_SRC_ALPHA),this.gl.drawArrays(this.gl.TRIANGLE_STRIP,0,4)}}class Lt{constructor(t,e){this.gl=t,this.blurLevel=e;const i=[4,3.8,3.5,2.9,1.9,1.2,.7,.3],s=i[0]+i[1],r=i[2]+i[3],a=i[4]+i[5],o=i[6]+i[7],h=0+2*i[1]/s,A=2+2*i[3]/r,n=4+2*i[5]/a,l=6+2*i[7]/o;this.ws=new Float32Array([s,r,a,o]),this.ds=new Float32Array([h,A,n,l]),this.wDiv=.5/(s+r+a+o),this.positions=new Float32Array([-1,-1,1,-1,-1,1,1,1]),this.vertexBuf=this.gl.createBuffer(),this.floatPrecision=_t.getFragmentFloatPrecision(this.gl),this.createShader()}createShader(){this.shaderProgram=this.gl.createProgram();const t=this.gl.createShader(this.gl.VERTEX_SHADER);this.gl.shaderSource(t,"\n #version 300 es\n const vec2 halfmad = vec2(0.5);\n in vec2 aPos;\n out vec2 uv;\n void main(void) {\n gl_Position = vec4(aPos, 0.0, 1.0);\n uv = aPos * halfmad + halfmad;\n }\n ".trim()),this.gl.compileShader(t);const e=this.gl.createShader(this.gl.FRAGMENT_SHADER);this.gl.shaderSource(e,`#version 300 es\n precision ${this.floatPrecision} float;\n precision highp int;\n precision mediump sampler2D;\n\n in vec2 uv;\n out vec4 fragColor;\n uniform sampler2D uTexture;\n uniform vec4 texsize;\n uniform float scale;\n uniform float bias;\n uniform vec4 ws;\n uniform vec4 ds;\n uniform float wdiv;\n\n void main(void) {\n float w1 = ws[0];\n float w2 = ws[1];\n float w3 = ws[2];\n float w4 = ws[3];\n float d1 = ds[0];\n float d2 = ds[1];\n float d3 = ds[2];\n float d4 = ds[3];\n\n vec2 uv2 = uv.xy;\n\n vec3 blur =\n ( texture(uTexture, uv2 + vec2( d1 * texsize.z,0.0) ).xyz\n + texture(uTexture, uv2 + vec2(-d1 * texsize.z,0.0) ).xyz) * w1 +\n ( texture(uTexture, uv2 + vec2( d2 * texsize.z,0.0) ).xyz\n + texture(uTexture, uv2 + vec2(-d2 * texsize.z,0.0) ).xyz) * w2 +\n ( texture(uTexture, uv2 + vec2( d3 * texsize.z,0.0) ).xyz\n + texture(uTexture, uv2 + vec2(-d3 * texsize.z,0.0) ).xyz) * w3 +\n ( texture(uTexture, uv2 + vec2( d4 * texsize.z,0.0) ).xyz\n + texture(uTexture, uv2 + vec2(-d4 * texsize.z,0.0) ).xyz) * w4;\n\n blur.xyz *= wdiv;\n blur.xyz = blur.xyz * scale + bias;\n\n fragColor = vec4(blur, 1.0);\n }`),this.gl.compileShader(e),this.gl.attachShader(this.shaderProgram,t),this.gl.attachShader(this.shaderProgram,e),this.gl.linkProgram(this.shaderProgram),this.positionLocation=this.gl.getAttribLocation(this.shaderProgram,"aPos"),this.textureLoc=this.gl.getUniformLocation(this.shaderProgram,"uTexture"),this.texsizeLocation=this.gl.getUniformLocation(this.shaderProgram,"texsize"),this.scaleLoc=this.gl.getUniformLocation(this.shaderProgram,"scale"),this.biasLoc=this.gl.getUniformLocation(this.shaderProgram,"bias"),this.wsLoc=this.gl.getUniformLocation(this.shaderProgram,"ws"),this.dsLocation=this.gl.getUniformLocation(this.shaderProgram,"ds"),this.wdivLoc=this.gl.getUniformLocation(this.shaderProgram,"wdiv")}getScaleAndBias(t,e){const i=[1,1,1],s=[0,0,0];let r,a;return i[0]=1/(e[0]-t[0]),s[0]=-t[0]*i[0],r=(t[1]-t[0])/(e[0]-t[0]),a=(e[1]-t[0])/(e[0]-t[0]),i[1]=1/(a-r),s[1]=-r*i[1],r=(t[2]-t[1])/(e[1]-t[1]),a=(e[2]-t[1])/(e[1]-t[1]),i[2]=1/(a-r),s[2]=-r*i[2],{scale:i[this.blurLevel],bias:s[this.blurLevel]}}renderQuadTexture(t,e,i,s,r){this.gl.useProgram(this.shaderProgram),this.gl.bindBuffer(this.gl.ARRAY_BUFFER,this.vertexBuf),this.gl.bufferData(this.gl.ARRAY_BUFFER,this.positions,this.gl.STATIC_DRAW),this.gl.vertexAttribPointer(this.positionLocation,2,this.gl.FLOAT,!1,0,0),this.gl.enableVertexAttribArray(this.positionLocation),this.gl.activeTexture(this.gl.TEXTURE0),this.gl.bindTexture(this.gl.TEXTURE_2D,t),this.gl.uniform1i(this.textureLoc,0);const{scale:a,bias:o}=this.getScaleAndBias(i,s);this.gl.uniform4fv(this.texsizeLocation,[r[0],r[1],1/r[0],1/r[1]]),this.gl.uniform1f(this.scaleLoc,a),this.gl.uniform1f(this.biasLoc,o),this.gl.uniform4fv(this.wsLoc,this.ws),this.gl.uniform4fv(this.dsLocation,this.ds),this.gl.uniform1f(this.wdivLoc,this.wDiv),this.gl.blendFunc(this.gl.SRC_ALPHA,this.gl.ONE_MINUS_SRC_ALPHA),this.gl.drawArrays(this.gl.TRIANGLE_STRIP,0,4)}}class Ct{constructor(t,e,i,s={}){this.blurLevel=t,this.blurRatios=e,this.gl=i,this.texsizeX=s.texsizeX,this.texsizeY=s.texsizeY,this.anisoExt=this.gl.getExtension("EXT_texture_filter_anisotropic")||this.gl.getExtension("MOZ_EXT_texture_filter_anisotropic")||this.gl.getExtension("WEBKIT_EXT_texture_filter_anisotropic"),this.blurHorizontalFrameBuffer=this.gl.createFramebuffer(),this.blurVerticalFrameBuffer=this.gl.createFramebuffer(),this.blurHorizontalTexture=this.gl.createTexture(),this.blurVerticalTexture=this.gl.createTexture(),this.setupFrameBufferTextures(),this.blurHorizontal=new Lt(i,this.blurLevel,s),this.blurVertical=new Bt(i,this.blurLevel,s)}updateGlobals(t){this.texsizeX=t.texsizeX,this.texsizeY=t.texsizeY,this.setupFrameBufferTextures()}getTextureSize(t){let e=Math.max(this.texsizeX*t,16);e=16*Math.floor((e+3)/16);let i=Math.max(this.texsizeY*t,16);return i=4*Math.floor((i+3)/4),[e,i]}setupFrameBufferTextures(){const t=this.blurLevel>0?this.blurRatios[this.blurLevel-1]:[1,1],e=this.blurRatios[this.blurLevel],i=this.getTextureSize(t[1]),s=this.getTextureSize(e[0]);this.bindFrameBufferTexture(this.blurHorizontalFrameBuffer,this.blurHorizontalTexture,s);const r=s,a=this.getTextureSize(e[1]);this.bindFrameBufferTexture(this.blurVerticalFrameBuffer,this.blurVerticalTexture,a),this.horizontalTexsizes=[i,s],this.verticalTexsizes=[r,a]}bindFrambufferAndSetViewport(t,e){this.gl.bindFramebuffer(this.gl.FRAMEBUFFER,t),this.gl.viewport(0,0,e[0],e[1])}bindFrameBufferTexture(t,e,i){if(this.gl.bindTexture(this.gl.TEXTURE_2D,e),this.gl.pixelStorei(this.gl.UNPACK_ALIGNMENT,1),this.gl.texImage2D(this.gl.TEXTURE_2D,0,this.gl.RGBA,i[0],i[1],0,this.gl.RGBA,this.gl.UNSIGNED_BYTE,new Uint8Array(i[0]*i[1]*4)),this.gl.generateMipmap(this.gl.TEXTURE_2D),this.gl.texParameteri(this.gl.TEXTURE_2D,this.gl.TEXTURE_WRAP_S,this.gl.CLAMP_TO_EDGE),this.gl.texParameteri(this.gl.TEXTURE_2D,this.gl.TEXTURE_WRAP_T,this.gl.CLAMP_TO_EDGE),this.gl.texParameteri(this.gl.TEXTURE_2D,this.gl.TEXTURE_MIN_FILTER,this.gl.LINEAR_MIPMAP_LINEAR),this.gl.texParameteri(this.gl.TEXTURE_2D,this.gl.TEXTURE_MAG_FILTER,this.gl.LINEAR),this.anisoExt){const t=this.gl.getParameter(this.anisoExt.MAX_TEXTURE_MAX_ANISOTROPY_EXT);this.gl.texParameterf(this.gl.TEXTURE_2D,this.anisoExt.TEXTURE_MAX_ANISOTROPY_EXT,t)}this.gl.bindFramebuffer(this.gl.FRAMEBUFFER,t),this.gl.framebufferTexture2D(this.gl.FRAMEBUFFER,this.gl.COLOR_ATTACHMENT0,this.gl.TEXTURE_2D,e,0)}renderBlurTexture(t,e,i,s){this.bindFrambufferAndSetViewport(this.blurHorizontalFrameBuffer,this.horizontalTexsizes[1]),this.blurHorizontal.renderQuadTexture(t,e,i,s,this.horizontalTexsizes[0]),this.gl.bindTexture(this.gl.TEXTURE_2D,this.blurHorizontalTexture),this.gl.generateMipmap(this.gl.TEXTURE_2D),this.bindFrambufferAndSetViewport(this.blurVerticalFrameBuffer,this.verticalTexsizes[1]),this.blurVertical.renderQuadTexture(this.blurHorizontalTexture,e,this.verticalTexsizes[0]),this.gl.bindTexture(this.gl.TEXTURE_2D,this.blurVerticalTexture),this.gl.generateMipmap(this.gl.TEXTURE_2D)}}class Ut{constructor(t){this.gl=t,this.randomFn=mt().random,this.anisoExt=this.gl.getExtension("EXT_texture_filter_anisotropic")||this.gl.getExtension("MOZ_EXT_texture_filter_anisotropic")||this.gl.getExtension("WEBKIT_EXT_texture_filter_anisotropic"),this.noiseTexLQ=this.gl.createTexture(),this.noiseTexLQLite=this.gl.createTexture(),this.noiseTexMQ=this.gl.createTexture(),this.noiseTexHQ=this.gl.createTexture(),this.noiseTexVolLQ=this.gl.createTexture(),this.noiseTexVolHQ=this.gl.createTexture(),this.nTexArrLQ=Ut.createNoiseTex(256,1,this.randomFn),this.nTexArrLQLite=Ut.createNoiseTex(32,1,this.randomFn),this.nTexArrMQ=Ut.createNoiseTex(256,4,this.randomFn),this.nTexArrHQ=Ut.createNoiseTex(256,8,this.randomFn),this.nTexArrVolLQ=Ut.createNoiseVolTex(32,1,this.randomFn),this.nTexArrVolHQ=Ut.createNoiseVolTex(32,4,this.randomFn),this.bindTexture(this.noiseTexLQ,this.nTexArrLQ,256,256),this.bindTexture(this.noiseTexLQLite,this.nTexArrLQLite,32,32),this.bindTexture(this.noiseTexMQ,this.nTexArrMQ,256,256),this.bindTexture(this.noiseTexHQ,this.nTexArrHQ,256,256),this.bindTexture3D(this.noiseTexVolLQ,this.nTexArrVolLQ,32,32,32),this.bindTexture3D(this.noiseTexVolHQ,this.nTexArrVolHQ,32,32,32),this.noiseTexPointLQ=this.gl.createSampler(),t.samplerParameteri(this.noiseTexPointLQ,t.TEXTURE_MIN_FILTER,t.NEAREST_MIPMAP_NEAREST),t.samplerParameteri(this.noiseTexPointLQ,t.TEXTURE_MAG_FILTER,t.NEAREST),t.samplerParameteri(this.noiseTexPointLQ,t.TEXTURE_WRAP_S,t.REPEAT),t.samplerParameteri(this.noiseTexPointLQ,t.TEXTURE_WRAP_T,t.REPEAT)}bindTexture(t,e,i,s){if(this.gl.bindTexture(this.gl.TEXTURE_2D,t),this.gl.pixelStorei(this.gl.UNPACK_ALIGNMENT,1),this.gl.texImage2D(this.gl.TEXTURE_2D,0,this.gl.RGBA,i,s,0,this.gl.RGBA,this.gl.UNSIGNED_BYTE,e),this.gl.generateMipmap(this.gl.TEXTURE_2D),this.gl.texParameteri(this.gl.TEXTURE_2D,this.gl.TEXTURE_WRAP_S,this.gl.REPEAT),this.gl.texParameteri(this.gl.TEXTURE_2D,this.gl.TEXTURE_WRAP_T,this.gl.REPEAT),this.gl.texParameteri(this.gl.TEXTURE_2D,this.gl.TEXTURE_MIN_FILTER,this.gl.LINEAR_MIPMAP_LINEAR),this.gl.texParameteri(this.gl.TEXTURE_2D,this.gl.TEXTURE_MAG_FILTER,this.gl.LINEAR),this.anisoExt){const t=this.gl.getParameter(this.anisoExt.MAX_TEXTURE_MAX_ANISOTROPY_EXT);this.gl.texParameterf(this.gl.TEXTURE_2D,this.anisoExt.TEXTURE_MAX_ANISOTROPY_EXT,t)}}bindTexture3D(t,e,i,s,r){if(this.gl.bindTexture(this.gl.TEXTURE_3D,t),this.gl.pixelStorei(this.gl.UNPACK_ALIGNMENT,1),this.gl.texImage3D(this.gl.TEXTURE_3D,0,this.gl.RGBA,i,s,r,0,this.gl.RGBA,this.gl.UNSIGNED_BYTE,e),this.gl.generateMipmap(this.gl.TEXTURE_3D),this.gl.texParameteri(this.gl.TEXTURE_3D,this.gl.TEXTURE_WRAP_S,this.gl.REPEAT),this.gl.texParameteri(this.gl.TEXTURE_3D,this.gl.TEXTURE_WRAP_T,this.gl.REPEAT),this.gl.texParameteri(this.gl.TEXTURE_3D,this.gl.TEXTURE_WRAP_R,this.gl.REPEAT),this.gl.texParameteri(this.gl.TEXTURE_3D,this.gl.TEXTURE_MIN_FILTER,this.gl.LINEAR_MIPMAP_LINEAR),this.gl.texParameteri(this.gl.TEXTURE_3D,this.gl.TEXTURE_MAG_FILTER,this.gl.LINEAR),this.anisoExt){const t=this.gl.getParameter(this.anisoExt.MAX_TEXTURE_MAX_ANISOTROPY_EXT);this.gl.texParameterf(this.gl.TEXTURE_3D,this.anisoExt.TEXTURE_MAX_ANISOTROPY_EXT,t)}}static fCubicInterpolate(t,e,i,s,r){const a=r*r,o=s-i-t+e;return o*(r*a)+(t-e-o)*a+(i-t)*r+e}static dwCubicInterpolate(t,e,i,s,r){const a=[];for(let o=0;o<4;o++){let h=Ut.fCubicInterpolate(t[o]/255,e[o]/255,i[o]/255,s[o]/255,r);h=Math.clamp(h,0,1),a[o]=255*h}return a}static createNoiseVolTex(t,e,i){const s=t*t*t,r=new Uint8Array(4*s),a=e>1?216:256,o=.5*a;for(let t=0;t1){for(let i=0;i1?216:256,o=.5*a;for(let t=0;t1){for(let i=0;i{this.samplers.clouds2=this.gl.createTexture(),this.bindTexture(this.samplers.clouds2,this.clouds2Image,128,128)},this.clouds2Image.src="data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEASABIAAD/4RP+RXhpZgAASUkqAAgAAAAJAA8BAgAGAAAAegAAABABAgAVAAAAgAAAABIBAwABAAAAAQAAABoBBQABAAAAoAAAABsBBQABAAAAqAAAACgBAwABAAAAAgAAADIBAgAUAAAAsAAAABMCAwABAAAAAQAAAGmHBAABAAAAxAAAAGYFAABDYW5vbgBDYW5vbiBQb3dlclNob3QgUzExMAAAAAAAAAAAAAAAAEgAAAABAAAASAAAAAEAAAAyMDAyOjAxOjE5IDE3OjMzOjIwABsAmoIFAAEAAABWAwAAnYIFAAEAAABeAwAAAJAHAAQAAAAwMjEwA5ACABQAAAAOAgAABJACABQAAAAiAgAAAZEHAAQAAAABAgMAApEFAAEAAAA+AwAAAZIKAAEAAABGAwAAApIFAAEAAABOAwAABJIKAAEAAABmAwAABZIFAAEAAABuAwAABpIFAAEAAAB2AwAAB5IDAAEAAAAFAAAACZIDAAEAAAAAAAAACpIFAAEAAAB+AwAAfJIHAJoBAACGAwAAhpIHAAgBAAA2AgAAAKAHAAQAAAAwMTAwAaADAAEAAAABAAAAAqAEAAEAAACAAAAAA6AEAAEAAACAAAAABaAEAAEAAAAwBQAADqIFAAEAAAAgBQAAD6IFAAEAAAAoBQAAEKIDAAEAAAACAAAAF6IDAAEAAAACAAAAAKMHAAEAAAADAAAAAAAAADIwMDI6MDE6MTkgMTc6MzM6MjAAMjAwMjowMToxOSAxNzozMzoyMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFAAAAAQAAACoBAAAgAAAAuAAAACAAAAABAAAAgAIAAEgAAAAKAAAA/////wMAAACK+AIAAAABAL8BAADoAwAArQAAACAAAAAMAAEAAwAmAAAAHAQAAAIAAwAEAAAAaAQAAAMAAwAEAAAAcAQAAAQAAwAaAAAAeAQAAAAAAwAGAAAArAQAAAAAAwAEAAAAuAQAAAYAAgAgAAAAwAQAAAcAAgAYAAAA4AQAAAgABAABAAAAkc4UAAkAAgAgAAAA+AQAABAABAABAAAAAAAJAQ0AAwAEAAAAGAUAAAAAAABMAAIAAAAFAAAAAAAAAAQAAAABAAAAAQAAAAAAAAAAAAAAAwABAAEwAAD/////WgGtACAAYgC4AP//AAAAAAAAAAAAAP//SABABkAGAgCtANMAngAAAAAAAAAAADQAAACPAEYBtQAqAfT/AgABAAEAAAAAAAAAAAAEMAAAAAAAAAAAvwEAALgAJwEAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAElNRzpQb3dlclNob3QgUzExMCBKUEVHAAAAAAAAAAAARmlybXdhcmUgVmVyc2lvbiAxLjAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAMgAuQC5AABqGADOAAAAgE8SAJsAAAAEAAEAAgAEAAAAUjk4AAIABwAEAAAAMDEwMAEQAwABAAAAQAYAAAIQAwABAAAAsAQAAAAAAAAGAAMBAwABAAAABgAAABoBBQABAAAAtAUAABsBBQABAAAAvAUAACgBAwABAAAAAgAAAAECBAABAAAA9AUAAAICBAABAAAAuA0AAAAAAAC0AAAAAQAAALQAAAABAAAAaM5qp6ps7vXbS52etpVdo/tuYZ2wtrDFXnrx1HK+braKpineV1+3VFWVteo72Poc/9j/2wCEAAkGBggGBQkIBwgKCQkLDRYPDQwMDRwTFRAWIR0jIiEcIB8kKTQsJCcxJx4fLT0tMTY3Ojo6Iio/RD44QjM3OTYBCQkJDAoMFAwMFA8KCgoPGhoKChoaTxoaGhoaT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT//AABEIAHgAoAMBIQACEQEDEQH/xAGiAAABBQEBAQEBAQAAAAAAAAAAAQIDBAUGBwgJCgsQAAIBAwMCBAMFBQQEAAABfQECAwAEEQUSITFBBhNRYQcicRQygZGhCCNCscEVUtHwJDNicoIJChYXGBkaJSYnKCkqNDU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6g4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2drh4uPk5ebn6Onq8fLz9PX29/j5+gEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoLEQACAQIEBAMEBwUEBAABAncAAQIDEQQFITEGEkFRB2FxEyIygQgUQpGhscEJIzNS8BVictEKFiQ04SXxFxgZGiYnKCkqNTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqCg4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2dri4+Tl5ufo6ery8/T19vf4+fr/2gAMAwEAAhEDEQA/AOdCcU4R11HMSLHTxFTAXy6PLxQIUJTglIDo9KtbWzjScNvnK/gtao1FkycjaO1ebWvOWvyR307RjZfM5zXoraacTW3DtkyD1PrWathui39q66cmoK+60OacU5O2xA8ZQlT2qBkrdfmYsiZMUwpxVCImXNRMntTERlaaRg0CN5Y8iniOszUlWOniOgQhj5o2UwDZS7KBFmAuoCnIAq69wUjIHPHWuaok5HTBtIqrbzXCMyAEDqCarPvGV6Yqlbb+Xch337kBTOd1RNHxgCrc+xKgNWAPxyD2qCWMAY7g81UJ83yJlGxCy4qJlzWqMyMpTClAjoxCUbDCniP2rK5qOVKkEdMA8ummPmgA2Vd0m1S4vMTIXjUEtjtUzdotrdLQcFeSXQfcQqJ2y/GaZL5fkhE5Y9TXPFt2Zu7K6IUinVWVW+XvjvSNCsceScsa0k1067kRT69NisY8mnC2YoWA4qL2KtcglyjcVVdd78daqnK3zImr/IheFgTkdKiZK6ou6MJKxGyUwrTJOxmjaS2WYqwjLHbnp9KBaeeB5MbZxzXLGVlfotzpcdbdXsQiKniOtSBfLppjoTE0NMdPiYxElSRmiSurAnZiSMTzmmKSDmpUdCpS1NvT0TUoHEjpGQcYC8n3qM6MJdxgYuF46VyyfI2ui6nQlzJPq+hDPo0qcKNz/wB0U54Es7co/wAzkcgdAamU01ZbtjUWnrsjDn+dzxiqpjYHK1aZDHJGQmM9ahe2zk+lbU5WZlOOhWZKjKV1nOddYTPLpptjztbcB2NTBXibaSUOOma4IWt+h2y3/Uj8rmlEdbJmLQpTjpTNlNCYnl00x1RI0x00x4oARd6tmPIPtW1o+uf2fGd+GORlcdffNZVaaqRt1NKc+R36HQxWsWoqbmGQ/MMkg4rL1bSdi5UV5fM4ys9LHfZNXXU599Lkd+FNMbSzGPmHNb85lyFaS32HgUx8pGcqK2g72M5aGY8fPSomSvRRwndafZfYtRCzL8rHFaPiPTTHKlxHGEjKhTj1ryKU/wB4uzR6dSPuPujF2YIzTxHxXamtuxyNPfuIY+KYY6okDHg4pHQIMsQKLhYhV0dtq8mr6aQ8loZRy390DNZVKqgr92aQpczKcd8+nXefLHAwVI6028nt7mTzIY/KJ5IB4qI3UuZO6fxIuSTjy21WzLmjXs9rKFidgM/dzxXTJeRECC5ZN5XPWscVTTlePxM0oS0s9kUriaIEiIKAPzrFup/3uBzmopU3fUqc0isTEQWftVWZ0dPlWuqNNr0RhKafqzOlh6mq7x12RZytHqssMcwSfy0wwyDuxRq2oCew8gxjdx1HT3rx6Uby9GenUdkc/wCSpPzdaV4WVeFJru226nLv8iFVc/eXFKYsCqi7omSIjHzS3EKSRZBJbHNOWwRMp4WjO/O0Z4NWUubuGParnafSsXFS0ZonYRo/Pwzcmk8gL0FbQgkjOUncfFK9sSU4JpkkzO+7Jz9atRV7mbk7WHpczAcOT9aUqzgu3Ud6lxSd1oylJvRkMgDZJJzVSTK9KqKJbIGJqJlzWiViG7nfW1/ZK8XJUDqT0q9q08V2sRiL5HAG35SD3Bryaalzps9KduWyKt1pjWoXzG2uRnkcCs+8ee2YKJUbIzx0Iq/bXemiRPs7IY15Ey7m+TA5BrPuNUDIyCMDnhs81rz3SsZ8tmXbFDe2DTKVzHwyk8n6Vl3944Zo04A7jvT9pp5oOTX1Mp5GVsnmtG21aEQKkikFRj604SFKJOmpWrHAYr9RUjMGXKcg9xW0WmYyTREwNN281qZkqphQRwacCMYPHvUPUpCPGhXORmqU0fNEXqEkV2j9qjKVoQa+GAALE47VPDezRYUOdo7V5CkelY0pb+eayOJt4PG1uSKxpEkQkkmp0T9StX8hnm5GCM1GUBzVXsIj+deFYge1NMTueuapyJURr2jMvTmqclq4PK4ohMJRIhGwNadgLolUjDMvcVtz217GfLc2PsuSQQdw7Uw2pU/MCK6FU6eWhg4afmWLeKFkZJcg9mFRzac8MSyMRhumKnns7PZvQOS6utLblaRMLyR9KhkhVVBDZzV21TFeysVXWoiK1MjttV8O/YWyXVgegFZRsTu4FeHdp2e63PWSvqupZtrbadpHFPnst4xgVDlqUkUX03ax7VEbNd3ByapSbFYDYKw4PPpTv7LdT0wRVq703J0XkBtlU7Sy7qje1yMMtJpoaaZWbTCZOB+FdVo+n/ZrRXaEh/pwacptxEo2ZZfRBLmQNskY8g1lXmm3VsS4IZaaxDvZ9NifZK35mUZbp7odD6jGK3jcotogmgUrWsp3tZ2sTGO+nqZr3Flco6JEEdc7eetLDoElxEH81Vz0FbQrOEby9530MZUlJ+7ppqOOgRxDMrqcdumaqz6Xa55YJnphqaxE5PRadgdGKWr17nd+cl4VFzGHAq0NEspRuRNp9K5vYxm3e6b2ZvzuK027CroNsPvLz6iql7oICFkOQO1RPCuMbp3a3Q41ruzWj2MG604xZJrInQoSVHPrXPB3NZEYlm6bM0gup0+SQttPXmt42W25DuRTW7ht6qXX1qxZSSSttZcqPWrjJPfXuiWrbGgFiADHBxW9p1z5dv8AvW3J2B7VbUeXuQnK/kM+0SyTt5GSg/ic8VUv7xpodrDn26Gs5wj0+LqXGT67dDFWLEhfkGo5nklyrE4qlC9vwJcrFRbJVl3GtO1njhTqQR61u4StYyU1civ7sSLtAJ981kSLnPJrelHlRhVlzM7yLTdTtJuu9Qe3NdBbGUorMFJxz2NcFPnUrWO2XK4lsdKCARg13bmBSurCGU4aMtn0qjJ4Xt3YnP0GK4pYbmk+X3bGyq2WvvFKTw5IpIRAR61Fc+Gttvvfn1GOlYeynHVq1uprzxfzKcCW1mdroXU8YIqQR2KA7AxPUgDGKiz3TKutjPnjic74jtB9TzT4p58Bc7yOm6tItrfoQ0mWEubtZf367l7DtUqq1w24gKg6kDpW0FFrm7Gc207dynKqqzAoOehFVmhLdFJ/CumKtuYN9gGnzuPlibmoXs5VJBXkH1qlVjtdEezlvYimtJEXLow/CqErIDWkZp7WZEotbnrsTkjrmphz1rGDutdToloxaK0EMkU9VGSKRDIQd4A9MVm+ZS0+F7selvPoNDuHw3T2oJWUlWH50r3Vn1HtqjG1LSmVS6DdzxxWQ+nTSTcghjXBKPs3Z/I6IvmV/vK7aWYptsp2jua0LG3tllLQZkK8dO9C95227g9FfcmuFnnUrtyF9BUthHhfLkjO0n14zXToo2WhiruV2JqFtFGNyxoSPUVztzrdzBJhdoVewFZJ8zs3dLY0a5dVu9yCTxLKUPyDd2NZE+tXDyF84J74rSMEiJSbKFxqFxMpDyuQe2azpN3dj+dbRlbYzkr7nvCJkYxsP95eDUqxyA584t7EVnTi+j5fLoaSa66+ZOM45orqMgooAYwqNhis5DQ0yMBio2Zm7ZrNu+5VrDNizPsdFI9CKjNrDCuEiCZ6kcVlKEd7fMtSe34DY2jV8YKknvzTLqUQcs+PwqJuyuVHU5TWtVeaX5coq/dGaxpLxpUw4zjvRFKwSepAF85SUGcdRVeaJh/DiqvZ2JsZ86sDz0qBo2xu/hq0yLHvy9KeK2pkvcdRWogpM0AIaYwqJAhNq1FcPKoHlIHHesZNqPu6vsWtXrou5HuK5YLzjjNZ1/c3YiIUZX+8vauec36LqbRivV9DNivriYlWOdo6HmrxleWIBgDx3HSpaugvZmDqFuWYgwKSPQVlsjxIym3BUgjmoXa+xT7lSOzd3PkAq3YZpby8vVASeNendBzWukt+nUz22Jo7S2v4A3lFGxzg1Rm0l4m+UMVPqKlSa03Q2k9T/9n4qqwQ2C6FUcJKhVwpbQ1vCsihOUlK0km1lS0VoSE2qiF4TrpDJE0aZJK5EgBF7pQGeoyWHrHyLxlrwklpeaZbWWmyFkkIa43/2P/bAEMAAgEBAQEBAgEBAQICAgICBAMCAgICBQQEAwQGBQYGBgUGBgYHCQgGBwkHBgYICwgJCgoKCgoGCAsMCwoMCQoKCv/bAEMBAgICAgICBQMDBQoHBgcKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCv/AABEIAIAAgAMBIgACEQEDEQH/xAAeAAACAwEAAwEBAAAAAAAAAAAGBwQFCAMBAgkACv/EADcQAAEDAwMDAgUDAgYCAwAAAAECAwQFBhEAEiEHMUETUQgiMmFxFIGRFaEjQlKxwdEW8ReCov/EABsBAAICAwEAAAAAAAAAAAAAAAUGAwQBAgcA/8QAMREAAgEDAwMCBQMDBQAAAAAAAQIDAAQRBRIhMUFRE3EiYYGRwaHR4QYU8BUjMnKx/9oADAMBAAIRAxEAPwDNEamJCR8v9tT4dJ3Zwn+2rSHStzaVBvOrSDShnBTpvDYpbIBqsi0QKRn0+QO2uwpJQQCjRFEpR8D+2uj1LIXjb/bWwfmtNvFDqaWE/LsHfXZFNB/y6uVU75uUjj7a6NwMfMEfjWd3Fa0f/DB0mtK7KpIum8KgUxqQ+0pmE2EqMlzOQFA/5MgZ/J1q2L1glUxsPtIbbitNpW80EgbwSO+PGsWWjUqhRZy/0Tqkh1OFgH78aaKLzm0i28SnlLddYwk+wGdJH9QafJd3QLtkdh4802aNeRwWxCjBHU+aA/iosex//ktysdPnN8SpAOymM/M1IUo7/wD6k8jS8uTpxPthCJL3yuJSFKGOwPY50wavS7gnU3+vro7i4QXkyA3naoc86FrhnVGqpQl1SvTI5QVZzycHR6zkmiiSMvkLwSevtQe7WJ5HcLyeRS/q0BHqLc9NIKjyB50Pz6cEkkj+2j2qUlDRWfrJSQEgdjqqRbKKkVMJe2uBO5KSngn20SW9t1OC1DjaTsMhaBKhBCWt23A841QVGnBaiQ3n86O67TGWigR1bsg7hjkHPnVFNiJSgpIyc8DRBDxVRhjigmVAAP041CcaW2rcgYI9tE82n5PCedVkqAUkgJ1uQDUXfFaZplIUMsqb2kHke2rGNSylf0g8+2j2rWvRZtbjvxXY7EV14tuymdxzknCiD9hnge+oU+110+WtoLS4hKylDiBwoe/+2gkVysgB80akhZCQao4lMCk528jXRykKJ3bfxq8jUopABT31KXSRn6NS7sVFjihNVM+Y5T24zr1FPIVt26I3aUoEkA9+2uCqaUuDKdShs1oQM0bVvpPAtizaDUKLKVIVUYaZcxTrQSpl4jBQPOE/7k6rK1QUU213PUmJVLeWG4zTSgoff8Ht/Op1239WbjjNqqMgKDLKW0hCQkAJAHYceNC8aprVNbW+nKErG7nxnnGlyG3vJcvIckHP8f4KNyz20QCxjqP4rlFq98KoZs5ptxmKuQQ4kZBK/PPtjx21U3NbopREMhKlgfOQex9taAhdK3uofT7/AMo6eUh2PBElXqOyn0bFKT9XJOQRuHccg6BKn0RvByUUyqI+pxbZWnCchSQcZyOMZxzqs97E5IwFweR3z86nS0dFByWyOD2x8qULduuOOfIwVcZOBquqaEUV9t1EMBQz3HjTz6c9OpUibLl1aKGIsMelIekfKncoHAB8nj9tK/qfDpiqu9Hp3KWyQCR3++q7XStcel4FSiAiLf5pTVmEhcl1aOQok8e+h2bTVBZJGD99HAYnQZKxCYSXHRt3LQFAZ+x17XBbjT0VpLURKNqcFwJ5Ufvpms9VUuEfvQC609gpZaWMqAcnjzxqslQwBx+2jGr0ZyI6WHmsKx/OqaXTu4KfxjxpgBDDNBDuU1t2HUKReHSW0yqB6D9NEhh+Q0jIWvcFBC/bgkhX3I8al1mQ5ULdj0gUeKw2zIW6hbKDuJICeSSf9I0c/Bn0Pi3xcL1o1iSmP6chKz6qcjaPlPB78Ej99D9etp63K1OtySfUMSU4zuAwCUqIz++Nc70q8huB6SHLJz9yaeNQt3hbe3Rhj7AUJMUc8fJru5S0+n9HI99EcOkFxO5ScY9hr2k0hIbPy+PbTCX3UEA2mg1ym7gfl51Hk0rCdwbOilVLUkkFGvC6SVEkI/IOrAkAqBlNBbkJQQQnODxqK7TFIPKNGTtFZS4d+AAMnOvU2dPqEN6bAhuuMxwPWdbbJSjPbJ8aw9xFEMk4FeSOSQ4UZqNY/V26LLpj1qR5CjT5K8uhP1oJKclJJ4+ka2DZLVgdROlbVDtKII9wohsKeDxG8Mn/AD4BI2naPPdWsxdOennSm511K27kulcCqlgKpUpxQ9FSwPpV7A++ovTq+Lw6IdUGJcSWmQuG56DjbUrc082T9IUONvn/AI0rana2msB1tjtlX4vG79x2/wDaYLO4udM2mcZjbjzinj1f6PXNEtfDtIYjts8+nETj1FEY3qz3JwNZJvGw566u4n0FbiTu419Ird6o2r18oaWnIiYr8mKlT0dXdteSCArGCMAY/wCNKq8ehtl2tMcl1LY8+SpSGkjsOcE/9aRrbULm0maKZfiHamiW1huI1dDxWGHOmU9tkPyIpSM5STqGKHBTIEea2VJB5GtFXzCob812AkIbUjgADHGgWo9OY7Sf1jrjYDhJQpRxxpktbidjlxig08MSjC81nbqPSKe3Wj/Twop9IbwrsFew0HzaeE8lPfTav+22WqissELUSd2DxjQRVKQGx8qPyddMsJA1qgz2pDvEK3LH519dunnRiPZfXiDc8OoxUU1x8IdUy6NqwrIBx3wSM6B/jNsG2aZ1fdlW5LbWJ0Rtx5pAyW1425J7HIAOmjYxrN8yqTb9UoEanKXT0h+ey8lTrxGcKScZRn2PnzpWdXKVKYvqo0559+U7EfLSJMiOW3HAnspSTnx57Ec65F/TyYuid3IGDjx710nV2zAo28Z/X2pVU+2JMJrZIVk9xrg6xDkLWww8lS0n5kA8jRo7NtiAwpF0SVNEK+YIQdwGq9u16ImOzWqO8l1qWne24MHI/wCD9jpvhugGEakEDrzS/Lb7gXYYJ+VCS6c5HUHkJ+dJyCR2OudJpEya86zGirce27m/TTnGOSSPbV7dM2FRkw0uOMqEuQWfkeSVIUMd0jkdxqM4HqK8qR6oZ9MEOlRxgeQdXBcJIp2HmqZt3jcFhxShvufX6ZWQuS84SlZJaSOMZ9tMzpz8RVmUmy5do120UuNPJBSyklG5eACSR3yB2++ll1F6rW69WZKItHTIUUFDD7rpGxefqwO478atrNtyFeVoR6o84gPeotC1NEDJB4PbQie3W/X02PGc9aKRTf2R3gVUXJRH59xuVSgRzGZcXuQ2CcIB8DXWHClMOIdlLKlA5yfHPfRk1bbkOElp9e5aBtzjwO2qmpNMxspTjPuPGjVnZpGB5FCLq7eQkY4o+HXyRYtowaBY4ALMlt5ySpeVhSQNwPH0nAI9hka6TPiakXWt2Rcqn23HUkrDaApJXwMjz7/zpRyWSpzcPOplOghLaHZLSi2VYCgNYk0PT2G5kyx79+awurXoOA3HjtVjWqgqq1FdVUVqbWCGyDhQOPOhK6KnV3VoVJdWG0AhAHkaNJUQrpbcVLSAVnd6iOVHuMaFrnp0tpKv1BJUgYIOpLeKFTtA6cVFNNKRknrzQLV5sV1agWjz/mPfQjVYSFLUWxx4zorqsBwun5cA6qJEEkH7edGIY1iHw0NkdpDzWvLB+KW9rXr0OpN1x55tbXpTQtsbkoOAQkqBwQBweccadHTfrT0wrFz1K5ruuWfOcl00x4s2SylTsde0JCl+OEgpBHP2GsvVG0ajCfUw7CIKDjKRqw6eyKjb9cbdMcPNKc2vMujhSc9jri6Tw+myrhdwwSPFdSaNyyk84OaPut/WO1oTkuzG6PFmul8LYrDBO5SMHIVu5UVcfg9u+l1Gvup0+lLRb0v/AA8ENtvEkNk8naNEd4dNl1J1+tNx0oU4srS0Owz4GfGltMo1VgTDGfWpKEqzwO+orW8WIARtgit5oC+d65BoaqIqqpSprkle71crKlHg50fdVevFq31ZdPt+NbyoU+PT249RloUNstaCT6pAAwo55P2Gh1+lSnt7CmS5nJScarUWstThbciFWOT8vYaIJqWcFjyPzVVrME4A4oErdLE1tamV5JOQfY6pqZeN22Sp1mkVd5lLowtKF8HTjh2HBfaSEIBJByPbQ/cnRhLzS5cTJOSSlQ7a2ttYEUmCaxNp5kTIFD1rfEHekScluoTjKaUseo2/yQnzg+NNinTqPdba36FN9cJA9RJGFJJ5wRpNW/02nTa81SGYpLrrwQkbfJONao6f/C3UunPTxd5Sn1LefdQlUb0+R3IP8aY7bW0jnRC3/LigdxpfqRMwHSl2/RH23Ni2SD7EauaRa1RlUaRLjxS4iMAp7YeQCcZx5AP8Z0aVyg0RgNvSZxafWfodSBzjjj+PxrzRK43aFX/Rwq9CccqLKmlNMvhRJIKcKT7j799GG1ZJIvhI3ePahY0x1k+LO3zS+juvtOBpvCcqHJAONV931CVP+R2GhWVY3oRjb/Gn51R6ET0Uin1i0LUHomIgyW2RvWF4PJH1DPck+4xxxpS3ZR61Zlddi16gNtnaU+m4nKT9xrW3vYL0BoSN3jIzxWJbSazOJQdv1xSlrFLbSokg5OqWRBSXDuIH50dVKmVCrOLMOEpz8J7aoa9Z1w0Vaf6tRZLBcA9NLjJG7PI/9aPRyDAVjzQhkJOQOK+lfxU/DzTVXM2enFkf4D6C4+7FbKxu85OcD8AaTUH4erjaeLrNGcSsKwpBbP8AbWtOiV5zKnVG00SptyUrOFpS8FA/YjPGnW3QrdrITOcpLaXQQTubwQR7++uKLok12zehIBz0I4x8iD+mK6h/qKQKokQnjrnmsCu9MJ8ajpZqNLWktpwoKTpe3TZtDZlrUI+1e3JCm+M6+md1dN7VuuCqPPpTW8NkNrQkAg447ayz1t6Ff0FMh5qlrKjnZhPnGhGqaZe6RIDL8St0I/Pir9nfW98pAGCOx/FZFbpkB2oKQ5BbbU2rAUrhK/tqxj2pa8qQp+tPMw1hISyMEpd57HGcHnPtgak3h0/uKbP/AEkeI6CFH6UEYOqef0lvNcb1XZDoWk7kJUrnOtreSHgsRXnVyOBXpd67Jst8xKdHMtfqAKLY+VQ8lKh3/OuUe2oVxRjPpAzv5LDn1t/Y++ulF6e1y9YZtp9paKgw5hlwpJ9XOePznU/p70tvqgXO8K3EfZEMFBTggLXgkDH7dtEi9hM2w4WqoFzGu5cmudk9B4NWvmImcoRGluBTkoJ4SnI5/OtnMdO2rdZgVKt1mNJgtsJERQQPTkYCRtxyO2SSeTu1nqk3TETV4dKVFTGUtwpkGQsJSnHPCjxp41S9alWbWVY1syI7UVhLf6mXJeAbYHOTvP8AqHAAz286llsrV1TEmfwKhW5uFZspj8mqjq58PfTe6KC7Vo8KNGU2hS1ORlggr5OMDkcax3UulMFfUVuO5MUhppe5DxPbHOONa2u2NVKBSlMUCVNkMuR0plPvpAaWvn6M4OPzpL1C3pcOovOymwXSFbVBOdufI/71pY288UpEDllPT81m5nieMGVQDUTqj1OrNm2221bF3PrdRGLLxaePJ5899DvTLqJROq9VpznVGC++mG2WnGwCQ8rOAT5z7/jXpUbcW+46mpI3kqyk9+NelvvtWe4h2nx0ZQ4CpJT3HnTFp2n3CpvHXnnoaDXt/AW2k8ccdRTerNsdGbepiq7SbPZSQz6qmxFUSkHt4IHP99KK7OtdlxnltsUKS4VEpfadOAMdsfcHVldvVKtVOkriQ3VRy4r/ABdijhQHYY8aUldil1TinkBSl87jotpmj78tdkk/9iaGX+rCMhbYAD2FfTe1PgzqHT+7UXJatwF6M1IC22ivDm0HI5Hn99Puh0+RTssKqLzzeMpTJBKk/bJ1CtaWzMbJizUOBBIWE5BB/BAP76vmySnn++hul6faxH14iefnkfT5e+aLXl1O/wDtv2+VedVdx04TlMtoajFS1FCvXZ3ZSe+PY41aaj1GK7LjlEd703ByheOx0VuohNAVxmqcTbJAaD698P3TisQZDDVDbZfeOQ+ngpP/AFoJY+Du3xUkzKrLalsDOWcFOD+f402Y9MqzVLdaqNS9V8kltxJIIGOBqPGl1OBGcDzO9RPClL57HQKXR9JkZXaDZx24+4HFEEvrxAVWTPv+M1k7qf03c6UXG5Kt+2W3S0slmSpsgd+/PfA/31VT+rw5XV7Tgxqi9HLzsh5IWXMA4wk8Jz/61qfqf0ypfUSkqnMtgzWo69iSTySOBrOVT+Fy8H6k2xVqTIbS4fmf2ZShOlG+0xrOUqyZU9CBnj+KN214J1BBwR1FI+5axbN0SRL9L0pTqgXGkNYQhWPA0QWv0pvrqJRAqgz5amow/wAJv1fkGMnsfHJ0Vv8ASGj9La+5Vbzt+XLisglpLUc7XecABXj8nTHoTFTdsaIbcguUlh0BSWW1J3ZcAyFecD/nWbRTI/pxnbjz+1YuJPTTe4z7UtbWoF2XPOYtepy1L/TIUpwOOhKUJQMq559j/Oqu+qXW4tYcRS6bMQzKQENMrQcqTjgcDkeR9tN+2enl4Wncypj8OO+AMu5SpaCnIzyPOrvrrU6bS7f/AFKKm1FfWgpSoqSTvxnA9iNMM+orZlSoDADH17mg8Nm90DklST+nYVmdfQq/6q4hX9CDKXRu3PvISEjPcjOf7Z1X3T0BlW/SHKtU7jhD0nQhxDIUoJ9yTjxnwNBV/dYep9r3K8+xXpYCuEoWtQBTnjH2Ol31P+IPqddDCI8utO7UIx6bR2p/cDv++rKanqbspVlA9v3qBtPsVBDBif8APFMWtWPSqdTnahIuultpwfSbmv8ApKUARhQye2Of20lbs6o2bDkriqrsJWxW0rbVuSr99ANzXLXZ29dSlur+XlS3CdLyvRW1rWsOg55I76MWupyoT6jbvpihtxp8LD4Bj61/RJHoRq8ZmNWFvJWyrcxIjultxP7juNXdEoJouRFqT7rSvqTJXuOffOvaIT6YBJOBxnU9ogpwBjVbTrSDAkxyMc9/5q7NcSOSvbxXtr9r9r920ZqrXhYBSQdQJjQIJx+dTVup7ajSNqknPtqCcAx1lTg5qllPvxcltwj8agSnqpIQSEuqB7nB51dqYjlRLo75BP2xquu+ZckWnoNqw0StqgH2lOYUUeQPzoHM/pRM7E4HYDJ+1EEw7hRxnueB96rabFcqrkmPJa9UNoBLK+x+bng9+NU9woj0+Utb1vtObAMteiR6ae+5I8du+plWqFah0t5VKbEV1xW4uuIO5IA4Bz986z71mvbqpRbmTUaqX429sNhyO4r03BnIWOfIxn8aA3N9CsigDnyen3olFayFDk0665W4Eq1v69HlyC00raWmlBSkKzwSPtwceQdYw+L3rDWLhqggJQ41FiI2RcnBWc/MtQAABJ8eO2tAWXcl2/p3WX3S4pwpVuWySl3I/wD1pQ9erfrM2c+0i3I8sFBcQtMTkI7c7e3PvoZNcPHcCQjj371aiCPGUB5rLNfviqyKYiTU2VrbQdiXHBnIz21CqNq1WpwUzaPDMhtxsLCmkZwD747aOLwgXNHt522avZjQiLWHEEp+dsDcBt9uSM/jVFRLZ6vWBSZF2dNHZSIzzKm5jbRStSRzwUkHgZznHfVxLkyLxgH9DVdo1j6nIpK31QaoylfqMEEDCgBoHl0OU7HVUm2VpS3wpvGc8d9ak6WVGL1IdnW51Ht6NMmuO+ozMGGHMEYKSBhJAPIOO5OfGqC//h1doNVcnUOnThGUopKS0HAoc9iO/wDHjUqak0bGNxz+lQtbK3xrX//Z",this.emptyImage=new Image,this.emptyImage.onload=()=>{this.samplers.empty=this.gl.createTexture(),this.bindTexture(this.samplers.empty,this.emptyImage,1,1)},this.emptyImage.src="data:image/gif;base64,R0lGODlhAQABAIAAAAUEBAAAACwAAAAAAQABAAACAkQBADs="}bindTexture(t,e,i,s){if(this.gl.bindTexture(this.gl.TEXTURE_2D,t),this.gl.pixelStorei(this.gl.UNPACK_ALIGNMENT,1),this.gl.texImage2D(this.gl.TEXTURE_2D,0,this.gl.RGBA,i,s,0,this.gl.RGBA,this.gl.UNSIGNED_BYTE,e),this.gl.generateMipmap(this.gl.TEXTURE_2D),this.gl.texParameteri(this.gl.TEXTURE_2D,this.gl.TEXTURE_WRAP_S,this.gl.REPEAT),this.gl.texParameteri(this.gl.TEXTURE_2D,this.gl.TEXTURE_WRAP_T,this.gl.REPEAT),this.gl.texParameteri(this.gl.TEXTURE_2D,this.gl.TEXTURE_MIN_FILTER,this.gl.LINEAR_MIPMAP_LINEAR),this.gl.texParameteri(this.gl.TEXTURE_2D,this.gl.TEXTURE_MAG_FILTER,this.gl.LINEAR),this.anisoExt){const t=this.gl.getParameter(this.anisoExt.MAX_TEXTURE_MAX_ANISOTROPY_EXT);this.gl.texParameterf(this.gl.TEXTURE_2D,this.anisoExt.TEXTURE_MAX_ANISOTROPY_EXT,t)}}loadExtraImages(t){Object.keys(t).forEach(e=>{const{data:i,width:s,height:r}=t[e];if(!this.samplers[e]){const t=new Image;t.onload=()=>{this.samplers[e]=this.gl.createTexture(),this.bindTexture(this.samplers[e],t,s,r)},t.src=i}})}getTexture(t){const e=this.samplers[t];return e||this.samplers.clouds2}}class Ft{constructor(t,e={}){this.gl=t,this.texsizeX=e.texsizeX,this.texsizeY=e.texsizeY,this.aspectx=e.aspectx,this.aspecty=e.aspecty,this.invAspectx=1/this.aspectx,this.invAspecty=1/this.aspecty,this.buildPositions(),this.textTexture=this.gl.createTexture(),this.indexBuf=t.createBuffer(),this.positionVertexBuf=this.gl.createBuffer(),this.vertexBuf=this.gl.createBuffer(),this.canvas=document.createElement("canvas"),this.canvas.width=this.texsizeX,this.canvas.height=this.texsizeY,this.context2D=this.canvas.getContext("2d",{willReadFrequently:!1}),this.floatPrecision=_t.getFragmentFloatPrecision(this.gl),this.createShader()}generateTitleTexture(t){this.context2D.clearRect(0,0,this.texsizeX,this.texsizeY),this.fontSize=Math.floor(this.texsizeX/256*16),this.fontSize=Math.max(this.fontSize,6),this.context2D.font=`italic ${this.fontSize}px Times New Roman`;let e=t,i=this.context2D.measureText(e).width;if(i>this.texsizeX){const t=this.texsizeX/i*.91;e=`${e.substring(0,Math.floor(e.length*t))}...`,i=this.context2D.measureText(e).width}this.context2D.fillStyle="#FFFFFF",this.context2D.fillText(e,(this.texsizeX-i)/2,this.texsizeY/2);const s=new Uint8Array(this.context2D.getImageData(0,0,this.texsizeX,this.texsizeY).data.buffer);this.gl.pixelStorei(this.gl.UNPACK_FLIP_Y_WEBGL,!0),this.gl.bindTexture(this.gl.TEXTURE_2D,this.textTexture),this.gl.texImage2D(this.gl.TEXTURE_2D,0,this.gl.RGBA,this.texsizeX,this.texsizeY,0,this.gl.RGBA,this.gl.UNSIGNED_BYTE,s),this.gl.texParameteri(this.gl.TEXTURE_2D,this.gl.TEXTURE_MAG_FILTER,this.gl.LINEAR),this.gl.texParameteri(this.gl.TEXTURE_2D,this.gl.TEXTURE_MIN_FILTER,this.gl.LINEAR_MIPMAP_LINEAR),this.gl.texParameteri(this.gl.TEXTURE_2D,this.gl.TEXTURE_WRAP_S,this.gl.CLAMP_TO_EDGE),this.gl.texParameteri(this.gl.TEXTURE_2D,this.gl.TEXTURE_WRAP_T,this.gl.CLAMP_TO_EDGE),this.gl.generateMipmap(this.gl.TEXTURE_2D),this.gl.bindTexture(this.gl.TEXTURE_2D,null)}updateGlobals(t){this.texsizeX=t.texsizeX,this.texsizeY=t.texsizeY,this.aspectx=t.aspectx,this.aspecty=t.aspecty,this.invAspectx=1/this.aspectx,this.invAspecty=1/this.aspecty,this.canvas.width=this.texsizeX,this.canvas.height=this.texsizeY}buildPositions(){const t=2/15,e=2/7,i=[];for(let s=0;s<8;s++){const r=s*e-1;for(let e=0;e<16;e++){const s=e*t-1;i.push(s,-r,0)}}const s=[];for(let t=0;t<7;t++)for(let e=0;e<15;e++){const i=e+16*t,r=e+16*(t+1),a=e+1+16*(t+1),o=e+1+16*t;s.push(i,r,o),s.push(r,a,o)}this.vertices=new Float32Array(i),this.indices=new Uint16Array(s)}createShader(){this.shaderProgram=this.gl.createProgram();const t=this.gl.createShader(this.gl.VERTEX_SHADER);this.gl.shaderSource(t,"#version 300 es\n const vec2 halfmad = vec2(0.5);\n in vec2 aPos;\n in vec2 aUv;\n out vec2 uv_orig;\n out vec2 uv;\n void main(void) {\n gl_Position = vec4(aPos, 0.0, 1.0);\n uv_orig = aPos * halfmad + halfmad;\n uv = aUv;\n }"),this.gl.compileShader(t);const e=this.gl.createShader(this.gl.FRAGMENT_SHADER);this.gl.shaderSource(e,`#version 300 es\n precision ${this.floatPrecision} float;\n precision highp int;\n precision mediump sampler2D;\n\n in vec2 uv_orig;\n in vec2 uv;\n out vec4 fragColor;\n uniform sampler2D uTexture;\n uniform float textColor;\n\n void main(void) {\n fragColor = texture(uTexture, uv) * vec4(textColor);\n }`),this.gl.compileShader(e),this.gl.attachShader(this.shaderProgram,t),this.gl.attachShader(this.shaderProgram,e),this.gl.linkProgram(this.shaderProgram),this.positionLocation=this.gl.getAttribLocation(this.shaderProgram,"aPos"),this.uvLocation=this.gl.getAttribLocation(this.shaderProgram,"aUv"),this.textureLoc=this.gl.getUniformLocation(this.shaderProgram,"uTexture"),this.textColorLoc=this.gl.getUniformLocation(this.shaderProgram,"textColor")}generateUvs(t,e,i){const s=[];for(let i=0;i<8;i++)for(let r=0;r<16;r++){const a=2*(r/15)-1;let o=2*(.75*(i/7-.5)+.5)-1;t>=1&&(o+=1/this.texsizeY),s.push(a,e?o:-o)}const r=Math.max(0,1-1.5*t)**1.8*1.3;for(let t=0;t<8;t++)for(let e=0;e<16;e++){const a=16*t+e;s[a]+=.07*r*Math.sin(.31*i.time+.39*s[a]-1.94*s[a+1]),s[a]+=.044*r*Math.sin(.81*i.time-1.91*s[a]+.27*s[a+1]),s[a]+=.061*r*Math.sin(1.31*i.time+.61*s[a]+.74*s[a+1]),s[a+1]+=.061*r*Math.sin(.37*i.time+1.83*s[a]+.69*s[a+1]),s[a+1]+=.07*r*Math.sin(.67*i.time+.42*s[a]-1.39*s[a+1]),s[a+1]+=.087*r*Math.sin(1.07*i.time+3.55*s[a]+.89*s[a+1])}const a=1.01/(t**.21+.01);for(let t=0;t=2&&(0===t&&(this.vertInfoC[o*(this.mesh_width+1)+t]=.5*(h+n)+(2*this.rng.random()-1)*r*this.aspecty),this.vertInfoC[o*(this.mesh_width+1)+e]=.5*(A+l)+(2*this.rng.random()-1)*r*this.aspecty),e-t>=2&&(0===i&&(this.vertInfoC[i*(this.mesh_width+1)+a]=.5*(h+A)+(2*this.rng.random()-1)*r*this.aspectx),this.vertInfoC[s*(this.mesh_width+1)+a]=.5*(n+l)+(2*this.rng.random()-1)*r*this.aspectx),s-i>=2&&e-t>=2&&(h=this.vertInfoC[o*(this.mesh_width+1)+t],A=this.vertInfoC[o*(this.mesh_width+1)+e],n=this.vertInfoC[i*(this.mesh_width+1)+a],l=this.vertInfoC[s*(this.mesh_width+1)+a],this.vertInfoC[o*(this.mesh_width+1)+a]=.25*(n+l+h+A)+(2*this.rng.random()-1)*r,this.genPlasma(t,a,i,o,.5*r),this.genPlasma(a,e,i,o,.5*r),this.genPlasma(t,a,o,s,.5*r),this.genPlasma(a,e,o,s,.5*r))}createBlendPattern(){const t=1+Math.floor(3*this.rng.random());if(0===t){let t=0;for(let e=0;e<=this.mesh_height;e++)for(let e=0;e<=this.mesh_width;e++)this.vertInfoA[t]=1,this.vertInfoC[t]=0,t+=1}else if(1===t){const t=6.28*this.rng.random(),e=Math.cos(t),i=Math.sin(t),s=.1+.2*this.rng.random(),r=1/s;let a=0;for(let t=0;t<=this.mesh_height;t++){const o=t/this.mesh_height*this.aspecty;for(let t=0;t<=this.mesh_width;t++){let h=(t/this.mesh_width*this.aspectx-.5)*e+(o-.5)*i+.5;h=(h-.5)/Math.sqrt(2)+.5,this.vertInfoA[a]=r*(1+s),this.vertInfoC[a]=r*h-r,a+=1}}}else if(2===t){const t=.12+.13*this.rng.random(),e=1/t;this.vertInfoC[0]=this.rng.random(),this.vertInfoC[this.mesh_width]=this.rng.random(),this.vertInfoC[this.mesh_height*(this.mesh_width+1)]=this.rng.random(),this.vertInfoC[this.mesh_height*(this.mesh_width+1)+this.mesh_width]=this.rng.random(),this.genPlasma(0,this.mesh_width,0,this.mesh_height,.25);let i=this.vertInfoC[0],s=this.vertInfoC[0],r=0;for(let t=0;t<=this.mesh_height;t++)for(let t=0;t<=this.mesh_width;t++)i>this.vertInfoC[r]&&(i=this.vertInfoC[r]),sthis.texsizeX?this.texsizeX/this.texsizeY:1,this.aspecty=this.texsizeX>this.texsizeY?this.texsizeY/this.texsizeX:1,this.invAspectx=1/this.aspectx,this.invAspecty=1/this.aspecty,this.qs=At.range(1,33).map(t=>`q${t}`),this.ts=At.range(1,9).map(t=>`t${t}`),this.regs=At.range(0,100).map(t=>t<10?`reg0${t}`:`reg${t}`),this.blurRatios=[[.5,.25],[.125,.125],[.0625,.0625]],this.audioLevels=new ot(this.audio),this.prevFrameBuffer=this.gl.createFramebuffer(),this.targetFrameBuffer=this.gl.createFramebuffer(),this.prevTexture=this.gl.createTexture(),this.targetTexture=this.gl.createTexture(),this.compFrameBuffer=this.gl.createFramebuffer(),this.compTexture=this.gl.createTexture(),this.anisoExt=this.gl.getExtension("EXT_texture_filter_anisotropic")||this.gl.getExtension("MOZ_EXT_texture_filter_anisotropic")||this.gl.getExtension("WEBKIT_EXT_texture_filter_anisotropic"),this.bindFrameBufferTexture(this.prevFrameBuffer,this.prevTexture),this.bindFrameBufferTexture(this.targetFrameBuffer,this.targetTexture),this.bindFrameBufferTexture(this.compFrameBuffer,this.compTexture);const s={pixelRatio:this.pixelRatio,textureRatio:this.textureRatio,texsizeX:this.texsizeX,texsizeY:this.texsizeY,mesh_width:this.mesh_width,mesh_height:this.mesh_height,aspectx:this.aspectx,aspecty:this.aspecty};this.noise=new Ut(t),this.image=new Mt(t),this.warpShader=new wt(t,this.noise,this.image,s),this.compShader=new It(t,this.noise,this.image,s),this.outputShader=new Rt(t,s),this.prevWarpShader=new wt(t,this.noise,this.image,s),this.prevCompShader=new It(t,this.noise,this.image,s),this.numBlurPasses=0,this.blurShader1=new Ct(0,this.blurRatios,t,s),this.blurShader2=new Ct(1,this.blurRatios,t,s),this.blurShader3=new Ct(2,this.blurRatios,t,s),this.blurTexture1=this.blurShader1.blurVerticalTexture,this.blurTexture2=this.blurShader2.blurVerticalTexture,this.blurTexture3=this.blurShader3.blurVerticalTexture,this.basicWaveform=new bt(t,s),this.customWaveforms=At.range(4).map(e=>new xt(e,t,s)),this.customShapes=At.range(4).map(e=>new vt(e,t,s)),this.prevCustomWaveforms=At.range(4).map(e=>new xt(e,t,s)),this.prevCustomShapes=At.range(4).map(e=>new vt(e,t,s)),this.darkenCenter=new St(t,s),this.innerBorder=new Tt(t,s),this.outerBorder=new Tt(t,s),this.motionVectors=new Pt(t,s),this.titleText=new Ft(t,s),this.blendPattern=new Qt(s),this.resampleShader=new yt(t),this.supertext={startTime:-1},this.warpUVs=new Float32Array((this.mesh_width+1)*(this.mesh_height+1)*2),this.warpColor=new Float32Array((this.mesh_width+1)*(this.mesh_height+1)*4),this.gl.clearColor(0,0,0,1),this.blankPreset=ht;const r={frame:0,time:0,fps:45,bass:1,bass_att:1,mid:1,mid_att:1,treb:1,treb_att:1};this.preset=ht,this.prevPreset=this.preset,this.presetEquationRunner=new ut(this.preset,r,s),this.prevPresetEquationRunner=new ut(this.prevPreset,r,s),this.preset.useWASM||(this.regVars=this.presetEquationRunner.mdVSRegs)}static getHighestBlur(t){return/sampler_blur3/.test(t)?3:/sampler_blur2/.test(t)?2:/sampler_blur1/.test(t)?1:0}loadPreset(t,e){this.blendPattern.createBlendPattern(),this.blending=!0,this.blendStartTime=this.time,this.blendDuration=e,this.blendProgress=0,this.prevPresetEquationRunner=this.presetEquationRunner,this.prevPreset=this.preset,this.preset=t,this.presetTime=this.time;const i={frame:this.frameNum,time:this.time,fps:this.fps,bass:this.audioLevels.bass,bass_att:this.audioLevels.bass_att,mid:this.audioLevels.mid,mid_att:this.audioLevels.mid_att,treb:this.audioLevels.treb,treb_att:this.audioLevels.treb_att},s={pixelRatio:this.pixelRatio,textureRatio:this.textureRatio,texsizeX:this.texsizeX,texsizeY:this.texsizeY,mesh_width:this.mesh_width,mesh_height:this.mesh_height,aspectx:this.aspectx,aspecty:this.aspecty};t.useWASM?(this.preset.globalPools.perFrame.old_wave_mode.value=this.prevPreset.baseVals.wave_mode,this.preset.baseVals.old_wave_mode=this.prevPreset.baseVals.wave_mode,this.presetEquationRunner=new ft(this.preset,i,s),this.preset.pixel_eqs_initialize_array&&this.preset.pixel_eqs_initialize_array(this.mesh_width,this.mesh_height)):(this.preset.baseVals.old_wave_mode=this.prevPreset.baseVals.wave_mode,this.presetEquationRunner=new ut(this.preset,i,s),this.regVars=this.presetEquationRunner.mdVSRegs);const r=this.prevWarpShader;this.prevWarpShader=this.warpShader,this.warpShader=r;const a=this.prevCompShader;this.prevCompShader=this.compShader,this.compShader=a;const o=this.preset.warp.trim(),h=this.preset.comp.trim();this.warpShader.updateShader(o),this.compShader.updateShader(h),0===o.length?this.numBlurPasses=0:this.numBlurPasses=Vt.getHighestBlur(o),0!==h.length&&(this.numBlurPasses=Math.max(this.numBlurPasses,Vt.getHighestBlur(h)))}loadExtraImages(t){this.image.loadExtraImages(t)}setRendererSize(t,e,i){const s=this.texsizeX,r=this.texsizeY;if(this.width=t,this.height=e,this.mesh_width=i.meshWidth||this.mesh_width,this.mesh_height=i.meshHeight||this.mesh_height,this.pixelRatio=i.pixelRatio||this.pixelRatio,this.textureRatio=i.textureRatio||this.textureRatio,this.texsizeX=t*this.pixelRatio*this.textureRatio,this.texsizeY=e*this.pixelRatio*this.textureRatio,this.aspectx=this.texsizeY>this.texsizeX?this.texsizeX/this.texsizeY:1,this.aspecty=this.texsizeX>this.texsizeY?this.texsizeY/this.texsizeX:1,this.texsizeX!==s||this.texsizeY!==r){const t=this.gl.createTexture();this.bindFrameBufferTexture(this.targetFrameBuffer,t),this.bindFrambufferAndSetViewport(this.targetFrameBuffer,this.texsizeX,this.texsizeY),this.resampleShader.renderQuadTexture(this.targetTexture),this.targetTexture=t,this.bindFrameBufferTexture(this.prevFrameBuffer,this.prevTexture),this.bindFrameBufferTexture(this.compFrameBuffer,this.compTexture)}this.updateGlobals(),this.frameNum>0&&this.renderToScreen()}setInternalMeshSize(t,e){this.mesh_width=t,this.mesh_height=e,this.updateGlobals()}setOutputAA(t){this.outputFXAA=t}updateGlobals(){const t={pixelRatio:this.pixelRatio,textureRatio:this.textureRatio,texsizeX:this.texsizeX,texsizeY:this.texsizeY,mesh_width:this.mesh_width,mesh_height:this.mesh_height,aspectx:this.aspectx,aspecty:this.aspecty};this.presetEquationRunner.updateGlobals(t),this.prevPresetEquationRunner.updateGlobals(t),this.warpShader.updateGlobals(t),this.prevWarpShader.updateGlobals(t),this.compShader.updateGlobals(t),this.prevCompShader.updateGlobals(t),this.outputShader.updateGlobals(t),this.blurShader1.updateGlobals(t),this.blurShader2.updateGlobals(t),this.blurShader3.updateGlobals(t),this.basicWaveform.updateGlobals(t),this.customWaveforms.forEach(e=>e.updateGlobals(t)),this.customShapes.forEach(e=>e.updateGlobals(t)),this.prevCustomWaveforms.forEach(e=>e.updateGlobals(t)),this.prevCustomShapes.forEach(e=>e.updateGlobals(t)),this.darkenCenter.updateGlobals(t),this.innerBorder.updateGlobals(t),this.outerBorder.updateGlobals(t),this.motionVectors.updateGlobals(t),this.titleText.updateGlobals(t),this.blendPattern.updateGlobals(t),this.warpUVs=new Float32Array((this.mesh_width+1)*(this.mesh_height+1)*2),this.warpColor=new Float32Array((this.mesh_width+1)*(this.mesh_height+1)*4),this.preset.pixel_eqs_initialize_array&&this.preset.pixel_eqs_initialize_array(this.mesh_width,this.mesh_height)}calcTimeAndFPS(t){let e;if(t)e=t;else{const t=performance.now();e=(t-this.lastTime)/1e3,(e>1||e<0||this.frame<2)&&(e=1/30),this.lastTime=t}this.time+=1/this.fps,this.blending&&(this.blendProgress=(this.time-this.blendStartTime)/this.blendDuration,this.blendProgress>1&&(this.blending=!1));const i=this.timeHist[this.timeHist.length-1]+e;this.timeHist.push(i),this.timeHist.length>this.timeHistMax&&this.timeHist.shift();const s=this.timeHist.length/(i-this.timeHist[0]);if(Math.abs(s-this.fps)>3&&this.frame>this.timeHistMax)this.fps=s;else{const t=.93;this.fps=t*this.fps+(1-t)*s}}runPixelEquations(t,e,i,s){const r=this.mesh_width,a=this.mesh_height,o=r+1,h=a+1,A=this.time*e.warpanimspeed,n=1/e.warpscale,l=11.68+4*Math.cos(1.413*A+10),c=8.77+3*Math.cos(1.113*A+7),g=10.54+3*Math.cos(1.233*A+3),m=11.49+4*Math.cos(.933*A+5),u=0/this.texsizeX,f=0/this.texsizeY,d=this.aspectx,p=this.aspecty;let _=0,E=0;if(t.preset.useWASM){const r=t.preset.globalPools.perVertex;if(At.setWasm(r,i,t.globalKeys),At.setWasm(r,t.mdVSQAfterFrame,t.qs),r.zoom.value=e.zoom,r.zoomexp.value=e.zoomexp,r.rot.value=e.rot,r.warp.value=e.warp,r.cx.value=e.cx,r.cy.value=e.cy,r.dx.value=e.dx,r.dy.value=e.dy,r.sx.value=e.sx,r.sy.value=e.sy,t.preset.pixel_eqs_wasm(t.runVertEQs,this.mesh_width,this.mesh_height,this.time,e.warpanimspeed,e.warpscale,this.aspectx,this.aspecty),s){const e=t.preset.pixel_eqs_get_array();let i=0,s=0;for(let t=0;t0&&(this.blurShader1.renderBlurTexture(this.targetTexture,r,A,n),this.numBlurPasses>1&&(this.blurShader2.renderBlurTexture(this.blurTexture1,r,A,n),this.numBlurPasses>2&&this.blurShader3.renderBlurTexture(this.blurTexture2,r,A,n)),this.bindFrambufferAndSetViewport(this.targetFrameBuffer,this.texsizeX,this.texsizeY)),this.motionVectors.drawMotionVectors(a,this.warpUVs),this.preset.shapes&&this.preset.shapes.length>0&&this.customShapes.forEach((t,e)=>{t.drawCustomShape(this.blending?this.blendProgress:1,i,this.presetEquationRunner,this.preset.shapes[e],this.prevTexture)}),this.preset.waves&&this.preset.waves.length>0&&this.customWaveforms.forEach((t,e)=>{t.drawCustomWaveform(this.blending?this.blendProgress:1,this.audio.timeArrayL,this.audio.timeArrayR,this.audio.freqArrayL,this.audio.freqArrayR,i,this.presetEquationRunner,this.preset.waves[e])}),this.blending&&(this.prevPreset.shapes&&this.prevPreset.shapes.length>0&&this.prevCustomShapes.forEach((t,e)=>{t.drawCustomShape(1-this.blendProgress,s,this.prevPresetEquationRunner,this.prevPreset.shapes[e],this.prevTexture)}),this.prevPreset.waves&&this.prevPreset.waves.length>0&&this.prevCustomWaveforms.forEach((t,e)=>{t.drawCustomWaveform(1-this.blendProgress,this.audio.timeArrayL,this.audio.timeArrayR,this.audio.freqArrayL,this.audio.freqArrayR,s,this.prevPresetEquationRunner,this.prevPreset.waves[e])})),this.basicWaveform.drawBasicWaveform(this.blending,this.blendProgress,this.audio.timeArrayL,this.audio.timeArrayR,a),this.darkenCenter.drawDarkenCenter(a);const l=[a.ob_r,a.ob_g,a.ob_b,a.ob_a];this.outerBorder.drawBorder(l,a.ob_size,0);const c=[a.ib_r,a.ib_g,a.ib_b,a.ib_a];if(this.innerBorder.drawBorder(c,a.ib_size,a.ob_size),this.supertext.startTime>=0){const t=(this.time-this.supertext.startTime)/this.supertext.duration;t>=1&&this.titleText.renderTitle(t,!0,i)}this.globalVars=i,this.mdVSFrame=r,this.mdVSFrameMixed=a,this.renderToScreen()}renderToScreen(){this.outputFXAA?this.bindFrambufferAndSetViewport(this.compFrameBuffer,this.texsizeX,this.texsizeY):this.bindFrambufferAndSetViewport(null,this.width,this.height),this.gl.clear(this.gl.COLOR_BUFFER_BIT),this.gl.enable(this.gl.BLEND),this.gl.blendEquation(this.gl.FUNC_ADD),this.gl.blendFunc(this.gl.SRC_ALPHA,this.gl.ONE_MINUS_SRC_ALPHA);const{blurMins:t,blurMaxs:e}=Vt.getBlurValues(this.mdVSFrameMixed);if(this.blending?(this.prevCompShader.renderQuadTexture(!1,this.targetTexture,this.blurTexture1,this.blurTexture2,this.blurTexture3,t,e,this.prevMDVSFrame,this.prevPresetEquationRunner.mdVSQAfterFrame,this.warpColor),this.compShader.renderQuadTexture(!0,this.targetTexture,this.blurTexture1,this.blurTexture2,this.blurTexture3,t,e,this.mdVSFrameMixed,this.presetEquationRunner.mdVSQAfterFrame,this.warpColor)):this.compShader.renderQuadTexture(!1,this.targetTexture,this.blurTexture1,this.blurTexture2,this.blurTexture3,t,e,this.mdVSFrame,this.presetEquationRunner.mdVSQAfterFrame,this.warpColor),this.supertext.startTime>=0){const t=(this.time-this.supertext.startTime)/this.supertext.duration;this.titleText.renderTitle(t,!1,this.globalVars),t>=1&&(this.supertext.startTime=-1)}this.outputFXAA&&(this.gl.bindTexture(this.gl.TEXTURE_2D,this.compTexture),this.gl.generateMipmap(this.gl.TEXTURE_2D),this.bindFrambufferAndSetViewport(null,this.width,this.height),this.outputShader.renderQuadTexture(this.compTexture))}launchSongTitleAnim(t){this.supertext={startTime:this.time,duration:1.7},this.titleText.generateTitleTexture(t)}toDataURL(){const t=new Uint8Array(this.texsizeX*this.texsizeY*4),e=this.gl.createFramebuffer(),i=this.gl.createTexture();this.bindFrameBufferTexture(e,i);const{blurMins:s,blurMaxs:r}=Vt.getBlurValues(this.mdVSFrameMixed);this.compShader.renderQuadTexture(!1,this.targetTexture,this.blurTexture1,this.blurTexture2,this.blurTexture3,s,r,this.mdVSFrame,this.presetEquationRunner.mdVSQAfterFrame,this.warpColor),this.gl.readPixels(0,0,this.texsizeX,this.texsizeY,this.gl.RGBA,this.gl.UNSIGNED_BYTE,t),Array.from({length:this.texsizeY},(e,i)=>t.slice(i*this.texsizeX*4,(i+1)*this.texsizeX*4)).forEach((e,i)=>t.set(e,(this.texsizeY-i-1)*this.texsizeX*4));const a=document.createElement("canvas");a.width=this.texsizeX,a.height=this.texsizeY;const o=a.getContext("2d",{willReadFrequently:!1}),h=o.createImageData(this.texsizeX,this.texsizeY);return h.data.set(t),o.putImageData(h,0,0),this.gl.deleteTexture(i),this.gl.deleteFramebuffer(e),a.toDataURL()}warpBufferToDataURL(){const t=new Uint8Array(this.texsizeX*this.texsizeY*4);this.gl.bindFramebuffer(this.gl.FRAMEBUFFER,this.targetFrameBuffer),this.gl.readPixels(0,0,this.texsizeX,this.texsizeY,this.gl.RGBA,this.gl.UNSIGNED_BYTE,t);const e=document.createElement("canvas");e.width=this.texsizeX,e.height=this.texsizeY;const i=e.getContext("2d",{willReadFrequently:!1}),s=i.createImageData(this.texsizeX,this.texsizeY);return s.data.set(t),i.putImageData(s,0,0),e.toDataURL()}}class Dt{constructor(t,e,i){this.opts=i,this.rng=gt(i),this.deterministicMode=i.deterministic||i.testMode,this.audio=new at(t);const s=i.width||1200,r=i.height||900;window.OffscreenCanvas?this.internalCanvas=new OffscreenCanvas(s,r):(this.internalCanvas=document.createElement("canvas"),this.internalCanvas.width=s,this.internalCanvas.height=r),this.gl=this.internalCanvas.getContext("webgl2",{alpha:!1,antialias:!1,depth:!1,stencil:!1,premultipliedAlpha:!1}),this.outputGl=e.getContext("2d",{willReadFrequently:!1}),this.baseValsDefaults={decay:.98,gammaadj:2,echo_zoom:2,echo_alpha:0,echo_orient:0,red_blue:0,brighten:0,darken:0,wrap:1,darken_center:0,solarize:0,invert:0,bmotionvectorson:1,fshader:0,b1n:0,b2n:0,b3n:0,b1x:1,b2x:1,b3x:1,b1ed:.25,wave_mode:0,additivewave:0,wave_dots:0,wave_thick:0,wave_a:.8,wave_scale:1,wave_smoothing:.75,wave_mystery:0,modwavealphabyvolume:0,modwavealphastart:.75,modwavealphaend:.95,wave_r:1,wave_g:1,wave_b:1,wave_x:.5,wave_y:.5,wave_brighten:1,mv_x:12,mv_y:9,mv_dx:0,mv_dy:0,mv_l:.9,mv_r:1,mv_g:1,mv_b:1,mv_a:1,warpanimspeed:1,warpscale:1,zoomexp:1,zoom:1,rot:0,cx:.5,cy:.5,dx:0,dy:0,warp:1,sx:1,sy:1,ob_size:.01,ob_r:0,ob_g:0,ob_b:0,ob_a:0,ib_size:.01,ib_r:.25,ib_g:.25,ib_b:.25,ib_a:0},this.shapeBaseValsDefaults={enabled:0,sides:4,additive:0,thickoutline:0,textured:0,num_inst:1,tex_zoom:1,tex_ang:0,x:.5,y:.5,rad:.1,ang:0,r:1,g:0,b:0,a:1,r2:0,g2:1,b2:0,a2:0,border_r:1,border_g:1,border_b:1,border_a:.1},this.waveBaseValsDefaults={enabled:0,samples:512,sep:0,scaling:1,smoothing:.5,r:1,g:1,b:1,a:1,spectrum:0,usedots:0,thick:0,additive:0},this.qs=At.range(1,33).map(t=>`q${t}`),this.ts=At.range(1,9).map(t=>`t${t}`),this.globalPerFrameVars=["old_wave_mode","frame","time","fps","bass","bass_att","mid","mid_att","treb","treb_att","meshx","meshy","aspectx","aspecty","pixelsx","pixelsy","rand_start","rand_preset"],this.globalPerPixelVars=["frame","time","fps","bass","bass_att","mid","mid_att","treb","treb_att","meshx","meshy","aspectx","aspecty","pixelsx","pixelsy","rand_start","rand_preset","x","y","rad","ang"],this.globalShapeVars=["frame","time","fps","bass","bass_att","mid","mid_att","treb","treb_att","meshx","meshy","aspectx","aspecty","pixelsx","pixelsy","rand_start","rand_preset","instance"],this.shapeBaseVars=["x","y","rad","ang","r","g","b","a","r2","g2","b2","a2","border_r","border_g","border_b","border_a","thickoutline","textured","tex_zoom","tex_ang","additive"],this.globalWaveVars=["frame","time","fps","bass","bass_att","mid","mid_att","treb","treb_att","meshx","meshy","aspectx","aspecty","pixelsx","pixelsy","rand_start","rand_preset","x","y","sample","value1","value2"],this.renderer=new Vt(this.gl,this.audio,i)}loseGLContext(){this.gl.getExtension("WEBGL_lose_context").loseContext(),this.outputGl=null}connectAudio(t){this.audioNode=t,this.audio.connectAudio(t)}disconnectAudio(t){this.audio.disconnectAudio(t)}static overrideDefaultVars(t,e){const i={};return Object.keys(t).forEach(s=>{Object.prototype.hasOwnProperty.call(e,s)?i[s]=e[s]:i[s]=t[s]}),i}createQVars(){const t={};return this.qs.forEach(e=>{t[e]=new WebAssembly.Global({value:"f64",mutable:!0},0)}),t}createTVars(){const t={};return this.ts.forEach(e=>{t[e]=new WebAssembly.Global({value:"f64",mutable:!0},0)}),t}createPerFramePool(t){const e={};return Object.keys(this.baseValsDefaults).forEach(i=>{e[i]=new WebAssembly.Global({value:"f64",mutable:!0},t[i])}),this.globalPerFrameVars.forEach(t=>{e[t]=new WebAssembly.Global({value:"f64",mutable:!0},0)}),e}createPerPixelPool(t){const e={};return Object.keys(this.baseValsDefaults).forEach(i=>{e[i]=new WebAssembly.Global({value:"f64",mutable:!0},t[i])}),this.globalPerPixelVars.forEach(t=>{e[t]=new WebAssembly.Global({value:"f64",mutable:!0},0)}),e}createCustomShapePerFramePool(t){const e={};return Object.keys(this.shapeBaseValsDefaults).forEach(i=>{e[i]=new WebAssembly.Global({value:"f64",mutable:!0},t[i])}),this.globalShapeVars.forEach(t=>{e[t]=new WebAssembly.Global({value:"f64",mutable:!0},0)}),e}createCustomWavePerFramePool(t){const e={};return Object.keys(this.waveBaseValsDefaults).forEach(i=>{e[i]=new WebAssembly.Global({value:"f64",mutable:!0},t[i])}),this.globalWaveVars.forEach(t=>{e[t]=new WebAssembly.Global({value:"f64",mutable:!0},0)}),e}static makeShapeResetPool(t,e,i){return e.reduce((e,s)=>({...e,[`${s}_${i}`]:t[s]}),{})}static base64ToArrayBuffer(t){for(var e=window.atob(t),i=e.length,s=new Uint8Array(i),r=0;rt||(()=>{}),A=await st.instantiate(Dt.base64ToArrayBuffer("AGFzbQEAAAABPQpgAABgAXwBfGACfHwBfGACf38AYAR/f39/AGAJf39/f3x8fHx8AGADf399AGABfwF/YAJ/fwF/YAF+AX8CuBWMAQNlbnYFYWJvcnQABAhwaXhlbEVxcwtwZXJQaXhlbEVxcwAADHBpeGVsVmFyUG9vbAR3YXJwA3wBDHBpeGVsVmFyUG9vbAR6b29tA3wBDHBpeGVsVmFyUG9vbAd6b29tZXhwA3wBDHBpeGVsVmFyUG9vbAJjeAN8AQxwaXhlbFZhclBvb2wCY3kDfAEMcGl4ZWxWYXJQb29sAnN4A3wBDHBpeGVsVmFyUG9vbAJzeQN8AQxwaXhlbFZhclBvb2wCZHgDfAEMcGl4ZWxWYXJQb29sAmR5A3wBDHBpeGVsVmFyUG9vbANyb3QDfAEMcGl4ZWxWYXJQb29sA3JhZAN8AQxwaXhlbFZhclBvb2wDYW5nA3wBDHBpeGVsVmFyUG9vbAF4A3wBDHBpeGVsVmFyUG9vbAF5A3wBCHFWYXJQb29sAnExA3wBCHFWYXJQb29sAnEyA3wBCHFWYXJQb29sAnEzA3wBCHFWYXJQb29sAnE0A3wBCHFWYXJQb29sAnE1A3wBCHFWYXJQb29sAnE2A3wBCHFWYXJQb29sAnE3A3wBCHFWYXJQb29sAnE4A3wBCHFWYXJQb29sAnE5A3wBCHFWYXJQb29sA3ExMAN8AQhxVmFyUG9vbANxMTEDfAEIcVZhclBvb2wDcTEyA3wBCHFWYXJQb29sA3ExMwN8AQhxVmFyUG9vbANxMTQDfAEIcVZhclBvb2wDcTE1A3wBCHFWYXJQb29sA3ExNgN8AQhxVmFyUG9vbANxMTcDfAEIcVZhclBvb2wDcTE4A3wBCHFWYXJQb29sA3ExOQN8AQhxVmFyUG9vbANxMjADfAEIcVZhclBvb2wDcTIxA3wBCHFWYXJQb29sA3EyMgN8AQhxVmFyUG9vbANxMjMDfAEIcVZhclBvb2wDcTI0A3wBCHFWYXJQb29sA3EyNQN8AQhxVmFyUG9vbANxMjYDfAEIcVZhclBvb2wDcTI3A3wBCHFWYXJQb29sA3EyOAN8AQhxVmFyUG9vbANxMjkDfAEIcVZhclBvb2wDcTMwA3wBCHFWYXJQb29sA3EzMQN8AQhxVmFyUG9vbANxMzIDfAEIdFZhclBvb2wCdDEDfAEIdFZhclBvb2wCdDIDfAEIdFZhclBvb2wCdDMDfAEIdFZhclBvb2wCdDQDfAEIdFZhclBvb2wCdDUDfAEIdFZhclBvb2wCdDYDfAEIdFZhclBvb2wCdDcDfAEIdFZhclBvb2wCdDgDfAEKc2hhcGVQb29sMAN4XzADfAEKc2hhcGVQb29sMAN5XzADfAEKc2hhcGVQb29sMAVyYWRfMAN8AQpzaGFwZVBvb2wwBWFuZ18wA3wBCnNoYXBlUG9vbDADcl8wA3wBCnNoYXBlUG9vbDADZ18wA3wBCnNoYXBlUG9vbDADYl8wA3wBCnNoYXBlUG9vbDADYV8wA3wBCnNoYXBlUG9vbDAEcjJfMAN8AQpzaGFwZVBvb2wwBGcyXzADfAEKc2hhcGVQb29sMARiMl8wA3wBCnNoYXBlUG9vbDAEYTJfMAN8AQpzaGFwZVBvb2wwCmJvcmRlcl9yXzADfAEKc2hhcGVQb29sMApib3JkZXJfZ18wA3wBCnNoYXBlUG9vbDAKYm9yZGVyX2JfMAN8AQpzaGFwZVBvb2wwCmJvcmRlcl9hXzADfAEKc2hhcGVQb29sMA50aGlja291dGxpbmVfMAN8AQpzaGFwZVBvb2wwCnRleHR1cmVkXzADfAEKc2hhcGVQb29sMAp0ZXhfem9vbV8wA3wBCnNoYXBlUG9vbDAJdGV4X2FuZ18wA3wBCnNoYXBlUG9vbDAKYWRkaXRpdmVfMAN8AQpzaGFwZVBvb2wxA3hfMQN8AQpzaGFwZVBvb2wxA3lfMQN8AQpzaGFwZVBvb2wxBXJhZF8xA3wBCnNoYXBlUG9vbDEFYW5nXzEDfAEKc2hhcGVQb29sMQNyXzEDfAEKc2hhcGVQb29sMQNnXzEDfAEKc2hhcGVQb29sMQNiXzEDfAEKc2hhcGVQb29sMQNhXzEDfAEKc2hhcGVQb29sMQRyMl8xA3wBCnNoYXBlUG9vbDEEZzJfMQN8AQpzaGFwZVBvb2wxBGIyXzEDfAEKc2hhcGVQb29sMQRhMl8xA3wBCnNoYXBlUG9vbDEKYm9yZGVyX3JfMQN8AQpzaGFwZVBvb2wxCmJvcmRlcl9nXzEDfAEKc2hhcGVQb29sMQpib3JkZXJfYl8xA3wBCnNoYXBlUG9vbDEKYm9yZGVyX2FfMQN8AQpzaGFwZVBvb2wxDnRoaWNrb3V0bGluZV8xA3wBCnNoYXBlUG9vbDEKdGV4dHVyZWRfMQN8AQpzaGFwZVBvb2wxCnRleF96b29tXzEDfAEKc2hhcGVQb29sMQl0ZXhfYW5nXzEDfAEKc2hhcGVQb29sMQphZGRpdGl2ZV8xA3wBCnNoYXBlUG9vbDIDeF8yA3wBCnNoYXBlUG9vbDIDeV8yA3wBCnNoYXBlUG9vbDIFcmFkXzIDfAEKc2hhcGVQb29sMgVhbmdfMgN8AQpzaGFwZVBvb2wyA3JfMgN8AQpzaGFwZVBvb2wyA2dfMgN8AQpzaGFwZVBvb2wyA2JfMgN8AQpzaGFwZVBvb2wyA2FfMgN8AQpzaGFwZVBvb2wyBHIyXzIDfAEKc2hhcGVQb29sMgRnMl8yA3wBCnNoYXBlUG9vbDIEYjJfMgN8AQpzaGFwZVBvb2wyBGEyXzIDfAEKc2hhcGVQb29sMgpib3JkZXJfcl8yA3wBCnNoYXBlUG9vbDIKYm9yZGVyX2dfMgN8AQpzaGFwZVBvb2wyCmJvcmRlcl9iXzIDfAEKc2hhcGVQb29sMgpib3JkZXJfYV8yA3wBCnNoYXBlUG9vbDIOdGhpY2tvdXRsaW5lXzIDfAEKc2hhcGVQb29sMgp0ZXh0dXJlZF8yA3wBCnNoYXBlUG9vbDIKdGV4X3pvb21fMgN8AQpzaGFwZVBvb2wyCXRleF9hbmdfMgN8AQpzaGFwZVBvb2wyCmFkZGl0aXZlXzIDfAEKc2hhcGVQb29sMwN4XzMDfAEKc2hhcGVQb29sMwN5XzMDfAEKc2hhcGVQb29sMwVyYWRfMwN8AQpzaGFwZVBvb2wzBWFuZ18zA3wBCnNoYXBlUG9vbDMDcl8zA3wBCnNoYXBlUG9vbDMDZ18zA3wBCnNoYXBlUG9vbDMDYl8zA3wBCnNoYXBlUG9vbDMDYV8zA3wBCnNoYXBlUG9vbDMEcjJfMwN8AQpzaGFwZVBvb2wzBGcyXzMDfAEKc2hhcGVQb29sMwRiMl8zA3wBCnNoYXBlUG9vbDMEYTJfMwN8AQpzaGFwZVBvb2wzCmJvcmRlcl9yXzMDfAEKc2hhcGVQb29sMwpib3JkZXJfZ18zA3wBCnNoYXBlUG9vbDMKYm9yZGVyX2JfMwN8AQpzaGFwZVBvb2wzCmJvcmRlcl9hXzMDfAEKc2hhcGVQb29sMw50aGlja291dGxpbmVfMwN8AQpzaGFwZVBvb2wzCnRleHR1cmVkXzMDfAEKc2hhcGVQb29sMwp0ZXhfem9vbV8zA3wBCnNoYXBlUG9vbDMJdGV4X2FuZ18zA3wBCnNoYXBlUG9vbDMKYWRkaXRpdmVfMwN8AQMZGAgDBwkBAQICAQYFAAAAAAAAAAAAAAAAAAUDAQABBuwMigF8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt/AUEAC3wBRAAAAAAAAAAAC3wBRAAAAAAAAAAAC34BQgALB9kB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"),{pixelEqs:{perPixelEqs:h(o.exports.perPixel)},pixelVarPool:{warp:r.perVertex.warp,zoom:r.perVertex.zoom,zoomexp:r.perVertex.zoomexp,cx:r.perVertex.cx,cy:r.perVertex.cy,sx:r.perVertex.sx,sy:r.perVertex.sy,dx:r.perVertex.dx,dy:r.perVertex.dy,rot:r.perVertex.rot,x:r.perVertex.x,y:r.perVertex.y,ang:r.perVertex.ang,rad:r.perVertex.rad},qVarPool:i,tVarPool:s,shapePool0:Dt.makeShapeResetPool(r.shapePerFrame0,this.shapeBaseVars,0),shapePool1:Dt.makeShapeResetPool(r.shapePerFrame1,this.shapeBaseVars,1),shapePool2:Dt.makeShapeResetPool(r.shapePerFrame2,this.shapeBaseVars,2),shapePool3:Dt.makeShapeResetPool(r.shapePerFrame3,this.shapeBaseVars,3),console:{logi:t=>{console.log("logi: "+t)},logf:t=>{console.log("logf: "+t)}},env:{abort:()=>{}}});t.globalPools=r,t.init_eqs=h(o.exports.presetInit),t.frame_eqs=h(o.exports.perFrame),t.save_qs=A.exports.saveQs,t.restore_qs=A.exports.restoreQs,t.save_ts=A.exports.saveTs,t.restore_ts=A.exports.restoreTs,o.exports.perPixel&&(t.pixel_eqs=o.exports.perPixel),t.pixel_eqs_initialize_array=(e,i)=>{const s=A.exports.createFloat32Array((e+1)*(i+1)*2);t.pixel_eqs_array=s},t.pixel_eqs_get_array=()=>A.exports.__getFloat32ArrayView(t.pixel_eqs_array),t.pixel_eqs_wasm=(...e)=>A.exports.runPixelEquations(t.pixel_eqs_array,...e);for(let e=0;eA.exports[`shape${e}_save`](),t.shapes[e].frame_eqs_restore=()=>A.exports[`shape${e}_restore`]());for(let e=0;e{var i="function"==typeof e,s="function"==typeof e,r="function"==typeof e;Object.defineProperty(Math,t,{configurable:i,enumerable:r,writable:s,value:e})};t("DEG_PER_RAD",Math.PI/180),t("RAD_PER_DEG",180/Math.PI);const e=new Float32Array(1);t("scale",function(t,e,i,s,r){return 0===arguments.length||Number.isNaN(t)||Number.isNaN(e)||Number.isNaN(i)||Number.isNaN(s)||Number.isNaN(r)?NaN:t===1/0||t===-1/0?t:(t-e)*(r-s)/(i-e)+s}),t("fscale",function(t,i,s,r,a){return e[0]=Math.scale(t,i,s,r,a),e[0]}),t("clamp",function(t,e,i){return Math.min(i,Math.max(e,t))}),t("radians",function(t){return t*Math.DEG_PER_RAD}),t("degrees",function(t){return t*Math.RAD_PER_DEG})}var t=1e-5;function e(t,e){let i={destCol:1,srcCol:1,srcLine:1};t.forEach(t=>{t.destCol>e||(i=t)});const s=e-i.destCol;return{column:i.srcCol+s,line:i.srcLine}}window.sqr=function(t){return t*t},window.sqrt=function(t){return Math.sqrt(Math.abs(t))},window.log10=function(t){return Math.log(t)*Math.LOG10E},window.sign=function(t){return t>0?1:t<0?-1:0},window.rand=function(t){var e=Math.floor(t);return e<1?Math.random():Math.random()*e},window.randint=function(t){return Math.floor(rand(t))},window.bnot=function(e){return Math.abs(e)t?1/s:0},window.bor=function(e,i){return Math.abs(e)>t||Math.abs(i)>t?1:0},window.band=function(e,i){return Math.abs(e)>t&&Math.abs(i)>t?1:0},window.equal=function(e,i){return Math.abs(e-i)e?1:0},window.below=function(t,e){return tt?i:s},window.memcpy=function(t,e,i,s){let r=e,a=i,o=s;return a<0&&(o+=a,r-=a,a=0),r<0&&(o+=r,a-=r,r=0),o>0&&t.copyWithin(r,a,o),e};var i=function(){var t=function(t,e,i,s){for(i=i||{},s=t.length;s--;i[t[s]]=e);return i},e=[1,18],i=[1,7],s=[1,19],r=[1,20],a=[1,14],o=[1,15],h=[1,16],A=[1,33],n=[1,31],l=[1,23],g=[1,22],c=[1,24],m=[1,25],u=[1,26],f=[1,27],d=[1,28],p=[1,29],E=[1,30],_=[5,8,15,18,20,28,29,32,33,34,35,36,37,38],b=[5,15,18],x=[5,12,15,17,18,24,25,28,29,30],v=[1,57],T=[5,8,12,15,17,18,24,25,28,29,30],w=[15,18],S=[5,8,15,18,28,29,38],P=[5,8,15,18,28,29,32,33,38],R=[5,8,15,18,28,29,32,33,34,37,38],I=[5,8,15,18,28,29,32,33,34,35,36,37,38],y=[5,8,15,18],B=[5,8,15,18,20,22,28,29,32,33,34,35,36,37,38],L={trace:function(){},yy:{},symbols_:{error:2,SCRIPT:3,expression:4,EOF:5,expressionsOptionalTrailingSemi:6,separator:7,";":8,expressions:9,EXPRESSION_BLOCK:10,IDENTIFIER:11,IDENTIFIER_TOKEN:12,argument:13,arguments:14,",":15,FUNCTION_CALL:16,"(":17,")":18,LOGICAL_EXPRESSION:19,LOGICAL_OPERATOR_TOKEN:20,ASSIGNMENT:21,ASSIGNMENT_OPERATOR_TOKEN:22,number:23,DIGITS_TOKEN:24,".":25,NUMBER_LITERAL:26,UNARY_EXPRESSION:27,"-":28,"+":29,"!":30,BINARY_EXPRESSION:31,"*":32,"/":33,"%":34,"&":35,"|":36,"^":37,COMPARISON_TOKEN:38,$accept:0,$end:1},terminals_:{2:"error",5:"EOF",8:";",12:"IDENTIFIER_TOKEN",15:",",17:"(",18:")",20:"LOGICAL_OPERATOR_TOKEN",22:"ASSIGNMENT_OPERATOR_TOKEN",24:"DIGITS_TOKEN",25:".",28:"-",29:"+",30:"!",32:"*",33:"/",34:"%",35:"&",36:"|",37:"^",38:"COMPARISON_TOKEN"},productions_:[0,[3,2],[3,2],[3,1],[7,1],[7,2],[9,2],[9,3],[6,1],[6,2],[10,1],[11,1],[13,1],[13,1],[14,1],[14,3],[16,3],[16,4],[19,3],[21,3],[21,3],[23,1],[23,2],[23,3],[23,2],[23,1],[26,1],[27,2],[27,2],[27,2],[31,3],[31,3],[31,3],[31,3],[31,3],[31,3],[31,3],[31,3],[31,3],[4,1],[4,1],[4,3],[4,1],[4,1],[4,1],[4,1],[4,1],[4,3]],performAction:function(t,e,i,s,r,a,o){var h=a.length-1;switch(r){case 1:return{type:"SCRIPT",body:[a[h-1]],loc:this._$};case 2:return{type:"SCRIPT",body:a[h-1],loc:this._$};case 3:return{type:"SCRIPT",body:[],loc:this._$};case 6:this.$=[a[h-1]];break;case 7:this.$=a[h-2].concat([a[h-1]]);break;case 8:this.$=a[h];break;case 9:this.$=a[h-1].concat([a[h]]);break;case 10:this.$={type:"EXPRESSION_BLOCK",body:a[h],loc:this._$};break;case 11:this.$={type:"IDENTIFIER",value:a[h].toLowerCase(),loc:this._$};break;case 14:this.$=[a[h]];break;case 15:this.$=a[h-2].concat([a[h]]);break;case 16:this.$={type:"CALL_EXPRESSION",callee:a[h-2],arguments:[],loc:this._$};break;case 17:this.$={type:"CALL_EXPRESSION",callee:a[h-3],arguments:a[h-1],loc:this._$};break;case 18:this.$={type:"LOGICAL_EXPRESSION",left:a[h-2],right:a[h],operator:a[h-1],loc:this._$};break;case 19:case 20:this.$={type:"ASSIGNMENT_EXPRESSION",left:a[h-2],operator:a[h-1],right:a[h],loc:this._$};break;case 21:this.$=Number(a[h]);break;case 22:this.$=Number(a[h-1]);break;case 23:this.$=Number(a[h-2]+a[h-1]+a[h]);break;case 24:this.$=Number("0"+a[h-1]+a[h]);break;case 25:this.$=0;break;case 26:this.$={type:"NUMBER_LITERAL",value:a[h],loc:this._$};break;case 27:case 28:case 29:this.$={type:"UNARY_EXPRESSION",value:a[h],operator:a[h-1],loc:this._$};break;case 30:case 31:case 32:case 33:case 34:case 35:case 36:case 37:case 38:this.$={type:"BINARY_EXPRESSION",left:a[h-2],right:a[h],operator:a[h-1],loc:this._$};break;case 41:case 47:this.$=a[h-1]}},table:[{3:1,4:2,5:[1,4],6:3,9:13,11:11,12:e,16:10,17:i,19:12,21:9,23:17,24:s,25:r,26:8,27:6,28:a,29:o,30:h,31:5},{1:[3]},{5:[1,21],7:32,8:A,20:n,28:l,29:g,32:c,33:m,34:u,35:f,36:d,37:p,38:E},{5:[1,34]},{1:[2,3]},t(_,[2,39]),t(_,[2,40]),{4:35,6:37,9:13,10:36,11:11,12:e,16:10,17:i,19:12,21:9,23:17,24:s,25:r,26:8,27:6,28:a,29:o,30:h,31:5},t(_,[2,42]),t(_,[2,43]),t(_,[2,44],{22:[1,38]}),t(_,[2,45],{17:[1,40],22:[1,39]}),t(_,[2,46]),t(b,[2,8],{31:5,27:6,26:8,21:9,16:10,11:11,19:12,23:17,4:41,12:e,17:i,24:s,25:r,28:a,29:o,30:h}),{4:42,11:11,12:e,16:10,17:i,19:12,21:9,23:17,24:s,25:r,26:8,27:6,28:a,29:o,30:h,31:5},{4:43,11:11,12:e,16:10,17:i,19:12,21:9,23:17,24:s,25:r,26:8,27:6,28:a,29:o,30:h,31:5},{4:44,11:11,12:e,16:10,17:i,19:12,21:9,23:17,24:s,25:r,26:8,27:6,28:a,29:o,30:h,31:5},t(_,[2,26]),t([5,8,15,17,18,20,22,28,29,32,33,34,35,36,37,38],[2,11]),t(_,[2,21],{25:[1,45]}),t(_,[2,25],{24:[1,46]}),{1:[2,1]},{4:47,11:11,12:e,16:10,17:i,19:12,21:9,23:17,24:s,25:r,26:8,27:6,28:a,29:o,30:h,31:5},{4:48,11:11,12:e,16:10,17:i,19:12,21:9,23:17,24:s,25:r,26:8,27:6,28:a,29:o,30:h,31:5},{4:49,11:11,12:e,16:10,17:i,19:12,21:9,23:17,24:s,25:r,26:8,27:6,28:a,29:o,30:h,31:5},{4:50,11:11,12:e,16:10,17:i,19:12,21:9,23:17,24:s,25:r,26:8,27:6,28:a,29:o,30:h,31:5},{4:51,11:11,12:e,16:10,17:i,19:12,21:9,23:17,24:s,25:r,26:8,27:6,28:a,29:o,30:h,31:5},{4:52,11:11,12:e,16:10,17:i,19:12,21:9,23:17,24:s,25:r,26:8,27:6,28:a,29:o,30:h,31:5},{4:53,11:11,12:e,16:10,17:i,19:12,21:9,23:17,24:s,25:r,26:8,27:6,28:a,29:o,30:h,31:5},{4:54,11:11,12:e,16:10,17:i,19:12,21:9,23:17,24:s,25:r,26:8,27:6,28:a,29:o,30:h,31:5},{4:55,11:11,12:e,16:10,17:i,19:12,21:9,23:17,24:s,25:r,26:8,27:6,28:a,29:o,30:h,31:5},{4:56,11:11,12:e,16:10,17:i,19:12,21:9,23:17,24:s,25:r,26:8,27:6,28:a,29:o,30:h,31:5},t(x,[2,6],{8:v}),t(T,[2,4]),{1:[2,2]},{7:32,8:A,18:[1,58],20:n,28:l,29:g,32:c,33:m,34:u,35:f,36:d,37:p,38:E},{18:[1,59]},t(w,[2,10]),{4:60,11:11,12:e,16:10,17:i,19:12,21:9,23:17,24:s,25:r,26:8,27:6,28:a,29:o,30:h,31:5},{4:61,11:11,12:e,16:10,17:i,19:12,21:9,23:17,24:s,25:r,26:8,27:6,28:a,29:o,30:h,31:5},{4:65,6:37,9:13,10:66,11:11,12:e,13:64,14:63,16:10,17:i,18:[1,62],19:12,21:9,23:17,24:s,25:r,26:8,27:6,28:a,29:o,30:h,31:5},t(b,[2,9],{7:67,8:A,20:n,28:l,29:g,32:c,33:m,34:u,35:f,36:d,37:p,38:E}),t(S,[2,27],{20:n,32:c,33:m,34:u,35:f,36:d,37:p}),t(S,[2,28],{20:n,32:c,33:m,34:u,35:f,36:d,37:p}),t(S,[2,29],{20:n,32:c,33:m,34:u,35:f,36:d,37:p}),t(_,[2,22],{24:[1,68]}),t(_,[2,24]),t(S,[2,30],{20:n,32:c,33:m,34:u,35:f,36:d,37:p}),t(S,[2,31],{20:n,32:c,33:m,34:u,35:f,36:d,37:p}),t(P,[2,32],{20:n,34:u,35:f,36:d,37:p}),t(P,[2,33],{20:n,34:u,35:f,36:d,37:p}),t(R,[2,34],{20:n,35:f,36:d}),t(I,[2,35],{20:n}),t(I,[2,36],{20:n}),t(R,[2,37],{20:n,35:f,36:d}),t(y,[2,38],{20:n,28:l,29:g,32:c,33:m,34:u,35:f,36:d,37:p,38:E}),t(_,[2,18]),t(T,[2,5]),t(_,[2,41]),t(_,[2,47]),t(y,[2,20],{20:n,28:l,29:g,32:c,33:m,34:u,35:f,36:d,37:p,38:E}),t(y,[2,19],{20:n,28:l,29:g,32:c,33:m,34:u,35:f,36:d,37:p,38:E}),t(B,[2,16]),{15:[1,70],18:[1,69]},t(w,[2,14]),t(w,[2,12],{7:32,8:A,20:n,28:l,29:g,32:c,33:m,34:u,35:f,36:d,37:p,38:E}),t(w,[2,13]),t(x,[2,7],{8:v}),t(_,[2,23]),t(B,[2,17]),{4:65,6:37,9:13,10:66,11:11,12:e,13:71,16:10,17:i,19:12,21:9,23:17,24:s,25:r,26:8,27:6,28:a,29:o,30:h,31:5},t(w,[2,15])],defaultActions:{4:[2,3],21:[2,1],34:[2,2]},parseError:function(t,e){if(!e.recoverable){var i=new Error(t);throw i.hash=e,i}this.trace(t)},parse:function(t){var e=this,i=[0],s=[null],r=[],a=this.table,o="",h=0,A=0,n=r.slice.call(arguments,1),l=Object.create(this.lexer),g={yy:{}};for(var c in this.yy)Object.prototype.hasOwnProperty.call(this.yy,c)&&(g.yy[c]=this.yy[c]);l.setInput(t,g.yy),g.yy.lexer=l,g.yy.parser=this,void 0===l.yylloc&&(l.yylloc={});var m=l.yylloc;r.push(m);var u=l.options&&l.options.ranges;"function"==typeof g.yy.parseError?this.parseError=g.yy.parseError:this.parseError=Object.getPrototypeOf(this).parseError;for(var f,d,p,E,_,b,x,v,T=function(){var t;return"number"!=typeof(t=l.lex()||1)&&(t=e.symbols_[t]||t),t},w={};;){if(d=i[i.length-1],this.defaultActions[d]?p=this.defaultActions[d]:(null==f&&(f=T()),p=a[d]&&a[d][f]),void 0===p||!p.length||!p[0]){var S;for(_ in v=[],a[d])this.terminals_[_]&&_>2&&v.push("'"+this.terminals_[_]+"'");S=l.showPosition?"Parse error on line "+(h+1)+":\n"+l.showPosition()+"\nExpecting "+v.join(", ")+", got '"+(this.terminals_[f]||f)+"'":"Parse error on line "+(h+1)+": Unexpected "+(1==f?"end of input":"'"+(this.terminals_[f]||f)+"'"),this.parseError(S,{text:l.match,token:this.terminals_[f]||f,line:l.yylineno,loc:m,expected:v})}if(p[0]instanceof Array&&p.length>1)throw new Error("Parse Error: multiple actions possible at state: "+d+", token: "+f);switch(p[0]){case 1:i.push(f),s.push(l.yytext),r.push(l.yylloc),i.push(p[1]),f=null,A=l.yyleng,o=l.yytext,h=l.yylineno,m=l.yylloc;break;case 2:if(b=this.productions_[p[1]][1],w.$=s[s.length-b],w._$={first_line:r[r.length-(b||1)].first_line,last_line:r[r.length-1].last_line,first_column:r[r.length-(b||1)].first_column,last_column:r[r.length-1].last_column},u&&(w._$.range=[r[r.length-(b||1)].range[0],r[r.length-1].range[1]]),void 0!==(E=this.performAction.apply(w,[o,A,h,g.yy,p[1],s,r].concat(n))))return E;b&&(i=i.slice(0,-1*b*2),s=s.slice(0,-1*b),r=r.slice(0,-1*b)),i.push(this.productions_[p[1]][0]),s.push(w.$),r.push(w._$),x=a[i[i.length-2]][i[i.length-1]],i.push(x);break;case 3:return!0}}return!0}},U={EOF:1,parseError:function(t,e){if(!this.yy.parser)throw new Error(t);this.yy.parser.parseError(t,e)},setInput:function(t,e){return this.yy=e||this.yy||{},this._input=t,this._more=this._backtrack=this.done=!1,this.yylineno=this.yyleng=0,this.yytext=this.matched=this.match="",this.conditionStack=["INITIAL"],this.yylloc={first_line:1,first_column:0,last_line:1,last_column:0},this.options.ranges&&(this.yylloc.range=[0,0]),this.offset=0,this},input:function(){var t=this._input[0];return this.yytext+=t,this.yyleng++,this.offset++,this.match+=t,this.matched+=t,t.match(/(?:\r\n?|\n).*/g)?(this.yylineno++,this.yylloc.last_line++):this.yylloc.last_column++,this.options.ranges&&this.yylloc.range[1]++,this._input=this._input.slice(1),t},unput:function(t){var e=t.length,i=t.split(/(?:\r\n?|\n)/g);this._input=t+this._input,this.yytext=this.yytext.substr(0,this.yytext.length-e),this.offset-=e;var s=this.match.split(/(?:\r\n?|\n)/g);this.match=this.match.substr(0,this.match.length-1),this.matched=this.matched.substr(0,this.matched.length-1),i.length-1&&(this.yylineno-=i.length-1);var r=this.yylloc.range;return this.yylloc={first_line:this.yylloc.first_line,last_line:this.yylineno+1,first_column:this.yylloc.first_column,last_column:i?(i.length===s.length?this.yylloc.first_column:0)+s[s.length-i.length].length-i[0].length:this.yylloc.first_column-e},this.options.ranges&&(this.yylloc.range=[r[0],r[0]+this.yyleng-e]),this.yyleng=this.yytext.length,this},more:function(){return this._more=!0,this},reject:function(){return this.options.backtrack_lexer?(this._backtrack=!0,this):this.parseError("Lexical error on line "+(this.yylineno+1)+". You can only invoke reject() in the lexer when the lexer is of the backtracking persuasion (options.backtrack_lexer = true).\n"+this.showPosition(),{text:"",token:null,line:this.yylineno})},less:function(t){this.unput(this.match.slice(t))},pastInput:function(){var t=this.matched.substr(0,this.matched.length-this.match.length);return(t.length>20?"...":"")+t.substr(-20).replace(/\n/g,"")},upcomingInput:function(){var t=this.match;return t.length<20&&(t+=this._input.substr(0,20-t.length)),(t.substr(0,20)+(t.length>20?"...":"")).replace(/\n/g,"")},showPosition:function(){var t=this.pastInput(),e=new Array(t.length+1).join("-");return t+this.upcomingInput()+"\n"+e+"^"},test_match:function(t,e){var i,s,r;if(this.options.backtrack_lexer&&(r={yylineno:this.yylineno,yylloc:{first_line:this.yylloc.first_line,last_line:this.last_line,first_column:this.yylloc.first_column,last_column:this.yylloc.last_column},yytext:this.yytext,match:this.match,matches:this.matches,matched:this.matched,yyleng:this.yyleng,offset:this.offset,_more:this._more,_input:this._input,yy:this.yy,conditionStack:this.conditionStack.slice(0),done:this.done},this.options.ranges&&(r.yylloc.range=this.yylloc.range.slice(0))),(s=t[0].match(/(?:\r\n?|\n).*/g))&&(this.yylineno+=s.length),this.yylloc={first_line:this.yylloc.last_line,last_line:this.yylineno+1,first_column:this.yylloc.last_column,last_column:s?s[s.length-1].length-s[s.length-1].match(/\r?\n?/)[0].length:this.yylloc.last_column+t[0].length},this.yytext+=t[0],this.match+=t[0],this.matches=t,this.yyleng=this.yytext.length,this.options.ranges&&(this.yylloc.range=[this.offset,this.offset+=this.yyleng]),this._more=!1,this._backtrack=!1,this._input=this._input.slice(t[0].length),this.matched+=t[0],i=this.performAction.call(this,this.yy,this,e,this.conditionStack[this.conditionStack.length-1]),this.done&&this._input&&(this.done=!1),i)return i;if(this._backtrack){for(var a in r)this[a]=r[a];return!1}return!1},next:function(){if(this.done)return this.EOF;var t,e,i,s;this._input||(this.done=!0),this._more||(this.yytext="",this.match="");for(var r=this._currentRules(),a=0;ae[0].length)){if(e=i,s=a,this.options.backtrack_lexer){if(!1!==(t=this.test_match(i,r[a])))return t;if(this._backtrack){e=!1;continue}return!1}if(!this.options.flex)break}return e?!1!==(t=this.test_match(e,r[s]))&&t:""===this._input?this.EOF:this.parseError("Lexical error on line "+(this.yylineno+1)+". Unrecognized text.\n"+this.showPosition(),{text:"",token:null,line:this.yylineno})},lex:function(){return this.next()||this.lex()},begin:function(t){this.conditionStack.push(t)},popState:function(){return this.conditionStack.length-1>0?this.conditionStack.pop():this.conditionStack[0]},_currentRules:function(){return this.conditionStack.length&&this.conditionStack[this.conditionStack.length-1]?this.conditions[this.conditionStack[this.conditionStack.length-1]].rules:this.conditions.INITIAL.rules},topState:function(t){return(t=this.conditionStack.length-1-Math.abs(t||0))>=0?this.conditionStack[t]:"INITIAL"},pushState:function(t){this.begin(t)},stateStackSize:function(){return this.conditionStack.length},options:{},performAction:function(t,e,i,s){switch(i){case 0:break;case 1:return 24;case 2:return 38;case 3:return 22;case 4:return 20;case 5:return 12;case 6:return 5;case 7:return e.yytext[0]}},rules:[/^(?:\s+)/,/^(?:[0-9]+)/,/^(?:(==|!=|<=|>=|<|>))/,/^(?:[+\-*/%]?=)/,/^(?:(\&\&)|\|\|)/,/^(?:[a-zA-Z_][a-zA-Z0-9._]*)/,/^(?:$)/,/^(?:.)/],conditions:{INITIAL:{rules:[0,1,2,3,4,5,6,7],inclusive:!0}}};function C(){this.yy={}}return L.lexer=U,C.prototype=L,L.Parser=C,new C}();const s={ASSIGNMENT_EXPRESSION:[{type:"NODE",key:"right"}],SCRIPT:[{type:"ARRAY",key:"body"}],EXPRESSION_BLOCK:[{type:"ARRAY",key:"body"}],UNARY_EXPRESSION:[{type:"NODE",key:"value"}],NUMBER_LITERAL:[],IDENTIFIER:[],CALL_EXPRESSION:[{type:"ARRAY",key:"arguments"},{type:"NODE",key:"callee"}],BINARY_EXPRESSION:[{type:"NODE",key:"left"},{type:"NODE",key:"right"}],LOGICAL_EXPRESSION:[{type:"NODE",key:"left"},{type:"NODE",key:"right"}]};function r(t,e){const i=s[t.type];let a=t;if(null==i)throw new Error(`Unknown children definition for ${t.type}`);return i.forEach(i=>{if("NODE"===i.type){const s=t[i.key],o=r(s,e);o!==s&&(a={...a,[i.key]:o})}else if("ARRAY"===i.type){const s=t[i.key],o=s.map(t=>r(t,e)),h=s.some((t,e)=>t!==o[e]);h&&(a={...a,[i.key]:o})}}),e(a)}function a(t){return[].concat.apply([],t)}function o(t,e){return new Array(t).fill(e).join("")}class h{constructor(){this._map=new Map}get(t,e){const i=null==t?e:`${t}::${e}`;return this._map.has(i)||this._map.set(i,this._map.size),this._map.get(i)}size(){return this._map.size}}class A extends Error{constructor(t,e,i){super(t),this.sourceContext=function(t,e,i=1){const s=Math.max(t.first_line-1-i,0),r=t.last_line+i,a=e.split("\n").slice(s,r).map((e,i)=>{const r=i+s+1;return`${r>=t.first_line&&r<=t.last_line?">":" "} ${r} | ${e}`});if(t.first_line===t.last_line){const e=o(t.first_column," "),i=o(t.last_column-t.first_column,"^"),r=t.first_line-s;a.splice(r,0,` | ${e}${i}`)}return a.join("\n")}(e,i),this.loc=e}}class n extends A{}function l(t,e,i){return new n(t,e,i)}function g(t,e,i){return new A(t,e,i)}function c(t,i){const s=e(i,t.first_column),r=e(i,t.last_column);return{first_column:s.column,last_column:r.column,first_line:s.line,last_line:r.line}}function m(t){const[e,s]=function(t){const e=[];let i=1,s="",r=0,a=!1,o=!1,h=!1;for(let A=0;A{if(1!==e.loc.first_line||1!=e.loc.last_line)throw g("Unexpected multiline",e.loc,t);return Object.assign(Object.assign({},e),{loc:c(e.loc,s)})})}catch(e){if(null==e.hash)throw e;throw l(`Parse Error: ${e.message.split("\n")[3]}`,c(e.hash.loc,s),t)}}const u=[0,97,115,109],f=[1,0,0,0],d=1e-5,p=t=>[2,t],E=t=>[3,t],_=t=>[4,t],b=11,x=t=>[13,...G(t)],v=t=>[16,...G(t)],T=t=>[32,...G(t)],w=t=>[33,...G(t)],S=t=>[34,...G(t)],P=t=>[35,...G(t)],R=t=>[36,...G(t)],I=(t,e)=>[43,...G(t),...G(e)],y=(t,e)=>[57,...G(t),...G(e)],B=t=>[65,...O(t)],L=t=>[68,...X(t)],U=99,C=153,F=161,Q=176,M=183,D=127,V=124,q=124,z=[C,...L(d),U],N=[C,...L(d),100];function X(t){const e=new Uint8Array(8);return function(t,e){const i=new ArrayBuffer(8);new DataView(i).setFloat64(0,e);const s=new Uint8Array(i).reverse();t.set(s,0)}(e,t),e}const k=t=>[t.length].concat(t.split("").map(t=>t.charCodeAt(0)));function G(t){const e=[];do{let i=127&t;0!=(t>>>=7)&&(i|=128),e.push(i)}while(0!==t);return e}function O(t){let e=[],i=0,s=Math.ceil(Math.log2(Math.abs(t))),r=t<0,a=!0;for(;a;)i=127&t,t>>=7,r&&(t|=-(1<G(t.length).concat(t),Y=t=>G(t.length).concat(a(t));function J(t,e){if(0===e.length)return[];const i=W(Y(e));return i.unshift(t),i}const K=(t,e,i)=>{const s=[...k(e),...i];return[t,...W(s)]},j={sin:Math.sin,cos:Math.cos,tan:Math.tan,asin:Math.asin,acos:Math.acos,atan:Math.atan,atan2:Math.atan2,rand:t=>Math.random()*t,pow:Math.pow,log:Math.log,log10:Math.log10,exp:Math.exp,sigmoid:function(t,e){const i=1+Math.exp(-t*e);return Math.abs(i)>1e-5?1/i:0}},H=Math.ceil(2048),Z=L(Math.pow(2,31)),$=L(Math.pow(2,31));function tt(t){return[...T(t),...Z,U,..._(q),...T(t),C,5,...$,b,176]}const et={sqr:{args:[V],returns:[V],binary:[...T(0),...T(0),162]},bor:{args:[V,V],returns:[V],binary:[...T(0),...N,...T(1),...N,114,...B(0),71,M]},band:{args:[V,V],returns:[V],binary:[...T(0),...N,...T(1),...N,113,...B(0),71,M]},sign:{args:[V],returns:[V],binary:[...L(0),...T(0),U,...T(0),...L(0),U,107,M]},mod:{args:[V,V],returns:[V],localVariables:[126],binary:[...tt(1),...S(2),...(t=>[66,...O(t)])(0),82,..._(q),...tt(0),...T(2),129,185,5,...L(0),b]},bitwiseOr:{args:[V,V],returns:[V],binary:[...T(0),Q,...T(1),Q,132,185]},bitwiseAnd:{args:[V,V],returns:[V],binary:[...T(0),Q,...T(1),Q,131,185]},div:{args:[V,V],returns:[V],localVariables:[D],binary:[...T(1),...L(0),98,..._(q),...T(0),...T(1),163,5,...L(0),b]},_getBufferIndex:{args:[V],returns:[D],localVariables:[V,D],binary:[...L(d),...T(0),160,...S(1),170,...w(2),...B(-1),...T(2),...B(8),108,...T(2),...B(0),72,...T(2),...B(8388607),74,114,27]}};function it(t,e){var i,s,r;switch(t.type){case"SCRIPT":return a(t.body.map((t,i)=>[...it(t,e),26]));case"EXPRESSION_BLOCK":return st(t.body,e);case"BINARY_EXPRESSION":{const i=it(t.left,e),s=it(t.right,e),r={"+":[160],"-":[F],"*":[162],"/":e.resolveFunc("div"),"%":e.resolveFunc("mod"),"|":e.resolveFunc("bitwiseOr"),"&":e.resolveFunc("bitwiseAnd"),"^":e.resolveFunc("pow"),"==":[F,...z,M],"!=":[F,...N,M],"<":[U,M],">":[100,M],"<=":[101,M],">=":[102,M]}[t.operator];if(null==r)throw g(`Unknown binary expression operator ${t.operator}`,t.loc,e.rawSource);return[...i,...s,...r]}case"CALL_EXPRESSION":{const s=t.callee.value,r=t.arguments,o=i=>{if(r.lengthi)throw l(`Too many arguments passed to \`${s}()\`. Expected ${i} but got ${r.length}.`,r[i].loc,e.rawSource)};switch(s){case"exec2":return o(2),st(t.arguments,e);case"exec3":return o(3),st(t.arguments,e);case"if":o(3);const[r,a,h]=t.arguments;return function(t,e,i,s){return[...it(t,s),...N,..._(q),...it(e,s),5,...it(i,s),b]}(r,a,h,e);case"while":return o(1),function(t,e){const i=it(t,e),s=e.resolveLocal(D);return[...B(0),...w(s),...E(64),...T(s),...B(1),106,...S(s),...B(1048576),73,...i,...N,113,...x(0),b,...L(0)]}(t.arguments[0],e);case"loop":return o(2),function(t,e,i){const s=it(e,i),r=i.resolveLocal(D);return[...p(64),...it(t,i),170,...S(r),...B(0),76,...x(1),...E(64),...s,26,...T(r),...B(1),107,...S(r),...B(0),71,...x(0),b,b,...L(0)]}(t.arguments[0],t.arguments[1],e);case"megabuf":case"gmegabuf":o(1);const A=e.resolveLocal(D);return[...it(t.arguments[0],e),...null!==(i=e.resolveFunc("_getBufferIndex"))&&void 0!==i?i:[],...S(A),...B(-1),71,..._(q),...T(A),...I(3,rt(s)),5,...L(0),b];case"assign":o(2);const n=t.arguments[0];if("IDENTIFIER"!=n.type)throw l("Expected the first argument of `assign()` to be an identifier.",n.loc,e.rawSource);const g=e.resolveVar(n.value);return[...it(t.arguments[1],e),...R(g),...P(g)]}const h=a(t.arguments.map(t=>it(t,e)));switch(s){case"abs":return o(1),[...h,C];case"sqrt":return o(1),[...h,C,159];case"int":case"floor":return o(1),[...h,156];case"min":return o(2),[...h,164];case"max":return o(2),[...h,165];case"above":return o(2),[...h,100,M];case"below":return o(2),[...h,U,M];case"equal":return o(2),[...h,F,...z,M];case"bnot":return o(1),[...h,...z,M];case"ceil":return o(1),[...h,155]}const A=e.resolveFunc(s);if(null==A||s.startsWith("_"))throw l(`"${s}" is not defined.`,t.callee.loc,e.rawSource);if(null!=j[s])o(j[s].length);else{if(null==et[s])throw g(`Missing arity information for the function \`${s}()\``,t.callee.loc,e.rawSource);o(et[s].args.length)}return[...h,...A]}case"ASSIGNMENT_EXPRESSION":{const{left:i}=t,a=it(t.right,e),o=function(t,e){const i={"+=":[160],"-=":[F],"*=":[162],"/=":[163],"%=":e.resolveFunc("mod"),"=":null}[t.operator];if(void 0===i)throw g(`Unknown assignment operator "${t.operator}"`,t.loc,e.rawSource);return i}(t,e);if("IDENTIFIER"===i.type){const t=e.resolveVar(i.value),s=P(t),r=R(t);return null===o?[...a,...r,...s]:[...s,...a,...o,...r,...s]}if("CALL_EXPRESSION"!==i.type)throw g(`Unexpected left hand side type for assignment: ${i.type}`,t.loc,e.rawSource);const h=e.resolveLocal(D);if(1!==i.arguments.length)throw l(`Expected 1 argument when assigning to a buffer but got ${i.arguments.length}.`,0===i.arguments.length?i.loc:i.arguments[1].loc,e.rawSource);const A=i.callee.value;if("gmegabuf"!==A&&"megabuf"!==A)throw l("The only function calls which may be assigned to are `gmegabuf()` and `megabuf()`.",i.callee.loc,e.rawSource);const n=rt(A);if(null===o){const t=e.resolveLocal(D),r=e.resolveLocal(V);return[...a,...w(r),...it(i.arguments[0],e),...null!==(s=e.resolveFunc("_getBufferIndex"))&&void 0!==s?s:[],...S(t),...B(0),72,..._(q),...L(0),5,...T(t),...S(h),...T(r),...y(3,n),...T(r),b]}const c=e.resolveLocal(D),m=e.resolveLocal(D),u=e.resolveLocal(V),f=e.resolveLocal(V);return[...a,...w(u),...it(i.arguments[0],e),...null!==(r=e.resolveFunc("_getBufferIndex"))&&void 0!==r?r:[],...S(c),...B(-1),71,...S(m),..._(q),...T(c),...I(3,n),5,...L(0),b,...T(u),...o,...S(f),...T(m),..._(64),...T(c),...T(f),...y(3,n),b]}case"LOGICAL_EXPRESSION":{const i=it(t.left,e),s=it(t.right,e),r={"&&":{comparison:z,shortCircuitValue:0},"||":{comparison:N,shortCircuitValue:1}}[t.operator];if(null==r)throw g(`Unknown logical expression operator ${t.operator}`,t.loc,e.rawSource);const{comparison:a,shortCircuitValue:o}=r;return[...i,...a,..._(q),...L(o),5,...s,...N,M,b]}case"UNARY_EXPRESSION":{const i=it(t.value,e),s={"-":[154],"+":[],"!":[...z,M]}[t.operator];if(null==s)throw g(`Unknown logical unary operator ${t.operator}`,t.loc,e.rawSource);return[...i,...s]}case"IDENTIFIER":const o=t.value;return P(e.resolveVar(o));case"NUMBER_LITERAL":return L(t.value);default:throw g(`Unknown AST node type ${t.type}`,t.loc,e.rawSource)}}function st(t,e){return a(function(t,e){const i=[];for(let s=0;sit(t,e)),[26]))}function rt(t){switch(t){case"gmegabuf":return 67108864;case"megabuf":return 0}}function at({pools:t,functions:e,eelVersion:i=2,preParsed:s=!1}){if(Object.keys(t).includes("shims"))throw new Error('You may not name a pool "shims". "shims" is reserved for injected JavaScript functions.');const r=[];Object.entries(t).forEach(([t,e])=>{e.forEach(e=>{r.push([t,e])})});const a=new h;r.forEach(([t,e])=>{a.get(t,e)});const o=Object.entries(j).map(([t,e])=>({args:new Array(e.length).fill(null).map(t=>V),returns:[V],name:t})),A=[],n=[];Object.entries(e).forEach(([e,{pool:r,code:h}])=>{if(null==t[r]){const i=Object.keys(t);if(0===i.length)throw new Error(`The function "${e}" was declared as using a variable pool named "${r}" but no pools were defined.`);throw new Error(`The function "${e}" was declared as using a variable pool named "${r}" which is not among the variable pools defined. The defined variable pools are: ${function(t){if(0===t.length)throw new Error("Cannot format an empty list");if(1===t.length)return t[0];const e=t.map(t=>`"${t}"`),i=e.pop();return e.join(", ")+` and ${i}`}(i)}.`)}const l=s?h:m(h);if("string"==typeof l)throw new Error("Got passed unparsed code without setting the preParsed flag");if("SCRIPT"!==l.type)throw new Error("Invalid AST");if(0===l.body.length)return;const g=[],c={resolveVar:t=>/^reg\d\d$/.test(t)?a.get(null,t):a.get(r,t),resolveLocal:t=>(g.push(t),g.length-1),resolveFunc:t=>{const e=o.findIndex(e=>e.name===t);if(-1!==e){const s=v(e);return"rand"===t&&1===i?[...s,156]:s}if(null==et[t])return null;let s=A.indexOf(t);return-1===s&&(A.push(t),s=A.length-1),v(s+o.length)},rawSource:h},u=it(l,c);n.push({binary:u,exportName:e,args:[],returns:[],localVariables:g})});const l=A.map(t=>{const e=et[t];if(null==e)throw new Error(`Undefined local function "${t}"`);return e}),g=t=>[...t.args,"|",...t.returns].join("-"),c=[],d=new Map;function p(t){const e=g(t),i=d.get(e);if(null==i)throw new Error(`Failed to get a type index for key ${e}`);return i}[...o,...l,...n].forEach(t=>{const e=g(t);d.has(e)||(c.push([96,...W(t.args),...W(t.returns)]),d.set(e,c.length-1))});const E=[...r.map(([t,e])=>[...k(t),...k(e),3,V,1]),...o.map((t,e)=>{const i=p(t);return[...k("shims"),...k(t.name),0,...G(i)]})],_=[...l,...n].map(t=>G(p(t))),x=[[1,...G(H),...G(H)]],T=a.size()-r.length,w=(S=()=>[V,1,...L(0),b],new Array(T).fill(null).map((t,e)=>S()));var S;const P=[...n].map((t,e)=>{const i=e+o.length+l.length;return[...k(t.exportName),0,...G(i)]}),R=[...l,...n].map(t=>{var e;const i=(null!==(e=t.localVariables)&&void 0!==e?e:[]).map(t=>[...G(1),t]);return W([...Y(i),...t.binary,b])}),I=[...o.map(t=>t.name),...A,...n.map(t=>t.exportName)].map((t,e)=>[...G(e),...k(t)]),y=[1,...W(Y(I))];return new Uint8Array([...u,...f,...J(1,c),...J(2,E),...J(3,_),...J(5,x),...J(6,w),...J(7,P),...J(10,R),...K(0,"name",y)])}const ot="undefined"!=typeof BigUint64Array,ht=Symbol(),At=new TextDecoder("utf-16le");function nt(t,e){const i=new Uint32Array(t)[e+-4>>>2]>>>1,s=new Uint16Array(t,e,i);return i<=32?String.fromCharCode.apply(String,s):At.decode(s)}function lt(t){const e={};function i(t,e){return t?nt(t.buffer,e):""}const s=t.env=t.env||{};return s.abort=s.abort||function(t,r,a,o){const h=e.memory||s.memory;throw Error(`abort: ${i(h,t)} at ${i(h,r)}:${a}:${o}`)},s.trace=s.trace||function(t,r,...a){const o=e.memory||s.memory;console.log(`trace: ${i(o,t)}${r?" ":""}${a.slice(0,r).join(", ")}`)},s.seed=s.seed||Date.now,t.Math=t.Math||Math,t.Date=t.Date||Date,e}function gt(t,e){const i=e.exports,s=i.memory,r=i.table,a=i.__new,o=i.__retain,h=i.__rtti_base||-1;function A(t){const e=function(t){const e=new Uint32Array(s.buffer);if((t>>>=0)>=e[h>>>2])throw Error(`invalid id: ${t}`);return e[(h+4>>>2)+2*t]}(t);if(!(7&e))throw Error(`not an array: ${t}, flags=${e}`);return e}function n(t){const e=new Uint32Array(s.buffer);if((t>>>=0)>=e[h>>>2])throw Error(`invalid id: ${t}`);return e[(h+4>>>2)+2*t+1]}function l(t){return 31-Math.clz32(t>>>6&31)}function g(t,e,i){const r=s.buffer;if(i)switch(t){case 2:return new Float32Array(r);case 3:return new Float64Array(r)}else switch(t){case 0:return new(e?Int8Array:Uint8Array)(r);case 1:return new(e?Int16Array:Uint16Array)(r);case 2:return new(e?Int32Array:Uint32Array)(r);case 3:return new(e?BigInt64Array:BigUint64Array)(r)}throw Error(`unsupported align: ${t}`)}function c(t){const e=new Uint32Array(s.buffer),i=A(e[t+-8>>>2]),r=l(i);let a=4&i?t:e[t+4>>>2];const o=2&i?e[t+12>>>2]:e[a+-4>>>2]>>>r;return g(r,2048&i,4096&i).subarray(a>>>=r,a+o)}function m(t,e,i){return new t(u(t,e,i))}function u(t,e,i){const r=s.buffer,a=new Uint32Array(r),o=a[i+4>>>2];return new t(r,o,a[o+-4>>>2]>>>e)}function f(e,i,s){t[`__get${i}`]=m.bind(null,e,s),t[`__get${i}View`]=u.bind(null,e,s)}return t.__newString=function(t){if(null==t)return 0;const e=t.length,i=a(e<<1,1),r=new Uint16Array(s.buffer);for(var o=0,h=i>>>1;o>>2])throw Error(`not a string: ${t}`);return nt(e,t)},t.__newArray=function(t,e){const i=A(t),r=l(i),h=e.length,n=a(h<>>2]=o(n),A[e+4>>>2]=n,A[e+8>>>2]=h<>>2]=h),c=e}const m=g(r,2048&i,4096&i);if(16384&i)for(let t=0;t>>r)+t]=o(e[t]);else m.set(e,n>>>r);return c},t.__getArrayView=c,t.__getArray=function(t){const e=c(t),i=e.length,s=new Array(i);for(let t=0;t>>2];return e.slice(t,t+i)},[Int8Array,Uint8Array,Uint8ClampedArray,Int16Array,Uint16Array,Int32Array,Uint32Array,Float32Array,Float64Array].forEach(t=>{f(t,t.name,31-Math.clz32(t.BYTES_PER_ELEMENT))}),ot&&[BigUint64Array,BigInt64Array].forEach(t=>{f(t,t.name.slice(3),3)}),t.__instanceof=function(t,e){const i=new Uint32Array(s.buffer);let r=i[t+-8>>>2];if(r<=i[h>>>2])do{if(r==e)return!0;r=n(r)}while(r);return!1},t.memory=t.memory||s,t.table=t.table||r,dt(i,t)}function ct(t){return"undefined"!=typeof Response&&t instanceof Response}function mt(t){return t instanceof WebAssembly.Module}async function ut(t,e={}){if(ct(t=await t))return ft(t,e);const i=mt(t)?t:await WebAssembly.compile(t),s=lt(e),r=await WebAssembly.instantiate(i,e);return{module:i,instance:r,exports:gt(s,r)}}async function ft(t,e={}){if(!WebAssembly.instantiateStreaming)return ut(ct(t=await t)?t.arrayBuffer():t,e);const i=lt(e),s=await WebAssembly.instantiateStreaming(t,e),r=gt(i,s.instance);return{...s,exports:r}}function dt(t,e={}){const i=t.__argumentsLength?e=>{t.__argumentsLength.value=e}:t.__setArgumentsLength||t.__setargc||(()=>{});for(let s in t){if(!Object.prototype.hasOwnProperty.call(t,s))continue;const r=t[s];let a=s.split("."),o=e;for(;a.length>1;){let t=a.shift();Object.prototype.hasOwnProperty.call(o,t)||(o[t]={}),o=o[t]}let h=a[0],A=h.indexOf("#");if(A>=0){const e=h.substring(0,A),a=o[e];if(void 0===a||!a.prototype){const t=function(...e){return t.wrap(t.prototype.constructor(0,...e))};t.prototype={valueOf(){return this[ht]}},t.wrap=function(e){return Object.create(t.prototype,{[ht]:{value:e,writable:!1}})},a&&Object.getOwnPropertyNames(a).forEach(e=>Object.defineProperty(t,e,Object.getOwnPropertyDescriptor(a,e))),o[e]=t}if(h=h.substring(A+1),o=o[e].prototype,/^(get|set):/.test(h)){if(!Object.prototype.hasOwnProperty.call(o,h=h.substring(4))){let e=t[s.replace("set:","get:")],i=t[s.replace("get:","set:")];Object.defineProperty(o,h,{get(){return e(this[ht])},set(t){i(this[ht],t)},enumerable:!0})}}else"constructor"===h?(o[h]=(...t)=>(i(t.length),r(...t))).original=r:(o[h]=function(...t){return i(t.length),r(this[ht],...t)}).original=r}else/^(get|set):/.test(h)?Object.prototype.hasOwnProperty.call(o,h=h.substring(4))||Object.defineProperty(o,h,{get:t[s.replace("set:","get:")],set:t[s.replace("get:","set:")],enumerable:!0}):"function"==typeof r&&r!==i?(o[h]=(...t)=>(i(t.length),r(...t))).original=r:o[h]=r}return e}var pt={instantiate:ut,instantiateSync:function(t,e={}){const i=mt(t)?t:new WebAssembly.Module(t),s=lt(e),r=new WebAssembly.Instance(i,e);return{module:i,instance:r,exports:gt(s,r)}},instantiateStreaming:ft,demangle:dt};class Et{constructor(t,e,i=!1){this.samplesIn=t,this.samplesOut=e,this.equalize=i,this.NFREQ=2*e,this.equalize&&this.initEqualizeTable(),this.initBitRevTable(),this.initCosSinTable()}initEqualizeTable(){this.equalizeArr=new Float32Array(this.samplesOut);const t=1/this.samplesOut;for(let e=0;ee){const i=this.bitrevtable[e];this.bitrevtable[e]=this.bitrevtable[t],this.bitrevtable[t]=i}let i=this.NFREQ>>1;for(;i>=1&&t>=i;)t-=i,i>>=1;t+=i}}initCosSinTable(){let t=2,e=0;for(;t<=this.NFREQ;)e+=1,t<<=1;this.cossintable=[new Float32Array(e),new Float32Array(e)],t=2;let i=0;for(;t<=this.NFREQ;){const e=-2*Math.PI/t;this.cossintable[0][i]=Math.cos(e),this.cossintable[1][i]=Math.sin(e),i+=1,t<<=1}}timeToFrequencyDomain(t){const e=new Float32Array(this.NFREQ),i=new Float32Array(this.NFREQ);for(let s=0;s>1;for(let r=0;r0){let i=t;!bt.isFiniteNumber(i)||i<15?i=15:i>144&&(i=144),this.imm.fill(0);for(let t=0;t<3;t++)for(let e=this.starts[t];ethis.avg[t]?.2:.5,s=bt.adjustRateToFPS(s,30,i),this.avg[t]=this.avg[t]*s+this.imm[t]*(1-s),s=e<50?.9:.992,s=bt.adjustRateToFPS(s,30,i),this.longAvg[t]=this.longAvg[t]*s+this.imm[t]*(1-s),this.longAvg[t]<.001?(this.val[t]=1,this.att[t]=1):(this.val[t]=this.imm[t]/this.longAvg[t],this.att[t]=this.avg[t]/this.longAvg[t])}}}}const xt={baseVals:{gammaadj:1.25,wave_g:.5,mv_x:12,warpscale:1,brighten:0,mv_y:9,wave_scale:1,echo_alpha:0,additivewave:0,sx:1,sy:1,warp:.01,red_blue:0,wave_mode:0,wave_brighten:0,wrap:0,zoomexp:1,fshader:0,wave_r:.5,echo_zoom:1,wave_smoothing:.75,warpanimspeed:1,wave_dots:0,wave_x:.5,wave_y:.5,zoom:1,solarize:0,modwavealphabyvolume:0,dx:0,cx:.5,dy:0,darken_center:0,cy:.5,invert:0,bmotionvectorson:0,rot:0,modwavealphaend:.95,wave_mystery:-.2,decay:.9,wave_a:1,wave_b:.5,rating:5,modwavealphastart:.75,darken:0,echo_orient:0,ib_r:.5,ib_g:.5,ib_b:.5,ib_a:0,ib_size:0,ob_r:.5,ob_g:.5,ob_b:.5,ob_a:0,ob_size:0,mv_dx:0,mv_dy:0,mv_a:0,mv_r:.5,mv_g:.5,mv_b:.5,mv_l:0},init_eqs:function(){return{}},frame_eqs:function(t){return t.rkeys=["warp"],t.zoom=1.01+.02*t.treb_att,t.warp=.15+.25*t.bass_att,t},pixel_eqs:function(t){return t.warp=t.warp+.15*t.rad,t},waves:[{baseVals:{a:1,enabled:0,b:1,g:1,scaling:1,samples:512,additive:0,usedots:0,spectrum:0,r:1,smoothing:.5,thick:0,sep:0},init_eqs:function(t){return t.rkeys=[],t},frame_eqs:function(t){return t},point_eqs:""},{baseVals:{a:1,enabled:0,b:1,g:1,scaling:1,samples:512,additive:0,usedots:0,spectrum:0,r:1,smoothing:.5,thick:0,sep:0},init_eqs:function(t){return t.rkeys=[],t},frame_eqs:function(t){return t},point_eqs:""},{baseVals:{a:1,enabled:0,b:1,g:1,scaling:1,samples:512,additive:0,usedots:0,spectrum:0,r:1,smoothing:.5,thick:0,sep:0},init_eqs:function(t){return t.rkeys=[],t},frame_eqs:function(t){return t},point_eqs:""},{baseVals:{a:1,enabled:0,b:1,g:1,scaling:1,samples:512,additive:0,usedots:0,spectrum:0,r:1,smoothing:.5,thick:0,sep:0},init_eqs:function(t){return t.rkeys=[],t},frame_eqs:function(t){return t},point_eqs:""}],shapes:[{baseVals:{r2:0,a:1,enabled:0,b:0,tex_ang:0,thickoutline:0,g:0,textured:0,g2:1,tex_zoom:1,additive:0,border_a:.1,border_b:1,b2:0,a2:0,r:1,border_g:1,rad:.1,x:.5,y:.5,ang:0,sides:4,border_r:1},init_eqs:function(t){return t.rkeys=[],t},frame_eqs:function(t){return t}},{baseVals:{r2:0,a:1,enabled:0,b:0,tex_ang:0,thickoutline:0,g:0,textured:0,g2:1,tex_zoom:1,additive:0,border_a:.1,border_b:1,b2:0,a2:0,r:1,border_g:1,rad:.1,x:.5,y:.5,ang:0,sides:4,border_r:1},init_eqs:function(t){return t.rkeys=[],t},frame_eqs:function(t){return t}},{baseVals:{r2:0,a:1,enabled:0,b:0,tex_ang:0,thickoutline:0,g:0,textured:0,g2:1,tex_zoom:1,additive:0,border_a:.1,border_b:1,b2:0,a2:0,r:1,border_g:1,rad:.1,x:.5,y:.5,ang:0,sides:4,border_r:1},init_eqs:function(t){return t.rkeys=[],t},frame_eqs:function(t){return t}},{baseVals:{r2:0,a:1,enabled:0,b:0,tex_ang:0,thickoutline:0,g:0,textured:0,g2:1,tex_zoom:1,additive:0,border_a:.1,border_b:1,b2:0,a2:0,r:1,border_g:1,rad:.1,x:.5,y:.5,ang:0,sides:4,border_r:1},init_eqs:function(t){return t.rkeys=[],t},frame_eqs:function(t){return t}}],warp:"shader_body {\nret = texture2D(sampler_main, uv).rgb;\nret -= 0.004;\n}\n",comp:"shader_body {\nret = texture2D(sampler_main, uv).rgb;\nret *= hue_shader;\n}\n"};class vt{static atan2(t,e){let i=Math.atan2(t,e);return i<0&&(i+=2*Math.PI),i}static cloneVars(t){return Object.assign({},t)}static range(t,e){return void 0===e?[...Array(t).keys()]:Array.from({length:e-t},(e,i)=>i+t)}static pick(t,e){const i={};for(let s=0;s>>8,this.state[0]=t^e^e>>>19,(this.state[0]>>>0)/4294967296}nextInt(t){return Math.floor(this.next()*t)}rand(t){return t<1?this.next():Math.floor(this.next()*Math.floor(t))}reset(t){Tt.initializeState(this.state,t),this.warmUp()}}function wt(){return{random:Math.random,rand:t=>t<1?Math.random():Math.random()*Math.floor(t),randint:t=>Math.floor((t<1?Math.random():Math.random()*Math.floor(t))+1),getRNG:()=>null,reset:()=>{}}}let St=null;function Pt(t={}){return St=t.deterministic||t.testMode?function(t=1){const e=new Tt(t);return{random:()=>e.next(),rand:t=>e.rand(t),randint:t=>Math.floor(e.rand(t)+1),getRNG:()=>e,reset:i=>{void 0!==i?e.reset(i):e.reset(t)}}}(t.seed||12345):wt(),(t.deterministic||t.testMode)&&(window.rand=t=>St.rand(t),window.randint=t=>St.randint(t),Math.random=()=>St.random()),St}function Rt(){return St||(St=wt()),St}class It{constructor(t,e,i){this.rng=Rt(),this.preset=t,this.texsizeX=i.texsizeX,this.texsizeY=i.texsizeY,this.mesh_width=i.mesh_width,this.mesh_height=i.mesh_height,this.aspectx=i.aspectx,this.aspecty=i.aspecty,this.invAspectx=1/this.aspectx,this.invAspecty=1/this.aspecty,this.qs=vt.range(1,33).map(t=>`q${t}`),this.ts=vt.range(1,9).map(t=>`t${t}`),this.regs=vt.range(100).map(t=>t<10?`reg0${t}`:`reg${t}`),this.initializeEquations(e)}initializeEquations(t){this.runVertEQs=""!==this.preset.pixel_eqs,this.mdVSQInit=null,this.mdVSRegs=null,this.mdVSFrame=null,this.mdVSUserKeys=null,this.mdVSFrameMap=null,this.mdVSShapes=null,this.mdVSUserKeysShapes=null,this.mdVSFrameMapShapes=null,this.mdVSWaves=null,this.mdVSUserKeysWaves=null,this.mdVSFrameMapWaves=null,this.mdVSQAfterFrame=null,this.gmegabuf=new Array(1048576).fill(0);const e={frame:t.frame,time:t.time,fps:t.fps,bass:t.bass,bass_att:t.bass_att,mid:t.mid,mid_att:t.mid_att,treb:t.treb,treb_att:t.treb_att,meshx:this.mesh_width,meshy:this.mesh_height,aspectx:this.invAspectx,aspecty:this.invAspecty,pixelsx:this.texsizeX,pixelsy:this.texsizeY,gmegabuf:this.gmegabuf};this.mdVS=Object.assign({},this.preset.baseVals,e),this.mdVS.megabuf=new Array(1048576).fill(0),this.mdVS.rand_start=new Float32Array([this.rng.random(),this.rng.random(),this.rng.random(),this.rng.random()]),this.mdVS.rand_preset=new Float32Array([this.rng.random(),this.rng.random(),this.rng.random(),this.rng.random()]);const i=this.qs.concat(this.regs,Object.keys(this.mdVS)),s=this.preset.init_eqs(vt.cloneVars(this.mdVS));this.mdVSQInit=vt.pick(s,this.qs),this.mdVSRegs=vt.pick(s,this.regs);const r=vt.pick(s,Object.keys(vt.omit(s,i)));if(r.megabuf=s.megabuf,r.gmegabuf=s.gmegabuf,this.mdVSFrame=this.preset.frame_eqs(Object.assign({},this.mdVS,this.mdVSQInit,this.mdVSRegs,r)),this.mdVSUserKeys=Object.keys(vt.omit(this.mdVSFrame,i)),this.mdVSFrameMap=vt.pick(this.mdVSFrame,this.mdVSUserKeys),this.mdVSQAfterFrame=vt.pick(this.mdVSFrame,this.qs),this.mdVSRegs=vt.pick(this.mdVSFrame,this.regs),this.mdVSWaves=[],this.mdVSTWaveInits=[],this.mdVSUserKeysWaves=[],this.mdVSFrameMapWaves=[],this.preset.waves&&this.preset.waves.length>0)for(let t=0;t0)for(let t=0;t`q${t}`),this.ts=vt.range(1,9).map(t=>`t${t}`),this.regs=vt.range(100).map(t=>t<10?`reg0${t}`:`reg${t}`),this.globalKeys=["frame","time","fps","bass","bass_att","mid","mid_att","treb","treb_att","meshx","meshy","aspectx","aspecty","pixelsx","pixelsy"],this.frameKeys=["decay","wave_a","wave_r","wave_g","wave_b","wave_x","wave_y","wave_scale","wave_smoothing","wave_mode","old_wave_mode","wave_mystery","ob_size","ob_r","ob_g","ob_b","ob_a","ib_size","ib_r","ib_g","ib_b","ib_a","mv_x","mv_y","mv_dx","mv_dy","mv_l","mv_r","mv_g","mv_b","mv_a","echo_zoom","echo_alpha","echo_orient","wave_dots","wave_thick","additivewave","wave_brighten","modwavealphabyvolume","modwavealphastart","modwavealphaend","darken_center","gammaadj","warp","warpanimspeed","warpscale","zoom","zoomexp","rot","cx","cy","dx","dy","sx","sy","fshader","wrap","invert","brighten","darken","solarize","bmotionvectorson","b1n","b2n","b3n","b1x","b2x","b3x","b1ed"],this.waveFrameKeys=["samples","sep","scaling","spectrum","smoothing","r","g","b","a"],this.waveFrameInputKeys=["samples","r","g","b","a"],this.initializeEquations(e)}getQVars(t){return vt.pickWasm(this.preset.globalPools[t],this.qs)}getTVars(t){return vt.pickWasm(this.preset.globalPools[t],this.ts)}initializeEquations(t){this.runVertEQs=!!this.preset.pixel_eqs,this.mdVSQInit=null,this.mdVSQAfterFrame=null;const e={frame:t.frame,time:t.time,fps:t.fps,bass:t.bass,bass_att:t.bass_att,mid:t.mid,mid_att:t.mid_att,treb:t.treb,treb_att:t.treb_att,meshx:this.mesh_width,meshy:this.mesh_height,aspectx:this.invAspectx,aspecty:this.invAspecty,pixelsx:this.texsizeX,pixelsy:this.texsizeY};if(this.mdVS=Object.assign({},this.preset.baseVals,e),vt.setWasm(this.preset.globalPools.perFrame,this.mdVS,Object.keys(this.mdVS)),this.rand_start=new Float32Array([this.rng.random(),this.rng.random(),this.rng.random(),this.rng.random()]),this.rand_preset=new Float32Array([this.rng.random(),this.rng.random(),this.rng.random(),this.rng.random()]),this.preset.init_eqs(),this.mdVSQInit=this.getQVars("perFrame"),this.preset.frame_eqs(),this.mdVSQAfterFrame=this.getQVars("perFrame"),this.mdVSTWaveInits=[],this.preset.waves&&this.preset.waves.length>0)for(let t=0;t0)for(let t=0;t-1){const i=t.substring(0,e),s=t.substring(e),r=s.indexOf("{"),a=s.lastIndexOf("}");return[i,s.substring(r+1,a)]}return["",t]}static getFragmentFloatPrecision(t){return t.getShaderPrecisionFormat(t.FRAGMENT_SHADER,t.HIGH_FLOAT).precision>0?"highp":t.getShaderPrecisionFormat(t.FRAGMENT_SHADER,t.MEDIUM_FLOAT).precision>0?"mediump":"lowp"}static getUserSamplers(t){const e=[],i=t.match(Bt);if(i&&i.length>0)for(let t=0;t0){const t=s[1];e.push({sampler:t})}}return e}}class Ct{static smoothWave(t,e,i,s=!1){const r=-.15,a=1.15,o=1.15,h=-.15;let A,n=0,l=0,g=1;for(let c=0;c-.01&&a>.001&&i.length>0){const h=Ft.processWaveform(i,r),A=Ft.processWaveform(s,r),n=Math.floor(r.wave_mode)%8,l=Math.floor(r.old_wave_mode)%8,g=2*r.wave_x-1,c=2*r.wave_y-1;this.numVert=0,this.oldNumVert=0;const m=t&&n!==l?2:1;for(let t=0;t1)||(u=.5*u+.5,u-=Math.floor(u),u=Math.abs(u),u=2*u-1),0===t?(s=this.positions,m=this.positions2):(s=this.oldPositions,m=this.oldPositions2),a=r.wave_a,0===e){if(r.modwavealphabyvolume>0){const t=r.modwavealphaend-r.modwavealphastart;a*=(o-r.modwavealphastart)/t}a=Math.clamp(a,0,1),i=Math.floor(h.length/2)+1;const t=1/(i-1),e=Math.floor((h.length-i)/2);for(let a=0;a0){const t=r.modwavealphaend-r.modwavealphastart;a*=(o-r.modwavealphastart)/t}a=Math.clamp(a,0,1),i=Math.floor(h.length/2);for(let t=0;t=1024&&this.texsizeX<2048?a*=.11:a*=.13,r.modwavealphabyvolume>0){const t=r.modwavealphaend-r.modwavealphastart;a*=(o-r.modwavealphastart)/t}a=Math.clamp(a,0,1),i=h.length;for(let t=0;t=1024&&this.texsizeX<2048?a*=.22:a*=.33,a*=1.3,a*=r.treb*r.treb,r.modwavealphabyvolume>0){const t=r.modwavealphaend-r.modwavealphastart;a*=(o-r.modwavealphastart)/t}a=Math.clamp(a,0,1),i=h.length;for(let t=0;t0){const t=r.modwavealphaend-r.modwavealphastart;a*=(o-r.modwavealphastart)/t}a=Math.clamp(a,0,1),i=h.length,i>this.texsizeX/3&&(i=Math.floor(this.texsizeX/3));const t=1/i,e=Math.floor((h.length-i)/2),n=.45+.5*(.5*u+.5),l=1-n;for(let r=0;r1&&(i=i*l+n*(2*s[3*(r-1)+0]-s[3*(r-2)+0]),a=a*l+n*(2*s[3*(r-1)+1]-s[3*(r-2)+1])),s[3*r+0]=i,s[3*r+1]=a,s[3*r+2]=0}}else if(5===e){if(this.texsizeX<1024?a*=.09:this.texsizeX>=1024&&this.texsizeX<2048?a*=.11:a*=.13,r.modwavealphabyvolume>0){const t=r.modwavealphaend-r.modwavealphastart;a*=(o-r.modwavealphastart)/t}a=Math.clamp(a,0,1);const t=Math.cos(.3*r.time),e=Math.sin(.3*r.time);i=h.length;for(let i=0;i0){const t=r.modwavealphaend-r.modwavealphastart;a*=(o-r.modwavealphastart)/t}a=Math.clamp(a,0,1),i=Math.floor(h.length/2),i>this.texsizeX/3&&(i=Math.floor(this.texsizeX/3));const t=Math.floor((h.length-i)/2),n=.5*Math.PI*u;let l=Math.cos(n),f=Math.sin(n);const d=[g*Math.cos(n+.5*Math.PI)-3*l,g*Math.cos(n+.5*Math.PI)+3*l],p=[g*Math.sin(n+.5*Math.PI)-3*f,g*Math.sin(n+.5*Math.PI)+3*f];for(let t=0;t<2;t++)for(let e=0;e<4;e++){let i,s=!1;switch(e){case 0:d[t]>1.1&&(i=(1.1-d[1-t])/(d[t]-d[1-t]),s=!0);break;case 1:d[t]<-1.1&&(i=(-1.1-d[1-t])/(d[t]-d[1-t]),s=!0);break;case 2:p[t]>1.1&&(i=(1.1-p[1-t])/(p[t]-p[1-t]),s=!0);break;case 3:p[t]<-1.1&&(i=(-1.1-p[1-t])/(p[t]-p[1-t]),s=!0)}if(s){const e=d[t]-d[1-t],s=p[t]-p[1-t];d[t]=d[1-t]+e*i,p[t]=p[1-t]+s*i}}l=(d[1]-d[0])/i,f=(p[1]-p[0])/i;const E=Math.atan2(f,l),_=Math.cos(E+.5*Math.PI),b=Math.sin(E+.5*Math.PI);if(6===e)for(let e=0;e0&&(a=u*this.alpha+f*this.oldAlpha);let d=Math.clamp(r.wave_r,0,1),p=Math.clamp(r.wave_g,0,1),E=Math.clamp(r.wave_b,0,1);if(0!==r.wave_brighten){const t=Math.max(d,p,E);t>.01&&(d/=t,p/=t,E/=t)}if(this.color=[d,p,E,a],this.oldNumVert>0)if(7===n){const t=(this.oldNumVert-1)/(2*this.numVert);for(let e=0;e0){let A;if(a.preset.useWASM)A=a.runWaveFrameEquations(this.index,r);else{const t=Object.assign({},a.mdVSWaves[this.index],a.mdVSFrameMapWaves[this.index],a.mdVSQAfterFrame,a.mdVSTWaveInits[this.index],r);A=a.runWaveFrameEquations(this.index,t)}const n=512;Object.prototype.hasOwnProperty.call(A,"samples")?this.samples=A.samples:this.samples=n,this.samples>n&&(this.samples=n),this.samples=Math.floor(this.samples);const l=a.preset.waves[this.index].baseVals,g=Math.floor(A.sep),c=A.scaling,m=A.spectrum,u=A.smoothing,f=l.usedots,d=A.r,p=A.g,E=A.b,_=A.a,b=a.preset.baseVals.wave_scale;if(this.samples-=g,this.samples>=2||0!==f&&this.samples>=1){const r=0!==m,x=(r?.15:.004)*c*b,v=r?i:t,T=r?s:e,w=r?0:Math.floor((n-this.samples)/2-g/2),S=r?0:Math.floor((n-this.samples)/2+g/2),P=r?(n-g)/this.samples:1,R=(.98*u)**.5,I=1-R;this.pointsData[0][0]=v[w],this.pointsData[1][0]=T[S];for(let t=1;t=0;t--)this.pointsData[0][t]=this.pointsData[0][t]*I+this.pointsData[0][t+1]*R,this.pointsData[1][t]=this.pointsData[1][t]*I+this.pointsData[1][t+1]*R;for(let t=0;t=1024?1:0)):this.gl.uniform1f(this.sizeLoc,1+(this.texsizeX>=1024?1:0)):(this.gl.uniform1f(this.sizeLoc,1),e&&(o=4)),i?this.gl.blendFunc(this.gl.SRC_ALPHA,this.gl.ONE):this.gl.blendFunc(this.gl.SRC_ALPHA,this.gl.ONE_MINUS_SRC_ALPHA);const h=t?this.gl.POINTS:this.gl.LINE_STRIP;for(let t=0;t0,I=Math.abs(T)>=1,y=Math.abs(v)>=1,B=Math.abs(P)>=1;this.positions[0]=A,this.positions[1]=n,this.positions[2]=0,this.colors[0]=l,this.colors[1]=g,this.colors[2]=c,this.colors[3]=m*t,I&&(this.uvs[0]=.5,this.uvs[1]=.5);const L=.25*Math.PI;for(let e=1;e<=s+1;e++){const i=2*((e-1)/s)*Math.PI,r=i+h+L;if(this.positions[3*e+0]=A+o*Math.cos(r)*this.aspecty,this.positions[3*e+1]=n+o*Math.sin(r),this.positions[3*e+2]=0,this.colors[4*e+0]=u,this.colors[4*e+1]=f,this.colors[4*e+2]=d,this.colors[4*e+3]=p*t,I){const t=i+S+L;this.uvs[2*e+0]=.5+.5*Math.cos(t)/w*this.aspecty,this.uvs[2*e+1]=.5+.5*Math.sin(t)/w}R&&(this.borderPositions[3*(e-1)+0]=this.positions[3*e+0],this.borderPositions[3*(e-1)+1]=this.positions[3*e+1],this.borderPositions[3*(e-1)+2]=this.positions[3*e+2])}this.drawCustomShapeInstance(r,s,I,R,y,B)}}else{this.setupShapeBuffers(i.mdVSFrame.wrap);let s=Object.assign({},i.mdVSShapes[this.index],i.mdVSFrameMapShapes[this.index],e);""===i.preset.shapes[this.index].frame_eqs_str&&(s=Object.assign(s,i.mdVSQAfterFrame,i.mdVSTShapeInits[this.index]));const a=i.preset.shapes[this.index].baseVals,o=Math.clamp(a.num_inst,1,1024);for(let e=0;e0,B=Math.abs(S)>=1,L=Math.abs(w)>=1,U=Math.abs(I)>=1;this.positions[0]=l,this.positions[1]=g,this.positions[2]=0,this.colors[0]=c,this.colors[1]=m,this.colors[2]=u,this.colors[3]=f*t,B&&(this.uvs[0]=.5,this.uvs[1]=.5);const C=.25*Math.PI;for(let e=1;e<=h+1;e++){const i=2*((e-1)/h)*Math.PI,s=i+n+C;if(this.positions[3*e+0]=l+A*Math.cos(s)*this.aspecty,this.positions[3*e+1]=g+A*Math.sin(s),this.positions[3*e+2]=0,this.colors[4*e+0]=d,this.colors[4*e+1]=p,this.colors[4*e+2]=E,this.colors[4*e+3]=_*t,B){const t=i+R+C;this.uvs[2*e+0]=.5+.5*Math.cos(t)/P*this.aspecty,this.uvs[2*e+1]=.5+.5*Math.sin(t)/P}y&&(this.borderPositions[3*(e-1)+0]=this.positions[3*e+0],this.borderPositions[3*(e-1)+1]=this.positions[3*e+1],this.borderPositions[3*(e-1)+2]=this.positions[3*e+2])}this.mdVSShapeFrame=o,this.drawCustomShapeInstance(r,h,B,y,L,U)}const h=i.mdVSUserKeysShapes[this.index],A=vt.pick(this.mdVSShapeFrame,h);i.mdVSFrameMapShapes[this.index]=A}}setupShapeBuffers(t){this.gl.bindBuffer(this.gl.ARRAY_BUFFER,this.positionVertexBuf),this.gl.bufferData(this.gl.ARRAY_BUFFER,this.positions,this.gl.DYNAMIC_DRAW),this.gl.vertexAttribPointer(this.aPosLocation,3,this.gl.FLOAT,!1,0,0),this.gl.enableVertexAttribArray(this.aPosLocation),this.gl.bindBuffer(this.gl.ARRAY_BUFFER,this.colorVertexBuf),this.gl.bufferData(this.gl.ARRAY_BUFFER,this.colors,this.gl.DYNAMIC_DRAW),this.gl.vertexAttribPointer(this.aColorLocation,4,this.gl.FLOAT,!1,0,0),this.gl.enableVertexAttribArray(this.aColorLocation),this.gl.bindBuffer(this.gl.ARRAY_BUFFER,this.uvVertexBuf),this.gl.bufferData(this.gl.ARRAY_BUFFER,this.uvs,this.gl.DYNAMIC_DRAW),this.gl.vertexAttribPointer(this.aUvLocation,2,this.gl.FLOAT,!1,0,0),this.gl.enableVertexAttribArray(this.aUvLocation),this.gl.bindBuffer(this.gl.ARRAY_BUFFER,this.borderPositionVertexBuf),this.gl.bufferData(this.gl.ARRAY_BUFFER,this.borderPositions,this.gl.DYNAMIC_DRAW),this.gl.vertexAttribPointer(this.aBorderPosLoc,3,this.gl.FLOAT,!1,0,0),this.gl.enableVertexAttribArray(this.aBorderPosLoc);const e=0!==t?this.gl.REPEAT:this.gl.CLAMP_TO_EDGE;this.gl.samplerParameteri(this.mainSampler,this.gl.TEXTURE_WRAP_S,e),this.gl.samplerParameteri(this.mainSampler,this.gl.TEXTURE_WRAP_T,e)}drawCustomShapeInstance(t,e,i,s,r,a){if(this.gl.useProgram(this.shaderProgram),this.gl.bindBuffer(this.gl.ARRAY_BUFFER,this.positionVertexBuf),this.gl.bufferSubData(this.gl.ARRAY_BUFFER,0,this.positions,0,3*(e+2)),this.gl.vertexAttribPointer(this.aPosLocation,3,this.gl.FLOAT,!1,0,0),this.gl.enableVertexAttribArray(this.aPosLocation),this.gl.bindBuffer(this.gl.ARRAY_BUFFER,this.colorVertexBuf),this.gl.bufferSubData(this.gl.ARRAY_BUFFER,0,this.colors,0,4*(e+2)),this.gl.vertexAttribPointer(this.aColorLocation,4,this.gl.FLOAT,!1,0,0),this.gl.enableVertexAttribArray(this.aColorLocation),i&&(this.gl.bindBuffer(this.gl.ARRAY_BUFFER,this.uvVertexBuf),this.gl.bufferSubData(this.gl.ARRAY_BUFFER,0,this.uvs,0,2*(e+2)),this.gl.vertexAttribPointer(this.aUvLocation,2,this.gl.FLOAT,!1,0,0),this.gl.enableVertexAttribArray(this.aUvLocation)),this.gl.uniform1f(this.texturedLoc,i?1:0),this.gl.activeTexture(this.gl.TEXTURE0),this.gl.bindTexture(this.gl.TEXTURE_2D,t),this.gl.bindSampler(0,this.mainSampler),this.gl.uniform1i(this.textureLoc,0),a?this.gl.blendFunc(this.gl.SRC_ALPHA,this.gl.ONE):this.gl.blendFunc(this.gl.SRC_ALPHA,this.gl.ONE_MINUS_SRC_ALPHA),this.gl.drawArrays(this.gl.TRIANGLE_FAN,0,e+2),s){this.gl.useProgram(this.borderShaderProgram),this.gl.bindBuffer(this.gl.ARRAY_BUFFER,this.borderPositionVertexBuf),this.gl.bufferSubData(this.gl.ARRAY_BUFFER,0,this.borderPositions,0,3*(e+1)),this.gl.vertexAttribPointer(this.aBorderPosLoc,3,this.gl.FLOAT,!1,0,0),this.gl.enableVertexAttribArray(this.aBorderPosLoc),this.gl.uniform4fv(this.uBorderColorLoc,this.borderColor);const t=r?4:1;for(let i=0;i0&&t[3]>0){const t=2,s=2,r=t/2,a=s/2,o=i/2,h=e/2+o,A=o*t,n=o*s,l=h*t,g=h*s;let c=[-r+A,-a+g,0],m=[-r+A,a-g,0],u=[-r+l,a-g,0],f=[-r+l,-a+g,0];return this.addTriangle(0,f,m,c),this.addTriangle(9,f,u,m),c=[r-A,-a+g,0],m=[r-A,a-g,0],u=[r-l,a-g,0],f=[r-l,-a+g,0],this.addTriangle(18,c,m,f),this.addTriangle(27,m,u,f),c=[-r+A,-a+n,0],m=[-r+A,g-a,0],u=[r-A,g-a,0],f=[r-A,-a+n,0],this.addTriangle(36,f,m,c),this.addTriangle(45,f,u,m),c=[-r+A,a-n,0],m=[-r+A,a-g,0],u=[r-A,a-g,0],f=[r-A,a-n,0],this.addTriangle(54,c,m,f),this.addTriangle(63,m,u,f),!0}return!1}drawBorder(t,e,i){this.generateBorder(t,e,i)&&(this.gl.useProgram(this.shaderProgram),this.gl.bindBuffer(this.gl.ARRAY_BUFFER,this.vertexBuf),this.gl.bufferData(this.gl.ARRAY_BUFFER,this.positions,this.gl.STATIC_DRAW),this.gl.vertexAttribPointer(this.aPosLoc,3,this.gl.FLOAT,!1,0,0),this.gl.enableVertexAttribArray(this.aPosLoc),this.gl.uniform4fv(this.colorLoc,t),this.gl.blendFunc(this.gl.SRC_ALPHA,this.gl.ONE_MINUS_SRC_ALPHA),this.gl.drawArrays(this.gl.TRIANGLES,0,this.positions.length/3))}}class Vt{constructor(t,e){this.gl=t,this.aspectx=e.aspectx,this.aspecty=e.aspecty,this.invAspectx=1/this.aspectx,this.invAspecty=1/this.aspecty,this.generatePositions(),this.colors=new Float32Array([0,0,0,3/32,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]),this.positionVertexBuf=this.gl.createBuffer(),this.colorVertexBuf=this.gl.createBuffer(),this.floatPrecision=Ut.getFragmentFloatPrecision(this.gl),this.createShader()}updateGlobals(t){this.aspectx=t.aspectx,this.aspecty=t.aspecty,this.invAspectx=1/this.aspectx,this.invAspecty=1/this.aspecty,this.generatePositions()}generatePositions(){const t=.05;this.positions=new Float32Array([0,0,0,-.05*this.aspecty,0,0,0,-.05,0,t*this.aspecty,0,0,0,t,0,-.05*this.aspecty,0,0])}createShader(){this.shaderProgram=this.gl.createProgram();const t=this.gl.createShader(this.gl.VERTEX_SHADER);this.gl.shaderSource(t,"\n #version 300 es\n in vec3 aPos;\n in vec4 aColor;\n out vec4 vColor;\n void main(void) {\n vColor = aColor;\n gl_Position = vec4(aPos, 1.0);\n }\n ".trim()),this.gl.compileShader(t);const e=this.gl.createShader(this.gl.FRAGMENT_SHADER);this.gl.shaderSource(e,`\n #version 300 es\n precision ${this.floatPrecision} float;\n precision highp int;\n precision mediump sampler2D;\n in vec4 vColor;\n out vec4 fragColor;\n void main(void) {\n fragColor = vColor;\n }\n `.trim()),this.gl.compileShader(e),this.gl.attachShader(this.shaderProgram,t),this.gl.attachShader(this.shaderProgram,e),this.gl.linkProgram(this.shaderProgram),this.aPosLocation=this.gl.getAttribLocation(this.shaderProgram,"aPos"),this.aColorLocation=this.gl.getAttribLocation(this.shaderProgram,"aColor")}drawDarkenCenter(t){0!==t.darken_center&&(this.gl.useProgram(this.shaderProgram),this.gl.bindBuffer(this.gl.ARRAY_BUFFER,this.positionVertexBuf),this.gl.bufferData(this.gl.ARRAY_BUFFER,this.positions,this.gl.STATIC_DRAW),this.gl.vertexAttribPointer(this.aPosLocation,3,this.gl.FLOAT,!1,0,0),this.gl.enableVertexAttribArray(this.aPosLocation),this.gl.bindBuffer(this.gl.ARRAY_BUFFER,this.colorVertexBuf),this.gl.bufferData(this.gl.ARRAY_BUFFER,this.colors,this.gl.STATIC_DRAW),this.gl.vertexAttribPointer(this.aColorLocation,4,this.gl.FLOAT,!1,0,0),this.gl.enableVertexAttribArray(this.aColorLocation),this.gl.blendFunc(this.gl.SRC_ALPHA,this.gl.ONE_MINUS_SRC_ALPHA),this.gl.drawArrays(this.gl.TRIANGLE_FAN,0,this.positions.length/3))}}class qt{constructor(t,e){this.gl=t,this.maxX=64,this.maxY=48,this.positions=new Float32Array(this.maxX*this.maxY*2*3),this.texsizeX=e.texsizeX,this.texsizeY=e.texsizeY,this.mesh_width=e.mesh_width,this.mesh_height=e.mesh_height,this.positionVertexBuf=this.gl.createBuffer(),this.floatPrecision=Ut.getFragmentFloatPrecision(this.gl),this.createShader()}updateGlobals(t){this.texsizeX=t.texsizeX,this.texsizeY=t.texsizeY,this.mesh_width=t.mesh_width,this.mesh_height=t.mesh_height}createShader(){this.shaderProgram=this.gl.createProgram();const t=this.gl.createShader(this.gl.VERTEX_SHADER);this.gl.shaderSource(t,"\n #version 300 es\n in vec3 aPos;\n void main(void) {\n gl_Position = vec4(aPos, 1.0);\n }\n ".trim()),this.gl.compileShader(t);const e=this.gl.createShader(this.gl.FRAGMENT_SHADER);this.gl.shaderSource(e,`\n #version 300 es\n precision ${this.floatPrecision} float;\n precision highp int;\n precision mediump sampler2D;\n out vec4 fragColor;\n uniform vec4 u_color;\n void main(void) {\n fragColor = u_color;\n }\n `.trim()),this.gl.compileShader(e),this.gl.attachShader(this.shaderProgram,t),this.gl.attachShader(this.shaderProgram,e),this.gl.linkProgram(this.shaderProgram),this.aPosLoc=this.gl.getAttribLocation(this.shaderProgram,"aPos"),this.colorLoc=this.gl.getUniformLocation(this.shaderProgram,"u_color")}getMotionDir(t,e,i){const s=Math.floor(i*this.mesh_height),r=i*this.mesh_height-s,a=Math.floor(e*this.mesh_width),o=e*this.mesh_width-a,h=a+1,A=s+1,n=this.mesh_width+1;let l,g;return l=t[2*(s*n+a)+0]*(1-o)*(1-r),g=t[2*(s*n+a)+1]*(1-o)*(1-r),l+=t[2*(s*n+h)+0]*o*(1-r),g+=t[2*(s*n+h)+1]*o*(1-r),l+=t[2*(A*n+a)+0]*(1-o)*r,g+=t[2*(A*n+a)+1]*(1-o)*r,l+=t[2*(A*n+h)+0]*o*r,g+=t[2*(A*n+h)+1]*o*r,[l,1-g]}generateMotionVectors(t,e){const i=0===t.bmotionvectorson?0:t.mv_a;let s=Math.floor(t.mv_x),r=Math.floor(t.mv_y);if(i>.001&&s>0&&r>0){let a=t.mv_x-s,o=t.mv_y-r;s>this.maxX&&(s=this.maxX,a=0),r>this.maxY&&(r=this.maxY,o=0);const h=t.mv_dx,A=t.mv_dy,n=t.mv_l,l=1/this.texsizeX;this.numVecVerts=0;for(let t=0;t1e-4&&i<.9999)for(let t=0;t1e-4&&r<.9999){const t=this.getMotionDir(e,r,i);let s=t[0],a=t[1],o=s-r,h=a-i;o*=n,h*=n;let A=Math.sqrt(o*o+h*h);A1e-8?(A=l/A,o*=A,h*=A):(o=l,o=l),s=r+o,a=i+h;const g=2*r-1,c=2*i-1,m=2*s-1,u=2*a-1;this.positions[3*this.numVecVerts+0]=g,this.positions[3*this.numVecVerts+1]=c,this.positions[3*this.numVecVerts+2]=0,this.positions[3*(this.numVecVerts+1)+0]=m,this.positions[3*(this.numVecVerts+1)+1]=u,this.positions[3*(this.numVecVerts+1)+2]=0,this.numVecVerts+=2}}}if(this.numVecVerts>0)return this.color=[t.mv_r,t.mv_g,t.mv_b,i],!0}return!1}drawMotionVectors(t,e){this.generateMotionVectors(t,e)&&(this.gl.useProgram(this.shaderProgram),this.gl.bindBuffer(this.gl.ARRAY_BUFFER,this.positionVertexBuf),this.gl.bufferData(this.gl.ARRAY_BUFFER,this.positions,this.gl.STATIC_DRAW),this.gl.vertexAttribPointer(this.aPosLoc,3,this.gl.FLOAT,!1,0,0),this.gl.enableVertexAttribArray(this.aPosLoc),this.gl.uniform4fv(this.colorLoc,this.color),this.gl.lineWidth(1),this.gl.blendFunc(this.gl.SRC_ALPHA,this.gl.ONE_MINUS_SRC_ALPHA),this.gl.drawArrays(this.gl.LINES,0,this.numVecVerts))}}class zt{constructor(t,e,i,s={}){this.gl=t,this.noise=e,this.image=i,this.rng=Rt(),this.texsizeX=s.texsizeX,this.texsizeY=s.texsizeY,this.mesh_width=s.mesh_width,this.mesh_height=s.mesh_height,this.aspectx=s.aspectx,this.aspecty=s.aspecty,this.invAspectx=1/this.aspectx,this.invAspecty=1/this.aspecty,this.buildPositions(),this.indexBuf=t.createBuffer(),this.positionVertexBuf=this.gl.createBuffer(),this.warpUvVertexBuf=this.gl.createBuffer(),this.warpColorVertexBuf=this.gl.createBuffer(),this.floatPrecision=Ut.getFragmentFloatPrecision(this.gl),this.createShader(),this.mainSampler=this.gl.createSampler(),this.mainSamplerFW=this.gl.createSampler(),this.mainSamplerFC=this.gl.createSampler(),this.mainSamplerPW=this.gl.createSampler(),this.mainSamplerPC=this.gl.createSampler(),t.samplerParameteri(this.mainSampler,t.TEXTURE_MIN_FILTER,t.LINEAR_MIPMAP_LINEAR),t.samplerParameteri(this.mainSampler,t.TEXTURE_MAG_FILTER,t.LINEAR),t.samplerParameteri(this.mainSampler,t.TEXTURE_WRAP_S,t.REPEAT),t.samplerParameteri(this.mainSampler,t.TEXTURE_WRAP_T,t.REPEAT),t.samplerParameteri(this.mainSamplerFW,t.TEXTURE_MIN_FILTER,t.LINEAR_MIPMAP_LINEAR),t.samplerParameteri(this.mainSamplerFW,t.TEXTURE_MAG_FILTER,t.LINEAR),t.samplerParameteri(this.mainSamplerFW,t.TEXTURE_WRAP_S,t.REPEAT),t.samplerParameteri(this.mainSamplerFW,t.TEXTURE_WRAP_T,t.REPEAT),t.samplerParameteri(this.mainSamplerFC,t.TEXTURE_MIN_FILTER,t.LINEAR_MIPMAP_LINEAR),t.samplerParameteri(this.mainSamplerFC,t.TEXTURE_MAG_FILTER,t.LINEAR),t.samplerParameteri(this.mainSamplerFC,t.TEXTURE_WRAP_S,t.CLAMP_TO_EDGE),t.samplerParameteri(this.mainSamplerFC,t.TEXTURE_WRAP_T,t.CLAMP_TO_EDGE),t.samplerParameteri(this.mainSamplerPW,t.TEXTURE_MIN_FILTER,t.NEAREST_MIPMAP_NEAREST),t.samplerParameteri(this.mainSamplerPW,t.TEXTURE_MAG_FILTER,t.NEAREST),t.samplerParameteri(this.mainSamplerPW,t.TEXTURE_WRAP_S,t.REPEAT),t.samplerParameteri(this.mainSamplerPW,t.TEXTURE_WRAP_T,t.REPEAT),t.samplerParameteri(this.mainSamplerPC,t.TEXTURE_MIN_FILTER,t.NEAREST_MIPMAP_NEAREST),t.samplerParameteri(this.mainSamplerPC,t.TEXTURE_MAG_FILTER,t.NEAREST),t.samplerParameteri(this.mainSamplerPC,t.TEXTURE_WRAP_S,t.CLAMP_TO_EDGE),t.samplerParameteri(this.mainSamplerPC,t.TEXTURE_WRAP_T,t.CLAMP_TO_EDGE)}buildPositions(){const t=this.mesh_width,e=this.mesh_height,i=t+1,s=e+1,r=2/t,a=2/e,o=[];for(let t=0;t= 2.0) ? -1.0 : 1.0;\n vec2 uv_echo = ((uv - 0.5) *\n (1.0 / echo_zoom) *\n vec2(orient_x, orient_y)) + 0.5;\n\n ret = mix(texture(sampler_main, uv).rgb,\n texture(sampler_main, uv_echo).rgb,\n echo_alpha);\n\n ret *= gammaAdj;\n\n if(fShader >= 1.0) {\n ret *= hue_shader;\n } else if(fShader > 0.001) {\n ret *= (1.0 - fShader) + (fShader * hue_shader);\n }\n\n if(brighten != 0) ret = sqrt(ret);\n if(darken != 0) ret = ret*ret;\n if(solarize != 0) ret = ret * (1.0 - ret) * 4.0;\n if(invert != 0) ret = 1.0 - ret;",i="";else{const s=Ut.getShaderParts(t);i=s[0],e=s[1]}e=e.replace(/texture2D/g,"texture"),e=e.replace(/texture3D/g,"texture"),this.userTextures=Ut.getUserSamplers(i),this.shaderProgram=this.gl.createProgram();const s=this.gl.createShader(this.gl.VERTEX_SHADER);this.gl.shaderSource(s,"\n #version 300 es\n const vec2 halfmad = vec2(0.5);\n in vec2 aPos;\n in vec4 aCompColor;\n out vec2 vUv;\n out vec4 vColor;\n void main(void) {\n gl_Position = vec4(aPos, 0.0, 1.0);\n vUv = aPos * halfmad + halfmad;\n vColor = aCompColor;\n }\n ".trim()),this.gl.compileShader(s);const r=this.gl.createShader(this.gl.FRAGMENT_SHADER);this.gl.shaderSource(r,`\n #version 300 es\n precision ${this.floatPrecision} float;\n precision highp int;\n precision mediump sampler2D;\n precision mediump sampler3D;\n\n vec3 lum(vec3 v){\n return vec3(dot(v, vec3(0.32,0.49,0.29)));\n }\n\n in vec2 vUv;\n in vec4 vColor;\n out vec4 fragColor;\n uniform sampler2D sampler_main;\n uniform sampler2D sampler_fw_main;\n uniform sampler2D sampler_fc_main;\n uniform sampler2D sampler_pw_main;\n uniform sampler2D sampler_pc_main;\n uniform sampler2D sampler_blur1;\n uniform sampler2D sampler_blur2;\n uniform sampler2D sampler_blur3;\n uniform sampler2D sampler_noise_lq;\n uniform sampler2D sampler_noise_lq_lite;\n uniform sampler2D sampler_noise_mq;\n uniform sampler2D sampler_noise_hq;\n uniform sampler2D sampler_pw_noise_lq;\n uniform sampler3D sampler_noisevol_lq;\n uniform sampler3D sampler_noisevol_hq;\n\n uniform float time;\n uniform float gammaAdj;\n uniform float echo_zoom;\n uniform float echo_alpha;\n uniform float echo_orientation;\n uniform int invert;\n uniform int brighten;\n uniform int darken;\n uniform int solarize;\n uniform vec2 resolution;\n uniform vec4 aspect;\n uniform vec4 texsize;\n uniform vec4 texsize_noise_lq;\n uniform vec4 texsize_noise_mq;\n uniform vec4 texsize_noise_hq;\n uniform vec4 texsize_noise_lq_lite;\n uniform vec4 texsize_noisevol_lq;\n uniform vec4 texsize_noisevol_hq;\n\n uniform float bass;\n uniform float mid;\n uniform float treb;\n uniform float vol;\n uniform float bass_att;\n uniform float mid_att;\n uniform float treb_att;\n uniform float vol_att;\n\n uniform float frame;\n uniform float fps;\n\n uniform vec4 _qa;\n uniform vec4 _qb;\n uniform vec4 _qc;\n uniform vec4 _qd;\n uniform vec4 _qe;\n uniform vec4 _qf;\n uniform vec4 _qg;\n uniform vec4 _qh;\n\n #define q1 _qa.x\n #define q2 _qa.y\n #define q3 _qa.z\n #define q4 _qa.w\n #define q5 _qb.x\n #define q6 _qb.y\n #define q7 _qb.z\n #define q8 _qb.w\n #define q9 _qc.x\n #define q10 _qc.y\n #define q11 _qc.z\n #define q12 _qc.w\n #define q13 _qd.x\n #define q14 _qd.y\n #define q15 _qd.z\n #define q16 _qd.w\n #define q17 _qe.x\n #define q18 _qe.y\n #define q19 _qe.z\n #define q20 _qe.w\n #define q21 _qf.x\n #define q22 _qf.y\n #define q23 _qf.z\n #define q24 _qf.w\n #define q25 _qg.x\n #define q26 _qg.y\n #define q27 _qg.z\n #define q28 _qg.w\n #define q29 _qh.x\n #define q30 _qh.y\n #define q31 _qh.z\n #define q32 _qh.w\n\n uniform vec4 slow_roam_cos;\n uniform vec4 roam_cos;\n uniform vec4 slow_roam_sin;\n uniform vec4 roam_sin;\n\n uniform float blur1_min;\n uniform float blur1_max;\n uniform float blur2_min;\n uniform float blur2_max;\n uniform float blur3_min;\n uniform float blur3_max;\n\n uniform float scale1;\n uniform float scale2;\n uniform float scale3;\n uniform float bias1;\n uniform float bias2;\n uniform float bias3;\n\n uniform vec4 rand_frame;\n uniform vec4 rand_preset;\n\n uniform float fShader;\n\n float PI = ${Math.PI};\n\n ${i}\n\n void main(void) {\n vec3 ret;\n vec2 uv = vUv;\n vec2 uv_orig = vUv;\n uv.y = 1.0 - uv.y;\n uv_orig.y = 1.0 - uv_orig.y;\n float rad = length(uv - 0.5);\n float ang = atan(uv.x - 0.5, uv.y - 0.5);\n vec3 hue_shader = vColor.rgb;\n\n ${e}\n\n fragColor = vec4(ret, vColor.a);\n }\n `.trim()),this.gl.compileShader(r),this.gl.attachShader(this.shaderProgram,s),this.gl.attachShader(this.shaderProgram,r),this.gl.linkProgram(this.shaderProgram),this.positionLocation=this.gl.getAttribLocation(this.shaderProgram,"aPos"),this.compColorLocation=this.gl.getAttribLocation(this.shaderProgram,"aCompColor"),this.textureLoc=this.gl.getUniformLocation(this.shaderProgram,"sampler_main"),this.textureFWLoc=this.gl.getUniformLocation(this.shaderProgram,"sampler_fw_main"),this.textureFCLoc=this.gl.getUniformLocation(this.shaderProgram,"sampler_fc_main"),this.texturePWLoc=this.gl.getUniformLocation(this.shaderProgram,"sampler_pw_main"),this.texturePCLoc=this.gl.getUniformLocation(this.shaderProgram,"sampler_pc_main"),this.blurTexture1Loc=this.gl.getUniformLocation(this.shaderProgram,"sampler_blur1"),this.blurTexture2Loc=this.gl.getUniformLocation(this.shaderProgram,"sampler_blur2"),this.blurTexture3Loc=this.gl.getUniformLocation(this.shaderProgram,"sampler_blur3"),this.noiseLQLoc=this.gl.getUniformLocation(this.shaderProgram,"sampler_noise_lq"),this.noiseMQLoc=this.gl.getUniformLocation(this.shaderProgram,"sampler_noise_mq"),this.noiseHQLoc=this.gl.getUniformLocation(this.shaderProgram,"sampler_noise_hq"),this.noiseLQLiteLoc=this.gl.getUniformLocation(this.shaderProgram,"sampler_noise_lq_lite"),this.noisePointLQLoc=this.gl.getUniformLocation(this.shaderProgram,"sampler_pw_noise_lq"),this.noiseVolLQLoc=this.gl.getUniformLocation(this.shaderProgram,"sampler_noisevol_lq"),this.noiseVolHQLoc=this.gl.getUniformLocation(this.shaderProgram,"sampler_noisevol_hq"),this.timeLoc=this.gl.getUniformLocation(this.shaderProgram,"time"),this.gammaAdjLoc=this.gl.getUniformLocation(this.shaderProgram,"gammaAdj"),this.echoZoomLoc=this.gl.getUniformLocation(this.shaderProgram,"echo_zoom"),this.echoAlphaLoc=this.gl.getUniformLocation(this.shaderProgram,"echo_alpha"),this.echoOrientationLoc=this.gl.getUniformLocation(this.shaderProgram,"echo_orientation"),this.invertLoc=this.gl.getUniformLocation(this.shaderProgram,"invert"),this.brightenLoc=this.gl.getUniformLocation(this.shaderProgram,"brighten"),this.darkenLoc=this.gl.getUniformLocation(this.shaderProgram,"darken"),this.solarizeLoc=this.gl.getUniformLocation(this.shaderProgram,"solarize"),this.texsizeLoc=this.gl.getUniformLocation(this.shaderProgram,"texsize"),this.texsizeNoiseLQLoc=this.gl.getUniformLocation(this.shaderProgram,"texsize_noise_lq"),this.texsizeNoiseMQLoc=this.gl.getUniformLocation(this.shaderProgram,"texsize_noise_mq"),this.texsizeNoiseHQLoc=this.gl.getUniformLocation(this.shaderProgram,"texsize_noise_hq"),this.texsizeNoiseLQLiteLoc=this.gl.getUniformLocation(this.shaderProgram,"texsize_noise_lq_lite"),this.texsizeNoiseVolLQLoc=this.gl.getUniformLocation(this.shaderProgram,"texsize_noisevol_lq"),this.texsizeNoiseVolHQLoc=this.gl.getUniformLocation(this.shaderProgram,"texsize_noisevol_hq"),this.resolutionLoc=this.gl.getUniformLocation(this.shaderProgram,"resolution"),this.aspectLoc=this.gl.getUniformLocation(this.shaderProgram,"aspect"),this.bassLoc=this.gl.getUniformLocation(this.shaderProgram,"bass"),this.midLoc=this.gl.getUniformLocation(this.shaderProgram,"mid"),this.trebLoc=this.gl.getUniformLocation(this.shaderProgram,"treb"),this.volLoc=this.gl.getUniformLocation(this.shaderProgram,"vol"),this.bassAttLoc=this.gl.getUniformLocation(this.shaderProgram,"bass_att"),this.midAttLoc=this.gl.getUniformLocation(this.shaderProgram,"mid_att"),this.trebAttLoc=this.gl.getUniformLocation(this.shaderProgram,"treb_att"),this.volAttLoc=this.gl.getUniformLocation(this.shaderProgram,"vol_att"),this.frameLoc=this.gl.getUniformLocation(this.shaderProgram,"frame"),this.fpsLoc=this.gl.getUniformLocation(this.shaderProgram,"fps"),this.blur1MinLoc=this.gl.getUniformLocation(this.shaderProgram,"blur1_min"),this.blur1MaxLoc=this.gl.getUniformLocation(this.shaderProgram,"blur1_max"),this.blur2MinLoc=this.gl.getUniformLocation(this.shaderProgram,"blur2_min"),this.blur2MaxLoc=this.gl.getUniformLocation(this.shaderProgram,"blur2_max"),this.blur3MinLoc=this.gl.getUniformLocation(this.shaderProgram,"blur3_min"),this.blur3MaxLoc=this.gl.getUniformLocation(this.shaderProgram,"blur3_max"),this.scale1Loc=this.gl.getUniformLocation(this.shaderProgram,"scale1"),this.scale2Loc=this.gl.getUniformLocation(this.shaderProgram,"scale2"),this.scale3Loc=this.gl.getUniformLocation(this.shaderProgram,"scale3"),this.bias1Loc=this.gl.getUniformLocation(this.shaderProgram,"bias1"),this.bias2Loc=this.gl.getUniformLocation(this.shaderProgram,"bias2"),this.bias3Loc=this.gl.getUniformLocation(this.shaderProgram,"bias3"),this.randPresetLoc=this.gl.getUniformLocation(this.shaderProgram,"rand_preset"),this.randFrameLoc=this.gl.getUniformLocation(this.shaderProgram,"rand_frame"),this.fShaderLoc=this.gl.getUniformLocation(this.shaderProgram,"fShader"),this.qaLoc=this.gl.getUniformLocation(this.shaderProgram,"_qa"),this.qbLoc=this.gl.getUniformLocation(this.shaderProgram,"_qb"),this.qcLoc=this.gl.getUniformLocation(this.shaderProgram,"_qc"),this.qdLoc=this.gl.getUniformLocation(this.shaderProgram,"_qd"),this.qeLoc=this.gl.getUniformLocation(this.shaderProgram,"_qe"),this.qfLoc=this.gl.getUniformLocation(this.shaderProgram,"_qf"),this.qgLoc=this.gl.getUniformLocation(this.shaderProgram,"_qg"),this.qhLoc=this.gl.getUniformLocation(this.shaderProgram,"_qh"),this.slowRoamCosLoc=this.gl.getUniformLocation(this.shaderProgram,"slow_roam_cos"),this.roamCosLoc=this.gl.getUniformLocation(this.shaderProgram,"roam_cos"),this.slowRoamSinLoc=this.gl.getUniformLocation(this.shaderProgram,"slow_roam_sin"),this.roamSinLoc=this.gl.getUniformLocation(this.shaderProgram,"roam_sin");for(let t=0;t lumaMax))\n color = vec4(rgbA, 1.0);\n else\n color = vec4(rgbB, 1.0);\n\n fragColor = color;\n }`),this.gl.compileShader(e),this.gl.attachShader(this.shaderProgram,t),this.gl.attachShader(this.shaderProgram,e),this.gl.linkProgram(this.shaderProgram),this.positionLocation=this.gl.getAttribLocation(this.shaderProgram,"aPos"),this.textureLoc=this.gl.getUniformLocation(this.shaderProgram,"uTexture"),this.texsizeLoc=this.gl.getUniformLocation(this.shaderProgram,"texsize")}createShader(){this.shaderProgram=this.gl.createProgram();const t=this.gl.createShader(this.gl.VERTEX_SHADER);this.gl.shaderSource(t,"#version 300 es\n const vec2 halfmad = vec2(0.5);\n in vec2 aPos;\n out vec2 uv;\n void main(void) {\n gl_Position = vec4(aPos, 0.0, 1.0);\n uv = aPos * halfmad + halfmad;\n }"),this.gl.compileShader(t);const e=this.gl.createShader(this.gl.FRAGMENT_SHADER);this.gl.shaderSource(e,`#version 300 es\n precision ${this.floatPrecision} float;\n precision highp int;\n precision mediump sampler2D;\n\n in vec2 uv;\n out vec4 fragColor;\n uniform sampler2D uTexture;\n\n void main(void) {\n fragColor = vec4(texture(uTexture, uv).rgb, 1.0);\n }`),this.gl.compileShader(e),this.gl.attachShader(this.shaderProgram,t),this.gl.attachShader(this.shaderProgram,e),this.gl.linkProgram(this.shaderProgram),this.positionLocation=this.gl.getAttribLocation(this.shaderProgram,"aPos"),this.textureLoc=this.gl.getUniformLocation(this.shaderProgram,"uTexture")}renderQuadTexture(t){this.gl.useProgram(this.shaderProgram),this.gl.bindBuffer(this.gl.ARRAY_BUFFER,this.vertexBuf),this.gl.bufferData(this.gl.ARRAY_BUFFER,this.positions,this.gl.STATIC_DRAW),this.gl.vertexAttribPointer(this.positionLocation,2,this.gl.FLOAT,!1,0,0),this.gl.enableVertexAttribArray(this.positionLocation),this.gl.activeTexture(this.gl.TEXTURE0),this.gl.bindTexture(this.gl.TEXTURE_2D,t),this.gl.uniform1i(this.textureLoc,0),this.useFXAA()&&this.gl.uniform4fv(this.texsizeLoc,new Float32Array([this.texsizeX,this.texsizeY,1/this.texsizeX,1/this.texsizeY])),this.gl.blendFunc(this.gl.SRC_ALPHA,this.gl.ONE_MINUS_SRC_ALPHA),this.gl.drawArrays(this.gl.TRIANGLE_STRIP,0,4)}}class kt{constructor(t){this.gl=t,this.positions=new Float32Array([-1,-1,1,-1,-1,1,1,1]),this.vertexBuf=this.gl.createBuffer(),this.floatPrecision=Ut.getFragmentFloatPrecision(this.gl),this.createShader()}createShader(){this.shaderProgram=this.gl.createProgram();const t=this.gl.createShader(this.gl.VERTEX_SHADER);this.gl.shaderSource(t,"#version 300 es\n const vec2 halfmad = vec2(0.5);\n in vec2 aPos;\n out vec2 uv;\n void main(void) {\n gl_Position = vec4(aPos, 0.0, 1.0);\n uv = aPos * halfmad + halfmad;\n }"),this.gl.compileShader(t);const e=this.gl.createShader(this.gl.FRAGMENT_SHADER);this.gl.shaderSource(e,`#version 300 es\n precision ${this.floatPrecision} float;\n precision highp int;\n precision mediump sampler2D;\n\n in vec2 uv;\n out vec4 fragColor;\n uniform sampler2D uTexture;\n\n void main(void) {\n fragColor = vec4(texture(uTexture, uv).rgb, 1.0);\n }`),this.gl.compileShader(e),this.gl.attachShader(this.shaderProgram,t),this.gl.attachShader(this.shaderProgram,e),this.gl.linkProgram(this.shaderProgram),this.positionLocation=this.gl.getAttribLocation(this.shaderProgram,"aPos"),this.textureLoc=this.gl.getUniformLocation(this.shaderProgram,"uTexture")}renderQuadTexture(t){this.gl.useProgram(this.shaderProgram),this.gl.bindBuffer(this.gl.ARRAY_BUFFER,this.vertexBuf),this.gl.bufferData(this.gl.ARRAY_BUFFER,this.positions,this.gl.STATIC_DRAW),this.gl.vertexAttribPointer(this.positionLocation,2,this.gl.FLOAT,!1,0,0),this.gl.enableVertexAttribArray(this.positionLocation),this.gl.activeTexture(this.gl.TEXTURE0),this.gl.bindTexture(this.gl.TEXTURE_2D,t),this.gl.generateMipmap(this.gl.TEXTURE_2D),this.gl.uniform1i(this.textureLoc,0),this.gl.blendFunc(this.gl.SRC_ALPHA,this.gl.ONE_MINUS_SRC_ALPHA),this.gl.drawArrays(this.gl.TRIANGLE_STRIP,0,4)}}class Gt{constructor(t,e){this.gl=t,this.blurLevel=e;const i=[4,3.8,3.5,2.9,1.9,1.2,.7,.3],s=i[0]+i[1]+i[2]+i[3],r=i[4]+i[5]+i[6]+i[7],a=0+(i[2]+i[3])/s*2,o=2+(i[6]+i[7])/r*2;this.wds=new Float32Array([s,r,a,o]),this.wDiv=1/(2*(s+r)),this.positions=new Float32Array([-1,-1,1,-1,-1,1,1,1]),this.vertexBuf=this.gl.createBuffer(),this.floatPrecision=Ut.getFragmentFloatPrecision(this.gl),this.createShader()}createShader(){this.shaderProgram=this.gl.createProgram();const t=this.gl.createShader(this.gl.VERTEX_SHADER);this.gl.shaderSource(t,"\n #version 300 es\n const vec2 halfmad = vec2(0.5);\n in vec2 aPos;\n out vec2 uv;\n void main(void) {\n gl_Position = vec4(aPos, 0.0, 1.0);\n uv = aPos * halfmad + halfmad;\n }\n ".trim()),this.gl.compileShader(t);const e=this.gl.createShader(this.gl.FRAGMENT_SHADER);this.gl.shaderSource(e,`#version 300 es\n precision ${this.floatPrecision} float;\n precision highp int;\n precision mediump sampler2D;\n\n in vec2 uv;\n out vec4 fragColor;\n uniform sampler2D uTexture;\n uniform vec4 texsize;\n uniform float ed1;\n uniform float ed2;\n uniform float ed3;\n uniform vec4 wds;\n uniform float wdiv;\n\n void main(void) {\n float w1 = wds[0];\n float w2 = wds[1];\n float d1 = wds[2];\n float d2 = wds[3];\n\n vec2 uv2 = uv.xy;\n\n vec3 blur =\n ( texture(uTexture, uv2 + vec2(0.0, d1 * texsize.w) ).xyz\n + texture(uTexture, uv2 + vec2(0.0,-d1 * texsize.w) ).xyz) * w1 +\n ( texture(uTexture, uv2 + vec2(0.0, d2 * texsize.w) ).xyz\n + texture(uTexture, uv2 + vec2(0.0,-d2 * texsize.w) ).xyz) * w2;\n\n blur.xyz *= wdiv;\n\n float t = min(min(uv.x, uv.y), 1.0 - max(uv.x, uv.y));\n t = sqrt(t);\n t = ed1 + ed2 * clamp(t * ed3, 0.0, 1.0);\n blur.xyz *= t;\n\n fragColor = vec4(blur, 1.0);\n }`),this.gl.compileShader(e),this.gl.attachShader(this.shaderProgram,t),this.gl.attachShader(this.shaderProgram,e),this.gl.linkProgram(this.shaderProgram),this.positionLocation=this.gl.getAttribLocation(this.shaderProgram,"aPos"),this.textureLoc=this.gl.getUniformLocation(this.shaderProgram,"uTexture"),this.texsizeLocation=this.gl.getUniformLocation(this.shaderProgram,"texsize"),this.ed1Loc=this.gl.getUniformLocation(this.shaderProgram,"ed1"),this.ed2Loc=this.gl.getUniformLocation(this.shaderProgram,"ed2"),this.ed3Loc=this.gl.getUniformLocation(this.shaderProgram,"ed3"),this.wdsLocation=this.gl.getUniformLocation(this.shaderProgram,"wds"),this.wdivLoc=this.gl.getUniformLocation(this.shaderProgram,"wdiv")}renderQuadTexture(t,e,i){this.gl.useProgram(this.shaderProgram),this.gl.bindBuffer(this.gl.ARRAY_BUFFER,this.vertexBuf),this.gl.bufferData(this.gl.ARRAY_BUFFER,this.positions,this.gl.STATIC_DRAW),this.gl.vertexAttribPointer(this.positionLocation,2,this.gl.FLOAT,!1,0,0),this.gl.enableVertexAttribArray(this.positionLocation),this.gl.activeTexture(this.gl.TEXTURE0),this.gl.bindTexture(this.gl.TEXTURE_2D,t),this.gl.uniform1i(this.textureLoc,0);const s=0===this.blurLevel?e.b1ed:0;this.gl.uniform4fv(this.texsizeLocation,[i[0],i[1],1/i[0],1/i[1]]),this.gl.uniform1f(this.ed1Loc,1-s),this.gl.uniform1f(this.ed2Loc,s),this.gl.uniform1f(this.ed3Loc,5),this.gl.uniform4fv(this.wdsLocation,this.wds),this.gl.uniform1f(this.wdivLoc,this.wDiv),this.gl.blendFunc(this.gl.SRC_ALPHA,this.gl.ONE_MINUS_SRC_ALPHA),this.gl.drawArrays(this.gl.TRIANGLE_STRIP,0,4)}}class Ot{constructor(t,e){this.gl=t,this.blurLevel=e;const i=[4,3.8,3.5,2.9,1.9,1.2,.7,.3],s=i[0]+i[1],r=i[2]+i[3],a=i[4]+i[5],o=i[6]+i[7],h=0+2*i[1]/s,A=2+2*i[3]/r,n=4+2*i[5]/a,l=6+2*i[7]/o;this.ws=new Float32Array([s,r,a,o]),this.ds=new Float32Array([h,A,n,l]),this.wDiv=.5/(s+r+a+o),this.positions=new Float32Array([-1,-1,1,-1,-1,1,1,1]),this.vertexBuf=this.gl.createBuffer(),this.floatPrecision=Ut.getFragmentFloatPrecision(this.gl),this.createShader()}createShader(){this.shaderProgram=this.gl.createProgram();const t=this.gl.createShader(this.gl.VERTEX_SHADER);this.gl.shaderSource(t,"\n #version 300 es\n const vec2 halfmad = vec2(0.5);\n in vec2 aPos;\n out vec2 uv;\n void main(void) {\n gl_Position = vec4(aPos, 0.0, 1.0);\n uv = aPos * halfmad + halfmad;\n }\n ".trim()),this.gl.compileShader(t);const e=this.gl.createShader(this.gl.FRAGMENT_SHADER);this.gl.shaderSource(e,`#version 300 es\n precision ${this.floatPrecision} float;\n precision highp int;\n precision mediump sampler2D;\n\n in vec2 uv;\n out vec4 fragColor;\n uniform sampler2D uTexture;\n uniform vec4 texsize;\n uniform float scale;\n uniform float bias;\n uniform vec4 ws;\n uniform vec4 ds;\n uniform float wdiv;\n\n void main(void) {\n float w1 = ws[0];\n float w2 = ws[1];\n float w3 = ws[2];\n float w4 = ws[3];\n float d1 = ds[0];\n float d2 = ds[1];\n float d3 = ds[2];\n float d4 = ds[3];\n\n vec2 uv2 = uv.xy;\n\n vec3 blur =\n ( texture(uTexture, uv2 + vec2( d1 * texsize.z,0.0) ).xyz\n + texture(uTexture, uv2 + vec2(-d1 * texsize.z,0.0) ).xyz) * w1 +\n ( texture(uTexture, uv2 + vec2( d2 * texsize.z,0.0) ).xyz\n + texture(uTexture, uv2 + vec2(-d2 * texsize.z,0.0) ).xyz) * w2 +\n ( texture(uTexture, uv2 + vec2( d3 * texsize.z,0.0) ).xyz\n + texture(uTexture, uv2 + vec2(-d3 * texsize.z,0.0) ).xyz) * w3 +\n ( texture(uTexture, uv2 + vec2( d4 * texsize.z,0.0) ).xyz\n + texture(uTexture, uv2 + vec2(-d4 * texsize.z,0.0) ).xyz) * w4;\n\n blur.xyz *= wdiv;\n blur.xyz = blur.xyz * scale + bias;\n\n fragColor = vec4(blur, 1.0);\n }`),this.gl.compileShader(e),this.gl.attachShader(this.shaderProgram,t),this.gl.attachShader(this.shaderProgram,e),this.gl.linkProgram(this.shaderProgram),this.positionLocation=this.gl.getAttribLocation(this.shaderProgram,"aPos"),this.textureLoc=this.gl.getUniformLocation(this.shaderProgram,"uTexture"),this.texsizeLocation=this.gl.getUniformLocation(this.shaderProgram,"texsize"),this.scaleLoc=this.gl.getUniformLocation(this.shaderProgram,"scale"),this.biasLoc=this.gl.getUniformLocation(this.shaderProgram,"bias"),this.wsLoc=this.gl.getUniformLocation(this.shaderProgram,"ws"),this.dsLocation=this.gl.getUniformLocation(this.shaderProgram,"ds"),this.wdivLoc=this.gl.getUniformLocation(this.shaderProgram,"wdiv")}getScaleAndBias(t,e){const i=[1,1,1],s=[0,0,0];let r,a;return i[0]=1/(e[0]-t[0]),s[0]=-t[0]*i[0],r=(t[1]-t[0])/(e[0]-t[0]),a=(e[1]-t[0])/(e[0]-t[0]),i[1]=1/(a-r),s[1]=-r*i[1],r=(t[2]-t[1])/(e[1]-t[1]),a=(e[2]-t[1])/(e[1]-t[1]),i[2]=1/(a-r),s[2]=-r*i[2],{scale:i[this.blurLevel],bias:s[this.blurLevel]}}renderQuadTexture(t,e,i,s,r){this.gl.useProgram(this.shaderProgram),this.gl.bindBuffer(this.gl.ARRAY_BUFFER,this.vertexBuf),this.gl.bufferData(this.gl.ARRAY_BUFFER,this.positions,this.gl.STATIC_DRAW),this.gl.vertexAttribPointer(this.positionLocation,2,this.gl.FLOAT,!1,0,0),this.gl.enableVertexAttribArray(this.positionLocation),this.gl.activeTexture(this.gl.TEXTURE0),this.gl.bindTexture(this.gl.TEXTURE_2D,t),this.gl.uniform1i(this.textureLoc,0);const{scale:a,bias:o}=this.getScaleAndBias(i,s);this.gl.uniform4fv(this.texsizeLocation,[r[0],r[1],1/r[0],1/r[1]]),this.gl.uniform1f(this.scaleLoc,a),this.gl.uniform1f(this.biasLoc,o),this.gl.uniform4fv(this.wsLoc,this.ws),this.gl.uniform4fv(this.dsLocation,this.ds),this.gl.uniform1f(this.wdivLoc,this.wDiv),this.gl.blendFunc(this.gl.SRC_ALPHA,this.gl.ONE_MINUS_SRC_ALPHA),this.gl.drawArrays(this.gl.TRIANGLE_STRIP,0,4)}}class Wt{constructor(t,e,i,s={}){this.blurLevel=t,this.blurRatios=e,this.gl=i,this.texsizeX=s.texsizeX,this.texsizeY=s.texsizeY,this.anisoExt=this.gl.getExtension("EXT_texture_filter_anisotropic")||this.gl.getExtension("MOZ_EXT_texture_filter_anisotropic")||this.gl.getExtension("WEBKIT_EXT_texture_filter_anisotropic"),this.blurHorizontalFrameBuffer=this.gl.createFramebuffer(),this.blurVerticalFrameBuffer=this.gl.createFramebuffer(),this.blurHorizontalTexture=this.gl.createTexture(),this.blurVerticalTexture=this.gl.createTexture(),this.setupFrameBufferTextures(),this.blurHorizontal=new Ot(i,this.blurLevel,s),this.blurVertical=new Gt(i,this.blurLevel,s)}updateGlobals(t){this.texsizeX=t.texsizeX,this.texsizeY=t.texsizeY,this.setupFrameBufferTextures()}getTextureSize(t){let e=Math.max(this.texsizeX*t,16);e=16*Math.floor((e+3)/16);let i=Math.max(this.texsizeY*t,16);return i=4*Math.floor((i+3)/4),[e,i]}setupFrameBufferTextures(){const t=this.blurLevel>0?this.blurRatios[this.blurLevel-1]:[1,1],e=this.blurRatios[this.blurLevel],i=this.getTextureSize(t[1]),s=this.getTextureSize(e[0]);this.bindFrameBufferTexture(this.blurHorizontalFrameBuffer,this.blurHorizontalTexture,s);const r=s,a=this.getTextureSize(e[1]);this.bindFrameBufferTexture(this.blurVerticalFrameBuffer,this.blurVerticalTexture,a),this.horizontalTexsizes=[i,s],this.verticalTexsizes=[r,a]}bindFrambufferAndSetViewport(t,e){this.gl.bindFramebuffer(this.gl.FRAMEBUFFER,t),this.gl.viewport(0,0,e[0],e[1])}bindFrameBufferTexture(t,e,i){if(this.gl.bindTexture(this.gl.TEXTURE_2D,e),this.gl.pixelStorei(this.gl.UNPACK_ALIGNMENT,1),this.gl.texImage2D(this.gl.TEXTURE_2D,0,this.gl.RGBA,i[0],i[1],0,this.gl.RGBA,this.gl.UNSIGNED_BYTE,new Uint8Array(i[0]*i[1]*4)),this.gl.generateMipmap(this.gl.TEXTURE_2D),this.gl.texParameteri(this.gl.TEXTURE_2D,this.gl.TEXTURE_WRAP_S,this.gl.CLAMP_TO_EDGE),this.gl.texParameteri(this.gl.TEXTURE_2D,this.gl.TEXTURE_WRAP_T,this.gl.CLAMP_TO_EDGE),this.gl.texParameteri(this.gl.TEXTURE_2D,this.gl.TEXTURE_MIN_FILTER,this.gl.LINEAR_MIPMAP_LINEAR),this.gl.texParameteri(this.gl.TEXTURE_2D,this.gl.TEXTURE_MAG_FILTER,this.gl.LINEAR),this.anisoExt){const t=this.gl.getParameter(this.anisoExt.MAX_TEXTURE_MAX_ANISOTROPY_EXT);this.gl.texParameterf(this.gl.TEXTURE_2D,this.anisoExt.TEXTURE_MAX_ANISOTROPY_EXT,t)}this.gl.bindFramebuffer(this.gl.FRAMEBUFFER,t),this.gl.framebufferTexture2D(this.gl.FRAMEBUFFER,this.gl.COLOR_ATTACHMENT0,this.gl.TEXTURE_2D,e,0)}renderBlurTexture(t,e,i,s){this.bindFrambufferAndSetViewport(this.blurHorizontalFrameBuffer,this.horizontalTexsizes[1]),this.blurHorizontal.renderQuadTexture(t,e,i,s,this.horizontalTexsizes[0]),this.gl.bindTexture(this.gl.TEXTURE_2D,this.blurHorizontalTexture),this.gl.generateMipmap(this.gl.TEXTURE_2D),this.bindFrambufferAndSetViewport(this.blurVerticalFrameBuffer,this.verticalTexsizes[1]),this.blurVertical.renderQuadTexture(this.blurHorizontalTexture,e,this.verticalTexsizes[0]),this.gl.bindTexture(this.gl.TEXTURE_2D,this.blurVerticalTexture),this.gl.generateMipmap(this.gl.TEXTURE_2D)}}class Yt{constructor(t){this.gl=t,this.randomFn=Rt().random,this.anisoExt=this.gl.getExtension("EXT_texture_filter_anisotropic")||this.gl.getExtension("MOZ_EXT_texture_filter_anisotropic")||this.gl.getExtension("WEBKIT_EXT_texture_filter_anisotropic"),this.noiseTexLQ=this.gl.createTexture(),this.noiseTexLQLite=this.gl.createTexture(),this.noiseTexMQ=this.gl.createTexture(),this.noiseTexHQ=this.gl.createTexture(),this.noiseTexVolLQ=this.gl.createTexture(),this.noiseTexVolHQ=this.gl.createTexture(),this.nTexArrLQ=Yt.createNoiseTex(256,1,this.randomFn),this.nTexArrLQLite=Yt.createNoiseTex(32,1,this.randomFn),this.nTexArrMQ=Yt.createNoiseTex(256,4,this.randomFn),this.nTexArrHQ=Yt.createNoiseTex(256,8,this.randomFn),this.nTexArrVolLQ=Yt.createNoiseVolTex(32,1,this.randomFn),this.nTexArrVolHQ=Yt.createNoiseVolTex(32,4,this.randomFn),this.bindTexture(this.noiseTexLQ,this.nTexArrLQ,256,256),this.bindTexture(this.noiseTexLQLite,this.nTexArrLQLite,32,32),this.bindTexture(this.noiseTexMQ,this.nTexArrMQ,256,256),this.bindTexture(this.noiseTexHQ,this.nTexArrHQ,256,256),this.bindTexture3D(this.noiseTexVolLQ,this.nTexArrVolLQ,32,32,32),this.bindTexture3D(this.noiseTexVolHQ,this.nTexArrVolHQ,32,32,32),this.noiseTexPointLQ=this.gl.createSampler(),t.samplerParameteri(this.noiseTexPointLQ,t.TEXTURE_MIN_FILTER,t.NEAREST_MIPMAP_NEAREST),t.samplerParameteri(this.noiseTexPointLQ,t.TEXTURE_MAG_FILTER,t.NEAREST),t.samplerParameteri(this.noiseTexPointLQ,t.TEXTURE_WRAP_S,t.REPEAT),t.samplerParameteri(this.noiseTexPointLQ,t.TEXTURE_WRAP_T,t.REPEAT)}bindTexture(t,e,i,s){if(this.gl.bindTexture(this.gl.TEXTURE_2D,t),this.gl.pixelStorei(this.gl.UNPACK_ALIGNMENT,1),this.gl.texImage2D(this.gl.TEXTURE_2D,0,this.gl.RGBA,i,s,0,this.gl.RGBA,this.gl.UNSIGNED_BYTE,e),this.gl.generateMipmap(this.gl.TEXTURE_2D),this.gl.texParameteri(this.gl.TEXTURE_2D,this.gl.TEXTURE_WRAP_S,this.gl.REPEAT),this.gl.texParameteri(this.gl.TEXTURE_2D,this.gl.TEXTURE_WRAP_T,this.gl.REPEAT),this.gl.texParameteri(this.gl.TEXTURE_2D,this.gl.TEXTURE_MIN_FILTER,this.gl.LINEAR_MIPMAP_LINEAR),this.gl.texParameteri(this.gl.TEXTURE_2D,this.gl.TEXTURE_MAG_FILTER,this.gl.LINEAR),this.anisoExt){const t=this.gl.getParameter(this.anisoExt.MAX_TEXTURE_MAX_ANISOTROPY_EXT);this.gl.texParameterf(this.gl.TEXTURE_2D,this.anisoExt.TEXTURE_MAX_ANISOTROPY_EXT,t)}}bindTexture3D(t,e,i,s,r){if(this.gl.bindTexture(this.gl.TEXTURE_3D,t),this.gl.pixelStorei(this.gl.UNPACK_ALIGNMENT,1),this.gl.texImage3D(this.gl.TEXTURE_3D,0,this.gl.RGBA,i,s,r,0,this.gl.RGBA,this.gl.UNSIGNED_BYTE,e),this.gl.generateMipmap(this.gl.TEXTURE_3D),this.gl.texParameteri(this.gl.TEXTURE_3D,this.gl.TEXTURE_WRAP_S,this.gl.REPEAT),this.gl.texParameteri(this.gl.TEXTURE_3D,this.gl.TEXTURE_WRAP_T,this.gl.REPEAT),this.gl.texParameteri(this.gl.TEXTURE_3D,this.gl.TEXTURE_WRAP_R,this.gl.REPEAT),this.gl.texParameteri(this.gl.TEXTURE_3D,this.gl.TEXTURE_MIN_FILTER,this.gl.LINEAR_MIPMAP_LINEAR),this.gl.texParameteri(this.gl.TEXTURE_3D,this.gl.TEXTURE_MAG_FILTER,this.gl.LINEAR),this.anisoExt){const t=this.gl.getParameter(this.anisoExt.MAX_TEXTURE_MAX_ANISOTROPY_EXT);this.gl.texParameterf(this.gl.TEXTURE_3D,this.anisoExt.TEXTURE_MAX_ANISOTROPY_EXT,t)}}static fCubicInterpolate(t,e,i,s,r){const a=r*r,o=s-i-t+e;return o*(r*a)+(t-e-o)*a+(i-t)*r+e}static dwCubicInterpolate(t,e,i,s,r){const a=[];for(let o=0;o<4;o++){let h=Yt.fCubicInterpolate(t[o]/255,e[o]/255,i[o]/255,s[o]/255,r);h=Math.clamp(h,0,1),a[o]=255*h}return a}static createNoiseVolTex(t,e,i){const s=t*t*t,r=new Uint8Array(4*s),a=e>1?216:256,o=.5*a;for(let t=0;t1){for(let i=0;i1?216:256,o=.5*a;for(let t=0;t1){for(let i=0;i{this.samplers.clouds2=this.gl.createTexture(),this.bindTexture(this.samplers.clouds2,this.clouds2Image,128,128)},this.clouds2Image.src="data:image/jpeg;base64,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",this.emptyImage=new Image,this.emptyImage.onload=()=>{this.samplers.empty=this.gl.createTexture(),this.bindTexture(this.samplers.empty,this.emptyImage,1,1)},this.emptyImage.src="data:image/gif;base64,R0lGODlhAQABAIAAAAUEBAAAACwAAAAAAQABAAACAkQBADs="}bindTexture(t,e,i,s){if(this.gl.bindTexture(this.gl.TEXTURE_2D,t),this.gl.pixelStorei(this.gl.UNPACK_ALIGNMENT,1),this.gl.texImage2D(this.gl.TEXTURE_2D,0,this.gl.RGBA,i,s,0,this.gl.RGBA,this.gl.UNSIGNED_BYTE,e),this.gl.generateMipmap(this.gl.TEXTURE_2D),this.gl.texParameteri(this.gl.TEXTURE_2D,this.gl.TEXTURE_WRAP_S,this.gl.REPEAT),this.gl.texParameteri(this.gl.TEXTURE_2D,this.gl.TEXTURE_WRAP_T,this.gl.REPEAT),this.gl.texParameteri(this.gl.TEXTURE_2D,this.gl.TEXTURE_MIN_FILTER,this.gl.LINEAR_MIPMAP_LINEAR),this.gl.texParameteri(this.gl.TEXTURE_2D,this.gl.TEXTURE_MAG_FILTER,this.gl.LINEAR),this.anisoExt){const t=this.gl.getParameter(this.anisoExt.MAX_TEXTURE_MAX_ANISOTROPY_EXT);this.gl.texParameterf(this.gl.TEXTURE_2D,this.anisoExt.TEXTURE_MAX_ANISOTROPY_EXT,t)}}loadExtraImages(t){Object.keys(t).forEach(e=>{const{data:i,width:s,height:r}=t[e];if(!this.samplers[e]){const t=new Image;t.onload=()=>{this.samplers[e]=this.gl.createTexture(),this.bindTexture(this.samplers[e],t,s,r)},t.src=i}})}getTexture(t){const e=this.samplers[t];return e||this.samplers.clouds2}}class Kt{constructor(t,e={}){this.gl=t,this.texsizeX=e.texsizeX,this.texsizeY=e.texsizeY,this.aspectx=e.aspectx,this.aspecty=e.aspecty,this.invAspectx=1/this.aspectx,this.invAspecty=1/this.aspecty,this.buildPositions(),this.textTexture=this.gl.createTexture(),this.indexBuf=t.createBuffer(),this.positionVertexBuf=this.gl.createBuffer(),this.vertexBuf=this.gl.createBuffer(),this.canvas=document.createElement("canvas"),this.canvas.width=this.texsizeX,this.canvas.height=this.texsizeY,this.context2D=this.canvas.getContext("2d",{willReadFrequently:!1}),this.floatPrecision=Ut.getFragmentFloatPrecision(this.gl),this.createShader()}generateTitleTexture(t){this.context2D.clearRect(0,0,this.texsizeX,this.texsizeY),this.fontSize=Math.floor(this.texsizeX/256*16),this.fontSize=Math.max(this.fontSize,6),this.context2D.font=`italic ${this.fontSize}px Times New Roman`;let e=t,i=this.context2D.measureText(e).width;if(i>this.texsizeX){const t=this.texsizeX/i*.91;e=`${e.substring(0,Math.floor(e.length*t))}...`,i=this.context2D.measureText(e).width}this.context2D.fillStyle="#FFFFFF",this.context2D.fillText(e,(this.texsizeX-i)/2,this.texsizeY/2);const s=new Uint8Array(this.context2D.getImageData(0,0,this.texsizeX,this.texsizeY).data.buffer);this.gl.pixelStorei(this.gl.UNPACK_FLIP_Y_WEBGL,!0),this.gl.bindTexture(this.gl.TEXTURE_2D,this.textTexture),this.gl.texImage2D(this.gl.TEXTURE_2D,0,this.gl.RGBA,this.texsizeX,this.texsizeY,0,this.gl.RGBA,this.gl.UNSIGNED_BYTE,s),this.gl.texParameteri(this.gl.TEXTURE_2D,this.gl.TEXTURE_MAG_FILTER,this.gl.LINEAR),this.gl.texParameteri(this.gl.TEXTURE_2D,this.gl.TEXTURE_MIN_FILTER,this.gl.LINEAR_MIPMAP_LINEAR),this.gl.texParameteri(this.gl.TEXTURE_2D,this.gl.TEXTURE_WRAP_S,this.gl.CLAMP_TO_EDGE),this.gl.texParameteri(this.gl.TEXTURE_2D,this.gl.TEXTURE_WRAP_T,this.gl.CLAMP_TO_EDGE),this.gl.generateMipmap(this.gl.TEXTURE_2D),this.gl.bindTexture(this.gl.TEXTURE_2D,null)}updateGlobals(t){this.texsizeX=t.texsizeX,this.texsizeY=t.texsizeY,this.aspectx=t.aspectx,this.aspecty=t.aspecty,this.invAspectx=1/this.aspectx,this.invAspecty=1/this.aspecty,this.canvas.width=this.texsizeX,this.canvas.height=this.texsizeY}buildPositions(){const t=2/15,e=2/7,i=[];for(let s=0;s<8;s++){const r=s*e-1;for(let e=0;e<16;e++){const s=e*t-1;i.push(s,-r,0)}}const s=[];for(let t=0;t<7;t++)for(let e=0;e<15;e++){const i=e+16*t,r=e+16*(t+1),a=e+1+16*(t+1),o=e+1+16*t;s.push(i,r,o),s.push(r,a,o)}this.vertices=new Float32Array(i),this.indices=new Uint16Array(s)}createShader(){this.shaderProgram=this.gl.createProgram();const t=this.gl.createShader(this.gl.VERTEX_SHADER);this.gl.shaderSource(t,"#version 300 es\n const vec2 halfmad = vec2(0.5);\n in vec2 aPos;\n in vec2 aUv;\n out vec2 uv_orig;\n out vec2 uv;\n void main(void) {\n gl_Position = vec4(aPos, 0.0, 1.0);\n uv_orig = aPos * halfmad + halfmad;\n uv = aUv;\n }"),this.gl.compileShader(t);const e=this.gl.createShader(this.gl.FRAGMENT_SHADER);this.gl.shaderSource(e,`#version 300 es\n precision ${this.floatPrecision} float;\n precision highp int;\n precision mediump sampler2D;\n\n in vec2 uv_orig;\n in vec2 uv;\n out vec4 fragColor;\n uniform sampler2D uTexture;\n uniform float textColor;\n\n void main(void) {\n fragColor = texture(uTexture, uv) * vec4(textColor);\n }`),this.gl.compileShader(e),this.gl.attachShader(this.shaderProgram,t),this.gl.attachShader(this.shaderProgram,e),this.gl.linkProgram(this.shaderProgram),this.positionLocation=this.gl.getAttribLocation(this.shaderProgram,"aPos"),this.uvLocation=this.gl.getAttribLocation(this.shaderProgram,"aUv"),this.textureLoc=this.gl.getUniformLocation(this.shaderProgram,"uTexture"),this.textColorLoc=this.gl.getUniformLocation(this.shaderProgram,"textColor")}generateUvs(t,e,i){const s=[];for(let i=0;i<8;i++)for(let r=0;r<16;r++){const a=2*(r/15)-1;let o=2*(.75*(i/7-.5)+.5)-1;t>=1&&(o+=1/this.texsizeY),s.push(a,e?o:-o)}const r=Math.max(0,1-1.5*t)**1.8*1.3;for(let t=0;t<8;t++)for(let e=0;e<16;e++){const a=16*t+e;s[a]+=.07*r*Math.sin(.31*i.time+.39*s[a]-1.94*s[a+1]),s[a]+=.044*r*Math.sin(.81*i.time-1.91*s[a]+.27*s[a+1]),s[a]+=.061*r*Math.sin(1.31*i.time+.61*s[a]+.74*s[a+1]),s[a+1]+=.061*r*Math.sin(.37*i.time+1.83*s[a]+.69*s[a+1]),s[a+1]+=.07*r*Math.sin(.67*i.time+.42*s[a]-1.39*s[a+1]),s[a+1]+=.087*r*Math.sin(1.07*i.time+3.55*s[a]+.89*s[a+1])}const a=1.01/(t**.21+.01);for(let t=0;t=2&&(0===t&&(this.vertInfoC[o*(this.mesh_width+1)+t]=.5*(h+n)+(2*this.rng.random()-1)*r*this.aspecty),this.vertInfoC[o*(this.mesh_width+1)+e]=.5*(A+l)+(2*this.rng.random()-1)*r*this.aspecty),e-t>=2&&(0===i&&(this.vertInfoC[i*(this.mesh_width+1)+a]=.5*(h+A)+(2*this.rng.random()-1)*r*this.aspectx),this.vertInfoC[s*(this.mesh_width+1)+a]=.5*(n+l)+(2*this.rng.random()-1)*r*this.aspectx),s-i>=2&&e-t>=2&&(h=this.vertInfoC[o*(this.mesh_width+1)+t],A=this.vertInfoC[o*(this.mesh_width+1)+e],n=this.vertInfoC[i*(this.mesh_width+1)+a],l=this.vertInfoC[s*(this.mesh_width+1)+a],this.vertInfoC[o*(this.mesh_width+1)+a]=.25*(n+l+h+A)+(2*this.rng.random()-1)*r,this.genPlasma(t,a,i,o,.5*r),this.genPlasma(a,e,i,o,.5*r),this.genPlasma(t,a,o,s,.5*r),this.genPlasma(a,e,o,s,.5*r))}createBlendPattern(){const t=1+Math.floor(3*this.rng.random());if(0===t){let t=0;for(let e=0;e<=this.mesh_height;e++)for(let e=0;e<=this.mesh_width;e++)this.vertInfoA[t]=1,this.vertInfoC[t]=0,t+=1}else if(1===t){const t=6.28*this.rng.random(),e=Math.cos(t),i=Math.sin(t),s=.1+.2*this.rng.random(),r=1/s;let a=0;for(let t=0;t<=this.mesh_height;t++){const o=t/this.mesh_height*this.aspecty;for(let t=0;t<=this.mesh_width;t++){let h=(t/this.mesh_width*this.aspectx-.5)*e+(o-.5)*i+.5;h=(h-.5)/Math.sqrt(2)+.5,this.vertInfoA[a]=r*(1+s),this.vertInfoC[a]=r*h-r,a+=1}}}else if(2===t){const t=.12+.13*this.rng.random(),e=1/t;this.vertInfoC[0]=this.rng.random(),this.vertInfoC[this.mesh_width]=this.rng.random(),this.vertInfoC[this.mesh_height*(this.mesh_width+1)]=this.rng.random(),this.vertInfoC[this.mesh_height*(this.mesh_width+1)+this.mesh_width]=this.rng.random(),this.genPlasma(0,this.mesh_width,0,this.mesh_height,.25);let i=this.vertInfoC[0],s=this.vertInfoC[0],r=0;for(let t=0;t<=this.mesh_height;t++)for(let t=0;t<=this.mesh_width;t++)i>this.vertInfoC[r]&&(i=this.vertInfoC[r]),sthis.texsizeX?this.texsizeX/this.texsizeY:1,this.aspecty=this.texsizeX>this.texsizeY?this.texsizeY/this.texsizeX:1,this.invAspectx=1/this.aspectx,this.invAspecty=1/this.aspecty,this.qs=vt.range(1,33).map(t=>`q${t}`),this.ts=vt.range(1,9).map(t=>`t${t}`),this.regs=vt.range(0,100).map(t=>t<10?`reg0${t}`:`reg${t}`),this.blurRatios=[[.5,.25],[.125,.125],[.0625,.0625]],this.audioLevels=new bt(this.audio),this.prevFrameBuffer=this.gl.createFramebuffer(),this.targetFrameBuffer=this.gl.createFramebuffer(),this.prevTexture=this.gl.createTexture(),this.targetTexture=this.gl.createTexture(),this.compFrameBuffer=this.gl.createFramebuffer(),this.compTexture=this.gl.createTexture(),this.anisoExt=this.gl.getExtension("EXT_texture_filter_anisotropic")||this.gl.getExtension("MOZ_EXT_texture_filter_anisotropic")||this.gl.getExtension("WEBKIT_EXT_texture_filter_anisotropic"),this.bindFrameBufferTexture(this.prevFrameBuffer,this.prevTexture),this.bindFrameBufferTexture(this.targetFrameBuffer,this.targetTexture),this.bindFrameBufferTexture(this.compFrameBuffer,this.compTexture);const s={pixelRatio:this.pixelRatio,textureRatio:this.textureRatio,texsizeX:this.texsizeX,texsizeY:this.texsizeY,mesh_width:this.mesh_width,mesh_height:this.mesh_height,aspectx:this.aspectx,aspecty:this.aspecty};this.noise=new Yt(t),this.image=new Jt(t),this.warpShader=new zt(t,this.noise,this.image,s),this.compShader=new Nt(t,this.noise,this.image,s),this.outputShader=new Xt(t,s),this.prevWarpShader=new zt(t,this.noise,this.image,s),this.prevCompShader=new Nt(t,this.noise,this.image,s),this.numBlurPasses=0,this.blurShader1=new Wt(0,this.blurRatios,t,s),this.blurShader2=new Wt(1,this.blurRatios,t,s),this.blurShader3=new Wt(2,this.blurRatios,t,s),this.blurTexture1=this.blurShader1.blurVerticalTexture,this.blurTexture2=this.blurShader2.blurVerticalTexture,this.blurTexture3=this.blurShader3.blurVerticalTexture,this.basicWaveform=new Ft(t,s),this.customWaveforms=vt.range(4).map(e=>new Qt(e,t,s)),this.customShapes=vt.range(4).map(e=>new Mt(e,t,s)),this.prevCustomWaveforms=vt.range(4).map(e=>new Qt(e,t,s)),this.prevCustomShapes=vt.range(4).map(e=>new Mt(e,t,s)),this.darkenCenter=new Vt(t,s),this.innerBorder=new Dt(t,s),this.outerBorder=new Dt(t,s),this.motionVectors=new qt(t,s),this.titleText=new Kt(t,s),this.blendPattern=new jt(s),this.resampleShader=new kt(t),this.supertext={startTime:-1},this.warpUVs=new Float32Array((this.mesh_width+1)*(this.mesh_height+1)*2),this.warpColor=new Float32Array((this.mesh_width+1)*(this.mesh_height+1)*4),this.gl.clearColor(0,0,0,1),this.blankPreset=xt;const r={frame:0,time:0,fps:45,bass:1,bass_att:1,mid:1,mid_att:1,treb:1,treb_att:1};this.preset=xt,this.prevPreset=this.preset,this.presetEquationRunner=new It(this.preset,r,s),this.prevPresetEquationRunner=new It(this.prevPreset,r,s),this.preset.useWASM||(this.regVars=this.presetEquationRunner.mdVSRegs)}static getHighestBlur(t){return/sampler_blur3/.test(t)?3:/sampler_blur2/.test(t)?2:/sampler_blur1/.test(t)?1:0}loadPreset(t,e){this.blendPattern.createBlendPattern(),this.blending=!0,this.blendStartTime=this.time,this.blendDuration=e,this.blendProgress=0,this.prevPresetEquationRunner=this.presetEquationRunner,this.prevPreset=this.preset,this.preset=t,this.presetTime=this.time;const i={frame:this.frameNum,time:this.time,fps:this.fps,bass:this.audioLevels.bass,bass_att:this.audioLevels.bass_att,mid:this.audioLevels.mid,mid_att:this.audioLevels.mid_att,treb:this.audioLevels.treb,treb_att:this.audioLevels.treb_att},s={pixelRatio:this.pixelRatio,textureRatio:this.textureRatio,texsizeX:this.texsizeX,texsizeY:this.texsizeY,mesh_width:this.mesh_width,mesh_height:this.mesh_height,aspectx:this.aspectx,aspecty:this.aspecty};t.useWASM?(this.preset.globalPools.perFrame.old_wave_mode.value=this.prevPreset.baseVals.wave_mode,this.preset.baseVals.old_wave_mode=this.prevPreset.baseVals.wave_mode,this.presetEquationRunner=new yt(this.preset,i,s),this.preset.pixel_eqs_initialize_array&&this.preset.pixel_eqs_initialize_array(this.mesh_width,this.mesh_height)):(this.preset.baseVals.old_wave_mode=this.prevPreset.baseVals.wave_mode,this.presetEquationRunner=new It(this.preset,i,s),this.regVars=this.presetEquationRunner.mdVSRegs);const r=this.prevWarpShader;this.prevWarpShader=this.warpShader,this.warpShader=r;const a=this.prevCompShader;this.prevCompShader=this.compShader,this.compShader=a;const o=this.preset.warp.trim(),h=this.preset.comp.trim();this.warpShader.updateShader(o),this.compShader.updateShader(h),0===o.length?this.numBlurPasses=0:this.numBlurPasses=Ht.getHighestBlur(o),0!==h.length&&(this.numBlurPasses=Math.max(this.numBlurPasses,Ht.getHighestBlur(h)))}loadExtraImages(t){this.image.loadExtraImages(t)}setRendererSize(t,e,i){const s=this.texsizeX,r=this.texsizeY;if(this.width=t,this.height=e,this.mesh_width=i.meshWidth||this.mesh_width,this.mesh_height=i.meshHeight||this.mesh_height,this.pixelRatio=i.pixelRatio||this.pixelRatio,this.textureRatio=i.textureRatio||this.textureRatio,this.texsizeX=t*this.pixelRatio*this.textureRatio,this.texsizeY=e*this.pixelRatio*this.textureRatio,this.aspectx=this.texsizeY>this.texsizeX?this.texsizeX/this.texsizeY:1,this.aspecty=this.texsizeX>this.texsizeY?this.texsizeY/this.texsizeX:1,this.texsizeX!==s||this.texsizeY!==r){const t=this.gl.createTexture();this.bindFrameBufferTexture(this.targetFrameBuffer,t),this.bindFrambufferAndSetViewport(this.targetFrameBuffer,this.texsizeX,this.texsizeY),this.resampleShader.renderQuadTexture(this.targetTexture),this.targetTexture=t,this.bindFrameBufferTexture(this.prevFrameBuffer,this.prevTexture),this.bindFrameBufferTexture(this.compFrameBuffer,this.compTexture)}this.updateGlobals(),this.frameNum>0&&this.renderToScreen()}setInternalMeshSize(t,e){this.mesh_width=t,this.mesh_height=e,this.updateGlobals()}setOutputAA(t){this.outputFXAA=t}updateGlobals(){const t={pixelRatio:this.pixelRatio,textureRatio:this.textureRatio,texsizeX:this.texsizeX,texsizeY:this.texsizeY,mesh_width:this.mesh_width,mesh_height:this.mesh_height,aspectx:this.aspectx,aspecty:this.aspecty};this.presetEquationRunner.updateGlobals(t),this.prevPresetEquationRunner.updateGlobals(t),this.warpShader.updateGlobals(t),this.prevWarpShader.updateGlobals(t),this.compShader.updateGlobals(t),this.prevCompShader.updateGlobals(t),this.outputShader.updateGlobals(t),this.blurShader1.updateGlobals(t),this.blurShader2.updateGlobals(t),this.blurShader3.updateGlobals(t),this.basicWaveform.updateGlobals(t),this.customWaveforms.forEach(e=>e.updateGlobals(t)),this.customShapes.forEach(e=>e.updateGlobals(t)),this.prevCustomWaveforms.forEach(e=>e.updateGlobals(t)),this.prevCustomShapes.forEach(e=>e.updateGlobals(t)),this.darkenCenter.updateGlobals(t),this.innerBorder.updateGlobals(t),this.outerBorder.updateGlobals(t),this.motionVectors.updateGlobals(t),this.titleText.updateGlobals(t),this.blendPattern.updateGlobals(t),this.warpUVs=new Float32Array((this.mesh_width+1)*(this.mesh_height+1)*2),this.warpColor=new Float32Array((this.mesh_width+1)*(this.mesh_height+1)*4),this.preset.pixel_eqs_initialize_array&&this.preset.pixel_eqs_initialize_array(this.mesh_width,this.mesh_height)}calcTimeAndFPS(t){let e;if(t)e=t;else{const t=performance.now();e=(t-this.lastTime)/1e3,(e>1||e<0||this.frame<2)&&(e=1/30),this.lastTime=t}this.time+=1/this.fps,this.blending&&(this.blendProgress=(this.time-this.blendStartTime)/this.blendDuration,this.blendProgress>1&&(this.blending=!1));const i=this.timeHist[this.timeHist.length-1]+e;this.timeHist.push(i),this.timeHist.length>this.timeHistMax&&this.timeHist.shift();const s=this.timeHist.length/(i-this.timeHist[0]);if(Math.abs(s-this.fps)>3&&this.frame>this.timeHistMax)this.fps=s;else{const t=.93;this.fps=t*this.fps+(1-t)*s}}runPixelEquations(t,e,i,s){const r=this.mesh_width,a=this.mesh_height,o=r+1,h=a+1,A=this.time*e.warpanimspeed,n=1/e.warpscale,l=11.68+4*Math.cos(1.413*A+10),g=8.77+3*Math.cos(1.113*A+7),c=10.54+3*Math.cos(1.233*A+3),m=11.49+4*Math.cos(.933*A+5),u=0/this.texsizeX,f=0/this.texsizeY,d=this.aspectx,p=this.aspecty;let E=0,_=0;if(t.preset.useWASM){const r=t.preset.globalPools.perVertex;if(vt.setWasm(r,i,t.globalKeys),vt.setWasm(r,t.mdVSQAfterFrame,t.qs),r.zoom.value=e.zoom,r.zoomexp.value=e.zoomexp,r.rot.value=e.rot,r.warp.value=e.warp,r.cx.value=e.cx,r.cy.value=e.cy,r.dx.value=e.dx,r.dy.value=e.dy,r.sx.value=e.sx,r.sy.value=e.sy,t.preset.pixel_eqs_wasm(t.runVertEQs,this.mesh_width,this.mesh_height,this.time,e.warpanimspeed,e.warpscale,this.aspectx,this.aspecty),s){const e=t.preset.pixel_eqs_get_array();let i=0,s=0;for(let t=0;t0&&(this.blurShader1.renderBlurTexture(this.targetTexture,r,A,n),this.numBlurPasses>1&&(this.blurShader2.renderBlurTexture(this.blurTexture1,r,A,n),this.numBlurPasses>2&&this.blurShader3.renderBlurTexture(this.blurTexture2,r,A,n)),this.bindFrambufferAndSetViewport(this.targetFrameBuffer,this.texsizeX,this.texsizeY)),this.motionVectors.drawMotionVectors(a,this.warpUVs),this.preset.shapes&&this.preset.shapes.length>0&&this.customShapes.forEach((t,e)=>{t.drawCustomShape(this.blending?this.blendProgress:1,i,this.presetEquationRunner,this.preset.shapes[e],this.prevTexture)}),this.preset.waves&&this.preset.waves.length>0&&this.customWaveforms.forEach((t,e)=>{t.drawCustomWaveform(this.blending?this.blendProgress:1,this.audio.timeArrayL,this.audio.timeArrayR,this.audio.freqArrayL,this.audio.freqArrayR,i,this.presetEquationRunner,this.preset.waves[e])}),this.blending&&(this.prevPreset.shapes&&this.prevPreset.shapes.length>0&&this.prevCustomShapes.forEach((t,e)=>{t.drawCustomShape(1-this.blendProgress,s,this.prevPresetEquationRunner,this.prevPreset.shapes[e],this.prevTexture)}),this.prevPreset.waves&&this.prevPreset.waves.length>0&&this.prevCustomWaveforms.forEach((t,e)=>{t.drawCustomWaveform(1-this.blendProgress,this.audio.timeArrayL,this.audio.timeArrayR,this.audio.freqArrayL,this.audio.freqArrayR,s,this.prevPresetEquationRunner,this.prevPreset.waves[e])})),this.basicWaveform.drawBasicWaveform(this.blending,this.blendProgress,this.audio.timeArrayL,this.audio.timeArrayR,a),this.darkenCenter.drawDarkenCenter(a);const l=[a.ob_r,a.ob_g,a.ob_b,a.ob_a];this.outerBorder.drawBorder(l,a.ob_size,0);const g=[a.ib_r,a.ib_g,a.ib_b,a.ib_a];if(this.innerBorder.drawBorder(g,a.ib_size,a.ob_size),this.supertext.startTime>=0){const t=(this.time-this.supertext.startTime)/this.supertext.duration;t>=1&&this.titleText.renderTitle(t,!0,i)}this.globalVars=i,this.mdVSFrame=r,this.mdVSFrameMixed=a,this.renderToScreen()}renderToScreen(){this.outputFXAA?this.bindFrambufferAndSetViewport(this.compFrameBuffer,this.texsizeX,this.texsizeY):this.bindFrambufferAndSetViewport(null,this.width,this.height),this.gl.clear(this.gl.COLOR_BUFFER_BIT),this.gl.enable(this.gl.BLEND),this.gl.blendEquation(this.gl.FUNC_ADD),this.gl.blendFunc(this.gl.SRC_ALPHA,this.gl.ONE_MINUS_SRC_ALPHA);const{blurMins:t,blurMaxs:e}=Ht.getBlurValues(this.mdVSFrameMixed);if(this.blending?(this.prevCompShader.renderQuadTexture(!1,this.targetTexture,this.blurTexture1,this.blurTexture2,this.blurTexture3,t,e,this.prevMDVSFrame,this.prevPresetEquationRunner.mdVSQAfterFrame,this.warpColor),this.compShader.renderQuadTexture(!0,this.targetTexture,this.blurTexture1,this.blurTexture2,this.blurTexture3,t,e,this.mdVSFrameMixed,this.presetEquationRunner.mdVSQAfterFrame,this.warpColor)):this.compShader.renderQuadTexture(!1,this.targetTexture,this.blurTexture1,this.blurTexture2,this.blurTexture3,t,e,this.mdVSFrame,this.presetEquationRunner.mdVSQAfterFrame,this.warpColor),this.supertext.startTime>=0){const t=(this.time-this.supertext.startTime)/this.supertext.duration;this.titleText.renderTitle(t,!1,this.globalVars),t>=1&&(this.supertext.startTime=-1)}this.outputFXAA&&(this.gl.bindTexture(this.gl.TEXTURE_2D,this.compTexture),this.gl.generateMipmap(this.gl.TEXTURE_2D),this.bindFrambufferAndSetViewport(null,this.width,this.height),this.outputShader.renderQuadTexture(this.compTexture))}launchSongTitleAnim(t){this.supertext={startTime:this.time,duration:1.7},this.titleText.generateTitleTexture(t)}toDataURL(){const t=new Uint8Array(this.texsizeX*this.texsizeY*4),e=this.gl.createFramebuffer(),i=this.gl.createTexture();this.bindFrameBufferTexture(e,i);const{blurMins:s,blurMaxs:r}=Ht.getBlurValues(this.mdVSFrameMixed);this.compShader.renderQuadTexture(!1,this.targetTexture,this.blurTexture1,this.blurTexture2,this.blurTexture3,s,r,this.mdVSFrame,this.presetEquationRunner.mdVSQAfterFrame,this.warpColor),this.gl.readPixels(0,0,this.texsizeX,this.texsizeY,this.gl.RGBA,this.gl.UNSIGNED_BYTE,t),Array.from({length:this.texsizeY},(e,i)=>t.slice(i*this.texsizeX*4,(i+1)*this.texsizeX*4)).forEach((e,i)=>t.set(e,(this.texsizeY-i-1)*this.texsizeX*4));const a=document.createElement("canvas");a.width=this.texsizeX,a.height=this.texsizeY;const o=a.getContext("2d",{willReadFrequently:!1}),h=o.createImageData(this.texsizeX,this.texsizeY);return h.data.set(t),o.putImageData(h,0,0),this.gl.deleteTexture(i),this.gl.deleteFramebuffer(e),a.toDataURL()}warpBufferToDataURL(){const t=new Uint8Array(this.texsizeX*this.texsizeY*4);this.gl.bindFramebuffer(this.gl.FRAMEBUFFER,this.targetFrameBuffer),this.gl.readPixels(0,0,this.texsizeX,this.texsizeY,this.gl.RGBA,this.gl.UNSIGNED_BYTE,t);const e=document.createElement("canvas");e.width=this.texsizeX,e.height=this.texsizeY;const i=e.getContext("2d",{willReadFrequently:!1}),s=i.createImageData(this.texsizeX,this.texsizeY);return s.data.set(t),i.putImageData(s,0,0),e.toDataURL()}}class Zt{constructor(t,e,i){this.opts=i,this.rng=Pt(i),this.deterministicMode=i.deterministic||i.testMode,this.audio=new _t(t);const s=i.width||1200,r=i.height||900;window.OffscreenCanvas?this.internalCanvas=new OffscreenCanvas(s,r):(this.internalCanvas=document.createElement("canvas"),this.internalCanvas.width=s,this.internalCanvas.height=r),this.gl=this.internalCanvas.getContext("webgl2",{alpha:!1,antialias:!1,depth:!1,stencil:!1,premultipliedAlpha:!1}),this.outputGl=e.getContext("2d",{willReadFrequently:!1}),this.baseValsDefaults={decay:.98,gammaadj:2,echo_zoom:2,echo_alpha:0,echo_orient:0,red_blue:0,brighten:0,darken:0,wrap:1,darken_center:0,solarize:0,invert:0,bmotionvectorson:1,fshader:0,b1n:0,b2n:0,b3n:0,b1x:1,b2x:1,b3x:1,b1ed:.25,wave_mode:0,additivewave:0,wave_dots:0,wave_thick:0,wave_a:.8,wave_scale:1,wave_smoothing:.75,wave_mystery:0,modwavealphabyvolume:0,modwavealphastart:.75,modwavealphaend:.95,wave_r:1,wave_g:1,wave_b:1,wave_x:.5,wave_y:.5,wave_brighten:1,mv_x:12,mv_y:9,mv_dx:0,mv_dy:0,mv_l:.9,mv_r:1,mv_g:1,mv_b:1,mv_a:1,warpanimspeed:1,warpscale:1,zoomexp:1,zoom:1,rot:0,cx:.5,cy:.5,dx:0,dy:0,warp:1,sx:1,sy:1,ob_size:.01,ob_r:0,ob_g:0,ob_b:0,ob_a:0,ib_size:.01,ib_r:.25,ib_g:.25,ib_b:.25,ib_a:0},this.shapeBaseValsDefaults={enabled:0,sides:4,additive:0,thickoutline:0,textured:0,num_inst:1,tex_zoom:1,tex_ang:0,x:.5,y:.5,rad:.1,ang:0,r:1,g:0,b:0,a:1,r2:0,g2:1,b2:0,a2:0,border_r:1,border_g:1,border_b:1,border_a:.1},this.waveBaseValsDefaults={enabled:0,samples:512,sep:0,scaling:1,smoothing:.5,r:1,g:1,b:1,a:1,spectrum:0,usedots:0,thick:0,additive:0},this.qs=vt.range(1,33).map(t=>`q${t}`),this.ts=vt.range(1,9).map(t=>`t${t}`),this.globalPerFrameVars=["old_wave_mode","frame","time","fps","bass","bass_att","mid","mid_att","treb","treb_att","meshx","meshy","aspectx","aspecty","pixelsx","pixelsy","rand_start","rand_preset"],this.globalPerPixelVars=["frame","time","fps","bass","bass_att","mid","mid_att","treb","treb_att","meshx","meshy","aspectx","aspecty","pixelsx","pixelsy","rand_start","rand_preset","x","y","rad","ang"],this.globalShapeVars=["frame","time","fps","bass","bass_att","mid","mid_att","treb","treb_att","meshx","meshy","aspectx","aspecty","pixelsx","pixelsy","rand_start","rand_preset","instance"],this.shapeBaseVars=["x","y","rad","ang","r","g","b","a","r2","g2","b2","a2","border_r","border_g","border_b","border_a","thickoutline","textured","tex_zoom","tex_ang","additive"],this.globalWaveVars=["frame","time","fps","bass","bass_att","mid","mid_att","treb","treb_att","meshx","meshy","aspectx","aspecty","pixelsx","pixelsy","rand_start","rand_preset","x","y","sample","value1","value2"],this.renderer=new Ht(this.gl,this.audio,i)}loseGLContext(){this.gl.getExtension("WEBGL_lose_context").loseContext(),this.outputGl=null}connectAudio(t){this.audioNode=t,this.audio.connectAudio(t)}disconnectAudio(t){this.audio.disconnectAudio(t)}static overrideDefaultVars(t,e){const i={};return Object.keys(t).forEach(s=>{Object.prototype.hasOwnProperty.call(e,s)?i[s]=e[s]:i[s]=t[s]}),i}createQVars(){const t={};return this.qs.forEach(e=>{t[e]=new WebAssembly.Global({value:"f64",mutable:!0},0)}),t}createTVars(){const t={};return this.ts.forEach(e=>{t[e]=new WebAssembly.Global({value:"f64",mutable:!0},0)}),t}createPerFramePool(t){const e={};return Object.keys(this.baseValsDefaults).forEach(i=>{e[i]=new WebAssembly.Global({value:"f64",mutable:!0},t[i])}),this.globalPerFrameVars.forEach(t=>{e[t]=new WebAssembly.Global({value:"f64",mutable:!0},0)}),e}createPerPixelPool(t){const e={};return Object.keys(this.baseValsDefaults).forEach(i=>{e[i]=new WebAssembly.Global({value:"f64",mutable:!0},t[i])}),this.globalPerPixelVars.forEach(t=>{e[t]=new WebAssembly.Global({value:"f64",mutable:!0},0)}),e}createCustomShapePerFramePool(t){const e={};return Object.keys(this.shapeBaseValsDefaults).forEach(i=>{e[i]=new WebAssembly.Global({value:"f64",mutable:!0},t[i])}),this.globalShapeVars.forEach(t=>{e[t]=new WebAssembly.Global({value:"f64",mutable:!0},0)}),e}createCustomWavePerFramePool(t){const e={};return Object.keys(this.waveBaseValsDefaults).forEach(i=>{e[i]=new WebAssembly.Global({value:"f64",mutable:!0},t[i])}),this.globalWaveVars.forEach(t=>{e[t]=new WebAssembly.Global({value:"f64",mutable:!0},0)}),e}static makeShapeResetPool(t,e,i){return e.reduce((e,s)=>({...e,[`${s}_${i}`]:t[s]}),{})}static base64ToArrayBuffer(t){for(var e=window.atob(t),i=e.length,s=new Uint8Array(i),r=0;r{s[t]=new Set(Object.keys(e))});const r=at({pools:s,functions:e,eelVersion:i}),a=await WebAssembly.compile(r);var o=Object.assign(Object.assign({},t),{shims:j});return await WebAssembly.instantiate(a,o)}({pools:r,functions:a,eelVersion:t.version||2}),h=t=>t||(()=>{}),A=await pt.instantiate(Zt.base64ToArrayBuffer("AGFzbQEAAAABPQpgAABgAXwBfGACfHwBfGACf38AYAR/f39/AGAJf39/f3x8fHx8AGADf399AGABfwF/YAJ/fwF/YAF+AX8CuBWMAQNlbnYFYWJvcnQABAhwaXhlbEVxcwtwZXJQaXhlbEVxcwAADHBpeGVsVmFyUG9vbAR3YXJwA3wBDHBpeGVsVmFyUG9vbAR6b29tA3wBDHBpeGVsVmFyUG9vbAd6b29tZXhwA3wBDHBpeGVsVmFyUG9vbAJjeAN8AQxwaXhlbFZhclBvb2wCY3kDfAEMcGl4ZWxWYXJQb29sAnN4A3wBDHBpeGVsVmFyUG9vbAJzeQN8AQxwaXhlbFZhclBvb2wCZHgDfAEMcGl4ZWxWYXJQb29sAmR5A3wBDHBpeGVsVmFyUG9vbANyb3QDfAEMcGl4ZWxWYXJQb29sA3JhZAN8AQxwaXhlbFZhclBvb2wDYW5nA3wBDHBpeGVsVmFyUG9vbAF4A3wBDHBpeGVsVmFyUG9vbAF5A3wBCHFWYXJQb29sAnExA3wBCHFWYXJQb29sAnEyA3wBCHFWYXJQb29sAnEzA3wBCHFWYXJQb29sAnE0A3wBCHFWYXJQb29sAnE1A3wBCHFWYXJQb29sAnE2A3wBCHFWYXJQb29sAnE3A3wBCHFWYXJQb29sAnE4A3wBCHFWYXJQb29sAnE5A3wBCHFWYXJQb29sA3ExMAN8AQhxVmFyUG9vbANxMTEDfAEIcVZhclBvb2wDcTEyA3wBCHFWYXJQb29sA3ExMwN8AQhxVmFyUG9vbANxMTQDfAEIcVZhclBvb2wDcTE1A3wBCHFWYXJQb29sA3ExNgN8AQhxVmFyUG9vbANxMTcDfAEIcVZhclBvb2wDcTE4A3wBCHFWYXJQb29sA3ExOQN8AQhxVmFyUG9vbANxMjADfAEIcVZhclBvb2wDcTIxA3wBCHFWYXJQb29sA3EyMgN8AQhxVmFyUG9vbANxMjMDfAEIcVZhclBvb2wDcTI0A3wBCHFWYXJQb29sA3EyNQN8AQhxVmFyUG9vbANxMjYDfAEIcVZhclBvb2wDcTI3A3wBCHFWYXJQb29sA3EyOAN8AQhxVmFyUG9vbANxMjkDfAEIcVZhclBvb2wDcTMwA3wBCHFWYXJQb29sA3EzMQN8AQhxVmFyUG9vbANxMzIDfAEIdFZhclBvb2wCdDEDfAEIdFZhclBvb2wCdDIDfAEIdFZhclBvb2wCdDMDfAEIdFZhclBvb2wCdDQDfAEIdFZhclBvb2wCdDUDfAEIdFZhclBvb2wCdDYDfAEIdFZhclBvb2wCdDcDfAEIdFZhclBvb2wCdDgDfAEKc2hhcGVQb29sMAN4XzADfAEKc2hhcGVQb29sMAN5XzADfAEKc2hhcGVQb29sMAVyYWRfMAN8AQpzaGFwZVBvb2wwBWFuZ18wA3wBCnNoYXBlUG9vbDADcl8wA3wBCnNoYXBlUG9vbDADZ18wA3wBCnNoYXBlUG9vbDADYl8wA3wBCnNoYXBlUG9vbDADYV8wA3wBCnNoYXBlUG9vbDAEcjJfMAN8AQpzaGFwZVBvb2wwBGcyXzADfAEKc2hhcGVQb29sMARiMl8wA3wBCnNoYXBlUG9vbDAEYTJfMAN8AQpzaGFwZVBvb2wwCmJvcmRlcl9yXzADfAEKc2hhcGVQb29sMApib3JkZXJfZ18wA3wBCnNoYXBlUG9vbDAKYm9yZGVyX2JfMAN8AQpzaGFwZVBvb2wwCmJvcmRlcl9hXzADfAEKc2hhcGVQb29sMA50aGlja291dGxpbmVfMAN8AQpzaGFwZVBvb2wwCnRleHR1cmVkXzADfAEKc2hhcGVQb29sMAp0ZXhfem9vbV8wA3wBCnNoYXBlUG9vbDAJdGV4X2FuZ18wA3wBCnNoYXBlUG9vbDAKYWRkaXRpdmVfMAN8AQpzaGFwZVBvb2wxA3hfMQN8AQpzaGFwZVBvb2wxA3lfMQN8AQpzaGFwZVBvb2wxBXJhZF8xA3wBCnNoYXBlUG9vbDEFYW5nXzEDfAEKc2hhcGVQb29sMQNyXzEDfAEKc2hhcGVQb29sMQNnXzEDfAEKc2hhcGVQb29sMQNiXzEDfAEKc2hhcGVQb29sMQNhXzEDfAEKc2hhcGVQb29sMQRyMl8xA3wBCnNoYXBlUG9vbDEEZzJfMQN8AQpzaGFwZVBvb2wxBGIyXzEDfAEKc2hhcGVQb29sMQRhMl8xA3wBCnNoYXBlUG9vbDEKYm9yZGVyX3JfMQN8AQpzaGFwZVBvb2wxCmJvcmRlcl9nXzEDfAEKc2hhcGVQb29sMQpib3JkZXJfYl8xA3wBCnNoYXBlUG9vbDEKYm9yZGVyX2FfMQN8AQpzaGFwZVBvb2wxDnRoaWNrb3V0bGluZV8xA3wBCnNoYXBlUG9vbDEKdGV4dHVyZWRfMQN8AQpzaGFwZVBvb2wxCnRleF96b29tXzEDfAEKc2hhcGVQb29sMQl0ZXhfYW5nXzEDfAEKc2hhcGVQb29sMQphZGRpdGl2ZV8xA3wBCnNoYXBlUG9vbDIDeF8yA3wBCnNoYXBlUG9vbDIDeV8yA3wBCnNoYXBlUG9vbDIFcmFkXzIDfAEKc2hhcGVQb29sMgVhbmdfMgN8AQpzaGFwZVBvb2wyA3JfMgN8AQpzaGFwZVBvb2wyA2dfMgN8AQpzaGFwZVBvb2wyA2JfMgN8AQpzaGFwZVBvb2wyA2FfMgN8AQpzaGFwZVBvb2wyBHIyXzIDfAEKc2hhcGVQb29sMgRnMl8yA3wBCnNoYXBlUG9vbDIEYjJfMgN8AQpzaGFwZVBvb2wyBGEyXzIDfAEKc2hhcGVQb29sMgpib3JkZXJfcl8yA3wBCnNoYXBlUG9vbDIKYm9yZGVyX2dfMgN8AQpzaGFwZVBvb2wyCmJvcmRlcl9iXzIDfAEKc2hhcGVQb29sMgpib3JkZXJfYV8yA3wBCnNoYXBlUG9vbDIOdGhpY2tvdXRsaW5lXzIDfAEKc2hhcGVQb29sMgp0ZXh0dXJlZF8yA3wBCnNoYXBlUG9vbDIKdGV4X3pvb21fMgN8AQpzaGFwZVBvb2wyCXRleF9hbmdfMgN8AQpzaGFwZVBvb2wyCmFkZGl0aXZlXzIDfAEKc2hhcGVQb29sMwN4XzMDfAEKc2hhcGVQb29sMwN5XzMDfAEKc2hhcGVQb29sMwVyYWRfMwN8AQpzaGFwZVBvb2wzBWFuZ18zA3wBCnNoYXBlUG9vbDMDcl8zA3wBCnNoYXBlUG9vbDMDZ18zA3wBCnNoYXBlUG9vbDMDYl8zA3wBCnNoYXBlUG9vbDMDYV8zA3wBCnNoYXBlUG9vbDMEcjJfMwN8AQpzaGFwZVBvb2wzBGcyXzMDfAEKc2hhcGVQb29sMwRiMl8zA3wBCnNoYXBlUG9vbDMEYTJfMwN8AQpzaGFwZVBvb2wzCmJvcmRlcl9yXzMDfAEKc2hhcGVQb29sMwpib3JkZXJfZ18zA3wBCnNoYXBlUG9vbDMKYm9yZGVyX2JfMwN8AQpzaGFwZVBvb2wzCmJvcmRlcl9hXzMDfAEKc2hhcGVQb29sMw50aGlja291dGxpbmVfMwN8AQpzaGFwZVBvb2wzCnRleHR1cmVkXzMDfAEKc2hhcGVQb29sMwp0ZXhfem9vbV8zA3wBCnNoYXBlUG9vbDMJdGV4X2FuZ18zA3wBCnNoYXBlUG9vbDMKYWRkaXRpdmVfMwN8AQMZGAgDBwkBAQICAQYFAAAAAAAAAAAAAAAAAAUDAQABBuwMigF8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt8AUQAAAAAAAAAAAt/AUEAC3wBRAAAAAAAAAAAC3wBRAAAAAAAAAAAC34BQgALB9kB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"),{pixelEqs:{perPixelEqs:h(o.exports.perPixel)},pixelVarPool:{warp:r.perVertex.warp,zoom:r.perVertex.zoom,zoomexp:r.perVertex.zoomexp,cx:r.perVertex.cx,cy:r.perVertex.cy,sx:r.perVertex.sx,sy:r.perVertex.sy,dx:r.perVertex.dx,dy:r.perVertex.dy,rot:r.perVertex.rot,x:r.perVertex.x,y:r.perVertex.y,ang:r.perVertex.ang,rad:r.perVertex.rad},qVarPool:i,tVarPool:s,shapePool0:Zt.makeShapeResetPool(r.shapePerFrame0,this.shapeBaseVars,0),shapePool1:Zt.makeShapeResetPool(r.shapePerFrame1,this.shapeBaseVars,1),shapePool2:Zt.makeShapeResetPool(r.shapePerFrame2,this.shapeBaseVars,2),shapePool3:Zt.makeShapeResetPool(r.shapePerFrame3,this.shapeBaseVars,3),console:{logi:t=>{console.log("logi: "+t)},logf:t=>{console.log("logf: "+t)}},env:{abort:()=>{}}});t.globalPools=r,t.init_eqs=h(o.exports.presetInit),t.frame_eqs=h(o.exports.perFrame),t.save_qs=A.exports.saveQs,t.restore_qs=A.exports.restoreQs,t.save_ts=A.exports.saveTs,t.restore_ts=A.exports.restoreTs,o.exports.perPixel&&(t.pixel_eqs=o.exports.perPixel),t.pixel_eqs_initialize_array=(e,i)=>{const s=A.exports.createFloat32Array((e+1)*(i+1)*2);t.pixel_eqs_array=s},t.pixel_eqs_get_array=()=>A.exports.__getFloat32ArrayView(t.pixel_eqs_array),t.pixel_eqs_wasm=(...e)=>A.exports.runPixelEquations(t.pixel_eqs_array,...e);for(let e=0;eA.exports[`shape${e}_save`](),t.shapes[e].frame_eqs_restore=()=>A.exports[`shape${e}_restore`]());for(let e=0;e Date: Wed, 17 Dec 2025 16:51:37 -0800 Subject: [PATCH 58/78] Trusted publishing --- .github/workflows/ci.yml | 48 ++++++++++++++++++++++++++-------------- 1 file changed, 31 insertions(+), 17 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 28e61a06..5d6a8a2e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -50,6 +50,9 @@ jobs: runs-on: ubuntu-latest if: github.event_name == 'push' && github.repository == 'captbaritone/webamp' needs: [ci] + permissions: + contents: read + id-token: write # Required for OIDC trusted publishing steps: - uses: actions/checkout@v4 - name: Install pnpm @@ -61,6 +64,8 @@ jobs: node-version: 20.x registry-url: https://registry.npmjs.org/ cache: "pnpm" + - name: Update npm to latest version + run: npm install -g npm@latest - name: Install dependencies run: pnpm install --frozen-lockfile - name: Restore build artifacts @@ -81,31 +86,40 @@ jobs: cd ../winamp-eqf && npm version 0.0.0-next-${RELEASE_COMMIT_SHA::7} --no-git-tag-version env: RELEASE_COMMIT_SHA: ${{ github.sha }} - - name: Build release version + - name: Set version for tagged release if: github.ref_type == 'tag' && startsWith(github.ref_name, 'v') - run: exit 1 # TODO: Script to update version number in webampLazy.tsx + run: | + VERSION=${GITHUB_REF_NAME#v} + echo "Setting version to $VERSION for tagged release" + cd packages/webamp && npm version $VERSION --no-git-tag-version + cd ../ani-cursor && npm version $VERSION --no-git-tag-version + cd ../winamp-eqf && npm version $VERSION --no-git-tag-version + # TODO: Update version number in webampLazy.tsx if needed - name: Publish ani-cursor to npm working-directory: ./packages/ani-cursor - if: github.ref == 'refs/heads/master' || github.ref_type == 'tag' && startsWith(github.ref_name, 'v') + if: github.ref == 'refs/heads/master' || (github.ref_type == 'tag' && startsWith(github.ref_name, 'v')) run: | - npm publish ${TAG} --ignore-scripts - env: - TAG: ${{ github.ref == 'refs/heads/master' && '--tag=next' || ''}} - NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}} + if [ "${{ github.ref }}" = "refs/heads/master" ]; then + npm publish --tag=next --ignore-scripts --provenance + else + npm publish --ignore-scripts --provenance + fi - name: Publish winamp-eqf to npm working-directory: ./packages/winamp-eqf - if: github.ref == 'refs/heads/master' || github.ref_type == 'tag' && startsWith(github.ref_name, 'v') + if: github.ref == 'refs/heads/master' || (github.ref_type == 'tag' && startsWith(github.ref_name, 'v')) run: | - npm publish ${TAG} --ignore-scripts - env: - TAG: ${{ github.ref == 'refs/heads/master' && '--tag=next' || ''}} - NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}} + if [ "${{ github.ref }}" = "refs/heads/master" ]; then + npm publish --tag=next --ignore-scripts --provenance + else + npm publish --ignore-scripts --provenance + fi - name: Publish webamp to npm working-directory: ./packages/webamp - if: github.ref == 'refs/heads/master' || github.ref_type == 'tag' && startsWith(github.ref_name, 'v') + if: github.ref == 'refs/heads/master' || (github.ref_type == 'tag' && startsWith(github.ref_name, 'v')) # Use pre-built artifacts instead of rebuilding run: | - npm publish ${TAG} --ignore-scripts - env: - TAG: ${{ github.ref == 'refs/heads/master' && '--tag=next' || ''}} - NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}} + if [ "${{ github.ref }}" = "refs/heads/master" ]; then + npm publish --tag=next --ignore-scripts --provenance + else + npm publish --ignore-scripts --provenance + fi From 6997c852f97652c2812a90932ee98a5442b6cec0 Mon Sep 17 00:00:00 2001 From: Jordan Eldredge Date: Fri, 26 Dec 2025 19:17:46 +0000 Subject: [PATCH 59/78] Get uploads working again --- packages/skin-database/cli.ts | 1 + packages/skin-database/data/skins.ts | 1 + 2 files changed, 2 insertions(+) diff --git a/packages/skin-database/cli.ts b/packages/skin-database/cli.ts index 796687e2..b34efb6f 100755 --- a/packages/skin-database/cli.ts +++ b/packages/skin-database/cli.ts @@ -42,6 +42,7 @@ async function withHandler( cb: (handler: DiscordEventHandler) => Promise ) { const handler = new DiscordEventHandler(); + await handler._clientPromise; // Ensure client is initialized try { await cb(handler); } finally { diff --git a/packages/skin-database/data/skins.ts b/packages/skin-database/data/skins.ts index 7fef938c..3ab4c4c2 100644 --- a/packages/skin-database/data/skins.ts +++ b/packages/skin-database/data/skins.ts @@ -411,6 +411,7 @@ export async function getErroredUpload(): Promise<{ .where("status", "ERRORED") .where("skin_md5", "!=", "c7df44bde6eb3671bde5a03e6d03ce1e") .where("skin_md5", "!=", "fedc564eb2ce0a4ec5518b93983240ef") + .where("skin_md5", "!=", "a418fd00583006b6e79cf0b251c43771") .first(["skin_md5", "id", "filename"]); return found || null; } From d87cb6ffa3c2b912a13ff5964ff0431f62edd959 Mon Sep 17 00:00:00 2001 From: Jordan Eldredge Date: Fri, 26 Dec 2025 19:20:27 +0000 Subject: [PATCH 60/78] Switch away from cached api --- packages/skin-database/legacy-client/src/constants.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/skin-database/legacy-client/src/constants.js b/packages/skin-database/legacy-client/src/constants.js index c6cdb481..673f0329 100644 --- a/packages/skin-database/legacy-client/src/constants.js +++ b/packages/skin-database/legacy-client/src/constants.js @@ -33,9 +33,8 @@ export const SKIN_CDN = R2_CDN; // export const SCREENSHOT_CDN = "https://cdn.webampskins.org"; // export const SKIN_CDN = "https://cdn.webampskins.org"; -// Note: This is a Cloudflare proxy for api.webamp.org which -// provides some additional caching. -export const API_URL = "https://api.webampskins.org"; +// Sites have been unified, we can point to ourselves now +export const API_URL = "https://skins.webamp.org"; // export const API_URL = "https://dev.webamp.org"; export const HEADING_HEIGHT = 46; export const CHUNK_SIZE = 300; From 6c732f8e24f7beb633ce91c63340687351970d3e Mon Sep 17 00:00:00 2001 From: Jordan Eldredge Date: Mon, 29 Dec 2025 11:35:45 -0800 Subject: [PATCH 61/78] Add bulk download page --- .../resolvers/BulkDownloadConnection.ts | 93 ++++ .../graphql/resolvers/ClassicSkinResolver.ts | 2 +- .../graphql/resolvers/CommonSkinResolver.ts | 19 +- .../skin-database/api/graphql/schema.graphql | 24 + packages/skin-database/api/graphql/schema.ts | 182 ++++--- .../skin-database/app/bulk-download/page.tsx | 483 ++++++++++++++++++ packages/skin-database/data/SkinModel.ts | 4 +- packages/skin-database/types.ts | 2 +- 8 files changed, 745 insertions(+), 64 deletions(-) create mode 100644 packages/skin-database/api/graphql/resolvers/BulkDownloadConnection.ts create mode 100644 packages/skin-database/app/bulk-download/page.tsx diff --git a/packages/skin-database/api/graphql/resolvers/BulkDownloadConnection.ts b/packages/skin-database/api/graphql/resolvers/BulkDownloadConnection.ts new file mode 100644 index 00000000..c8e2535a --- /dev/null +++ b/packages/skin-database/api/graphql/resolvers/BulkDownloadConnection.ts @@ -0,0 +1,93 @@ +import { Int } from "grats"; +import { knex } from "../../../db"; +import SkinModel from "../../../data/SkinModel"; +import ClassicSkinResolver from "./ClassicSkinResolver"; +import ModernSkinResolver from "./ModernSkinResolver"; +import UserContext from "../../../data/UserContext"; +import { ISkin } from "./CommonSkinResolver"; +import { SkinRow } from "../../../types"; + +/** + * Connection for bulk download skin metadata + * @gqlType + */ +export class BulkDownloadConnection { + _offset: number; + _first: number; + + constructor(first: number, offset: number) { + this._first = first; + this._offset = offset; + } + + /** + * Total number of skins available for download + * @gqlField + */ + async totalCount(): Promise { + // Get count of both classic and modern skins + const [classicResult, modernResult] = await Promise.all([ + knex("skins").where({ skin_type: 1 }).count("* as count"), + knex("skins").where({ skin_type: 2 }).count("* as count"), + ]); + + const classicCount = Number(classicResult[0].count); + const modernCount = Number(modernResult[0].count); + + return classicCount + modernCount; + } + + /** + * Estimated total size in bytes (approximation for progress indication) + * @gqlField + */ + async estimatedSizeBytes(): Promise { + const totalCount = await this.totalCount(); + // Rough estimate: average skin is ~56KB + return (totalCount * 56 * 1024).toString(); + } + + /** + * List of skin metadata for bulk download + * @gqlField + */ + async nodes(ctx: UserContext): Promise> { + // Get skins ordered by skin_type (classic first, then modern) and id for consistency + const skins = await knex("skins") + .select(["id", "md5", "skin_type", "emails"]) + .orderBy([{ column: "skins.id", order: "asc" }]) + .where({ skin_type: 1 }) + .orWhere({ skin_type: 2 }) + .limit(this._first) + .offset(this._offset); + + return skins.map((skinRow) => { + const skinModel = new SkinModel(ctx, skinRow); + + if (skinRow.skin_type === 1) { + return new ClassicSkinResolver(skinModel); + } else if (skinRow.skin_type === 2) { + return new ModernSkinResolver(skinModel); + } else { + throw new Error("Expected classic or modern skin"); + } + }); + } +} + +/** + * Get metadata for bulk downloading all skins in the museum + * @gqlQueryField + */ +export function bulkDownload({ + first = 1000, + offset = 0, +}: { + first?: Int; + offset?: Int; +}): BulkDownloadConnection { + if (first > 10000) { + throw new Error("Maximum limit is 10000 for bulk download"); + } + return new BulkDownloadConnection(first, offset); +} diff --git a/packages/skin-database/api/graphql/resolvers/ClassicSkinResolver.ts b/packages/skin-database/api/graphql/resolvers/ClassicSkinResolver.ts index 8cadeb05..da9433c7 100644 --- a/packages/skin-database/api/graphql/resolvers/ClassicSkinResolver.ts +++ b/packages/skin-database/api/graphql/resolvers/ClassicSkinResolver.ts @@ -41,7 +41,7 @@ export default class ClassicSkinResolver implements NodeResolver, ISkin { nsfw(): Promise { return this._model.getIsNsfw(); } - average_color(): string { + average_color(): string | null { return this._model.getAverageColor(); } /** diff --git a/packages/skin-database/api/graphql/resolvers/CommonSkinResolver.ts b/packages/skin-database/api/graphql/resolvers/CommonSkinResolver.ts index c9d343a2..1ecd3800 100644 --- a/packages/skin-database/api/graphql/resolvers/CommonSkinResolver.ts +++ b/packages/skin-database/api/graphql/resolvers/CommonSkinResolver.ts @@ -69,19 +69,34 @@ export function id(skin: ISkin): ID { * has been uploaded under multiple names. Here we just pick one. * @gqlField */ -export function filename( +export async function filename( skin: ISkin, { normalize_extension = false, + include_museum_id = false, }: { /** * If true, the the correct file extension (.wsz or .wal) will be . * Otherwise, the original user-uploaded file extension will be used. */ normalize_extension?: boolean; + /** + * If true, the museum ID will be appended to the filename to ensure filenames are globally unique. + */ + include_museum_id?: boolean; } ): Promise { - return skin.filename(normalize_extension); + const baseFilename = await skin.filename(normalize_extension); + + if (!include_museum_id) { + return baseFilename; + } + + const museumId = skin._model.getId(); + const segments = baseFilename.split("."); + const fileExtension = segments.pop(); + + return `${segments.join(".")}_[S${museumId}].${fileExtension}`; } /** diff --git a/packages/skin-database/api/graphql/schema.graphql b/packages/skin-database/api/graphql/schema.graphql index a7a02820..5379b060 100644 --- a/packages/skin-database/api/graphql/schema.graphql +++ b/packages/skin-database/api/graphql/schema.graphql @@ -93,6 +93,10 @@ interface Skin { has been uploaded under multiple names. Here we just pick one. """ filename( + """ + If true, the museum ID will be appended to the filename to ensure filenames are globally unique. + """ + include_museum_id: Boolean! = false """ If true, the the correct file extension (.wsz or .wal) will be . Otherwise, the original user-uploaded file extension will be used. @@ -165,6 +169,16 @@ type ArchiveFile { url: String } +"""Connection for bulk download skin metadata""" +type BulkDownloadConnection { + """Estimated total size in bytes (approximation for progress indication)""" + estimatedSizeBytes: String @semanticNonNull + """List of skin metadata for bulk download""" + nodes: [Skin!] @semanticNonNull + """Total number of skins available for download""" + totalCount: Int @semanticNonNull +} + """A classic Winamp skin""" type ClassicSkin implements Node & Skin { """List of files contained within the skin's .wsz archive""" @@ -178,6 +192,10 @@ type ClassicSkin implements Node & Skin { has been uploaded under multiple names. Here we just pick one. """ filename( + """ + If true, the museum ID will be appended to the filename to ensure filenames are globally unique. + """ + include_museum_id: Boolean! = false """ If true, the the correct file extension (.wsz or .wal) will be . Otherwise, the original user-uploaded file extension will be used. @@ -308,6 +326,10 @@ type ModernSkin implements Node & Skin { has been uploaded under multiple names. Here we just pick one. """ filename( + """ + If true, the museum ID will be appended to the filename to ensure filenames are globally unique. + """ + include_museum_id: Boolean! = false """ If true, the the correct file extension (.wsz or .wal) will be . Otherwise, the original user-uploaded file extension will be used. @@ -385,6 +407,8 @@ type Mutation { } type Query { + """Get metadata for bulk downloading all skins in the museum""" + bulkDownload(first: Int! = 1000, offset: Int! = 0): BulkDownloadConnection @semanticNonNull """ Fetch archive file by it's MD5 hash diff --git a/packages/skin-database/api/graphql/schema.ts b/packages/skin-database/api/graphql/schema.ts index 4d9c0855..c6f19209 100644 --- a/packages/skin-database/api/graphql/schema.ts +++ b/packages/skin-database/api/graphql/schema.ts @@ -3,8 +3,9 @@ * Do not manually edit. Regenerate by running `npx grats`. */ -import { GraphQLSchema, GraphQLDirective, DirectiveLocation, GraphQLList, GraphQLInt, specifiedDirectives, GraphQLObjectType, GraphQLString, GraphQLBoolean, GraphQLInterfaceType, GraphQLNonNull, GraphQLID, GraphQLEnumType, defaultFieldResolver, GraphQLInputObjectType } from "graphql"; +import { GraphQLSchema, GraphQLDirective, DirectiveLocation, GraphQLList, GraphQLInt, specifiedDirectives, GraphQLObjectType, GraphQLString, defaultFieldResolver, GraphQLNonNull, GraphQLInterfaceType, GraphQLBoolean, GraphQLID, GraphQLEnumType, GraphQLInputObjectType } from "graphql"; import { getUserContext } from "./index"; +import { bulkDownload as queryBulkDownloadResolver } from "./resolvers/BulkDownloadConnection"; import { fetch_archive_file_by_md5 as queryFetch_archive_file_by_md5Resolver } from "./../../data/ArchiveFileModel"; import { fetch_internet_archive_item_by_identifier as queryFetch_internet_archive_item_by_identifierResolver } from "./../../data/IaItemModel"; import { fetch_skin_by_md5 as queryFetch_skin_by_md5Resolver, search_classic_skins as querySearch_classic_skinsResolver, search_skins as querySearch_skinsResolver, skin_to_review as querySkin_to_reviewResolver } from "./resolvers/SkinResolver"; @@ -27,6 +28,75 @@ async function assertNonNull(value: T | Promise): Promise { return awaited; } export function getSchema(): GraphQLSchema { + const ArchiveFileType: GraphQLObjectType = new GraphQLObjectType({ + name: "ArchiveFile", + description: "A file found within a Winamp Skin's .wsz archive", + fields() { + return { + date: { + description: "The date on the file inside the archive. Given in simplified extended ISO\nformat (ISO 8601).", + name: "date", + type: GraphQLString, + resolve(source) { + return assertNonNull(source.getIsoDate()); + } + }, + file_md5: { + description: "The md5 hash of the file within the archive", + name: "file_md5", + type: GraphQLString, + resolve(source) { + return assertNonNull(source.getFileMd5()); + } + }, + filename: { + description: "Filename of the file within the archive", + name: "filename", + type: GraphQLString, + resolve(source) { + return assertNonNull(source.getFileName()); + } + }, + is_directory: { + description: "Is the file a directory?", + name: "is_directory", + type: GraphQLBoolean, + resolve(source) { + return assertNonNull(source.getIsDirectory()); + } + }, + size: { + description: "The uncompressed size of the file in bytes.\n\n**Note:** Will be `null` for directories", + name: "size", + type: GraphQLInt, + resolve(source) { + return source.getFileSize(); + } + }, + skin: { + description: "The skin in which this file was found", + name: "skin", + type: SkinType + }, + text_content: { + description: "The content of the file, if it's a text file", + name: "text_content", + type: GraphQLString, + resolve(source) { + return source.getTextContent(); + } + }, + url: { + description: "A URL to download the file. **Note:** This is powered by a little\nserverless Cloudflare function which tries to exctact the file on the fly.\nIt may not work for all files.", + name: "url", + type: GraphQLString, + resolve(source) { + return source.getUrl(); + } + } + }; + } + }); const InternetArchiveItemType: GraphQLObjectType = new GraphQLObjectType({ name: "InternetArchiveItem", fields() { @@ -188,6 +258,11 @@ export function getSchema(): GraphQLSchema { name: "filename", type: GraphQLString, args: { + include_museum_id: { + description: "If true, the museum ID will be appended to the filename to ensure filenames are globally unique.", + type: new GraphQLNonNull(GraphQLBoolean), + defaultValue: false + }, normalize_extension: { description: "If true, the the correct file extension (.wsz or .wal) will be .\nOtherwise, the original user-uploaded file extension will be used.", type: new GraphQLNonNull(GraphQLBoolean), @@ -253,70 +328,33 @@ export function getSchema(): GraphQLSchema { }; } }); - const ArchiveFileType: GraphQLObjectType = new GraphQLObjectType({ - name: "ArchiveFile", - description: "A file found within a Winamp Skin's .wsz archive", + const BulkDownloadConnectionType: GraphQLObjectType = new GraphQLObjectType({ + name: "BulkDownloadConnection", + description: "Connection for bulk download skin metadata", fields() { return { - date: { - description: "The date on the file inside the archive. Given in simplified extended ISO\nformat (ISO 8601).", - name: "date", + estimatedSizeBytes: { + description: "Estimated total size in bytes (approximation for progress indication)", + name: "estimatedSizeBytes", type: GraphQLString, - resolve(source) { - return assertNonNull(source.getIsoDate()); + resolve(source, args, context, info) { + return assertNonNull(defaultFieldResolver(source, args, context, info)); } }, - file_md5: { - description: "The md5 hash of the file within the archive", - name: "file_md5", - type: GraphQLString, - resolve(source) { - return assertNonNull(source.getFileMd5()); + nodes: { + description: "List of skin metadata for bulk download", + name: "nodes", + type: new GraphQLList(new GraphQLNonNull(SkinType)), + resolve(source, _args, context) { + return assertNonNull(source.nodes(getUserContext(context))); } }, - filename: { - description: "Filename of the file within the archive", - name: "filename", - type: GraphQLString, - resolve(source) { - return assertNonNull(source.getFileName()); - } - }, - is_directory: { - description: "Is the file a directory?", - name: "is_directory", - type: GraphQLBoolean, - resolve(source) { - return assertNonNull(source.getIsDirectory()); - } - }, - size: { - description: "The uncompressed size of the file in bytes.\n\n**Note:** Will be `null` for directories", - name: "size", + totalCount: { + description: "Total number of skins available for download", + name: "totalCount", type: GraphQLInt, - resolve(source) { - return source.getFileSize(); - } - }, - skin: { - description: "The skin in which this file was found", - name: "skin", - type: SkinType - }, - text_content: { - description: "The content of the file, if it's a text file", - name: "text_content", - type: GraphQLString, - resolve(source) { - return source.getTextContent(); - } - }, - url: { - description: "A URL to download the file. **Note:** This is powered by a little\nserverless Cloudflare function which tries to exctact the file on the fly.\nIt may not work for all files.", - name: "url", - type: GraphQLString, - resolve(source) { - return source.getUrl(); + resolve(source, args, context, info) { + return assertNonNull(defaultFieldResolver(source, args, context, info)); } } }; @@ -382,6 +420,11 @@ export function getSchema(): GraphQLSchema { name: "filename", type: GraphQLString, args: { + include_museum_id: { + description: "If true, the museum ID will be appended to the filename to ensure filenames are globally unique.", + type: new GraphQLNonNull(GraphQLBoolean), + defaultValue: false + }, normalize_extension: { description: "If true, the the correct file extension (.wsz or .wal) will be .\nOtherwise, the original user-uploaded file extension will be used.", type: new GraphQLNonNull(GraphQLBoolean), @@ -544,6 +587,11 @@ export function getSchema(): GraphQLSchema { name: "filename", type: GraphQLString, args: { + include_museum_id: { + description: "If true, the museum ID will be appended to the filename to ensure filenames are globally unique.", + type: new GraphQLNonNull(GraphQLBoolean), + defaultValue: false + }, normalize_extension: { description: "If true, the the correct file extension (.wsz or .wal) will be .\nOtherwise, the original user-uploaded file extension will be used.", type: new GraphQLNonNull(GraphQLBoolean), @@ -901,6 +949,24 @@ export function getSchema(): GraphQLSchema { name: "Query", fields() { return { + bulkDownload: { + description: "Get metadata for bulk downloading all skins in the museum", + name: "bulkDownload", + type: BulkDownloadConnectionType, + args: { + first: { + type: new GraphQLNonNull(GraphQLInt), + defaultValue: 1000 + }, + offset: { + type: new GraphQLNonNull(GraphQLInt), + defaultValue: 0 + } + }, + resolve(_source, args) { + return assertNonNull(queryBulkDownloadResolver(args)); + } + }, fetch_archive_file_by_md5: { description: "Fetch archive file by it's MD5 hash\n\nGet information about a file found within a skin's wsz/wal/zip archive.", name: "fetch_archive_file_by_md5", @@ -1307,6 +1373,6 @@ export function getSchema(): GraphQLSchema { })], query: QueryType, mutation: MutationType, - types: [RatingType, SkinUploadStatusType, SkinsFilterOptionType, SkinsSortOptionType, TweetsSortOptionType, NodeType, SkinType, UploadUrlRequestType, ArchiveFileType, ClassicSkinType, DatabaseStatisticsType, InternetArchiveItemType, ModernSkinType, ModernSkinsConnectionType, MutationType, QueryType, ReviewType, SkinUploadType, SkinsConnectionType, TweetType, TweetsConnectionType, UploadMutationsType, UploadUrlType, UserType] + types: [RatingType, SkinUploadStatusType, SkinsFilterOptionType, SkinsSortOptionType, TweetsSortOptionType, NodeType, SkinType, UploadUrlRequestType, ArchiveFileType, BulkDownloadConnectionType, ClassicSkinType, DatabaseStatisticsType, InternetArchiveItemType, ModernSkinType, ModernSkinsConnectionType, MutationType, QueryType, ReviewType, SkinUploadType, SkinsConnectionType, TweetType, TweetsConnectionType, UploadMutationsType, UploadUrlType, UserType] }); } diff --git a/packages/skin-database/app/bulk-download/page.tsx b/packages/skin-database/app/bulk-download/page.tsx new file mode 100644 index 00000000..5c02e52b --- /dev/null +++ b/packages/skin-database/app/bulk-download/page.tsx @@ -0,0 +1,483 @@ +"use client"; + +import { useState, useCallback, useRef, useEffect } from "react"; +import { fetchGraphql, gql } from "../../legacy-client/src/utils"; + +interface BulkDownloadSkin { + md5: string; + filename: string; + download_url: string; + __typename: string; +} + +interface DownloadProgress { + totalSkins: number; + completedSkins: number; + failedSkins: number; + estimatedSizeBytes: string; + activeDownloads: Array<{ + filename: string; + md5: string; + status: "downloading" | "failed"; + error?: string; + }>; +} + +interface DirectoryHandle { + name: string; + getDirectoryHandle: ( + name: string, + options?: { create?: boolean } + ) => Promise; + getFileHandle: ( + name: string, + options?: { create?: boolean } + ) => Promise; +} + +declare global { + interface Window { + showDirectoryPicker?: () => Promise; + } +} + +const BULK_DOWNLOAD_QUERY = gql` + query BulkDownload($offset: Int!, $first: Int!) { + bulkDownload(offset: $offset, first: $first) { + totalCount + estimatedSizeBytes + nodes { + __typename + md5 + filename(normalize_extension: true, include_museum_id: true) + download_url + } + } + } +`; + +const MAX_CONCURRENT_DOWNLOADS = 6; +const CHUNK_SIZE = 1000; + +export default function BulkDownloadPage() { + const [directoryHandle, setDirectoryHandle] = + useState(null); + const [progress, setProgress] = useState({ + totalSkins: 0, + completedSkins: 0, + failedSkins: 0, + estimatedSizeBytes: "0", + activeDownloads: [], + }); + const [isDownloading, setIsDownloading] = useState(false); + const [error, setError] = useState(null); + const [isSupported] = useState( + typeof window !== "undefined" && "showDirectoryPicker" in window + ); + const abortController = useRef(null); + + const downloadSkin = useCallback( + async ( + skin: BulkDownloadSkin, + directoryHandle: DirectoryHandle, + signal: AbortSignal + ): Promise => { + const { filename, download_url, md5 } = skin; + + // Get the target directory and file path + const targetDirectory = await getDirectoryForSkin( + filename, + directoryHandle + ); + // Check if file already exists + try { + await targetDirectory.getFileHandle(filename); + // File exists, skip download + console.log(`Skipping ${filename} - already exists`); + setProgress((prev) => ({ + ...prev, + completedSkins: prev.completedSkins + 1, + })); + return; + } catch (error) { + // File doesn't exist, continue with download + } + + // Add to active downloads + setProgress((prev) => ({ + ...prev, + activeDownloads: [ + ...prev.activeDownloads, + { + filename, + md5, + status: "downloading" as const, + }, + ], + })); + + try { + const response = await fetch(download_url, { signal }); + if (!response.ok) { + throw new Error(`HTTP ${response.status}: ${response.statusText}`); + } + + // We don't need individual progress tracking anymore + // const contentLength = parseInt(response.headers.get("content-length") || "0", 10); + const reader = response.body?.getReader(); + + if (!reader) { + throw new Error("No response body"); + } + + // Use the targetDirectory and finalFilename we calculated earlier + const fileHandle = await targetDirectory.getFileHandle(filename, { + create: true, + }); + const writable = await fileHandle.createWritable(); + + // Track total bytes for this file (not needed for individual progress) + + try { + while (true) { + const { done, value } = await reader.read(); + if (done) break; + + await writable.write(value); + } + + await writable.close(); + + // Mark as completed and immediately remove from active downloads + setProgress((prev) => ({ + ...prev, + completedSkins: prev.completedSkins + 1, + activeDownloads: prev.activeDownloads.filter((d) => d.md5 !== md5), + })); + } catch (writeError) { + await writable.abort("Failed to write file"); + throw writeError; + } + } catch (error: any) { + if (error.name === "AbortError") { + console.log(`Download aborted: ${filename}`); + throw error; // Re-throw abort errors + } + + // Mark as failed and schedule removal + setProgress((prev) => ({ + ...prev, + failedSkins: prev.failedSkins + 1, + activeDownloads: prev.activeDownloads.map((d) => + d.md5 === md5 + ? { + ...d, + status: "failed" as const, + error: error.message, + } + : d + ), + })); + + // Remove failed download after 3 seconds + setTimeout(() => { + setProgress((prev) => ({ + ...prev, + activeDownloads: prev.activeDownloads.filter((d) => d.md5 !== md5), + })); + }, 3000); + + console.error(`Failed to download ${filename}:`, error); + } + }, + [getDirectoryForSkin] + ); + + // Load initial metadata when component mounts + useEffect(() => { + async function loadInitialData() { + try { + const { totalCount, estimatedSizeBytes } = await fetchSkins(0, 1); + setProgress((prev) => ({ + ...prev, + totalSkins: totalCount, + estimatedSizeBytes, + })); + } catch (error: any) { + console.error("Failed to load initial data:", error); + setError("Failed to load skin count information"); + } + } + + loadInitialData(); + }, [fetchSkins]); + + const selectDirectoryAndStart = useCallback(async () => { + // First, select directory if not already selected + if (!directoryHandle) { + if (!window.showDirectoryPicker) { + setError( + "File System Access API is not supported in this browser. Please use Chrome or Edge." + ); + return; + } + + try { + const handle = await window.showDirectoryPicker(); + setDirectoryHandle(handle); + setError(null); + + // Now start the download with the new directory + await startDownloadWithDirectory(handle as FileSystemDirectoryHandle); + } catch (err: any) { + if (err.name !== "AbortError") { + setError(`Failed to select directory: ${err.message}`); + } + } + } else { + // Directory already selected, just start download + await startDownloadWithDirectory( + directoryHandle as FileSystemDirectoryHandle + ); + } + }, [directoryHandle]); + + const startDownloadWithDirectory = useCallback( + async (handle: FileSystemDirectoryHandle) => { + setIsDownloading(true); + setError(null); + // setStartTime(Date.now()); + abortController.current = new AbortController(); + + try { + // Get initial metadata + const { totalCount, estimatedSizeBytes } = await fetchSkins(0, 1); + + setProgress({ + totalSkins: totalCount, + completedSkins: 0, + failedSkins: 0, + estimatedSizeBytes, + activeDownloads: [], + }); + + let offset = 0; + const activePromises = new Set>(); + + while (offset < totalCount && !abortController.current.signal.aborted) { + console.log(`Fetching batch: offset=${offset}, chunk=${CHUNK_SIZE}`); + + try { + const { skins } = await fetchSkins(offset, CHUNK_SIZE); + console.log(`Retrieved ${skins.length} skins in this batch`); + + if (skins.length === 0) { + console.log("No more skins to fetch, breaking"); + break; + } + + for (const skin of skins) { + if (abortController.current.signal.aborted) break; + + await waitForAvailableSlot( + activePromises, + abortController.current.signal + ); + + if (abortController.current.signal.aborted) break; + + const downloadPromise = downloadSkin( + skin, + handle, + abortController.current.signal + ).finally(() => { + activePromises.delete(downloadPromise); + }); + + activePromises.add(downloadPromise); + } + + offset += skins.length; + console.log(`Completed batch, new offset: ${offset}/${totalCount}`); + } catch (error: any) { + console.error(`Failed to fetch batch at offset ${offset}:`, error); + setError(`Failed to fetch skins: ${error.message}`); + break; + } + } + + // Wait for all remaining downloads to complete + await Promise.allSettled(activePromises); + console.log("All downloads completed!"); + } catch (error: any) { + if (error.name !== "AbortError") { + setError(`Download failed: ${error.message}`); + } + } finally { + setIsDownloading(false); + } + }, + [fetchSkins, downloadSkin] + ); + + const stopDownload = useCallback(() => { + if (abortController.current) { + abortController.current.abort("User Canceled"); + } + setIsDownloading(false); + // setStartTime(null); + }, []); + + const progressPercent = + progress.totalSkins > 0 + ? ((progress.completedSkins + progress.failedSkins) / + progress.totalSkins) * + 100 + : 0; + + if (!isSupported) { + return

Your browser does not support filesystem access.

; + } + + const gb = Math.round( + parseInt(progress.estimatedSizeBytes || "0", 10) / (1024 * 1024 * 1024) + ); + + return ( +
+
+
+
+

Bulk Download All Skins

+

+ Download the entire Winamp Skin Museum collection. +

    +
  • + Will download {progress.totalSkins.toLocaleString()} files (~ + {gb} + GB) into the selected directory +
  • +
  • + Files will be organized into directories (aa-zz, 0-9) based on + filename prefix +
  • +
  • + Supports resuming from previously interrupted bulk download +
  • +
+

+
+
+ + {error && ( +
+
{error}
+
+ )} + + {/* Download Controls */} +
+ {isDownloading ? ( + + ) : ( + + )} +
+ + {/* Progress Section */} + {(isDownloading || progress.completedSkins > 0) && ( +
+
+ + Downloaded{" "} + {( + progress.completedSkins + progress.failedSkins + ).toLocaleString()}{" "} + of {progress.totalSkins.toLocaleString()} skins + + {Math.round(progressPercent)}% complete +
+ +
+
+
+
+ )} +
+
+ ); +} + +async function getDirectoryForSkin( + filename: string, + rootHandle: DirectoryHandle +) { + // Create directory based on first two characters of filename (case insensitive) + const firstChar = filename.charAt(0).toLowerCase(); + const secondChar = + filename.length > 1 ? filename.charAt(1).toLowerCase() : ""; + + let dirName: string; + if (/[a-z]/.test(firstChar)) { + // For letters, use two-character prefix if second char is alphanumeric + if (/[a-z0-9]/.test(secondChar)) { + dirName = firstChar + secondChar; + } else { + // Fallback to single letter + 'x' for special characters + dirName = firstChar + "x"; + } + } else { + // For numbers/symbols, use "0-9" + dirName = "0-9"; + } + + try { + return await rootHandle.getDirectoryHandle(dirName, { create: true }); + } catch (err) { + console.warn(`Failed to create directory ${dirName}, using root:`, err); + return rootHandle; + } +} + +async function fetchSkins( + offset: number, + first: number +): Promise<{ + skins: BulkDownloadSkin[]; + totalCount: number; + estimatedSizeBytes: string; +}> { + const { bulkDownload } = await fetchGraphql(BULK_DOWNLOAD_QUERY, { + offset, + first, + }); + return { + skins: bulkDownload.nodes, + totalCount: bulkDownload.totalCount, + estimatedSizeBytes: bulkDownload.estimatedSizeBytes, + }; +} +// Helper function to wait for an available download slot +async function waitForAvailableSlot( + activePromises: Set>, + signal: AbortSignal +) { + while (activePromises.size >= MAX_CONCURRENT_DOWNLOADS && !signal.aborted) { + await Promise.race(activePromises); + } +} diff --git a/packages/skin-database/data/SkinModel.ts b/packages/skin-database/data/SkinModel.ts index d7543c39..450c2a02 100644 --- a/packages/skin-database/data/SkinModel.ts +++ b/packages/skin-database/data/SkinModel.ts @@ -232,8 +232,8 @@ export default class SkinModel { } } - getAverageColor(): string { - return this.row.average_color; + getAverageColor(): string | null { + return this.row.average_color ?? null; } getBuffer = mem(async (): Promise => { diff --git a/packages/skin-database/types.ts b/packages/skin-database/types.ts index d8ad0ebb..19e476f3 100644 --- a/packages/skin-database/types.ts +++ b/packages/skin-database/types.ts @@ -19,7 +19,7 @@ export type SkinRow = { skin_type: number; emails: string; // readme_text: string; - average_color: string; + average_color?: string; }; export type TweetRow = { From 91618c9c6b140d9f7c55606f1e327ceb72a3c522 Mon Sep 17 00:00:00 2001 From: Jordan Eldredge Date: Mon, 29 Dec 2025 11:52:11 -0800 Subject: [PATCH 62/78] Block downloading skin that seems to trigger security block --- .../resolvers/BulkDownloadConnection.ts | 6 ++++ .../skin-database/app/bulk-download/page.tsx | 32 +++++++++---------- 2 files changed, 21 insertions(+), 17 deletions(-) diff --git a/packages/skin-database/api/graphql/resolvers/BulkDownloadConnection.ts b/packages/skin-database/api/graphql/resolvers/BulkDownloadConnection.ts index c8e2535a..56da9a08 100644 --- a/packages/skin-database/api/graphql/resolvers/BulkDownloadConnection.ts +++ b/packages/skin-database/api/graphql/resolvers/BulkDownloadConnection.ts @@ -58,6 +58,12 @@ export class BulkDownloadConnection { .orderBy([{ column: "skins.id", order: "asc" }]) .where({ skin_type: 1 }) .orWhere({ skin_type: 2 }) + .where((builder) => { + builder.where({ skin_type: 1 }).orWhere({ skin_type: 2 }); + }) + // https://www.virustotal.com/gui/file/cc75df902c1e128433a7f7b8635aa928fe4cefbdcd91564b7e66305a25edd538 + // This got flagged as malicious. Unclear if it's a false positive or real. + .whereNot({ md5: "5dac271c708d620db7b29d5bcf1598c2" }) .limit(this._first) .offset(this._offset); diff --git a/packages/skin-database/app/bulk-download/page.tsx b/packages/skin-database/app/bulk-download/page.tsx index 5c02e52b..e8b8b81e 100644 --- a/packages/skin-database/app/bulk-download/page.tsx +++ b/packages/skin-database/app/bulk-download/page.tsx @@ -349,23 +349,21 @@ export default function BulkDownloadPage() {

Bulk Download All Skins

-

- Download the entire Winamp Skin Museum collection. -

    -
  • - Will download {progress.totalSkins.toLocaleString()} files (~ - {gb} - GB) into the selected directory -
  • -
  • - Files will be organized into directories (aa-zz, 0-9) based on - filename prefix -
  • -
  • - Supports resuming from previously interrupted bulk download -
  • -
-

+

Download the entire Winamp Skin Museum collection.

+
    +
  • + Will download {progress.totalSkins.toLocaleString()} files (~ + {gb} + GB) into the selected directory +
  • +
  • + Files will be organized into directories (aa-zz, 0-9) based on + filename prefix +
  • +
  • + Supports resuming from previously interrupted bulk download +
  • +
From 3b4e5b17c3a144204c6b67b7992eaf21e6e2bdd3 Mon Sep 17 00:00:00 2001 From: Jordan Eldredge Date: Mon, 29 Dec 2025 13:15:46 -0800 Subject: [PATCH 63/78] Remove blocklist of skins with viruses. These are now purged. --- .../api/graphql/resolvers/BulkDownloadConnection.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/packages/skin-database/api/graphql/resolvers/BulkDownloadConnection.ts b/packages/skin-database/api/graphql/resolvers/BulkDownloadConnection.ts index 56da9a08..bc97e0b2 100644 --- a/packages/skin-database/api/graphql/resolvers/BulkDownloadConnection.ts +++ b/packages/skin-database/api/graphql/resolvers/BulkDownloadConnection.ts @@ -61,9 +61,6 @@ export class BulkDownloadConnection { .where((builder) => { builder.where({ skin_type: 1 }).orWhere({ skin_type: 2 }); }) - // https://www.virustotal.com/gui/file/cc75df902c1e128433a7f7b8635aa928fe4cefbdcd91564b7e66305a25edd538 - // This got flagged as malicious. Unclear if it's a false positive or real. - .whereNot({ md5: "5dac271c708d620db7b29d5bcf1598c2" }) .limit(this._first) .offset(this._offset); From 0895f9191f523e0819fca7a0093c4ab0dad8c7a8 Mon Sep 17 00:00:00 2001 From: Jordan Eldredge Date: Mon, 29 Dec 2025 15:17:06 -0800 Subject: [PATCH 64/78] Add explicit app routes --- packages/skin-database/app/(legacy)/about/page.tsx | 3 +++ packages/skin-database/app/(legacy)/feedback/page.tsx | 3 +++ packages/skin-database/app/(legacy)/layout.tsx | 5 +++++ .../app/{ => (legacy)}/skin/[hash]/[fileName]/page.tsx | 3 +-- .../app/(legacy)/skin/[hash]/debug/[fileName]/page.tsx | 3 +++ .../skin-database/app/(legacy)/skin/[hash]/debug/page.tsx | 3 +++ .../skin-database/app/{ => (legacy)}/skin/[hash]/page.tsx | 3 +-- .../app/{ => (legacy)}/skin/[hash]/skinMetadata.ts | 4 ++-- packages/skin-database/app/(legacy)/upload/page.tsx | 3 +++ packages/skin-database/app/{[[...folderName]] => }/page.tsx | 2 +- 10 files changed, 25 insertions(+), 7 deletions(-) create mode 100644 packages/skin-database/app/(legacy)/about/page.tsx create mode 100644 packages/skin-database/app/(legacy)/feedback/page.tsx create mode 100644 packages/skin-database/app/(legacy)/layout.tsx rename packages/skin-database/app/{ => (legacy)}/skin/[hash]/[fileName]/page.tsx (85%) create mode 100644 packages/skin-database/app/(legacy)/skin/[hash]/debug/[fileName]/page.tsx create mode 100644 packages/skin-database/app/(legacy)/skin/[hash]/debug/page.tsx rename packages/skin-database/app/{ => (legacy)}/skin/[hash]/page.tsx (85%) rename packages/skin-database/app/{ => (legacy)}/skin/[hash]/skinMetadata.ts (91%) create mode 100644 packages/skin-database/app/(legacy)/upload/page.tsx rename packages/skin-database/app/{[[...folderName]] => }/page.tsx (98%) diff --git a/packages/skin-database/app/(legacy)/about/page.tsx b/packages/skin-database/app/(legacy)/about/page.tsx new file mode 100644 index 00000000..67e08591 --- /dev/null +++ b/packages/skin-database/app/(legacy)/about/page.tsx @@ -0,0 +1,3 @@ +export default function Page() { + return null; +} diff --git a/packages/skin-database/app/(legacy)/feedback/page.tsx b/packages/skin-database/app/(legacy)/feedback/page.tsx new file mode 100644 index 00000000..67e08591 --- /dev/null +++ b/packages/skin-database/app/(legacy)/feedback/page.tsx @@ -0,0 +1,3 @@ +export default function Page() { + return null; +} diff --git a/packages/skin-database/app/(legacy)/layout.tsx b/packages/skin-database/app/(legacy)/layout.tsx new file mode 100644 index 00000000..f13532e7 --- /dev/null +++ b/packages/skin-database/app/(legacy)/layout.tsx @@ -0,0 +1,5 @@ +import App from "../App"; + +export default function Layout() { + return ; +} diff --git a/packages/skin-database/app/skin/[hash]/[fileName]/page.tsx b/packages/skin-database/app/(legacy)/skin/[hash]/[fileName]/page.tsx similarity index 85% rename from packages/skin-database/app/skin/[hash]/[fileName]/page.tsx rename to packages/skin-database/app/(legacy)/skin/[hash]/[fileName]/page.tsx index 0000b2eb..2316d806 100644 --- a/packages/skin-database/app/skin/[hash]/[fileName]/page.tsx +++ b/packages/skin-database/app/(legacy)/skin/[hash]/[fileName]/page.tsx @@ -1,4 +1,3 @@ -import App from "../../../App"; import type { Metadata } from "next"; import { generateSkinPageMetadata } from "../skinMetadata"; @@ -8,5 +7,5 @@ export async function generateMetadata({ params }): Promise { } export default function Page() { - return ; + return null; } diff --git a/packages/skin-database/app/(legacy)/skin/[hash]/debug/[fileName]/page.tsx b/packages/skin-database/app/(legacy)/skin/[hash]/debug/[fileName]/page.tsx new file mode 100644 index 00000000..67e08591 --- /dev/null +++ b/packages/skin-database/app/(legacy)/skin/[hash]/debug/[fileName]/page.tsx @@ -0,0 +1,3 @@ +export default function Page() { + return null; +} diff --git a/packages/skin-database/app/(legacy)/skin/[hash]/debug/page.tsx b/packages/skin-database/app/(legacy)/skin/[hash]/debug/page.tsx new file mode 100644 index 00000000..67e08591 --- /dev/null +++ b/packages/skin-database/app/(legacy)/skin/[hash]/debug/page.tsx @@ -0,0 +1,3 @@ +export default function Page() { + return null; +} diff --git a/packages/skin-database/app/skin/[hash]/page.tsx b/packages/skin-database/app/(legacy)/skin/[hash]/page.tsx similarity index 85% rename from packages/skin-database/app/skin/[hash]/page.tsx rename to packages/skin-database/app/(legacy)/skin/[hash]/page.tsx index ad767245..0dc9fbf1 100644 --- a/packages/skin-database/app/skin/[hash]/page.tsx +++ b/packages/skin-database/app/(legacy)/skin/[hash]/page.tsx @@ -1,4 +1,3 @@ -import App from "../../App"; import type { Metadata } from "next"; import { generateSkinPageMetadata } from "./skinMetadata"; @@ -8,5 +7,5 @@ export async function generateMetadata({ params }): Promise { } export default function Page() { - return ; + return null; } diff --git a/packages/skin-database/app/skin/[hash]/skinMetadata.ts b/packages/skin-database/app/(legacy)/skin/[hash]/skinMetadata.ts similarity index 91% rename from packages/skin-database/app/skin/[hash]/skinMetadata.ts rename to packages/skin-database/app/(legacy)/skin/[hash]/skinMetadata.ts index a7685659..d8ffe9b9 100644 --- a/packages/skin-database/app/skin/[hash]/skinMetadata.ts +++ b/packages/skin-database/app/(legacy)/skin/[hash]/skinMetadata.ts @@ -1,6 +1,6 @@ import { Metadata } from "next"; -import SkinModel from "../../../data/SkinModel"; -import UserContext from "../../../data/UserContext"; +import SkinModel from "../../../../data/SkinModel"; +import UserContext from "../../../../data/UserContext"; export async function generateSkinPageMetadata( hash: string diff --git a/packages/skin-database/app/(legacy)/upload/page.tsx b/packages/skin-database/app/(legacy)/upload/page.tsx new file mode 100644 index 00000000..67e08591 --- /dev/null +++ b/packages/skin-database/app/(legacy)/upload/page.tsx @@ -0,0 +1,3 @@ +export default function Page() { + return null; +} diff --git a/packages/skin-database/app/[[...folderName]]/page.tsx b/packages/skin-database/app/page.tsx similarity index 98% rename from packages/skin-database/app/[[...folderName]]/page.tsx rename to packages/skin-database/app/page.tsx index 31b59f8a..958897a0 100644 --- a/packages/skin-database/app/[[...folderName]]/page.tsx +++ b/packages/skin-database/app/page.tsx @@ -1,4 +1,4 @@ -import App from "../App"; +import App from "./App"; import type { Metadata } from "next"; const DESCRIPTION = From e062a51a8811f20396d55f46b13fc9cbd9b93aa7 Mon Sep 17 00:00:00 2001 From: Jordan Eldredge Date: Mon, 29 Dec 2025 15:32:03 -0800 Subject: [PATCH 65/78] Improve scroll UI --- .../app/(modern)/scroll/BottomMenuBar.tsx | 48 +++++++++++-------- .../app/(modern)/scroll/grid/layout.tsx | 2 +- .../app/(modern)/scroll/layout.tsx | 7 ++- packages/skin-database/tsconfig.json | 18 ++----- 4 files changed, 39 insertions(+), 36 deletions(-) diff --git a/packages/skin-database/app/(modern)/scroll/BottomMenuBar.tsx b/packages/skin-database/app/(modern)/scroll/BottomMenuBar.tsx index b998c780..59a8fe5b 100644 --- a/packages/skin-database/app/(modern)/scroll/BottomMenuBar.tsx +++ b/packages/skin-database/app/(modern)/scroll/BottomMenuBar.tsx @@ -11,9 +11,15 @@ import { Github, } from "lucide-react"; import Link from "next/link"; -import { useState, useEffect, useRef } from "react"; import { usePathname } from "next/navigation"; import { MOBILE_MAX_WIDTH } from "../../../legacy-client/src/constants"; +import { + // @ts-expect-error - unstable_ViewTransition is not yet in @types/react + unstable_ViewTransition as ViewTransition, + useState, + useEffect, + useRef, +} from "react"; export default function BottomMenuBar() { const [isHamburgerOpen, setIsHamburgerOpen] = useState(false); @@ -128,6 +134,12 @@ export default function BottomMenuBar() { alignItems: "center", }} > + } + label="Grid" + isActive={pathname === "/scroll/grid"} + /> } @@ -136,12 +148,7 @@ export default function BottomMenuBar() { pathname === "/scroll" || pathname.startsWith("/scroll/skin") } /> - } - label="Grid" - isActive={pathname === "/scroll/grid"} - /> + } @@ -210,16 +217,18 @@ function MenuButton({ <> {/* Active indicator line */} {isActive && ( -
+ +
+ )}
- {label} - + > ); @@ -258,6 +265,7 @@ function MenuButton({ return ( {children}
; + return
{children}
; } diff --git a/packages/skin-database/app/(modern)/scroll/layout.tsx b/packages/skin-database/app/(modern)/scroll/layout.tsx index 819a1135..c14a138f 100644 --- a/packages/skin-database/app/(modern)/scroll/layout.tsx +++ b/packages/skin-database/app/(modern)/scroll/layout.tsx @@ -1,6 +1,7 @@ "use client"; -import { ReactNode } from "react"; +// @ts-expect-error - unstable_ViewTransition is not yet in @types/react +import { unstable_ViewTransition as ViewTransition, ReactNode } from "react"; import BottomMenuBar from "./BottomMenuBar"; import "./scroll.css"; @@ -17,7 +18,9 @@ export default function Layout({ children }: LayoutProps) { }} > {children} - + + +
); } diff --git a/packages/skin-database/tsconfig.json b/packages/skin-database/tsconfig.json index af3d1751..c748feab 100644 --- a/packages/skin-database/tsconfig.json +++ b/packages/skin-database/tsconfig.json @@ -23,11 +23,7 @@ "outDir": "dist", "strictNullChecks": true, "skipLibCheck": true, - "lib": [ - "dom", - "dom.iterable", - "esnext" - ], + "lib": ["dom", "dom.iterable", "esnext"], "strict": false, "noEmit": true, "incremental": true, @@ -37,13 +33,9 @@ }, "include": [ "./**/*", - ".next/types/**/*.ts" + ".next/types/**/*.ts", + ".next/types/app/(legacy)/about/page.tsx" ], - "lib": [ - "es2015" - ], - "exclude": [ - "node_modules", - "dist" - ] + "lib": ["es2015"], + "exclude": ["node_modules", "dist"] } From 8d4ff41f42b2e2385c60392cde839640735158c2 Mon Sep 17 00:00:00 2001 From: Jordan Eldredge Date: Mon, 29 Dec 2025 17:03:29 -0800 Subject: [PATCH 66/78] Improve scroll UI --- .../skin-database/app/(legacy)/layout.tsx | 1 + .../app/(modern)/scroll/BottomMenuBar.tsx | 10 +++--- .../app/(modern)/scroll/{grid => }/Grid.tsx | 4 +-- .../scroll/{grid => }/InfiniteScrollGrid.tsx | 4 +-- .../app/(modern)/scroll/SkinPage.tsx | 5 ++- .../app/(modern)/scroll/SkinScroller.tsx | 14 +++++++- .../scroll/{grid => }/getMuseumPageSkins.ts | 2 +- .../app/(modern)/scroll/grid/layout.tsx | 9 ----- .../app/(modern)/scroll/grid/page.tsx | 13 -------- .../app/(modern)/scroll/page.tsx | 33 +++++++------------ .../app/(modern)/scroll/skin/[md5]/page.tsx | 7 ++++ .../app/(modern)/scroll/skin/page.tsx | 25 ++++++++++++++ .../skin-database/app/bulk-download/page.tsx | 4 ++- 13 files changed, 73 insertions(+), 58 deletions(-) rename packages/skin-database/app/(modern)/scroll/{grid => }/Grid.tsx (97%) rename packages/skin-database/app/(modern)/scroll/{grid => }/InfiniteScrollGrid.tsx (98%) rename packages/skin-database/app/(modern)/scroll/{grid => }/getMuseumPageSkins.ts (86%) delete mode 100644 packages/skin-database/app/(modern)/scroll/grid/layout.tsx delete mode 100644 packages/skin-database/app/(modern)/scroll/grid/page.tsx create mode 100644 packages/skin-database/app/(modern)/scroll/skin/page.tsx diff --git a/packages/skin-database/app/(legacy)/layout.tsx b/packages/skin-database/app/(legacy)/layout.tsx index f13532e7..39f7ea18 100644 --- a/packages/skin-database/app/(legacy)/layout.tsx +++ b/packages/skin-database/app/(legacy)/layout.tsx @@ -1,5 +1,6 @@ import App from "../App"; export default function Layout() { + console.log("Render legacy layout"); return ; } diff --git a/packages/skin-database/app/(modern)/scroll/BottomMenuBar.tsx b/packages/skin-database/app/(modern)/scroll/BottomMenuBar.tsx index 59a8fe5b..4cab5632 100644 --- a/packages/skin-database/app/(modern)/scroll/BottomMenuBar.tsx +++ b/packages/skin-database/app/(modern)/scroll/BottomMenuBar.tsx @@ -135,18 +135,16 @@ export default function BottomMenuBar() { }} > } label="Grid" - isActive={pathname === "/scroll/grid"} + isActive={pathname === "/scroll"} /> } label="Feed" - isActive={ - pathname === "/scroll" || pathname.startsWith("/scroll/skin") - } + isActive={pathname.startsWith("/scroll/skin")} /> { + console.log("Mount SkinPage"); + }, []); return (
{ + // We want the URL and title to update as you scroll, but + // we can't trigger a NextJS navigation since that would remount the + // component. So, here we replicate the metadata behavior of the route. + const skinMd5 = skins[visibleSkinIndex].md5; + const newUrl = `/scroll/skin/${skinMd5}`; + window.document.title = `${skins[visibleSkinIndex].fileName} - Winamp Skin Museum`; + window.history.replaceState( + { ...window.history.state, as: newUrl, url: newUrl }, + "", + newUrl + ); + logUserEvent(sessionId, { type: "skin_view_start", - skinMd5: skins[visibleSkinIndex].md5, + skinMd5, }); const startTime = Date.now(); return () => { diff --git a/packages/skin-database/app/(modern)/scroll/grid/getMuseumPageSkins.ts b/packages/skin-database/app/(modern)/scroll/getMuseumPageSkins.ts similarity index 86% rename from packages/skin-database/app/(modern)/scroll/grid/getMuseumPageSkins.ts rename to packages/skin-database/app/(modern)/scroll/getMuseumPageSkins.ts index 4be6b404..a614cdc6 100644 --- a/packages/skin-database/app/(modern)/scroll/grid/getMuseumPageSkins.ts +++ b/packages/skin-database/app/(modern)/scroll/getMuseumPageSkins.ts @@ -1,6 +1,6 @@ "use server"; -import { getMuseumPage, getScreenshotUrl } from "../../../../data/skins"; +import { getMuseumPage, getScreenshotUrl } from "../../../data/skins"; export type GridSkin = { md5: string; diff --git a/packages/skin-database/app/(modern)/scroll/grid/layout.tsx b/packages/skin-database/app/(modern)/scroll/grid/layout.tsx deleted file mode 100644 index da627208..00000000 --- a/packages/skin-database/app/(modern)/scroll/grid/layout.tsx +++ /dev/null @@ -1,9 +0,0 @@ -import { ReactNode } from "react"; - -type LayoutProps = { - children: ReactNode; -}; - -export default function Layout({ children }: LayoutProps) { - return
{children}
; -} diff --git a/packages/skin-database/app/(modern)/scroll/grid/page.tsx b/packages/skin-database/app/(modern)/scroll/grid/page.tsx deleted file mode 100644 index 4a2a3dd2..00000000 --- a/packages/skin-database/app/(modern)/scroll/grid/page.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import React from "react"; -import Grid from "./Grid"; -import { getMuseumPageSkins } from "./getMuseumPageSkins"; -import * as Skins from "../../../../data/skins"; - -export default async function SkinTable() { - const [initialSkins, skinCount] = await Promise.all([ - getMuseumPageSkins(0, 50), - Skins.getClassicSkinCount(), - ]); - - return ; -} diff --git a/packages/skin-database/app/(modern)/scroll/page.tsx b/packages/skin-database/app/(modern)/scroll/page.tsx index 620c69a5..88c9e09c 100644 --- a/packages/skin-database/app/(modern)/scroll/page.tsx +++ b/packages/skin-database/app/(modern)/scroll/page.tsx @@ -1,24 +1,13 @@ -import SessionModel from "../../../data/SessionModel"; -import { getClientSkins } from "./getClientSkins"; -import SkinScroller from "./SkinScroller"; +import React from "react"; +import Grid from "./Grid"; +import { getMuseumPageSkins } from "./getMuseumPageSkins"; +import * as Skins from "../../..//data/skins"; -// Ensure each page load gets a new session -export const dynamic = "force-dynamic"; - -/** - * A tik-tok style scroll page where we display one skin at a time in full screen - */ -export default async function ScrollPage() { - // Create the session in the database - const sessionId = await SessionModel.create(); - - const initialSkins = await getClientSkins(sessionId); - - return ( - - ); +export default async function SkinTable() { + const [initialSkins, skinCount] = await Promise.all([ + getMuseumPageSkins(0, 50), + Skins.getClassicSkinCount(), + ]); + console.log("SERVER RENDER generic"); + return ; } diff --git a/packages/skin-database/app/(modern)/scroll/skin/[md5]/page.tsx b/packages/skin-database/app/(modern)/scroll/skin/[md5]/page.tsx index 89b8ac16..2cae8731 100644 --- a/packages/skin-database/app/(modern)/scroll/skin/[md5]/page.tsx +++ b/packages/skin-database/app/(modern)/scroll/skin/[md5]/page.tsx @@ -1,7 +1,14 @@ +import { Metadata } from "next"; import SessionModel from "../../../../../data/SessionModel"; import UserContext from "../../../../../data/UserContext"; import { getClientSkins, getSkinForSession } from "../../getClientSkins"; import SkinScroller from "../../SkinScroller"; +import { generateSkinPageMetadata } from "../../../../(legacy)/skin/[hash]/skinMetadata"; + +export async function generateMetadata({ params }): Promise { + const { md5 } = await params; + return generateSkinPageMetadata(md5); +} // Ensure each page load gets a new session export const dynamic = "force-dynamic"; diff --git a/packages/skin-database/app/(modern)/scroll/skin/page.tsx b/packages/skin-database/app/(modern)/scroll/skin/page.tsx new file mode 100644 index 00000000..c752b03a --- /dev/null +++ b/packages/skin-database/app/(modern)/scroll/skin/page.tsx @@ -0,0 +1,25 @@ +import SessionModel from "../../../../data/SessionModel"; +import { getClientSkins } from "../getClientSkins"; +import SkinScroller from "../SkinScroller"; + +// Ensure each page load gets a new session +export const dynamic = "force-dynamic"; + +/** + * A tik-tok style scroll page where we display one skin at a time in full screen + */ +export default async function ScrollPage() { + // Create the session in the database + const sessionId = await SessionModel.create(); + + const initialSkins = await getClientSkins(sessionId); + console.log("SERVER RENDER generic"); + + return ( + + ); +} diff --git a/packages/skin-database/app/bulk-download/page.tsx b/packages/skin-database/app/bulk-download/page.tsx index e8b8b81e..06c11a1c 100644 --- a/packages/skin-database/app/bulk-download/page.tsx +++ b/packages/skin-database/app/bulk-download/page.tsx @@ -99,7 +99,7 @@ export default function BulkDownloadPage() { completedSkins: prev.completedSkins + 1, })); return; - } catch (error) { + } catch (_) { // File doesn't exist, continue with download } @@ -277,6 +277,7 @@ export default function BulkDownloadPage() { } for (const skin of skins) { + // eslint-disable-next-line max-depth if (abortController.current.signal.aborted) break; await waitForAvailableSlot( @@ -284,6 +285,7 @@ export default function BulkDownloadPage() { abortController.current.signal ); + // eslint-disable-next-line max-depth if (abortController.current.signal.aborted) break; const downloadPromise = downloadSkin( From 1fb930cd6362df86163d414e94547f02f9ba2684 Mon Sep 17 00:00:00 2001 From: Jordan Eldredge Date: Mon, 29 Dec 2025 17:29:11 -0800 Subject: [PATCH 67/78] New pages for scroll --- .../app/(modern)/scroll/BottomMenuBar.tsx | 13 +- .../app/(modern)/scroll/StaticPage.tsx | 180 ++++++++++++++++++ .../app/(modern)/scroll/about/page.tsx | 67 +++++++ .../app/(modern)/scroll/feedback/page.tsx | 117 ++++++++++++ 4 files changed, 371 insertions(+), 6 deletions(-) create mode 100644 packages/skin-database/app/(modern)/scroll/StaticPage.tsx create mode 100644 packages/skin-database/app/(modern)/scroll/about/page.tsx create mode 100644 packages/skin-database/app/(modern)/scroll/feedback/page.tsx diff --git a/packages/skin-database/app/(modern)/scroll/BottomMenuBar.tsx b/packages/skin-database/app/(modern)/scroll/BottomMenuBar.tsx index 4cab5632..86f753f2 100644 --- a/packages/skin-database/app/(modern)/scroll/BottomMenuBar.tsx +++ b/packages/skin-database/app/(modern)/scroll/BottomMenuBar.tsx @@ -57,7 +57,7 @@ export default function BottomMenuBar() { {isHamburgerOpen && (
} label="About" onClick={() => { @@ -87,13 +89,12 @@ export default function BottomMenuBar() { }} />{" "} } label="Feedback" onClick={() => { setIsHamburgerOpen(false); }} - external /> +
+ {children} +
+
+ ); +} + +// Styled heading components +export function Heading({ children }: { children: ReactNode }) { + return ( +

+ {children} +

+ ); +} + +export function Subheading({ children }: { children: ReactNode }) { + return ( +

+ {children} +

+ ); +} + +// Styled link component +export function Link({ + href, + children, + ...props +}: { + href: string; + children: ReactNode; + target?: string; + rel?: string; +}) { + return ( + + {children} + + ); +} + +// Styled paragraph component +export function Paragraph({ children }: { children: ReactNode }) { + return

{children}

; +} + +// Styled form components +export function Label({ children }: { children: ReactNode }) { + return ( + + ); +} + +export function Input({ + style, + ...props +}: React.InputHTMLAttributes & { style?: CSSProperties }) { + return ( + + ); +} + +export function Textarea({ + style, + ...props +}: React.TextareaHTMLAttributes & { + style?: CSSProperties; +}) { + return ( +