From 8a0d6c623fca2472a63ef8d93fd727c171dc21a9 Mon Sep 17 00:00:00 2001 From: Afonso Jorge Ramos Date: Mon, 10 Aug 2026 19:08:34 +0200 Subject: [PATCH 01/12] fix(hooks): stop pre-commit failing on a markdown-only change oxfmt ignores md, mdx, yaml, html and css, but lint-staged handed it exactly those types, so any commit whose staged set was entirely markdown (most commits in a docs repo) died on "Expected at least one target file". The glob now matches what oxfmt actually formats. --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 6c1477cd..a2ae41ee 100644 --- a/package.json +++ b/package.json @@ -45,7 +45,7 @@ "oxlint --fix", "oxfmt" ], - "*.{json,jsonc,json5,md,mdx,yaml,yml,html}": "oxfmt" + "*.{json,jsonc,json5}": "oxfmt" }, "browserslist": { "production": [ From e0425eac03acff1b990f678f4cb29a032c4e9c85 Mon Sep 17 00:00:00 2001 From: Afonso Jorge Ramos Date: Mon, 10 Aug 2026 19:08:48 +0200 Subject: [PATCH 02/12] feat(docs): document v3 and archive v2 under a legacy section The site described v2 exclusively. The main tree is now v3 (install and first module, what changes from v2, modules and the store, config.toml, the CLI reference written from the binary's own help, building and publishing a module), and every v2 page keeps its content under /docs/legacy behind a banner pointing at its v3 equivalent, with a redirect for each URL that moved. The API reference is shared by both and was checked against a running v3 client: the Panel surface and GraphQL.Definitions are gone, PlatformData lost three documented fields and gained three undocumented ones, and isPlaylistV2URI is isPlaylistV2. Those pages now say so. Also removes the duplicate root docs/ tree. Only src/content/docs builds, the two were kept in sync by hand, and PR #222 had to patch both. --- astro.config.ts | 39 +- docs/customization/config-file.md | 165 ---- docs/customization/custom-apps.md | 158 ---- docs/customization/extensions.md | 245 ------ docs/customization/index.md | 48 -- docs/customization/marketplace.md | 121 --- .../api-wrapper/classes/context-menu.md | 230 ------ docs/development/api-wrapper/classes/menu.md | 229 ------ .../api-wrapper/classes/playbar.md | 254 ------- .../development/api-wrapper/classes/topbar.md | 93 --- .../api-wrapper/functions/add-to-queue.md | 37 - .../api-wrapper/functions/color-extractor.md | 42 - .../api-wrapper/functions/get-audio-data.md | 38 - .../api-wrapper/functions/get-font-style.md | 38 - .../functions/remove-from-queue.md | 40 - .../functions/show-notification.md | 32 - docs/development/api-wrapper/index.md | 15 - .../api-wrapper/methods/app-title.md | 128 ---- .../api-wrapper/methods/cosmos-async.md | 239 ------ .../api-wrapper/methods/graphql.md | 484 ------------ .../api-wrapper/methods/keyboard.md | 116 --- .../api-wrapper/methods/local-storage.md | 96 --- docs/development/api-wrapper/methods/panel.md | 301 -------- .../api-wrapper/methods/platform.md | 717 ------------------ .../development/api-wrapper/methods/player.md | 529 ------------- .../api-wrapper/methods/popup-modal.md | 68 -- docs/development/api-wrapper/methods/uri.md | 317 -------- docs/development/api-wrapper/modules.md | 98 --- .../api-wrapper/properties/config.md | 38 - .../api-wrapper/properties/queue.md | 43 -- .../properties/react-components.md | 375 --------- .../api-wrapper/properties/react-hook.md | 184 ----- .../api-wrapper/properties/svgicons.md | 43 -- .../api-wrapper/properties/tippy-props.md | 64 -- .../types/context-menu/onclick-callback.md | 20 - .../types/context-menu/should-add-callback.md | 24 - .../api-wrapper/types/context-option.md | 46 -- .../api-wrapper/types/context-track.md | 24 - .../api-wrapper/types/cosmos-async/body.md | 10 - .../api-wrapper/types/cosmos-async/error.md | 26 - .../api-wrapper/types/cosmos-async/headers.md | 10 - .../api-wrapper/types/cosmos-async/method.md | 10 - .../types/cosmos-async/response.md | 28 - .../api-wrapper/types/graphql/query.md | 104 --- .../api-wrapper/types/keyboard/keysdefine.md | 28 - .../api-wrapper/types/keyboard/validkey.md | 229 ------ .../development/api-wrapper/types/metadata.md | 66 -- .../api-wrapper/types/panel/panel-props.md | 38 - .../api-wrapper/types/player-state.md | 92 --- .../api-wrapper/types/provided-track.md | 26 - .../react-component/confirm-dialog-props.md | 42 - .../react-component/context-menu-props.md | 37 - .../react-component/icon-component-props.md | 38 - .../types/react-component/menu-item-props.md | 34 - .../types/react-component/menu-props.md | 26 - .../react-component/panel-content-props.md | 26 - .../react-component/panel-header-props.md | 42 - .../react-component/panel-skeleton-props.md | 32 - .../types/react-component/slider-props.md | 44 -- .../react-component/text-component-props.md | 32 - .../types/react-component/toggle-props.md | 32 - .../types/react-component/tooltip-props.md | 36 - .../api-wrapper/types/semantic-color.md | 41 - docs/development/api-wrapper/types/svgicon.md | 87 --- .../development/api-wrapper/types/uri/type.md | 79 -- .../types/uri/validation-functions.md | 69 -- docs/development/api-wrapper/types/variant.md | 32 - docs/development/compiling.md | 33 - docs/development/index.md | 10 - docs/development/react-devtools.md | 19 - .../spicetify-creator/building-and-testing.md | 115 --- .../spicetify-creator/create-custom-apps.md | 72 -- .../spicetify-creator/the-basics.md | 152 ---- docs/development/spotify-cli-flags.md | 106 --- docs/development/themes.md | 58 -- docs/getting-started.md | 406 ---------- docs/uninstallation.md | 111 --- src/components/Footer.astro | 2 +- src/components/homepage/ThemesBento.astro | 4 +- src/config/sidebar.ts | 114 ++- src/content/docs/cli/commands.md | 241 ++---- src/content/docs/cli/index.md | 58 +- src/content/docs/customization/themes.md | 146 ---- .../docs/development/api-wrapper/index.md | 5 + .../api-wrapper/methods/graphql.md | 6 + .../development/api-wrapper/methods/panel.md | 4 + .../api-wrapper/methods/platform.md | 24 +- .../development/api-wrapper/methods/player.md | 2 +- .../development/api-wrapper/methods/uri.md | 2 +- .../properties/react-components.md | 4 + .../api-wrapper/properties/react-hook.md | 4 + .../api-wrapper/types/graphql/query.md | 4 + .../api-wrapper/types/panel/panel-props.md | 4 + .../react-component/panel-content-props.md | 4 + .../react-component/panel-header-props.md | 4 + .../react-component/panel-skeleton-props.md | 4 + .../docs/development/building-a-module.md | 153 ++++ src/content/docs/development/compiling.md | 43 +- src/content/docs/development/custom-apps.md | 156 ---- src/content/docs/development/extensions.md | 288 ------- src/content/docs/development/index.md | 26 +- src/content/docs/development/js-modules.md | 153 ---- src/content/docs/development/publishing.md | 101 +++ .../spicetify-creator/create-extensions.md | 44 -- src/content/docs/faq.md | 125 +-- src/content/docs/getting-started.md | 140 ++++ .../content/docs/legacy}/cli/commands.md | 4 + .../content/docs/legacy}/cli/index.md | 4 + .../{ => legacy}/customization/config-file.md | 4 + .../{ => legacy}/customization/custom-apps.md | 4 + .../{ => legacy}/customization/extensions.md | 4 + .../docs/{ => legacy}/customization/index.md | 4 + .../customization/marketplace.mdx | 4 + .../docs/legacy}/customization/themes.md | 4 + .../docs/legacy}/development/custom-apps.md | 4 + .../docs/legacy}/development/extensions.md | 4 + .../docs/legacy}/development/js-modules.md | 4 + .../docs/{ => legacy}/development/themes.md | 4 + {docs => src/content/docs/legacy}/faq.md | 7 +- .../docs/{ => legacy}/getting-started.mdx | 4 + src/content/docs/legacy/index.md | 34 + .../building-and-testing.mdx | 4 + .../spicetify-creator/create-custom-apps.md | 4 + .../spicetify-creator/create-extensions.md | 4 + .../spicetify-creator/the-basics.mdx | 4 + .../docs/{ => legacy}/uninstallation.mdx | 4 + src/content/docs/modules/config-file.md | 77 ++ src/content/docs/modules/index.md | 93 +++ src/content/docs/uninstallation.md | 62 ++ src/content/docs/whats-new.md | 87 +++ src/pages/index.astro | 20 +- 131 files changed, 1252 insertions(+), 9321 deletions(-) delete mode 100644 docs/customization/config-file.md delete mode 100644 docs/customization/custom-apps.md delete mode 100644 docs/customization/extensions.md delete mode 100644 docs/customization/index.md delete mode 100644 docs/customization/marketplace.md delete mode 100644 docs/development/api-wrapper/classes/context-menu.md delete mode 100644 docs/development/api-wrapper/classes/menu.md delete mode 100644 docs/development/api-wrapper/classes/playbar.md delete mode 100644 docs/development/api-wrapper/classes/topbar.md delete mode 100644 docs/development/api-wrapper/functions/add-to-queue.md delete mode 100644 docs/development/api-wrapper/functions/color-extractor.md delete mode 100644 docs/development/api-wrapper/functions/get-audio-data.md delete mode 100644 docs/development/api-wrapper/functions/get-font-style.md delete mode 100644 docs/development/api-wrapper/functions/remove-from-queue.md delete mode 100644 docs/development/api-wrapper/functions/show-notification.md delete mode 100644 docs/development/api-wrapper/index.md delete mode 100644 docs/development/api-wrapper/methods/app-title.md delete mode 100644 docs/development/api-wrapper/methods/cosmos-async.md delete mode 100644 docs/development/api-wrapper/methods/graphql.md delete mode 100644 docs/development/api-wrapper/methods/keyboard.md delete mode 100644 docs/development/api-wrapper/methods/local-storage.md delete mode 100644 docs/development/api-wrapper/methods/panel.md delete mode 100644 docs/development/api-wrapper/methods/platform.md delete mode 100644 docs/development/api-wrapper/methods/player.md delete mode 100644 docs/development/api-wrapper/methods/popup-modal.md delete mode 100644 docs/development/api-wrapper/methods/uri.md delete mode 100644 docs/development/api-wrapper/modules.md delete mode 100644 docs/development/api-wrapper/properties/config.md delete mode 100644 docs/development/api-wrapper/properties/queue.md delete mode 100644 docs/development/api-wrapper/properties/react-components.md delete mode 100644 docs/development/api-wrapper/properties/react-hook.md delete mode 100644 docs/development/api-wrapper/properties/svgicons.md delete mode 100644 docs/development/api-wrapper/properties/tippy-props.md delete mode 100644 docs/development/api-wrapper/types/context-menu/onclick-callback.md delete mode 100644 docs/development/api-wrapper/types/context-menu/should-add-callback.md delete mode 100644 docs/development/api-wrapper/types/context-option.md delete mode 100644 docs/development/api-wrapper/types/context-track.md delete mode 100644 docs/development/api-wrapper/types/cosmos-async/body.md delete mode 100644 docs/development/api-wrapper/types/cosmos-async/error.md delete mode 100644 docs/development/api-wrapper/types/cosmos-async/headers.md delete mode 100644 docs/development/api-wrapper/types/cosmos-async/method.md delete mode 100644 docs/development/api-wrapper/types/cosmos-async/response.md delete mode 100644 docs/development/api-wrapper/types/graphql/query.md delete mode 100644 docs/development/api-wrapper/types/keyboard/keysdefine.md delete mode 100644 docs/development/api-wrapper/types/keyboard/validkey.md delete mode 100644 docs/development/api-wrapper/types/metadata.md delete mode 100644 docs/development/api-wrapper/types/panel/panel-props.md delete mode 100644 docs/development/api-wrapper/types/player-state.md delete mode 100644 docs/development/api-wrapper/types/provided-track.md delete mode 100644 docs/development/api-wrapper/types/react-component/confirm-dialog-props.md delete mode 100644 docs/development/api-wrapper/types/react-component/context-menu-props.md delete mode 100644 docs/development/api-wrapper/types/react-component/icon-component-props.md delete mode 100644 docs/development/api-wrapper/types/react-component/menu-item-props.md delete mode 100644 docs/development/api-wrapper/types/react-component/menu-props.md delete mode 100644 docs/development/api-wrapper/types/react-component/panel-content-props.md delete mode 100644 docs/development/api-wrapper/types/react-component/panel-header-props.md delete mode 100644 docs/development/api-wrapper/types/react-component/panel-skeleton-props.md delete mode 100644 docs/development/api-wrapper/types/react-component/slider-props.md delete mode 100644 docs/development/api-wrapper/types/react-component/text-component-props.md delete mode 100644 docs/development/api-wrapper/types/react-component/toggle-props.md delete mode 100644 docs/development/api-wrapper/types/react-component/tooltip-props.md delete mode 100644 docs/development/api-wrapper/types/semantic-color.md delete mode 100644 docs/development/api-wrapper/types/svgicon.md delete mode 100644 docs/development/api-wrapper/types/uri/type.md delete mode 100644 docs/development/api-wrapper/types/uri/validation-functions.md delete mode 100644 docs/development/api-wrapper/types/variant.md delete mode 100644 docs/development/compiling.md delete mode 100644 docs/development/index.md delete mode 100644 docs/development/react-devtools.md delete mode 100644 docs/development/spicetify-creator/building-and-testing.md delete mode 100644 docs/development/spicetify-creator/create-custom-apps.md delete mode 100644 docs/development/spicetify-creator/the-basics.md delete mode 100644 docs/development/spotify-cli-flags.md delete mode 100644 docs/development/themes.md delete mode 100644 docs/getting-started.md delete mode 100644 docs/uninstallation.md delete mode 100644 src/content/docs/customization/themes.md create mode 100644 src/content/docs/development/building-a-module.md delete mode 100644 src/content/docs/development/custom-apps.md delete mode 100644 src/content/docs/development/extensions.md delete mode 100644 src/content/docs/development/js-modules.md create mode 100644 src/content/docs/development/publishing.md delete mode 100644 src/content/docs/development/spicetify-creator/create-extensions.md create mode 100644 src/content/docs/getting-started.md rename {docs => src/content/docs/legacy}/cli/commands.md (98%) rename {docs => src/content/docs/legacy}/cli/index.md (87%) rename src/content/docs/{ => legacy}/customization/config-file.md (97%) rename src/content/docs/{ => legacy}/customization/custom-apps.md (97%) rename src/content/docs/{ => legacy}/customization/extensions.md (98%) rename src/content/docs/{ => legacy}/customization/index.md (93%) rename src/content/docs/{ => legacy}/customization/marketplace.mdx (97%) rename {docs => src/content/docs/legacy}/customization/themes.md (97%) rename {docs => src/content/docs/legacy}/development/custom-apps.md (98%) rename {docs => src/content/docs/legacy}/development/extensions.md (98%) rename {docs => src/content/docs/legacy}/development/js-modules.md (98%) rename src/content/docs/{ => legacy}/development/themes.md (94%) rename {docs => src/content/docs/legacy}/faq.md (97%) rename src/content/docs/{ => legacy}/getting-started.mdx (98%) create mode 100644 src/content/docs/legacy/index.md rename src/content/docs/{development => legacy}/spicetify-creator/building-and-testing.mdx (95%) rename src/content/docs/{development => legacy}/spicetify-creator/create-custom-apps.md (93%) rename {docs/development => src/content/docs/legacy}/spicetify-creator/create-extensions.md (88%) rename src/content/docs/{development => legacy}/spicetify-creator/the-basics.mdx (97%) rename src/content/docs/{ => legacy}/uninstallation.mdx (95%) create mode 100644 src/content/docs/modules/config-file.md create mode 100644 src/content/docs/modules/index.md create mode 100644 src/content/docs/uninstallation.md create mode 100644 src/content/docs/whats-new.md diff --git a/astro.config.ts b/astro.config.ts index 18212ed7..8e939ac4 100644 --- a/astro.config.ts +++ b/astro.config.ts @@ -33,13 +33,36 @@ export default defineConfig({ '/docs/blog/authors': '/blog', // Preserve the URL equity of the pre-2026 Docusaurus information // architecture. These pages moved rather than disappeared. - '/docs/advanced-usage': '/docs/customization', - '/docs/advanced-usage/installation': '/docs/getting-started', - '/docs/advanced-usage/uninstallation': '/docs/uninstallation', - '/docs/advanced-usage/command-line-interface': '/docs/cli', - '/docs/advanced-usage/themes': '/docs/customization/themes', - '/docs/advanced-usage/extensions': '/docs/customization/extensions', - '/docs/advanced-usage/custom-apps': '/docs/customization/custom-apps', - '/docs/customization/marketplace.md': '/docs/customization/marketplace', + '/docs/advanced-usage': '/docs/legacy/customization', + '/docs/advanced-usage/installation': '/docs/legacy/getting-started', + '/docs/advanced-usage/uninstallation': '/docs/legacy/uninstallation', + '/docs/advanced-usage/command-line-interface': '/docs/legacy/cli', + '/docs/advanced-usage/themes': '/docs/legacy/customization/themes', + '/docs/advanced-usage/extensions': '/docs/legacy/customization/extensions', + '/docs/advanced-usage/custom-apps': + '/docs/legacy/customization/custom-apps', + '/docs/customization/marketplace.md': + '/docs/legacy/customization/marketplace', + // v3 took over the main tree; every v2 page kept its content under + // /docs/legacy, so the old URLs point at the page they promised rather + // than at a v3 equivalent that says something different. + '/docs/customization': '/docs/legacy/customization', + '/docs/customization/marketplace': '/docs/legacy/customization/marketplace', + '/docs/customization/themes': '/docs/legacy/customization/themes', + '/docs/customization/extensions': '/docs/legacy/customization/extensions', + '/docs/customization/custom-apps': '/docs/legacy/customization/custom-apps', + '/docs/customization/config-file': '/docs/legacy/customization/config-file', + '/docs/development/themes': '/docs/legacy/development/themes', + '/docs/development/extensions': '/docs/legacy/development/extensions', + '/docs/development/custom-apps': '/docs/legacy/development/custom-apps', + '/docs/development/js-modules': '/docs/legacy/development/js-modules', + '/docs/development/spicetify-creator/the-basics': + '/docs/legacy/spicetify-creator/the-basics', + '/docs/development/spicetify-creator/create-extensions': + '/docs/legacy/spicetify-creator/create-extensions', + '/docs/development/spicetify-creator/create-custom-apps': + '/docs/legacy/spicetify-creator/create-custom-apps', + '/docs/development/spicetify-creator/building-and-testing': + '/docs/legacy/spicetify-creator/building-and-testing', }, }); diff --git a/docs/customization/config-file.md b/docs/customization/config-file.md deleted file mode 100644 index 9e0e68b2..00000000 --- a/docs/customization/config-file.md +++ /dev/null @@ -1,165 +0,0 @@ ---- -title: Configuration Reference -description: Complete reference for Spicetify's config file. ---- - -Spicetify stores its configuration in `config-xpui.ini`. This page documents all available options. - -## File Location - -| Platform | Path | -|----------|------| -| **Windows** | `%appdata%\spicetify\config-xpui.ini` | -| **Linux / macOS** | `~/.config/spicetify/config-xpui.ini` | - -To find the exact path: - -```bash -spicetify -c -``` - -To open the config folder: - -```bash -spicetify config-dir -``` - -## Editing Configuration - -### Via CLI (Recommended) - -```bash -spicetify config -spicetify apply -``` - -Examples: - -```bash -# Set a theme -spicetify config current_theme Sleek - -# Add an extension -spicetify config extensions fullAppDisplay.js - -# Remove an extension (note the trailing -) -spicetify config extensions fullAppDisplay.js- - -# Set multiple values at once -spicetify config current_theme Sleek color_scheme Dark -``` - -### Via Direct File Editing - -Open `config-xpui.ini` in any text editor, make changes, save, then run: - -```bash -spicetify apply -``` - ---- - -## Configuration Sections - -### [Setting] - -Core paths and settings. - -| Key | Description | Example | -|-----|-------------|---------| -| `spotify_path` | Path to Spotify installation | `/opt/spotify` | -| `prefs_path` | Path to Spotify's `prefs` file | `~/.config/spotify/prefs` | -| `spotify_launch_flags` | Flags passed when launching Spotify | `--remote-debugging-port=9222` | -| `check_spicetify_upgrade` | Check for Spicetify updates on startup | `1` (enabled) | - -### [Preprocesses] - -Control CSS/color injection. - -| Key | Description | Default | -|-----|-------------|---------| -| `disable_sentry` | Disable Spotify's error reporting | `1` | -| `disable_ui_logging` | Disable UI telemetry | `1` | -| `remove_rtl_rule` | Remove right-to-left CSS rules | `1` | -| `expose_apis` | Expose Spicetify APIs to extensions | `1` | - -### [AdditionalOptions] - -Theme and add-on configuration. - -| Key | Description | Example | -|-----|-------------|---------| -| `current_theme` | Active theme folder name | `Sleek` | -| `color_scheme` | Color scheme within current theme | `Dark` | -| `extensions` | Enabled extensions (pipe-separated) | `fullAppDisplay.js\|trashbin.js` | -| `custom_apps` | Enabled custom apps (pipe-separated) | `reddit\|lyrics-plus` | -| `inject_css` | Inject theme CSS | `1` | -| `inject_theme_js` | Inject theme JavaScript (if present) | `1` | -| `replace_colors` | Apply theme colors | `1` | -| `overwrite_assets` | Allow themes to replace assets | `0` | -| `sidebar_config` | Sidebar customization (auto-generated) | - | -| `home_config` | Homepage customization (auto-generated) | - | - -### [Patch] - -Low-level patches. Usually leave these alone. - -| Key | Description | Default | -|-----|-------------|---------| -| `xpui.js_find_8008` | Enable specific patching | Varies | -| `xpui.js_repl_8008` | Replacement value for patch | Varies | - ---- - -## Common Tasks - -### See All Current Settings - -```bash -spicetify config -``` - -### Reset to Defaults - -```bash -spicetify restore -``` - -This removes Spicetify modifications from Spotify but keeps your config file. - -### Backup Current State - -```bash -spicetify backup -``` - -Creates a backup of vanilla Spotify that can be restored later. - -### Full Reset - -To completely start fresh: - -```bash -spicetify restore -rm ~/.config/spicetify/config-xpui.ini # Linux/macOS -# or on Windows: del %appdata%\spicetify\config-xpui.ini -spicetify -``` - -Running `spicetify` with no config file regenerates the default configuration. - ---- - -## Getting Help - -For detailed help on any config option: - -```bash -spicetify --help config -``` - -For all available commands: - -```bash -spicetify --help -``` diff --git a/docs/customization/custom-apps.md b/docs/customization/custom-apps.md deleted file mode 100644 index ae9629b0..00000000 --- a/docs/customization/custom-apps.md +++ /dev/null @@ -1,158 +0,0 @@ ---- -title: Custom Apps -description: Add new pages to Spotify's sidebar with custom apps. ---- - -Custom Apps are JavaScript applications that add entirely new pages to Spotify's sidebar. Unlike extensions (which modify existing behavior), custom apps create standalone experiences within Spotify. - -## Installing Custom Apps - -### Via Marketplace - -Some custom apps are available through the **[Marketplace](./marketplace.md)**. Open Marketplace from Spotify's sidebar to browse. - -### Manual Installation - -1. Download or create the custom app folder (must contain at minimum `index.js` and `manifest.json`) - -2. Place it in your CustomApps folder: - - | Platform | Path | - |----------|------| - | **Windows** | `%appdata%\spicetify\CustomApps\` | - | **Linux / macOS** | `~/.config/spicetify/CustomApps/` | - -3. Enable the custom app: - - ```bash - spicetify config custom_apps - spicetify apply - ``` - -:::tip -The `config custom_apps` command **appends** to your existing custom apps. It doesn't replace them. -::: - -## Removing Custom Apps - -To remove a custom app, append a `-` after the folder name: - -```bash -spicetify config custom_apps - -spicetify apply -``` - -## Manual Config Editing - -You can also edit the config file directly. Custom apps are listed in the `custom_apps` key, separated by `|`: - -```ini -[AdditionalOptions] -custom_apps = reddit|lyrics-plus|new-releases -``` - -After editing, run `spicetify apply`. - ---- - -## Built-in Custom Apps - -These custom apps ship with Spicetify and are available immediately. - -### Lyrics Plus - -Advanced lyrics display with multiple providers (Musixmatch, Netease, LRCLIB). - -**Folder:** `lyrics-plus` - -```bash -spicetify config custom_apps lyrics-plus -spicetify apply -``` - -**Features:** -- Synced lyrics display -- Multiple lyrics sources -- Customizable colors and providers (via profile menu) - -![Lyrics Plus](/images/apps/lyrics-plus.png) - -:::note -If lyrics aren't loading, see the [FAQ](/docs/faq#sometimes-popup-lyrics-andor-lyrics-plus-seem-to-not-work) for Musixmatch token instructions. -::: - -Learn more: [Lyrics Plus on GitHub](https://github.com/spicetify/cli/tree/main/CustomApps/lyrics-plus) - ---- - -### New Releases - -Aggregates new releases from artists and podcasts you follow. - -**Folder:** `new-releases` - -```bash -spicetify config custom_apps new-releases -spicetify apply -``` - -**Features:** -- Filter by time range and release type -- Customizable date format (based on your locale) -- Settings in profile menu - -![New Releases](/images/apps/new-releases.png) - ---- - -### Reddit - -Browse Spotify-related subreddits and discover music shared by the community. - -**Folder:** `reddit` - -```bash -spicetify config custom_apps reddit -spicetify apply -``` - -**Features:** -- Fetch posts from music subreddits -- Add, remove, and arrange subreddits -- Customize post display (via profile menu) - -![Reddit](/images/apps/reddit.png) - ---- - -## Creating Custom Apps - -Want to build your own custom app? See the **[Custom Apps Development Guide](/docs/development/custom-apps)**. - -Custom apps are React applications. A basic app needs: - -- `index.js`: Main application code with a `render()` function -- `manifest.json`: Metadata including name, icons, and optional subfiles - -For a streamlined development experience, check out **[Spicetify Creator](/docs/development/spicetify-creator/the-basics)** which supports TypeScript, JSX, and hot reloading. - ---- - -## Troubleshooting - -### Custom app not showing in sidebar - -1. Verify the folder is in the correct CustomApps location -2. Check that `manifest.json` exists and has valid JSON -3. Run `spicetify apply` -4. Restart Spotify - -### App shows but doesn't load - -1. Check the browser console for errors (Ctrl + Shift + I) -2. Verify `index.js` exports a `render()` function -3. Check for JavaScript syntax errors - -### Settings menu not appearing - -Custom app settings appear in the profile menu (top-right). Make sure the app implements settings correctly. Check the app's documentation. diff --git a/docs/customization/extensions.md b/docs/customization/extensions.md deleted file mode 100644 index 8a5b29b4..00000000 --- a/docs/customization/extensions.md +++ /dev/null @@ -1,245 +0,0 @@ ---- -title: Extensions -description: Add features to Spotify with extensions. ---- - -Extensions are JavaScript files that add new features to Spotify. They run alongside Spotify's main code and can add buttons, modify behavior, and integrate with external services. - -## Installing Extensions - -### Via Marketplace - -The easiest way to install extensions is through the **[Marketplace](./marketplace.md)**. Open Marketplace from Spotify's sidebar and browse the Extensions tab. - -### Manual Installation - -For extensions not in Marketplace, or for Spicetify's built-in extensions: - -1. Place the extension file in your Extensions folder: - - | Platform | Path | - |----------|------| - | **Windows** | `%appdata%\spicetify\Extensions\` | - | **Linux / macOS** | `~/.config/spicetify/Extensions/` | - -2. Enable the extension: - - ```bash - spicetify config extensions - spicetify apply - ``` - -:::tip -The `config extensions` command **appends** to your existing extensions. It doesn't replace them. -::: - -## Removing Extensions - -To remove an extension, append a `-` after the filename: - -```bash -spicetify config extensions - -spicetify apply -``` - -## Manual Config Editing - -You can also edit the config file directly. Extensions are listed in the `extensions` key, separated by `|`: - -```ini -[AdditionalOptions] -extensions = fullAppDisplay.js|keyboardShortcut.js|trashbin.js -``` - -After editing, run `spicetify apply`. - ---- - -## Built-in Extensions - -These extensions ship with Spicetify and are available immediately after installation. - -### Full App Display - -A minimal, full-screen album art display with blur effect background. - -**Filename:** `fullAppDisplay.js` - -```bash -spicetify config extensions fullAppDisplay.js -spicetify apply -``` - -**Usage:** Click the button in the top bar to activate. Double-click anywhere to exit. Right-click to open settings. - -![Full App Display](/images/extensions/full-app-display.png) - ---- - -### Keyboard Shortcut - -Extends Spotify's keyboard shortcuts with vim-like navigation. - -**Filename:** `keyboardShortcut.js` - -```bash -spicetify config extensions keyboardShortcut.js -spicetify apply -``` - -**Shortcuts:** -- Ctrl + Tab / Ctrl + Shift + Tab: Navigate sidebar items -- Page Up / Page Down: Scroll the main page -- J / K: Scroll down/up (vim-style) -- G / Shift + G: Jump to top/bottom -- F: Open keyboard-driven navigation - -![Keyboard Shortcut](/images/extensions/keyboard-demo.png) - ---- - -### Bookmark - -Save and quickly access pages, tracks, or specific timestamps. - -**Filename:** `bookmark.js` - -```bash -spicetify config extensions bookmark.js -spicetify apply -``` - -**Usage:** Useful for bookmarking artists, albums, or playlists to check out later without following them. - -![Bookmark](/images/extensions/bookmark.png) - ---- - -### Trash Bin - -Skip songs or artists automatically. They'll never play again. - -**Filename:** `trashbin.js` - -```bash -spicetify config extensions trashbin.js -spicetify apply -``` - -**Usage:** Right-click any track or artist and select "Throw to Trashbin". Trashed items are automatically skipped. - -![Trash Bin](/images/extensions/trash.png) - ---- - -### Shuffle+ - -True shuffle using the Fisher-Yates algorithm (zero bias). - -**Filename:** `shuffle+.js` - -```bash -spicetify config extensions shuffle+.js -spicetify apply -``` - -**Usage:** Right-click any album, playlist, or artist and select "Play with Shuffle+". Also works with multi-selected tracks. - -![Shuffle+](/images/extensions/shuffle.png) - ---- - -### Loopy Loop - -Loop a specific portion of a track. - -**Filename:** `loopyLoop.js` - -```bash -spicetify config extensions loopyLoop.js -spicetify apply -``` - -**Usage:** Mark start and end points on the progress bar to loop that section automatically. - -![Loopy Loop](/images/extensions/loopy-loop.png) - ---- - -### Pop-up Lyrics - -Display lyrics in a separate pop-up window. - -**Filename:** `popupLyrics.js` - -```bash -spicetify config extensions popupLyrics.js -spicetify apply -``` - -**Usage:** Click the microphone icon in the top bar. Right-click for settings and lyrics provider options. - -![Pop-up Lyrics](/images/extensions/popup-lyrics.png) - -:::note -If lyrics aren't loading, see the [FAQ](/docs/faq#sometimes-popup-lyrics-andor-lyrics-plus-seem-to-not-work) for Musixmatch token instructions. -::: - ---- - -### Auto Skip Videos - -Automatically skip video content that can't play in your region. - -**Filename:** `autoSkipVideo.js` - -```bash -spicetify config extensions autoSkipVideo.js -spicetify apply -``` - -**Why:** Some video content is region-locked. Without this extension, playback just stops instead of moving to the next track. - ---- - -### Christian Spotify - -Automatically skip explicit tracks. - -**Filename:** `autoSkipExplicit.js` - -```bash -spicetify config extensions autoSkipExplicit.js -spicetify apply -``` - -**Usage:** Toggle in the profile menu (top-right button). - -![Christian Spotify](/images/extensions/christian-demo.png) - ---- - -### Web Now Playing - -Send track metadata to Rainmeter's WebNowPlaying plugin. - -**Filename:** `webnowplaying.js` - -```bash -spicetify config extensions webnowplaying.js -spicetify apply -``` - -**For minimal setup** (no UI changes): - -```bash -spicetify config inject_css 0 replace_colors 0 -spicetify config extensions webnowplaying.js -spicetify apply -``` - ---- - -## Creating Extensions - -Want to build your own extension? See the **[Development Guide](/docs/development)** and **[Spicetify Creator](/docs/development/spicetify-creator/the-basics)** for tools and tutorials. diff --git a/docs/customization/index.md b/docs/customization/index.md deleted file mode 100644 index 270751cb..00000000 --- a/docs/customization/index.md +++ /dev/null @@ -1,48 +0,0 @@ ---- -title: Customization -description: Customize Spotify with themes, extensions, and custom apps. ---- - -Spicetify lets you customize Spotify in three ways: - -| Type | What it does | -|------|--------------| -| **[Themes](./themes.md)** | Change colors, fonts, and visual styling | -| **[Extensions](./extensions.md)** | Add new features and functionality | -| **[Custom Apps](./custom-apps.md)** | Add entirely new pages to the sidebar | - -## Marketplace vs Manual Installation - -There are two ways to install customizations: - -### Marketplace (Recommended) - -The **[Spicetify Marketplace](./marketplace.md)** is a built-in browser that lets you discover, install, and manage themes, extensions, and snippets directly from Spotify's sidebar. - -If you followed the [Getting Started](/docs/getting-started) guide, you already have Marketplace installed. - -**Use Marketplace when:** -- You want a simple, visual way to browse and install customizations -- You prefer one-click installation -- You want automatic updates - -### Manual Installation - -Manual installation gives you direct control over your Spicetify configuration. - -**Use manual installation when:** -- The theme/extension isn't available in Marketplace -- You're developing your own customizations -- You want to use built-in extensions that ship with Spicetify -- You need precise control over what's installed - -Each customization page in this section covers both Marketplace usage and manual installation. - -## Configuration - -All manual customizations are controlled through: - -- **CLI commands**: `spicetify config ` followed by `spicetify apply` -- **Config file**: Direct editing of `config-xpui.ini` - -See the **[Configuration Reference](./config-file.md)** for details on the config file and all available options. diff --git a/docs/customization/marketplace.md b/docs/customization/marketplace.md deleted file mode 100644 index f8116d19..00000000 --- a/docs/customization/marketplace.md +++ /dev/null @@ -1,121 +0,0 @@ ---- -title: Marketplace -description: Browse and install themes, extensions, and snippets from Spotify. ---- - -The Spicetify Marketplace is a built-in store that lets you browse, install, and manage customizations directly from Spotify. - -## Installation - -If you followed the [Getting Started](/docs/getting-started) guide, Marketplace is already installed. If not: - -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; - - - - -```powershell -iwr -useb https://raw.githubusercontent.com/spicetify/marketplace/main/resources/install.ps1 | iex -``` - - - - -```bash -curl -fsSL https://raw.githubusercontent.com/spicetify/marketplace/main/resources/install.sh | sh -``` - - - - -## Using Marketplace - -After installation, you'll see a **Marketplace** item in Spotify's sidebar. Click it to open the store. - -### Browsing - -The Marketplace has several tabs: - -- **Extensions**: Add features like lyrics display, keyboard shortcuts, and more -- **Themes**: Change Spotify's visual appearance -- **Snippets**: Small CSS tweaks for minor visual adjustments - -Use the search bar to find specific items, or browse by category. - -### Installing - -1. Click on any item to see its details -2. Click **Install** to add it to your Spotify -3. The change takes effect immediately (no restart needed for most items) - -### Managing Installed Items - -Click the **Installed** tab to see everything you've added. From here you can: - -- **Enable/Disable**: Toggle items on and off without uninstalling -- **Uninstall**: Remove items completely -- **Update**: Apply updates when available (indicated by a badge) - -## Marketplace vs Manual - -| Feature | Marketplace | Manual | -|---------|-------------|--------| -| One-click install | Yes | No | -| Automatic updates | Yes | No | -| Built-in extensions | Some | All | -| Custom/local files | No | Yes | -| Fine-grained control | Limited | Full | - -For built-in extensions that ship with Spicetify (like Full App Display, Keyboard Shortcuts, etc.), see the **[Extensions](./extensions.md)** page for manual installation instructions. - -## Troubleshooting - -### Marketplace not showing in sidebar - -1. Make sure you ran the Marketplace install script -2. Run `spicetify apply` to ensure changes are applied -3. Restart Spotify - -### Items not installing - -1. Check your internet connection -2. Try running `spicetify restore backup apply` to refresh the installation -3. Check the [GitHub Issues](https://github.com/spicetify/marketplace/issues) for known problems - -### Marketplace disappeared after Spotify update - -After Spotify updates, you may need to re-apply Spicetify: - -```bash -spicetify backup apply -``` - -### Resetting Marketplace - -If Marketplace becomes unresponsive or you cannot access its settings: - -1. Enable DevTools: - - ```bash - spicetify enable-devtools - ``` - -2. Open DevTools in Spotify (Ctrl + Shift + I or Cmd + Option + I) - -3. In the Console tab, run: - - ```js - Marketplace.reset() - ``` - - This resets all Marketplace localStorage data. You can also reset specific types: - - ```js - Marketplace.reset("theme") // Reset theme only - Marketplace.reset("extensions") // Reset extensions only - Marketplace.reset("snippets") // Reset snippets only - Marketplace.reset("theme", "snippets") // Reset multiple types - ``` - -4. Restart Spotify diff --git a/docs/development/api-wrapper/classes/context-menu.md b/docs/development/api-wrapper/classes/context-menu.md deleted file mode 100644 index 807566f4..00000000 --- a/docs/development/api-wrapper/classes/context-menu.md +++ /dev/null @@ -1,230 +0,0 @@ ---- -title: ContextMenu -description: Create custom menu item and prepend to right click context menu. ---- - -Create custom menu item and prepend to right click context menu. - -Useful for adding custom actions to context menu (when a user right-clicks on a track, album, artist, etc.) - -## Classes - -### Item - -Single context menu item. - - -```ts -new Spicetify.ContextMenu.Item( - name: string, - onClick: OnClickCallback, - shouldAdd?: ShouldAddCallback, - icon?: SVGIcon | string, - disabled?: boolean, -) -``` - -#### Parameters - -| Parameter | Type | Description | -| :--- | :--- | :--- | -| name | `string` | Name of the menu item. | -| onClick | `OnClickCallback` | Callback function when the menu item is clicked. | -| shouldAdd | `ShouldAddCallback` | Callback function to determine if the menu item should be added. | -| icon | [`SVGIcon`](/docs/development/api-wrapper/types/svgicon) | `string` | Icon of the menu item. | -| disabled | `boolean` | Whether the menu item is disabled. | - -#### Properties - -:::tip - -All of the listed properties are dynamic and can be changed at any time. Look into the example below for more information. - -::: - -| Name | Type | Description | -| :--- | :--- | :--- | -| iconList | [`readonly SVGIcon[]`](/docs/development/api-wrapper/types/svgicon) | List of icons. | -| name | `string` | Name of the menu item. | -| icon | [`SVGIcon`](/docs/development/api-wrapper/types/svgicon) | `string` | Icon at the end of the menu item. | -| disabled | `boolean` | Whether the menu item is disabled. | -| shouldAdd | [`ShouldAddCallback`](/docs/development/api-wrapper/types/context-menu/should-add-callback) | Callback function to determine if the menu item should be added. | -| onClick | [`OnClickCallback`](/docs/development/api-wrapper/types/context-menu/onclick-callback) | Callback function when the menu item is clicked. | - -#### Methods - -##### `register` - -Register the menu item to context menu. - -```ts -register(): void -``` - -##### `deregister` - -Remove the menu item from context menu. - -```ts -deregister(): void -``` -#### Example - -```ts -// This function will determine if the selected item is a track -function ifItemIsTrack(uri) { - let uriObj = Spicetify.URI.fromString(uri[0]); - switch (uriObj.type) { - case Type.TRACK: - return true; - } - return false; -} - -// Create a new menu item that only appears when a track is selected -const menuItem = new Spicetify.ContextMenu.Item( - "My Menu Item", - () => { - Spicetify.showNotification("My Menu Item clicked!"); - }, - ifItemIsTrack, - Spicetify.SVGIcons["play"], - false, -); - -// Register the menu item -menuItem.register(); - -// Deregister the menu item -menuItem.deregister(); - -// Change the menu item's name -menuItem.name = "My New Menu Item"; - -// Change the menu item's icon -menuItem.icon = "pause" -``` - -### SubMenu - -Create a sub menu to contain `Item`s. - -`Item`s in `subItems` array shouldn't be registered. - - -```ts -new Spicetify.ContextMenu.SubMenu( - name: string, - subItems: Iterable, - shouldAdd?: ShouldAddCallback, - disabled?: boolean, -) -``` - -#### Parameters - -| Parameter | Type | Description | -| :--- | :--- | :--- | -| name | `string` | Name of the menu item. | -| subItems | [`Iterable`](#item) | Array of `Item`s to be added to the sub menu. | -| shouldAdd | [`ShouldAddCallback`](/docs/development/api-wrapper/types/context-menu/should-add-callback) | Callback function to determine if the menu item should be added. | -| disabled | `boolean` | Whether the menu item is disabled. | - -#### Properties - -:::tip - -All of the listed properties are dynamic and can be changed at any time. Look into the example below for more information. - -::: - -| Name | Type | Description | -| :--- | :--- | :--- | -| name | `string` | Name of the menu item. | -| disabled | `boolean` | Whether the menu item is disabled. | -| shouldAdd | [`ShouldAddCallback`](/docs/development/api-wrapper/types/context-menu/should-add-callback) | Callback function to determine if the menu item should be added. | - -#### Methods - -##### `addItem` - -Add an `Item` to the sub menu. - -```ts -addItem(item: Item): void -``` - -| Parameter | Type | Description | -| :--- | :--- | :--- | -| item | [`Item`](#item) | `Item` to be added to the sub menu. | - -##### `removeItem` - -Remove an `Item` from the sub menu. - -```ts -removeItem(item: Item): void -``` - -| Parameter | Type | Description | -| :--- | :--- | :--- | -| item | [`Item`](#item) | `Item` to be removed from the sub menu. | - -##### `register` - -Register the sub menu to context menu. - -```ts -register(): void -``` - -##### `deregister` - -Remove the sub menu from context menu. - -```ts -deregister(): void -``` - -#### Example - -```ts -// Create a new menu item -const menuItem = new Spicetify.ContextMenu.Item( - "My Menu Item", - () => { - Spicetify.showNotification("My Menu Item clicked!"); - }, - () => true, - Spicetify.SVGIcons["play"], - false, -); - -// Create a new sub menu -const subMenu = new Spicetify.ContextMenu.SubMenu( - "My Sub Menu", - [menuItem], - () => true, - false, -); - -// Register the sub menu -subMenu.register(); - -// Deregister the sub menu -subMenu.deregister(); - -// Change the sub menu's name -subMenu.name = "My New Sub Menu"; - -// Add a new menu item to the sub menu -subMenu.addItem(new Spicetify.ContextMenu.Item( - "My New Menu Item", - () => { - Spicetify.showNotification("My New Menu Item clicked!"); - }, - () => true, - Spicetify.SVGIcons["play"], - false, -)); -``` diff --git a/docs/development/api-wrapper/classes/menu.md b/docs/development/api-wrapper/classes/menu.md deleted file mode 100644 index 4ec235a7..00000000 --- a/docs/development/api-wrapper/classes/menu.md +++ /dev/null @@ -1,229 +0,0 @@ ---- -title: Menu -description: Create and prepend custom menu items in the profile menu. ---- - -Create and prepend custom menu items in the profile menu. - -## Classes - -### Item - -Single menu item. - -```ts -new Spicetify.Menu.Item( - name: string, - isEnabled: boolean, - onClick: (self: Item) => void, - icon?: SVGIcon | string, -) -``` - -#### Parameters - -| Parameter | Type | Description | -| :--- | :--- | :--- | -| name | `string` | Name of the menu item. | -| isEnabled | `boolean` | Whether the menu item is enabled. | -| onClick | `(self: Item) => void` | Callback function when the menu item is clicked. | -| icon | [`SVGIcon`](/docs/development/api-wrapper/types/svgicon) | `string` | Icon of the menu item. | - -#### Properties - -:::tip - -All of the listed properties are dynamic and can be changed at any time. Look into the example below for more information. - -::: - -| Name | Type | Description | -| :--- | :--- | :--- | -| name | `string` | Name of the menu item. | -| isEnabled | `boolean` | Whether the menu item is enabled. | -| icon | [`SVGIcon`](/docs/development/api-wrapper/types/svgicon) | `string` | Icon of the menu item. | - -#### Methods - -##### setName - -Set the label of the menu item. - -```ts -setName(name: string): void -``` - -| Parameter | Type | Description | -| :--- | :--- | :--- | -| name | `string` | Name of the menu item. | - -##### setState - -Set the state of the menu item. The item will have a tick icon next to it if its state is enabled. - -```ts -setState(isEnabled: boolean): void -``` - -| Parameter | Type | Description | -| :--- | :--- | :--- | -| isEnabled | `boolean` | Whether the menu item is enabled. | - -##### setIcon - -Set the icon at the end of the menu item. - -```ts -setIcon(icon: SVGIcon | string): void -``` - -| Parameter | Type | Description | -| :--- | :--- | :--- | -| icon | [`SVGIcon`](/docs/development/api-wrapper/types/svgicon) | `string` | Icon of the menu item. | - -##### register - -Register the menu item to the profile menu. - -```ts -register(): void -``` - -##### deregister - -Remove the menu item from the profile menu. - -```ts -deregister(): void -``` - -#### Example - -```ts -const item = new Spicetify.Menu.Item("My Item", true, () => { - console.log("My Item is clicked"); -}); - -item.register(); - -// item.name = "My Item (Updated)"; -item.setName("My Item (Updated)"); - -// item.isEnabled = false; -item.setState(false); - -// item.icon = "heart"; -item.setIcon("heart"); -``` - -### SubMenu - -Create a sub menu to contain `Item` toggles. - -`Item`s in `subItems` array shouldn't be registered. - -```ts -new Spicetify.Menu.SubMenu( - name: string, - subItems: Item[], -) -``` - -#### Parameters - -| Parameter | Type | Description | -| :--- | :--- | :--- | -| name | `string` | Name of the menu item. | -| subItems | [`Item[]`](/docs/development/api-wrapper/classes/menu#item) | Array of sub menu items. | - -#### Properties - -:::tip - -All of the listed properties are dynamic and can be changed at any time. Look into the example below for more information. - -::: - -| Name | Type | Description | -| :--- | :--- | :--- | -| name | `string` | Name of the menu item. | - -#### Methods - -##### setName - -Set the label of the menu item. - -```ts -setName(name: string): void -``` - -| Parameter | Type | Description | -| :--- | :--- | :--- | -| name | `string` | Name of the menu item. | - -##### addItem - -Add a sub menu item. - -```ts -addItem(item: Item): void -``` - -| Parameter | Type | Description | -| :--- | :--- | :--- | -| item | [`Item`](/docs/development/api-wrapper/classes/menu#item) | Sub menu item. | - -##### removeItem - -Remove a sub menu item. - -```ts -removeItem(item: Item): void -``` - -| Parameter | Type | Description | -| :--- | :--- | :--- | -| item | [`Item`](/docs/development/api-wrapper/classes/menu#item) | Sub menu item. | - -##### register - -Register the menu item to profile menu. - -```ts -register(): void -``` - -##### deregister - -Remove the menu item from profile menu. - -```ts -deregister(): void -``` - -#### Example - -```ts -const item1 = new Spicetify.Menu.Item("My Item 1", true, () => { - console.log("My Item 1 is clicked"); -}); - -const item2 = new Spicetify.Menu.Item("My Item 2", true, () => { - console.log("My Item 2 is clicked"); -}); - -const subMenu = new Spicetify.Menu.SubMenu("My Sub Menu", [item1, item2]); - -subMenu.register(); - -// subMenu.name = "My Sub Menu (Updated)"; -subMenu.setName("My Sub Menu (Updated)"); - -// subMenu.addItem(item3); -subMenu.addItem( - new Spicetify.Menu.Item("My Item 3", true, () => { - console.log("My Item 3 is clicked"); - }) -); -``` diff --git a/docs/development/api-wrapper/classes/playbar.md b/docs/development/api-wrapper/classes/playbar.md deleted file mode 100644 index 92316078..00000000 --- a/docs/development/api-wrapper/classes/playbar.md +++ /dev/null @@ -1,254 +0,0 @@ ---- -title: Playbar -description: Create buttons in the player. ---- - -Create buttons in the player. - -```ts -namespace Playbar { - class Button { - constructor(label: string, icon: SVGIcon | string, onClick: (self: Button) => void, disabled?: boolean, active?: boolean, registerOnCreate?: boolean); - label: string; - icon: string; - onClick: (self: Button) => void; - disabled: boolean; - active: boolean; - element: HTMLButtonElement; - tippy: any; - register: () => void; - deregister: () => void; - } - - class Widget { - constructor(label: string, icon: SVGIcon | string, onClick?: (self: Widget) => void, disabled?: boolean, active?: boolean, registerOnCreate?: boolean); - label: string; - icon: string; - onClick: (self: Widget) => void; - disabled: boolean; - active: boolean; - element: HTMLButtonElement; - tippy: any; - register: () => void; - deregister: () => void; - } -}; -``` - -## `Button` - -Create buttons next to the player extra control buttons (e.g. queue, lyrics, Now Playing View, etc.). - -This is useful for creating buttons whose actions have an impact on or relate to the player, and are generally dynamic/stateful, such as a button that toggles the player's loop mode. - -#### Parameters - -| Parameter | Type | Description | -| :--- | :--- | :--- | -| label | `string` | Label of the button. | -| icon | [`SVGIcon`](/docs/development/api-wrapper/types/svgicon) | `string` | Icon of the button. | -| onClick | `(self: Button) => void` | Callback function when the button is clicked. | -| disabled | `boolean` | `undefined` | Whether the button is disabled. | -| active | `boolean` | `undefined` | Whether the button is active. | -| registerOnCreate | `boolean` | `undefined` | Whether the button should be registered to the player on creation. | - -#### Properties - -:::tip - -All of the listed properties are dynamic and can be changed at any time. Look into the example below for more information. - -::: - -| Name | Type | Description | -| :--- | :--- | :--- | -| label | `string` | Label of the button. | -| icon | `string` | Icon of the button. | -| disabled | `boolean` | Whether the button is disabled. | -| active | `boolean` | Whether the button is active. | -| onClick | `(self: Button) => void` | Callback function when the button is clicked. | -| element | `HTMLButtonElement` | HTML element of the button. | -| tippy | `any` | Tippy instance of the button. For more information, see [Tippy.js](https://atomiks.github.io/tippyjs/v6/tippy-instance/). | - -#### Methods - -##### `register` - -Register the button to the player. - -```ts -register(): void; -``` - -##### `deregister` - -Deregister the button from the player. - -```ts -deregister(): void; -``` - -#### Example - -:::caution - -Tippy, `onclick` or any other click events will **not** work if `disabled` is set to `true`. You will need to manually enable the button inside your extension. - -This is due to the limitations of Tippy itself and how HTML elements work. - -::: - -```ts -// By default, the button will be registered to the player on creation. -// You can disable this by setting registerOnCreate to false. -// Each button comes with a preconfigured Tippy instance that aims to mimic the original Spotify tooltip. -const button = new Spicetify.Playbar.Button( - "My Button", - "play", - (self) => { - // Do something when the button is clicked. - }, - false, // Whether the button is disabled. - false, // Whether the button is active. -); - -// You can also register the button to the player later. -button.register(); - -// Remove the button from the player when it is no longer needed. -button.deregister(); -// If you don't want to remove the button entirely, you can also disable it. -button.disabled = true; - -// Change button properties. -// Changing label will also change the tooltip content. -button.label = "Hello world!"; -button.icon = "play"; - -// You can also set properties of the HTML element. -button.element.style.color = "red"; -button.element.oncontextmenu = () => { - Spicetify.showNotification("You right-clicked me!"); -}; -button.element.addEventListener("click", () => { - // Do something else. - Spicetify.showNotification("You clicked me!"); -}); - -// You can also change properties of the Tippy instance. For more information, see https://atomiks.github.io/tippyjs/v6/tippy-instance/. -button.tippy.setContent("Hello world!"); - -// Or if you want to use HTML. -button.tippy.setProps({ - content: "Hello world!", - allowHTML: true, -}); -``` - -## `Widget` - -Create widgets in the player, next to track information similar to the Heart button. - -This is useful for creating buttons whose actions have an impact on the state of the player and the track being played, such as a button that adds the current track to a playlist. - -#### Parameters - -| Parameter | Type | Description | -| :--- | :--- | :--- | -| label | `string` | Label of the widget. | -| icon | [`SVGIcon`](/docs/development/api-wrapper/types/svgicon) | `string` | Icon of the widget. | -| onClick | `(self: Widget) => void` | Callback function when the widget is clicked. | -| disabled | `boolean` | `undefined` | Whether the widget is disabled. | -| active | `boolean` | `undefined` | Whether the widget is active. | -| registerOnCreate | `boolean` | `undefined` | Whether the widget should be registered to the player on creation. | - -#### Properties - -:::tip - -All of the listed properties are dynamic and can be changed at any time. Look into the example below for more information. - -::: - -| Name | Type | Description | -| :--- | :--- | :--- | -| label | `string` | Label of the widget. | -| icon | `string` | Icon of the widget. | -| disabled | `boolean` | Whether the widget is disabled. | -| active | `boolean` | Whether the widget is active. | -| onClick | `(self: Widget) => void` | Callback function when the widget is clicked. | -| element | `HTMLButtonElement` | HTML element of the widget. | -| tippy | `any` | Tippy instance of the widget. For more information, see [Tippy.js](https://atomiks.github.io/tippyjs/v6/tippy-instance/). | - -#### Methods - -##### `register` - -Register the widget to the player. - -```ts -register(): void; -``` - -##### `deregister` - -Deregister the widget from the player. - -```ts -deregister(): void; -``` - -#### Example - -:::caution - -Tippy, `onclick` or any other click events will **not** work if `disabled` is set to `true`. You will need to manually enable the widget inside your extension. - -This is due to the limitations of Tippy itself and how HTML elements work. - -::: - -```ts -// By default, the widget will be registered to the player on creation. -// You can disable this by setting registerOnCreate to false. -// Each widget comes with a preconfigured Tippy instance that aims to mimic the original Spotify tooltip. -const widget = new Spicetify.Playbar.Widget( - "My Widget", - "play", - (self) => { - // Do something when the widget is clicked. - }, - false, // Whether the widget is disabled. - false, // Whether the widget is active. -); - -// You can also register the widget to the player later. -widget.register(); - -// Remove the widget from the player when it is no longer needed. -widget.deregister(); - -// Change widget properties. -// Changing label will also change the tooltip content. -widget.label = "Hello world!"; -widget.icon = "play"; - -// You can also set properties of the HTML element. -widget.element.style.color = "red"; -widget.element.oncontextmenu = () => { - Spicetify.showNotification("You right-clicked me!"); -}; -widget.element.addEventListener("click", () => { - // Do something else. - Spicetify.showNotification("You clicked me!"); -}); - -// You can also change properties of the Tippy instance. For more information, see https://atomiks.github.io/tippyjs/v6/tippy-instance/. -widget.tippy.setContent("Hello world!"); - -// Or if you want to use HTML. -widget.tippy.setProps({ - content: "Hello world!", - allowHTML: true, -}); -``` diff --git a/docs/development/api-wrapper/classes/topbar.md b/docs/development/api-wrapper/classes/topbar.md deleted file mode 100644 index 11b3c5e2..00000000 --- a/docs/development/api-wrapper/classes/topbar.md +++ /dev/null @@ -1,93 +0,0 @@ ---- -title: Topbar -description: Create buttons in the top bar. ---- - -Create buttons in the top bar, next to the navigation buttons. - -This is useful for creating buttons that are generally static and whose actions have an impact on the whole app, such as a button that opens a settings menu. - -```ts -namespace Topbar { - class Button { - constructor(label: string, icon: SVGIcon | string, onClick: (self: Button) => void, disabled?: boolean, isRight?: boolean); - label: string; - icon: string; - onClick: (self: Button) => void; - disabled: boolean; - isRight: boolean; - element: HTMLButtonElement; - tippy: any; - } -}; -``` - -#### Parameters - -| Parameter | Type | Description | -| :--- | :--- | :--- | -| label | `string` | Label of the button. | -| icon | [`SVGIcon`](/docs/development/api-wrapper/types/svgicon) | `string` | Icon of the button. | -| onClick | `(self: Button) => void` | Callback function when the button is clicked. | -| disabled | `boolean` | Whether the button is disabled. | -| isRight | `boolean` | Whether the button is button placed on the right side. | - -#### Properties - -:::tip - -All of the listed properties are dynamic and can be changed at any time. Look into the example below for more information. - -::: - -| Name | Type | Description | -| :--- | :--- | :--- | -| label | `string` | Label of the button. | -| icon | `string` | Icon of the button. | -| disabled | `boolean` | Whether the button is disabled. | -| onClick | `(self: Button) => void` | Callback function when the button is clicked. | -| element | `HTMLButtonElement` | HTML element of the button. | -| tippy | `any` | Tippy instance of the button. For more information, see [Tippy.js](https://atomiks.github.io/tippyjs/v6/tippy-instance/). | - -#### Example - -:::caution - -Tippy, `onclick` or any other click events will **not** work if `disabled` is set to `true`. You will need to manually enable the button inside your extension. - -This is due to the limitations of Tippy itself and how HTML elements work. - -::: - -```ts -// Button is automatically added to the top bar when created. -// Each button comes with a preconfigured Tippy instance that aims to mimic the original Spotify tooltip. -const button = new Spicetify.Topbar.Button("Hello", "download", () => { - Spicetify.showNotification("Hello world!"); -}); - -// Change button properties. -// Changing label will also change the tooltip content. -button.label = "Hello world!"; -button.icon = "play"; -button.disabled = true; - -// You can also set properties of the HTML element. -button.element.style.color = "red"; -button.element.oncontextmenu = () => { - Spicetify.showNotification("You right-clicked me!"); -}; -button.element.addEventListener("click", () => { - // Do something else. - Spicetify.showNotification("You clicked me!"); -}); - -// You can also change properties of the Tippy instance. For more information, see https://atomiks.github.io/tippyjs/v6/tippy-instance/. -button.tippy.setContent("Hello world!"); - -// Or if you want to use HTML. -button.tippy.setProps({ - content: "Hello world!", - allowHTML: true, -}); -``` diff --git a/docs/development/api-wrapper/functions/add-to-queue.md b/docs/development/api-wrapper/functions/add-to-queue.md deleted file mode 100644 index bfe56983..00000000 --- a/docs/development/api-wrapper/functions/add-to-queue.md +++ /dev/null @@ -1,37 +0,0 @@ ---- -title: addToQueue -description: Adds a track/album or array of tracks/albums to prioritized queue. ---- - -Adds a track or array of tracks to the bottom of the prioritized queue. - -:::tip - -This works similarly to [`Spicetify.Platform.PlayerAPI.addToQueue`](/docs/development/api-wrapper/methods/platform#addtoqueue) but works silently, meaning no notification will be shown. - -If you want default Spotify behavior, use [`Spicetify.Platform.PlayerAPI.addToQueue`](/docs/development/api-wrapper/methods/platform#addtoqueue) instead. - -::: - -```ts -function addToQueue(uri: ContextTrack[]): Promise; -``` - -#### Parameters - -| Name | Type | Description | -| :--- | :--- | :--- | -| `uri` | [`ContextTrack[]`](/docs/development/api-wrapper/types/context-track) | Array of tracks to add to queue. | - -#### Example - -```ts -// Add current track to queue -const currentTrack = Spicetify.Player.data.item; - -await Spicetify.addToQueue([currentTrack]); - -// Add a track to queue -const trackUri = "spotify:track:4iV5W9uYEdYUVa79Axb7Rh"; - -await Spicetify.addToQueue([ { uri: trackUri } ]); diff --git a/docs/development/api-wrapper/functions/color-extractor.md b/docs/development/api-wrapper/functions/color-extractor.md deleted file mode 100644 index bdac8189..00000000 --- a/docs/development/api-wrapper/functions/color-extractor.md +++ /dev/null @@ -1,42 +0,0 @@ ---- -title: colorExtractor -description: Extracts colors from a playlist, track, album, artist, show, etc. ---- - -Extracts colors from a playlist, track, album, artist, show, etc. - -```ts -function colorExtractor(uri: string): Promise<{ - DARK_VIBRANT: string; - DESATURATED: string; - LIGHT_VIBRANT: string; - PROMINENT: string; - VIBRANT: string; - VIBRANT_NON_ALARMING: string; -}>; -``` - -#### Parameters - -| Parameter | Type | Description | -| :--- | :--- | :--- | -| uri | `string` | URI of anything that has artwork (playlist, track, album, artist, show, etc.) | - -#### Returns - -| Name | Type | Description | -| :--- | :--- | :--- | -| DARK_VIBRANT | `string` | Dark vibrant color in hex format. | -| DESATURATED | `string` | Desaturated color in hex format. | -| LIGHT_VIBRANT | `string` | Light vibrant color in hex format. | -| PROMINENT | `string` | Prominent color in hex format. | -| VIBRANT | `string` | Vibrant color in hex format. | -| VIBRANT_NON_ALARMING | `string` | Vibrant non alarming color in hex format. | - -#### Example - -```ts -// Get color from current track -const currentTrack = Spicetify.Player.data.item; -const colors = await Spicetify.colorExtractor(currentTrack.uri); -``` diff --git a/docs/development/api-wrapper/functions/get-audio-data.md b/docs/development/api-wrapper/functions/get-audio-data.md deleted file mode 100644 index 21277c07..00000000 --- a/docs/development/api-wrapper/functions/get-audio-data.md +++ /dev/null @@ -1,38 +0,0 @@ ---- -title: getAudioData -description: Get the audio data from a track. ---- - -Fetch track analyzed audio data. - -Under the hood, it uses the `wg://audio-attributes/v1/audio-analysis/` endpoint, which is identical to Spotify Web API's [Get Track's Audio Analysis](https://developer.spotify.com/documentation/web-api/reference/get-audio-analysis). The only difference is that it doesn't require authentication. - -:::caution - -Beware, not all tracks have audio data. - -::: - -```ts -function getAudioData(uri?: string): Promise; -``` - -#### Parameters - -| Parameter | Type | Description | -| :--- | :--- | :--- | -| uri | `string` | `undefined` | URI of the track. If not provided, it will use the current track. | - -#### Returns - -An object containing the audio data. See the [Spotify Web API reference](https://developer.spotify.com/documentation/web-api/reference/get-audio-analysis) for more details. - -#### Example - -```ts -// Get audio data from current track -const audioData = await Spicetify.getAudioData(); - -// Get audio data from a specific track -const audioData = await Spicetify.getAudioData("spotify:track:1qDrWA6lyx8cLECdZE7TV7"); -``` diff --git a/docs/development/api-wrapper/functions/get-font-style.md b/docs/development/api-wrapper/functions/get-font-style.md deleted file mode 100644 index a6957c75..00000000 --- a/docs/development/api-wrapper/functions/get-font-style.md +++ /dev/null @@ -1,38 +0,0 @@ ---- -title: getFontStyle -description: Returns the font style for a given variant. ---- - -Spicetify provides a function that returns the CSS style for a given font variant used in the Spotify app. - -:::tip - -This function is used to provide backwards compatibility for older Spicetify extensions and custom apps that use `main-type-` classes. - -Instead of using this function to get Spotify stylings, you can simply add the `main-type-` class to your element. - -::: - -```ts -function getFontStyle(font: Variant): string; -``` - -#### Parameters - -| Parameter | Type | Description | -| :--- | :--- | :--- | -| font | [`Variant`](/docs/development/api-wrapper/types/variant) | Font variant | - -#### Returns - -`string` - CSS style for the given font variant. - -#### Example - -```ts -const style = getFontStyle("forte"); - -// Returns "viola" if given an invalid variant -// Equivalent to `getFontStyle("viola");` -const style = getFontStyle("invalid-variant"); -``` diff --git a/docs/development/api-wrapper/functions/remove-from-queue.md b/docs/development/api-wrapper/functions/remove-from-queue.md deleted file mode 100644 index 62a164b6..00000000 --- a/docs/development/api-wrapper/functions/remove-from-queue.md +++ /dev/null @@ -1,40 +0,0 @@ ---- -title: removeFromQueue -description: Removes a track or array of tracks from prioritized queue. ---- - -:::tip - -This works similarly to [`Spicetify.Platform.PlayerAPI.removeFromQueue`](/docs/development/api-wrapper/methods/platform#removefromqueue). - -::: - -:::caution - -If a `uid` is not provided, all tracks with the same `uri` will be removed. - -::: - -```ts -function removeFromQueue(uri: ContextTrack[]): Promise; -``` - -#### Parameters - -| Name | Type | Description | -| :--- | :--- | :--- | -| `uri` | [`ContextTrack[]`](/docs/development/api-wrapper/types/context-track) | Array of tracks to remove from queue. | - -#### Example - -```ts -// Remove current track from queue -const currentTrack = Spicetify.Player.data.item; - -await Spicetify.removeFromQueue([currentTrack]); - -// Remove a track from queue -const trackUri = "spotify:track:4iV5W9uYEdYUVa79Axb7Rh"; - -await Spicetify.removeFromQueue([ { uri: trackUri } ]); -``` diff --git a/docs/development/api-wrapper/functions/show-notification.md b/docs/development/api-wrapper/functions/show-notification.md deleted file mode 100644 index a98bcd67..00000000 --- a/docs/development/api-wrapper/functions/show-notification.md +++ /dev/null @@ -1,32 +0,0 @@ ---- -title: showNotification -description: Show a toast notification inside Spotify. ---- - -Show a toast notification inside Spotify. - -```ts -function showNotification(text: string, isError?: boolean, msTimeout?: number): void; -``` - -| Parameter | Type | Description | -| :--- | :--- | :--- | -| text | `string` | Message to display. Can use inline HTML for styling. | -| isError | `boolean` | If true, toast will be red. Defaults to false. | -| msTimeout | `number` | Time in milliseconds to display the toast. Defaults to Spotify's value. | - -#### Example - -```ts -// Display a notification -Spicetify.showNotification("My Menu Item clicked!"); - -// Display a notification with a custom timeout -Spicetify.showNotification("My Menu Item clicked!", false, 1000); - -// Display an error notification -Spicetify.showNotification("Something wrong happened", true); - -// Display a bolded error notification -Spicetify.showNotification("Something wrong happened", true); -``` diff --git a/docs/development/api-wrapper/index.md b/docs/development/api-wrapper/index.md deleted file mode 100644 index 31db7c9b..00000000 --- a/docs/development/api-wrapper/index.md +++ /dev/null @@ -1,15 +0,0 @@ ---- -title: API Wrapper -description: 🧰 Everything you need to know about the Spicetify object and API Wrapper. ---- - -Making an extension from scratch can be a daunting task. Luckily, Spicetify provides a powerful API Wrapper that makes it easy to interact with Spotify's internal APIs as well as provide out-of-the-box methods to help you easily create extensions. - -## Spicetify Object -You can access the Spicetify object by typing `Spicetify` in the DevTools console, inside your extension, or `window.top.Spicetify` if you're developing an app inside an `iframe`. - -```ts -Spicetify -``` - -Navigate the sidebar to see all the methods and properties available in the Spicetify object! diff --git a/docs/development/api-wrapper/methods/app-title.md b/docs/development/api-wrapper/methods/app-title.md deleted file mode 100644 index 4c55a4ba..00000000 --- a/docs/development/api-wrapper/methods/app-title.md +++ /dev/null @@ -1,128 +0,0 @@ ---- -title: AppTitle -description: Set of API methods to interact with the Spotify client app title. ---- - -Spicetify provides a set of API methods to interact with the Spotify client app title. - -:::note - -These methods only work for the default app title. - -::: - -```ts -namespace AppTitle { - function set(title: string): Promise<{ clear: () => void }>; - function reset(): Promise; - function get(): Promise; - function sub(callback: (title: string) => void): { clear: () => void }; -} -``` - -## Methods - -### `set` - -Set the default app title and force it until canceled. This will override any previous forced title. - -:::note - -This will temporarily override the current title if a track is being played until the player changes track or the user interacts with the player. - -::: - -```ts -function set(title: string): Promise<{ clear: () => void }>; -``` - -#### Parameters - -| Name | Type | Description | -| ------ | -------- | ----------- | -| `title` | `string` | Title to set | - -#### Returns - -Promise that resolves to a function to cancel forced title. This doesn't reset the title. - -#### Example - -```ts -await Spicetify.AppTitle.set("My Extension"); -``` - -### `reset` - -Reset app title to default. - -```ts -function reset(): Promise; -``` - -#### Example - -```ts -await Spicetify.AppTitle.reset(); // Spotify Premium -``` - -### `get` - -Get current default app title. - -:::note - -This method cannot get the title of the currently played track. - -::: - -```ts -function get(): Promise; -``` - -#### Returns - -Current default app title. - -#### Example - -```ts -const title = await Spicetify.AppTitle.get(); -console.log(title); // Spotify Premium -``` - -### `sub` - -Subscribe to title changes. - -:::note - -This event is not fired when the player changes app title. - -::: - -```ts -function sub(callback: (title: string) => void): { clear: () => void }; -``` - -#### Parameters - -| Name | Type | Description | -| ---- | ---- | ----------- | -| `callback` | `(title: string) => void` | Callback to call when title changes | - -#### Returns - -Object with method to unsubscribe. - -#### Example - -```ts -const { clear } = Spicetify.AppTitle.sub((title) => { - console.log(title); -}); - -await Spicetify.AppTitle.set("My Extension"); // Console: My Extension - -clear(); -``` \ No newline at end of file diff --git a/docs/development/api-wrapper/methods/cosmos-async.md b/docs/development/api-wrapper/methods/cosmos-async.md deleted file mode 100644 index 984ee062..00000000 --- a/docs/development/api-wrapper/methods/cosmos-async.md +++ /dev/null @@ -1,239 +0,0 @@ ---- -title: CosmosAsync -description: Asynchronous Cosmos API wrapper used by the Spotify client. ---- - -Asynchronous Cosmos API wrapper used by the Spotify client. It is used to make requests to the Spotify client's internal API as well as external URLs. - -```ts -Spicetify.CosmosAsync -``` - -It works similarly to `fetch` or `axios` but for each request it will automatically add the required headers and cookies (such as user session token). All responses are parsed as JSON. - -:::caution - -Be mindful of where you're making a request to, especially if you're making a request to an external URL as it *may* compromise the user's account. - -If you're not certain, only use `CosmosAsync` for internal Spotify URLs, or use `fetch` for external URLs. - -::: - -:::tip - -Feel free to reach out to the developers' community on [**Discord**](https://discord.gg/VnevqPp2Rr) if you need help with any of these methods, or if you need a list of all available internal/useful endpoints. - -::: - -## Methods - -```ts -namespace CosmosAsync { - function head(url: string, headers?: Headers): Promise; - function get(url: string, body?: Body, headers?: Headers): Promise; - function post(url: string, body?: Body, headers?: Headers): Promise; - function put(url: string, body?: Body, headers?: Headers): Promise; - function del(url: string, body?: Body, headers?: Headers): Promise; - function patch(url: string, body?: Body, headers?: Headers): Promise; - function sub(url: string, callback: ((b: Response["body"]) => void), onError?: ((e: Error) => void), body?: Body, headers?: Headers): Promise; - function postSub(url: string, body: Body | null, callback: ((b: Response["body"]) => void), onError?: ((e: Error) => void)): Promise; - function request(method: Method, url: string, body?: Body, headers?: Headers): Promise; - function resolve(method: Method, url: string, body?: Body, headers?: Headers): Promise; -} -``` - -It is worth noting that you can either make a request using the [`request`](#request) method, or use the shorthand methods for each HTTP method. - -For example, you can fetch the current client version using either of the following: - -```ts -await Spicetify.CosmosAsync.get("sp://desktop/v1/version"); -``` - -or - -```ts -await Spicetify.CosmosAsync.request("GET", "sp://desktop/v1/version"); -``` - -For a complete list of available HTTP methods, see [`Method`](/docs/development/api-wrapper/types/cosmos-async/method). - -You can also use `CosmosAsync` for Spotify Web API endpoints without having to manually add the required headers and cookies. - -```ts -// All endpoints that uses the `sp`, `wg`, and `hm` protocol are internal Spotify endpoints -await Spicetify.CosmosAsync.get("sp://desktop/v1/version"); - -// Spotify Web API endpoints also works -await Spicetify.CosmosAsync.get("https://api.spotify.com/v1/me"); - -// Requests to external URLs are NOT safe and may compromise the user's account -// Only use this if you're certain that the URL is safe -// If you need to make a request to an external URL, use `fetch` instead -await fetch("https://example.com"); -``` - -### `head` - -Make a `HEAD` request to the specified URL. - -```ts -function head(url: string, headers?: Headers): Promise; -``` - -| Parameter | Type | Description | -| --- | --- | --- | -| `url` | `string` | URL to make the request to. | - -### `get` - -Make a `GET` request to the specified URL. - -```ts -function get(url: string, body?: Body, headers?: Headers): Promise; -``` - -| Parameter | Type | Description | -| --- | --- | --- | -| `url` | `string` | URL to make the request to. | -| `body` | [`Body`](/docs/development/api-wrapper/types/cosmos-async/body) | Request body. | -| `headers` | [`Headers`](/docs/development/api-wrapper/types/cosmos-async/headers) | Request headers. | - -Example: - -```ts -// Get all playlists in user's library -const res = await Spicetify.CosmosAsync.get("sp://core-playlist/v1/rootlist"); -const playlists = res.rows.filter((row) => row.type === "playlist"); -``` - -### `post` - -Make a `POST` request to the specified URL. - -```ts -function post(url: string, body?: Body, headers?: Headers): Promise; -``` - -| Parameter | Type | Description | -| --- | --- | --- | -| `url` | `string` | URL to make the request to. | -| `body` | [`Body`](/docs/development/api-wrapper/types/cosmos-async/body) | Request body. | -| `headers` | [`Headers`](/docs/development/api-wrapper/types/cosmos-async/headers) | Request headers. | - -Example: - -```ts -// Skip to the next track in queue -const res = await Spicetify.CosmosAsync.post("sp://player/v2/main/skip_next"); -``` - -### `put` - -Make a `PUT` request to the specified URL. - -```ts -function put(url: string, body?: Body, headers?: Headers): Promise; -``` - -| Parameter | Type | Description | -| --- | --- | --- | -| `url` | `string` | URL to make the request to. | -| `body` | [`Body`](/docs/development/api-wrapper/types/cosmos-async/body) | Request body. | -| `headers` | [`Headers`](/docs/development/api-wrapper/types/cosmos-async/headers) | Request headers. | - -Example: - -```ts -// Enable/disable incognito mode -const res = await Spicetify.CosmosAsync.put("sp://scrobble/v1/incognito", { enabled: boolean }); -``` - -### `del` - -Make a `DELETE` request to the specified URL. - -```ts -function del(url: string, body?: Body, headers?: Headers): Promise; -``` - -| Parameter | Type | Description | -| --- | --- | --- | -| `url` | `string` | URL to make the request to. | -| `body` | [`Body`](/docs/development/api-wrapper/types/cosmos-async/body) | Request body. | -| `headers` | [`Headers`](/docs/development/api-wrapper/types/cosmos-async/headers) | Request headers. | - -### `patch` - -Make a `PATCH` request to the specified URL. - -```ts -function patch(url: string, body?: Body, headers?: Headers): Promise; -``` - -| Parameter | Type | Description | -| --- | --- | --- | -| `url` | `string` | URL to make the request to. | -| `body` | [`Body`](/docs/development/api-wrapper/types/cosmos-async/body) | Request body. | -| `headers` | [`Headers`](/docs/development/api-wrapper/types/cosmos-async/headers) | Request headers. | - -### `sub` - -Make a `SUB` request to the specified URL. - -```ts -function sub(url: string, callback: ((b: Response["body"]) => void), onError?: ((e: Error) => void), body?: Body, headers?: Headers): Promise; -``` - -| Parameter | Type | Description | -| --- | --- | --- | -| `url` | `string` | URL to make the request to. | -| `callback` | `(b: Response["body"]) => void` | Callback function to run when the request is successful. | -| `onError` | `(e: Error) => void` | Callback function to run when the request fails. | -| `body` | [`Body`](/docs/development/api-wrapper/types/cosmos-async/body) | Request body. | -| `headers` | [`Headers`](/docs/development/api-wrapper/types/cosmos-async/headers) | Request headers. | - -### `postSub` - -Make a `POST` request to the specified URL, and subscribe to the response. - -```ts -function postSub(url: string, body: Body | null, callback: ((b: Response["body"]) => void), onError?: ((e: Error) => void)): Promise; -``` - -| Parameter | Type | Description | -| --- | --- | --- | -| `url` | `string` | URL to make the request to. | -| `body` | [`Body`](/docs/development/api-wrapper/types/cosmos-async/body) | Request body. | -| `callback` | `(b: Response["body"]) => void` | Callback function to run when the request is successful. | -| `onError` | `(e: Error) => void` | Callback function to run when the request fails. | - -### `request` - -Make a request to the specified URL. - -```ts -function request(method: Method, url: string, body?: Body, headers?: Headers): Promise; -``` - -| Parameter | Type | Description | -| --- | --- | --- | -| `method` | [`Method`](/docs/development/api-wrapper/types/cosmos-async/method) | HTTP method to use. | -| `url` | `string` | URL to make the request to. | -| `body` | [`Body`](/docs/development/api-wrapper/types/cosmos-async/body) | Request body. | -| `headers` | [`Headers`](/docs/development/api-wrapper/types/cosmos-async/headers) | Request headers. | - -### `resolve` - -Make a request to the specified URL, and resolve the response. - -```ts -function resolve(method: Method, url: string, body?: Body, headers?: Headers): Promise; -``` - -| Parameter | Type | Description | -| --- | --- | --- | -| `method` | [`Method`](/docs/development/api-wrapper/types/cosmos-async/method) | HTTP method to use. | -| `url` | `string` | URL to make the request to. | -| `body` | [`Body`](/docs/development/api-wrapper/types/cosmos-async/body) | Request body. | -| `headers` | [`Headers`](/docs/development/api-wrapper/types/cosmos-async/headers) | Request headers. | diff --git a/docs/development/api-wrapper/methods/graphql.md b/docs/development/api-wrapper/methods/graphql.md deleted file mode 100644 index 7f0c3fcb..00000000 --- a/docs/development/api-wrapper/methods/graphql.md +++ /dev/null @@ -1,484 +0,0 @@ ---- -title: GraphQL -description: GraphQL API Wrapper ---- - -GraphQL API Wrapper used throughout the Spotify client. It is used to communicate with their GraphQL API endpoints for tailored operations such as fetching data, or making mutations. - -:::caution - -Because this is a private API, it is not documented by Spotify. This documentation is based on the usage of the API in the Spotify client, and may not be accurate. - -It is also subject to drastic changes in the future, and has gone through such changes in the past. - -Any issues or errors that arise from using this API are not the responsibility of this documentation. If you want to take advantage of this API, bear this in mind. - -::: - -## Introduction - -After reading [`CosmosAsync`](/docs/development/api-wrapper/methods/cosmos-async) and its usage with Spotify [Web API](https://developer.spotify.com/documentation/web-api/), you may be wondering why this is needed, and what is the difference between the two. - -In this section, we will be answering these questions: - -- What is GraphQL? -- Why do we need to use the GraphQL API wrapper? - -### What is GraphQL? - -[GraphQL](https://graphql.org/) is a query language for APIs and a runtime for fulfilling those queries with your existing data. It provides a complete and understandable description of the data in your API, gives clients the power to ask for exactly what they need and nothing more, makes it easier to evolve APIs over time, and enables powerful developer tools. - -Basically, it is a query language that allows you to query for data in a more flexible way, and allows you to get exactly what you need. - -### Why do we need to use the GraphQL API wrapper? - -**_"Why can't we just use the Web API?"_** - -Unlike the Web API, where we are allowed to get general data for Spotify resources, the GraphQL API is used to get data for tailored operations inside the client, such as fetching colors of the current track for the _Now Playing View_ panel, get all resources for a playlist displayed in the client, or return the search results for a query. - -**_"Why can't we use [`CosmosAsync`](/docs/development/api-wrapper/methods/cosmos-async) for GraphQL endpoints?"_** - -While you can technically make requests to the GraphQL endpoints using [`CosmosAsync`](/docs/development/api-wrapper/methods/cosmos-async), you would need a lot of special queries to do so, and it would be a lot more complicated than it needs to be. - -Take the following endpoint for example, to get the extracted colors used in the _Now Playing View_ panel, you would need to make a request to the following endpoint using [`CosmosAsync`](/docs/development/api-wrapper/methods/cosmos-async): - -```ts -await Spicetify.CosmosAsync.get( - `https://api-partner.spotify.com/pathfinder/v1/query?operationName=fetchExtractedColors&variables={"uris":["spotify:image:ab67616d00001e02f16ab998eea7e598a0928ad7"]}&extensions={"persistedQuery":{"version":1,"sha256Hash":"d7696dd106f3c84a1f3ca37225a1de292e66a2d5aced37a66632585eeb3bbbfa"}}` -); -``` - -compared to using the GraphQL API wrapper: - -```ts -const { fetchExtractedColors } = Spicetify.GraphQL.Definitions; -await Spicetify.GraphQL.Request( - fetchExtractedColors, - { uris: ['spotify:image:ab67616d00001e02f16ab998eea7e598a0928ad7'] }, -); -``` - -Both would return the same result, but the latter is a lot more readable and easier to use, so why is that? Let's break the endpoint down to understand why. - -```js -`https://api-partner.spotify.com/pathfinder/v1/query?operationName=fetchExtractedColors&variables={"uris":["spotify:image:ab67616d00001e02f16ab998eea7e598a0928ad7"]}&extensions={"persistedQuery":{"version":1,"sha256Hash":"d7696dd106f3c84a1f3ca37225a1de292e66a2d5aced37a66632585eeb3bbbfa"}}`; -``` - -Here in the request URL, we can see that there are 3 parts to it: - -- `operationName` -- `variables` -- `extensions` - -##### `operationName` - -This is the name of the operation that you want to perform. - -In this case, it is `fetchExtractedColors`. - -Nothing too complicated about that. - -##### `variables` - -Stringified JSON variables that you want to pass to the operation. - -In this case, it is `{"uris":["spotify:image:ab67616d00001e02f16ab998eea7e598a0928ad7"]}`. - -Not all requests have the same requirements, and without documentation, it would be hard to know which variables are required for each request. Of course, for a specific operation, you can track down the network request using DevTools and see what variables are being passed. - -##### `extensions` - -Stringified JSON extensions that you want to pass to the operation. - -In this case, it is `{"persistedQuery":{"version":1,"sha256Hash":"d7696dd106f3c84a1f3ca37225a1de292e66a2d5aced37a66632585eeb3bbbfa"}}`. - -This is the most complicated part of the request. It is used to cache the request, so that the same request does not need to be made again. This is done by passing a `sha256Hash` of the request to the server, and if the server has the same hash, it will return the cached result instead of making the request again. - -In this context, the hash is created from the following GraphQL query (as in Spotify `1.2.12`): - -```graphql -query fetchExtractedColors($uris: [ID!]!) { - extractedColors(uris: $uris) { - __typename - ... on ExtractedColors { - colorRaw { - hex - isFallback - } - colorDark { - hex - isFallback - } - colorLight { - hex - isFallback - } - } - ... on Error { - message - } - } -} -``` - -By itself, it looks like gibberish, since you have no way of telling what the hash is, and what it is used for. Additionally, as seen from the query above, each request will have their own unique hash, and the request would return an error if the hash is mismatched or not provided. - -You also wouldn't be able to tell if Spotify have changed the query server-side until the request breaks, and would need to track down the network request again to get the new hash. - -##### Conclusion - -As you can see, it is a lot more complicated to make a request to the GraphQL API using [`CosmosAsync`](/docs/development/api-wrapper/methods/cosmos-async), and it would be a lot more complicated to make a request to other GraphQL endpoints as well. - -The GraphQL API wrapper simplifies this process by providing a set of definitions used by Spotify themselves, and a method to make requests to the GraphQL API using these definitions. - -As long as you have a valid definition, the [`Request`](#request) method will automatically parse the definition and make the request for you. - -## Usage - -The `GraphQL` object is a set of methods that helps you make requests to the GraphQL API. - -```ts -namespace GraphQL { - const Definitions: Record; - const QueryDefinitions: Record; - const MutationDefinitions: Record; - const ResponseDefinitions: Record; - const Context: Record; - function Request(query: typeof Definitions[Query | string], variables?: Record, context?: Record): Promise; - function Handler(context: Record): (query: typeof Definitions[Query | string], variables?: Record, context?: Record) => Promise; -} -``` - -### `Definitions` - -The `Definitions` object is a collection of GraphQL definitions used by Spotify. - -You can use these definitions to make requests to the GraphQL API using the [`Request`](#request) method. - -```ts -const Definitions: Record; -``` - -For a list of definitions, see [`Query`](/docs/development/api-wrapper/types/graphql/query). - -:::caution - -The list of methods are subjected to change in the future depending on Spotify's usage. - -Also of the nature that this is a private API, the definitions are not documented by Spotify. They can also be removed in the future, both server-side and client-side. - -It is recommended to check the definitions in DevTools before using them, and to have several fallbacks in case a definition is removed. - -::: - -#### Understanding Definitions - -:::info - -To fully understand these definitions, see [Apollo GraphQL Schema](https://www.apollographql.com/docs/apollo-server/schema/schema/). - -In this section, we will only cover the basics of the definitions to get you started as quickly as possible. - -::: - -Each definition has a similar structure, for this example we will be using a fragment of the `fetchExtractedColors` definition. - -```ts -{ - "kind": "Document", - "definitions": [ - { - "kind": "OperationDefinition", - "operation": "query", - "name": { - "kind": "Name", - "value": "fetchExtractedColors" - }, - "variableDefinitions": [ - { - "kind": "VariableDefinition", - "variable": { - "kind": "Variable", - "name": { - "kind": "Name", - "value": "uris" - } - }, - "type": { - "kind": "NonNullType", - "type": { - "kind": "ListType", - "type": { - "kind": "NonNullType", - "type": { - "kind": "NamedType", - "name": { - "kind": "Name", - "value": "ID" - } - } - } - } - } - } - ], - "selectionSet": { - "kind": "SelectionSet", - "selections": [ - { - // ... - } - ] - } - } - ] -} -``` - -Inside a `Definition` object, there are 3 main parts: - -- `kind` -- `definitions` -- `selectionSet` - -For the time being, we will only need to care about `definitions`. - -##### `definitions` - -There are 2 types of definitions: - -- `OperationDefinition` -- `FragmentDefinition` - -Essentially, you can only use the `OperationDefinition` type to make requests to the GraphQL API, and `FragmentDefinition` is used to define a fragment that can be used in other definitions. - -Each type of definition is filtered into [subsets of the `Definitions` object](#querydefinitions-mutationdefinitions-responsedefinitions) for ease of use. - -Inside the `OperationDefinition` object, there are 3 main parts: - -- `operation` -- `name` -- `variableDefinitions` - -###### `operation` - -This is the type of operation that you want to perform, and can be either `query` or `mutation`. - -###### `name` - -This is the name of the operation that you want to perform. Usually the name of the definition is self-explanatory, so you can be sure of what the operation does by looking at the name. - -###### `variableDefinitions` - -This is the list of variables that you want to pass to the operation. - -This is considered the most important part of the definition, as it is required to make a request to the GraphQL API. We will break it down using the example above in the easiest way possible. - -```ts -"variableDefinitions": [ - { - "kind": "VariableDefinition", - "variable": { - "kind": "Variable", - "name": { - "kind": "Name", - "value": "uris" - } - }, - "type": { - "kind": "NonNullType", - "type": { - "kind": "ListType", - "type": { - "kind": "NonNullType", - "type": { - "kind": "NamedType", - "name": { - "kind": "Name", - "value": "ID" - } - } - } - } - } - } -] -``` - -The `variableDefinitions` object is an array of `VariableDefinition` objects. Each object represents a variable that you want to pass to the operation. Each objects can be represented as a property of the `variable` object passed onto the `Request` method. - -In this case, the `variableDefinitions` object only has 1 `VariableDefinition` object named `uris`, so we only need to pass 1 variable to the `Request` method. - -Inside the `VariableDefinition` object, there are 2 main parts: - -- `variable` -- `type` - -###### `variable` - -This is the name of the variable that you want to pass to the operation. We only need to care about the name of the variable, as the rest of the object is used to parse the definition. - -In the example above, the name of the variable is `uris`, so in the [`Request`](#request) method, we would pass the variable as follows: - -```ts -Spicetify.GraphQL.Request( - Spicetify.GraphQL.Definitions.fetchExtractedColors, - { uris: /* ... */ }, -); -``` - -###### `type` - -Each type has its equivalent type in JavaScript, and in the example above we get the following reference: - -- `NonNullType`: `!`, meaning that the variable is required. -- `ListType`: `[]`, meaning that the variable is an array. -- `NamedType`: `string`, meaning that the variable is a string. - - -:::note - -Sometimes a variable is not listed as `NonNullType`, but is still required to make a successful request. This is because the variable is required by the operation itself, and not the definition. - -::: - -:::info - -For a full list of types, see [GraphQL Scalar Types](https://graphql.org/learn/schema/#scalar-types). - -::: - -As we can see, the variable `uris` is a required array of strings. - -To conclude, the `fetchExtractedColors` method requires: - - 1. A parameter named `uris` - 2. The `uris` parameter is a required array of strings - -So the request would look like this: - -```ts -Spicetify.GraphQL.Request( - Spicetify.GraphQL.Definitions.fetchExtractedColors, - { uris: ['spotify:image:ab67616d00001e02f16ab998eea7e598a0928ad7'] }, -); -``` - -Using the same logic, you can parse the rest of the definitions to know the structure of the response by understanding the `selectionSet` object, or explore the response in DevTools. - -### `QueryDefinitions`, `MutationDefinitions`, `ResponseDefinitions` - -The `QueryDefinitions`, `MutationDefinitions` and `ResponseDefinitions` objects are subsets of the `Definitions` object. - -These are filtered by their type, and are used to make different types of requests to the GraphQL API using the [`Request`](#request) method. - -```ts -const QueryDefinitions: Record; -const MutationDefinitions: Record; -const ResponseDefinitions: Record; -``` - -Each type of definition is used for a different purpose: - -- `QueryDefinitions`: Used to make GraphQL queries. These are queries that are used to fetch data. (e.g. fetching playlist metadata) -- `MutationDefinitions`: Used to make GraphQL mutations. These are queries that are used to mutate data. (e.g. adding a track to a playlist) -- `ResponseDefinitions`: Used to parse GraphQL responses. - -Most of the time, you would only need to use the `QueryDefinitions` and `MutationDefinitions` subsets to make requests to the GraphQL API. - -### `Request` - -The `Request` method is used to make requests to the GraphQL API using the definitions provided. - -```ts -function Request( - query: typeof Definitions[Query | string], - variables?: Record, - context?: Record -): Promise; -``` - -| Parameter | Type | Description | -| --- | --- | --- | -| `query` | `Record` | The definition to use for the request. | -| `variables` | `Record` | The variables to pass to the operation. | -| `context` | `Record` | The context to use for the request. | - -| Return | Type | Description | -| --- | --- | --- | -| `Promise` | `any` | The response from the request. | - -#### `query` - -The `query` parameter is the definition to use for the request. It can be any definition from the [`Definitions`](#definitions) object. - -#### `variables` - -The `variables` parameter is the variables to pass to the operation. It can be any valid variables for the operation. - -#### `context` - -The `context` parameter is the context to use for the request. It can be any valid context for the operation. These will be passed onto the request as parameters. - -##### Example - -```ts -// Get all data displayed in an album page -const { getAlbum } = Spicetify.GraphQL.Definitions; -await Spicetify.GraphQL.Request( - getAlbum, - { uri: "spotify:album:3lvY0ag1k3qiPCsEM3Wnku", locale: "en", limit: 50, offset: 0 }, - { persistCache: true }, -); -``` - -### `Context` - -The `Context` class is used to set the context for the [`Handler`](#handler) method. - -```ts -const Context: Record; -``` - -On Spotify `1.2.12`, the class contains the following properties: - -| Property | Type | Description | -| --- | --- | --- | -| `accessToken` | `string` | The access token to use for the request. | -| `globalRequestHeaders` | `string[][]` | The global request headers to use for the request. | -| `locale` | `string` | The locale to use for the request. | -| `market` | `string` | The market to use for the request. | - -### `Handler` - -The `Handler` method is used to create a handler for the [`Request`](#request) method. - -```ts -function Handler( - context: Record -): ( - query: typeof Definitions[Query | string], - variables?: Record, - context?: Record -) => Promise; -``` - -| Parameter | Type | Description | -| --- | --- | --- | -| `context` | `Record` | The context to use for the request. | - -:::info - -The `context` parameter is the same as the [`Context`](#context) class. - -::: - -| Return | Type | Description | -| --- | --- | --- | -| `Function` | `(query: Record, variables?: Record, context?: Record) => Promise` | The handler for the request. | - -:::info - -The [`Request`](#request) method is a preinitialized version of the [`Handler`](#handler) method using the current context. - -::: \ No newline at end of file diff --git a/docs/development/api-wrapper/methods/keyboard.md b/docs/development/api-wrapper/methods/keyboard.md deleted file mode 100644 index 74386334..00000000 --- a/docs/development/api-wrapper/methods/keyboard.md +++ /dev/null @@ -1,116 +0,0 @@ ---- -title: Keyboard -description: A wrapper for keyboard shortcuts. ---- - -Spicetify provides its own method for global keyboard shortcuts. You can specify actions for your extension when the user presses a keyboard shortcut. - -:::tip - -`Spicetify.Keyboard` is a wrapper of [`Spicetify.Mousetrap`](/docs/development/api-wrapper/modules#mousetrap) configured to be compatible with legacy Spotify. - -New extensions are advised to use the module instead. - -::: - -:::caution - -All shortcuts registered by `Spicetify.Keyboard` are global. Be mindful of conflicts with other extensions or the Spotify client itself. - -::: - -```ts -namespace Keyboard { - const KEYS: Record; - function registerShortcut(keys: KeysDefine, callback: (event: KeyboardEvent) => void): void; - function _deregisterShortcut(keys: KeysDefine): void; - function changeShortcut(keys: KeysDefine, newKeys: KeysDefine): void; -}; -``` - -## Properties - -### `KEYS` - -An object containing a list of valid keys, mapped to their valid names. - -```ts -const { KEYS } = Spicetify.Keyboard; -``` - -Refer to [this table](/docs/development/api-wrapper/types/keyboard/validkey) for a list of valid keys. - -#### Example - -```ts -const { KEYS } = Spicetify.Keyboard; -console.log(KEYS["CAPS"]); // "Capslock" -``` - -## Methods - -### `registerShortcut` - -Register a global keyboard shortcut. - -```ts -function registerShortcut(keys: KeysDefine, callback: (event: KeyboardEvent) => void): void; -``` - -#### Parameters - -| Parameter | Type | Description | -| :--- | :--- | :--- | -| keys | [`KeysDefine`](/docs/development/api-wrapper/types/keyboard/keysdefine) | Keyboard shortcut to register. | -| callback | `(event: KeyboardEvent) => void` | Callback function to run when the shortcut is triggered. | - -#### Example - -```ts -// Equivalent to `Spicetify.Keyboard.registerShortcut({ key: "p", ctrl: true, shift: true }, (event) => { ... })` -Spicetify.Keyboard.registerShortcut("ctrl+shift+p", (event) => { - // Do something with the event - Spicetify.showNotification("Shortcut triggered!"); -}); -``` - -### `_deregisterShortcut` - -Deregister a global keyboard shortcut. - -```ts -function _deregisterShortcut(keys: KeysDefine): void; -``` - -#### Parameters - -| Parameter | Type | Description | -| :--- | :--- | :--- | -| keys | [`KeysDefine`](/docs/development/api-wrapper/types/keyboard/keysdefine) | Keyboard shortcut to deregister. | - -#### Example - -```ts -Spicetify.Keyboard._deregisterShortcut("ctrl+shift+p"); -``` - -### `changeShortcut` - -Change a global keyboard shortcut to a new shortcut while keeping the callback. - -```ts -function changeShortcut(keys: KeysDefine, newKeys: KeysDefine): void; -``` - -#### Parameters - -| Parameter | Type | Description | -| :--- | :--- | :--- | -| keys | [`KeysDefine`](/docs/development/api-wrapper/types/keyboard/keysdefine) | Keyboard shortcut to change. | -| newKeys | [`KeysDefine`](/docs/development/api-wrapper/types/keyboard/keysdefine) | New keyboard shortcut to change to. | - -#### Example - -```ts -Spicetify.Keyboard.changeShortcut("ctrl+shift+p", "ctrl+shift+o"); -``` diff --git a/docs/development/api-wrapper/methods/local-storage.md b/docs/development/api-wrapper/methods/local-storage.md deleted file mode 100644 index 9f311251..00000000 --- a/docs/development/api-wrapper/methods/local-storage.md +++ /dev/null @@ -1,96 +0,0 @@ ---- -title: LocalStorage -description: Get and set data in local storage. ---- - -Spicetify provides a wrapper for `localStorage` to make it easier to use. - -:::tip - -All keys created via this method are generic and stored as-is. - -If you wish to store values that are specific for each user account, you can use [`Platform.LocalStorageAPI`](/docs/development/api-wrapper/methods/platform#localstorageapi) instead. - -::: - -```ts -namespace LocalStorage { - function clear(): void; - function get(key: string): string | null; - function remove(key: string): void; - function set(key: string, value: string): void; -}; -``` - -## Methods - -### `clear` - -Empties the list associated with the object of all key/value pairs, if there are any. - -:::warning - -This method will remove all data in local storage, not just the data that Spicetify uses. This essentially resets the client to its default state. - -It will also wipe all data stored by extensions and custom apps (e.g. Marketplace, Lyrics Plus, etc.) - -::: - -```ts -clear(): void -``` - -### `get` - -Get key value from local storage. - -```ts -get(key: string): string | null -``` - -| Parameter | Type | Description | -| :--- | :--- | :--- | -| key | `string` | Key to get value from. | - -#### Example - -```ts -const value = Spicetify.LocalStorage.get("foo"); -``` - -### `remove` - -Delete key from local storage. - -```ts -remove(key: string): void -``` - -| Parameter | Type | Description | -| :--- | :--- | :--- | -| key | `string` | Key to delete. | - -#### Example - -```ts -Spicetify.LocalStorage.remove("foo"); -``` - -### `set` - -Set new value for key in local storage. - -```ts -set(key: string, value: string): void -``` - -| Parameter | Type | Description | -| :--- | :--- | :--- | -| key | `string` | Key to set value for. | -| value | `string` | Value to set. | - -#### Example - -```ts -Spicetify.LocalStorage.set("foo", "bar"); -``` diff --git a/docs/development/api-wrapper/methods/panel.md b/docs/development/api-wrapper/methods/panel.md deleted file mode 100644 index 87508a13..00000000 --- a/docs/development/api-wrapper/methods/panel.md +++ /dev/null @@ -1,301 +0,0 @@ ---- -title: Panel -description: API wrapper to interact with Spotify's panel/right sidebar. ---- - -Spicetify provides a wrapper for the Spotify `PanelAPI` method to make it easier to use, as well as providing a method to help you register your own panel. - -```ts -namespace Panel { - const reservedPanelIds: Record; - const Components: { - PanelSkeleton: any; - PanelContent: any; - PanelHeader: any; - }; - function hasPanel(id: number): boolean; - function getPanel(id: number): React.ReactNode | undefined; - function setPanel(id: number): Promise; - function subPanelState(callback: (id: number) => void): void; - function registerPanel(props: PanelProps): { - id: number; - toggle: () => Promise; - onStateChange: (callback: (isActive: boolean) => void) => void; - isActive: boolean; - }; - const currentPanel: number; -} -``` - -Internally, Spotify uses an enum to cross-reference the IDs of the panels with the panels themselves. As such, all methods in `Panel` will only accept IDs that are reserved by Spotify and those that are manually registered via `registerPanel`. - -## Properties - -### `reservedPanelIds` - -An object of reserved panel IDs used by Spotify. - -```ts -const reservedPanelIds: Record; -``` - -This list is dynamic, so it is recommended to check the list in the console. - -As of Spotify `1.2.12`, there are currently 5 reserved IDs: - -```ts -{ - 0: "Disabled", - 1: "BuddyFeed", - 2: "NowPlayingView", - 3: "WhatsNewFeed", - 4: "Puffin", -} -``` - -### `Components` - -Collection of React Components used by Spotify in the Panel. - -Refer to [Registering a custom Panel](#registering-a-custom-panel) for more information on how to use these components. - -```ts -const Components: { - PanelSkeleton: any; - PanelContent: any; - PanelHeader: any; -}; -``` - -### `currentPanel` - -ID of the current Panel. - -```ts -const currentPanel: number; -``` - -Example: - -```ts -console.log(Spicetify.Panel.currentPanel); // 0 -``` - -## Methods - -### `hasPanel` - -Check whether or not a Panel with the provided ID is registered. - -```ts -function hasPanel(id: number): boolean; -``` - -Example: - -```ts -Spicetify.Panel.hasPanel(0); // true, reserved to "Disabled" by Spotify -Spicetify.Panel.hasPanel('Disabled'); // false, ID is not a number -Spicetify.Panel.hasPanel(5); // false, ID is not registered -``` - -### `getPanel` - -Get the Panel with the provided ID. - -```ts -function getPanel(id: number): React.ReactNode | string | undefined; -``` - -Example: - -```ts -Spicetify.Panel.getPanel(0); // "Disabled" -Spicetify.Panel.getPanel('Disabled'); // undefined, ID is not a number -Spicetify.Panel.getPanel(5); // undefined, ID is not registered -``` - -### `setPanel` - -Set the Panel state with the provided ID. - -```ts -function setPanel(id: number): Promise; -``` - -:::caution - -On older Spotify versions (around `1.2.6`), the `PanelAPI` method is incomplete, most notably the `getPanel` method. - -Spicetify offers a workaround for this by storing its own state of the current Panel ID, however this state is only updated after Spotify internal components are rendered due to how the `PanelAPI` method works on older versions. - -If you decide to interact directly with the `PanelAPI` method instead of using the wrapper, the `render` method will not work as expected. - -```ts -// Normal way of using the Panel method, will work as expected -Spicetify.Panel.setPanel(0); - -// Interacting directly with the PanelAPI method will cause some abnormalities -// as state is updated after Spotify internal components are rendered -Spicetify.Platform.PanelAPI.setPanel(0); -``` - -::: - -Example: - -```ts -await Spicetify.Panel.setPanel(0); // Close the Panel - -await Spicetify.Panel.setPanel(1); // Open BuddyFeed - -// Open the Panel with ID 5 -// If the ID is not registered, it will be set to 0 -await Spicetify.Panel.setPanel(5); -``` - -### `subPanelState` - -Subscribe to Panel changes. - -```ts -function subPanelState(callback: (id: number) => void): void; -``` - -Example: - -```ts -Spicetify.Panel.subPanelState((id) => { - console.log(id); // 2 - // Do something -}); -``` - -### `registerPanel` - -Register a new Panel and return its methods and properties. - -:::note - -To avoid conflict with Spotify and other extensions on current version and future updates, an ID will be automatically assigned to the Panel. - -::: - -:::tip - -To make it easier and convenient for developers to use the Panel API, this method by default wraps the children passed into a Panel skeleton and content wrapper. - -However, this also means that the props passed onto the Panel cannot be updated after the Panel is registered. (most notably the `label` and `header` props) - -If you want to customize the Panel, look into [Registering a custom Panel](#registering-a-custom-panel). - -::: - -```ts -function registerPanel(props: PanelProps): { - id: number; - toggle: () => Promise; - onStateChange: (callback: (isActive: boolean) => void) => void; - isActive: boolean; -}; -``` - -#### Parameters - -| Name | Type | Description | -| ------- | ------------------------------------------------------------------- | ----------------------- | -| `props` | [PanelProps](/docs/development/api-wrapper/types/panel/panel-props) | Properties of the Panel | - -Example: - -```ts -const { id, toggle, onStateChange, isActive } = Spicetify.Panel.registerPanel({ - label: 'My Panel', - children:
My Panel
, -}); - -console.log(id); // 5 -console.log(isActive); // false - -await toggle(); // Open the Panel - -console.log(isActive); // true - -onStateChange((isActive) => { - console.log(isActive); // false -}); -``` - -:::tip - -`onStateChange` callback function will be called immediately after registering a callback due to how the `PanelAPI` method works. - -::: - -#### Registering a custom Panel - -Ideally, you would not need to use these components directly, but in case the `registerPanel` method does not satisfy your needs, you can use these along with the `isCustom` prop to create your own customised panel. - -#### Props - -See [`PanelHeaderProps`](/docs/development/api-wrapper/types/react-component/panel-header-props), [`PanelContentProps`](/docs/development/api-wrapper/types/react-component/panel-content-props), and [`PanelSkeletonProps`](/docs/development/api-wrapper/types/react-component/panel-skeleton-props). - -#### Example - -```tsx -const PanelAction = () => { - return ( - - - - ) -} - -// Ideally, you would want to memoize this component -// to prevent unnecessary re-renders if you were to pass props - -// Props will have a single `panel` property for the panel ID -const Panel = ({ panel }) => { - // For example, if you want to change the header title - const [title, setTitle] = React.useState('Hello World'); - // Or if you want to display extra actions on the header in a stateful manner - const [showActions, setShowActions] = React.useState(false); - - return ( - - - } - onClose={() => Spicetify.showNotification('Closed')} - onBack={() => Spicetify.showNotification('Back')} - /> -
- Hello World -
- - -
-
- ) -}; - -// Finally, register the Panel -const { id, toggle, onStateChange, isActive } = Spicetify.Panel.registerPanel({ - children: , - isCustom: true -}); -``` diff --git a/docs/development/api-wrapper/methods/platform.md b/docs/development/api-wrapper/methods/platform.md deleted file mode 100644 index 77dfdc16..00000000 --- a/docs/development/api-wrapper/methods/platform.md +++ /dev/null @@ -1,717 +0,0 @@ ---- -title: Platform -description: A vast collection of internal APIs used throughout the Spotify client. ---- - -Spicetify provides a vast collection of internal APIs used throughout the Spotify client. These APIs are used to interact with the Spotify client and modify its behavior. - -Explore all available methods in DevTools: - -```js -Spicetify.Platform -``` - -:::warning - -Because these APIs are internal, they are not guaranteed to be stable and may *drastically* change in the future. Use them at your own risk. - -::: - -## Usage - -Since these APIs differ between each version of the Spotify client, we cannot provide a complete list of all available APIs. Instead, we provide a list of APIs that may prove useful for extension developers and that have generally not changed much over the years. - -:::note - -If a method is not listed here, it could be that: - - It is objectively complicated to use. - - It is not well documented. - - It is generally not useful/convenient for extension developers. - - It is not stable and may change/has changed drastically throughout versions. - -::: - -:::tip - -Feel free to reach out to the developers' community on [**Discord**](https://discord.gg/VnevqPp2Rr) if you need help with any of these methods. - -::: - -### ClipboardAPI - -The Spotify client doesn't allow users to copy directly from the client (say `Ctrl + C`), but it does provide an API to copy text to the clipboard. - -```ts -interface ClipboardAPI { - copy: (text: string) => Promise; - paste: () => Promise; -}; -``` - -#### `copy` - -Copy text to clipboard. - -:::info - -The parameter passed will be stringified before being copied to the clipboard. - -::: - -| Parameter | Type | Description | -| --- | --- | --- | -| `text` | `string` | Text to copy. | - -Example: - -```ts -// Will be copied as "Hello World!" -await Spicetify.Platform.ClipboardAPI.copy("Hello World!"); - -// Will be stringified to '{"0":0,"1":0,"2":0,"3":0}' -await Spicetify.Platform.ClipboardAPI.copy(new Uint16Array(4)); -``` - -#### `paste` - -Paste text from clipboard. Returns a string. - -:::caution - -If the content of the clipboard is not a string, this method will return `undefined`. - -::: - -Example: - -```ts -await Spicetify.Platform.ClipboardAPI.copy("Hello World!"); -await Spicetify.Platform.ClipboardAPI.paste(); // "Hello World!" - -await Spicetify.Platform.ClipboardAPI.copy(new Uint16Array(4)); -await Spicetify.Platform.ClipboardAPI.paste(); // '{"0":0,"1":0,"2":0,"3":0}' -``` - -### History - -Spotify has their own router API that allows you to navigate to different pages within the client. You can use it to navigate within your custom apps or push new pages to the history stack and display them for the users. - -```ts -interface History { - push: (path: Location | string) => void; - replace: (path: Location | string) => void; - goBack: () => void; - goForward: () => void; - listen: (listener: (location: Location) => void) => () => void; - entries: Location[]; - location: Location; -}; -``` -Their `Location` object is a simple object that contains the `pathname` of the current page as well as the relevant query parameters and state. It looks roughly like this: - -```ts -interface Location { - pathname: string; - search: string; - hash: string; - state: Record; -}; -``` - -#### `push` - -Push a new location to the history stack. - -You can pass either a `Location` object or a `pathname` string to this method. - -| Parameter | Type | Description | -| --- | --- | --- | -| `path` | `Location` | `string` | Location to push. | - -```ts -Spicetify.Platform.History.push("/app/your-app"); - -Spicetify.Platform.History.push({ - pathname: "/app/your-app", - search: "?foo=bar", - hash: "#baz", - state: { foo: "bar" }, -}); -``` - -#### `replace` - -Replace the current location in the history stack. - -:::caution - -Users will **not** be able to go back to the previous page. - -::: - -You can pass either a `Location` object or a `pathname` string to this method. - -| Parameter | Type | Description | -| --- | --- | --- | -| `path` | `Location` | `string` | Location to replace. | - -```ts -// Replace the current location with a new one. -Spicetify.Platform.History.replace("/app/your-app"); - -Spicetify.Platform.History.replace({ - pathname: "/app/your-app", - search: "?foo=bar", - hash: "#baz", - state: { foo: "bar" }, -}); -``` - -#### `goBack` - -Go back to the previous location in the history stack. - -#### `goForward` - -Go forward to the next location in the history stack. - -:::caution - -The page may not be fully loaded when this event is fired. You may need to wait for the DOM to finish loading before you can interact with it. - -::: - -#### `listen` - -Listen to changes in the history stack. Fires whenever the user navigates to a new page. - -| Parameter | Type | Description | -| --- | --- | --- | -| `listener` | `(location: Location) => void` | Callback to fire when the user navigates to a new page. | - -Example: - -```ts -Spicetify.Platform.History.listen((location) => { - // Log the current pathname every time the user navigates to a new page. - console.log(location.pathname); -}); -``` - -#### `entries` - -An array of all locations in the history stack. - -#### `location` - -The current location in the history stack. - -### LocalStorageAPI - -Spotify provides a simple API to interact with the browser's local storage. All keys are stored using the current user's username as the namespace. - -Inside `localStorage`, the keys are stored using the following format: - -```ts -`${namespace}:${key}` -``` - -:::tip - -All keys created using this method will be namespaced using the current user's username. - -If you wish to create a generic key, you can use [`Spicetify.LocalStorage`](./local-storage) instead. - -::: - -```ts -interface LocalStorageAPI { - items: Record; - namespace: string; - getItem: (key: string) => any; - setItem: (key: string, value: any) => void; - clearItem: (key: string) => void; -}; -``` - -#### `items` - -An object containing all the keys and values stored in the local storage. - -All keys in `items` are stored using the aforementioned format, with it's value pair being the value stored in the local storage parsed using `JSON.parse`. - -Example: - -```ts -Spicetify.Platform.LocalStorageAPI.items; // { "username:foo": "bar" } -``` - -#### `namespace` - -The namespace used to store all keys in the local storage. Usually the current user's username. - -#### `getItem` - -Get a value from the local storage. Returns a parsed value using `JSON.parse`. - -| Parameter | Type | Description | -| --- | --- | --- | -| `key` | `string` | Key to get. | - -Example: - -```ts -// This is equivalent to Spicetify.Platform.LocalStorageAPI.items["username:foo"] -// or localStorage.getItem("username:foo") -Spicetify.Platform.LocalStorageAPI.getItem("foo"); // "bar" -``` - -#### `setItem` - -Set a value in the local storage. The value will be stringified using `JSON.stringify`. - -| Parameter | Type | Description | -| --- | --- | --- | -| `key` | `string` | Key to set. | -| `value` | `any` | Value to set. Can be any type. | - -Example: - -```ts -Spicetify.Platform.LocalStorageAPI.setItem("foo", { bar: "baz" }); -// localStorage.getItem("username:foo") === '{"bar":"baz"}' -``` - -#### `clearItem` - -Clear a value from the local storage. - -| Parameter | Type | Description | -| --- | --- | --- | -| `key` | `string` | Key to clear. | - -Example: - -```ts -Spicetify.Platform.LocalStorageAPI.clearItem("foo"); -// localStorage.getItem("username:foo") === null -``` - -### PlatformData - -Contains data about the current platform, such as the current Spotify client version, operating system, and more. - -```ts -interface PlatformData { - app_platform: string; - client_capabilities: Record; - client_version_triple: string; - client_version_quadruple: string; - client_version_quintuple: string; - event_sender_context_information: Record; - os_name: string; - os_version: string; -} -``` - -#### `app_platform` - -The current Spotify client platform. - -Example: - -```ts -Spicetify.Platform.PlatformData.app_platform; // "win32" -``` - -#### `client_capabilities` - -An object containing the current client capabilities. This usually contains information relating to functionality inside the Spotify client, such as whether or not the client can autostart. - -#### `client_version_triple`, `client_version_quadruple`, `client_version_quintuple` - -The current Spotify client version. Usually in the format `1.2.8`, `1.2.8.923`, or `1.2.8.923.g4f94bf0d`. - -#### `event_sender_context_information` - -An object containing information about the current operating system. Used for analytics throughout the Spotify client. - -Example: - -```ts -Spicetify.Platform.PlatformData.event_sender_context_information; // { "platform_type:"windows", "os_version": "10.0.19042" } -``` - -This could also help you diagnose issues with your custom apps. For example, if you're using a custom app on Windows and you're getting an error, you can check the `event_sender_context_information` object to see if the `platform_type` is `windows` or `macos`. - -#### `os_name` - -The current operating system. - -Example: - -```ts -Spicetify.Platform.PlatformData.os_name; // "windows" -``` - -#### `os_version` - -The current operating system version. - -Example: - -```ts -Spicetify.Platform.PlatformData.os_version; // "10.0.19042" -``` - -### Session - -Contains data about the current user session, such as the current user's access token, locale, and more. - -```ts -interface Session { - accessToken: string; - accessTokenExpirationTimestampMs: number; - locale: string; -} -``` - -:::note - -If you're trying to make an API request to internal Spotify endpoints, you should use [`CosmosAsync`](/docs/development/api-wrapper/methods/cosmos-async) - -::: - -#### `accessToken` - -The current user's access token. This is used to authenticate requests to the Spotify API. - -#### `accessTokenExpirationTimestampMs` - -The timestamp in milliseconds when the current user's access token expires. - -#### `locale` - -The current user's locale. - -Example: - -```ts -Spicetify.Platform.Session.locale; // "en" -``` - -### Translations - -Contains translation strings used throughout the current Spotify client. - -```ts -interface Translations { - [key: string]: string; -} -``` - -### PlayerAPI - -Contains methods to interact with the Spotify client's player. - -:::tip - -It is recommended to use [`Player`](/docs/development/api-wrapper/methods/player) instead for ease of use, or access `Player.origin` to get the `PlayerAPI` object. - -::: - -```ts -interface PlayerAPI { - addToQueue: (items: ContextTrack[]) => Promise; - clearQueue: () => Promise; - pause: () => Promise; - play: (uri: ContextTrack, context, options = {}) => Promise; - removeFromQueue: (items: ContextTrack[]) => Promise; - resume: () => Promise; - seekBackward: (ms: number) => Promise; - seekBy: (ms: number) => Promise; - seekForward: (ms: number) => Promise; - seekTo: (ms: number) => Promise; - setRepeat: (mode: RepeatMode) => Promise; - setShuffle: (shuffle: boolean) => Promise; - setSpeed: (speed: number) => Promise; -} -``` - -#### `RepeatMode` - -Enum for the repeat mode. - -```ts -enum RepeatMode { - Off = 0, - RepeatAll = 1, - RepeatOne = 2, -} -``` - -#### `addToQueue` - -Add items to the current user's queue. - -:::tip - -This works as if the user had clicked the `Add to queue` button in the Spotify client, meaning a notification will be shown. - -If you want to add items to the queue without showing a notification, use [`Spicetify.addToQueue`](/docs/development/api-wrapper/functions/add-to-queue). - -::: - -```ts -await Spicetify.Platform.PlayerAPI.addToQueue(items); -``` - -| Parameter | Type | Description | -| --- | --- | --- | -| `items` | [`ContextTrack[]`](/docs/development/api-wrapper/types/context-track) | Items to add to the queue. | - -##### Example - -```ts -// Add a track to the queue - -// 505 - Arctic Monkeys -const track = { uri: "spotify:track:0BxE4FqsDD1Ot4YuBXwAPp" }; - -await Spicetify.Platform.PlayerAPI.addToQueue([track]); -``` - -#### `clearQueue` - -Clear the current user's queue. - -```ts -await Spicetify.Platform.PlayerAPI.clearQueue(); -``` - -#### `pause` - -Pause the current user's playback. - -```ts -await Spicetify.Platform.PlayerAPI.pause(); -``` - -#### `play` - -:::tip - -It is recommended to use [`Player.playUri`](/docs/development/api-wrapper/methods/player#playuri) instead for ease of use. - -::: - -Start playback of a track. - -```ts -await Spicetify.Platform.PlayerAPI.play(uri, context, options); -``` - -| Parameter | Type | Description | -| --- | --- | --- | -| `uri` | [`ContextTrack`](/docs/development/api-wrapper/types/context-track) | The track to play. | -| `context` | `Record` | The context of the track. Must be an object. | -| `options` | `Record` | `undefined` | Playback options. | - -##### Example - -```ts -// 505 - Arctic Monkeys -const track = { uri: "spotify:track:0BxE4FqsDD1Ot4YuBXwAPp" }; - -// Play the track -// Spicetify.Player.playUri(track.uri); -await Spicetify.Platform.PlayerAPI.play(track, {}, {}); -``` - -#### `removeFromQueue` - -Remove items from the current user's queue. - -:::tip - -This works similarly to [`Spicetify.removeFromQueue`](/docs/development/api-wrapper/functions/remove-from-queue). - -::: - -```ts -await Spicetify.Platform.PlayerAPI.removeFromQueue(items); -``` - -| Parameter | Type | Description | -| --- | --- | --- | -| `items` | [`ContextTrack[]`](/docs/development/api-wrapper/types/context-track) | Items to remove from the queue. | - -##### Example - -```ts -// Remove a track from the queue - -// 505 - Arctic Monkeys -const track = { uri: "spotify:track:0BxE4FqsDD1Ot4YuBXwAPp" }; - -// Remove the track if it's in the queue -await Spicetify.Platform.PlayerAPI.removeFromQueue([track]); -``` - -#### `resume` - -Resume the current user's playback. - -```ts -await Spicetify.Platform.PlayerAPI.resume(); -``` - -#### `seekBackward` - -Seek backward in the current user's playback. - -```ts -await Spicetify.Platform.PlayerAPI.seekBackward(ms); -``` - -| Parameter | Type | Description | -| --- | --- | --- | -| `ms` | `number` | The number of milliseconds to seek backward. | - -##### Example - -```ts -// Seek backward 10 seconds -await Spicetify.Platform.PlayerAPI.seekBackward(10000); -``` - -#### `seekBy` - -Seek by a number of milliseconds in the current user's playback. - -If passed a negative number, it will seek backward. - -```ts -await Spicetify.Platform.PlayerAPI.seekBy(ms); -``` - -| Parameter | Type | Description | -| --- | --- | --- | -| `ms` | `number` | The number of milliseconds to seek by. | - -##### Example - -```ts -// Seek forward 10 seconds -await Spicetify.Platform.PlayerAPI.seekBy(10000); - -// Seek backward 10 seconds -await Spicetify.Platform.PlayerAPI.seekBy(-10000); -``` - -#### `seekForward` - -Seek forward in the current user's playback. - -```ts -await Spicetify.Platform.PlayerAPI.seekForward(ms); -``` - -| Parameter | Type | Description | -| --- | --- | --- | -| `ms` | `number` | The number of milliseconds to seek forward. | - -##### Example - -```ts -// Seek forward 10 seconds -await Spicetify.Platform.PlayerAPI.seekForward(10000); -``` - -#### `seekTo` - -Seek to a specific position in the current user's playback. - -:::tip - -[`Player.seek`](/docs/development/api-wrapper/methods/player#seek) support both seeking by a number of milliseconds and by a percentage. - -::: - -```ts -await Spicetify.Platform.PlayerAPI.seekTo(ms); -``` - -| Parameter | Type | Description | -| --- | --- | --- | -| `ms` | `number` | The position in milliseconds to seek to. | - -##### Example - -```ts -// Seek to 1 minute -await Spicetify.Platform.PlayerAPI.seekTo(60000); -``` - -#### `setRepeat` - -Set the current user's repeat mode. - -```ts -await Spicetify.Platform.PlayerAPI.setRepeat(mode); -``` - -| Parameter | Type | Description | -| --- | --- | --- | -| `mode` | [`RepeatMode`](#repeatmode) | The repeat mode to set. | - -##### Example - -```ts -// Set repeat mode to repeat one -await Spicetify.Platform.PlayerAPI.setRepeat(2); -``` - -#### `setShuffle` - -Set the current user's shuffle mode. - -```ts -await Spicetify.Platform.PlayerAPI.setShuffle(shuffle); -``` - -| Parameter | Type | Description | -| --- | --- | --- | -| `shuffle` | `boolean` | Whether to enable shuffle mode. | - -##### Example - -```ts -// Enable shuffle mode -await Spicetify.Platform.PlayerAPI.setShuffle(true); -``` - -#### `setSpeed` - -Set the current user's playback speed. - -:::note - -This only works for podcasts. Music playback speed is unaffected. - -::: - -```ts -await Spicetify.Platform.PlayerAPI.setSpeed(speed); -``` - -| Parameter | Type | Description | -| --- | --- | --- | -| `speed` | `number` | The playback speed to set. | - -##### Example - -```ts -// Set playback speed to 1.5x -await Spicetify.Platform.PlayerAPI.setSpeed(1.5); -``` diff --git a/docs/development/api-wrapper/methods/player.md b/docs/development/api-wrapper/methods/player.md deleted file mode 100644 index aba421d3..00000000 --- a/docs/development/api-wrapper/methods/player.md +++ /dev/null @@ -1,529 +0,0 @@ ---- -title: Player -description: A collection of methods to interact with the Spotify player. ---- - -Spicetify provides a collection of methods to interact with the Spotify player. You can get the current player state, play/pause, skip to next/previous track, set repeat/shuffle mode, and more. - -This is mostly a wrapper of the `Spicetify.Platform.PlayerAPI` object. - -```ts -namespace Player { - function addEventListener(type: string, callback: (event?: Event) => void): void; - function addEventListener(type: "songchange", callback: (event?: Event & { data: PlayerState }) => void): void; - function addEventListener(type: "onplaypause", callback: (event?: Event & { data: PlayerState }) => void): void; - function addEventListener(type: "onprogress", callback: (event?: Event & { data: number }) => void): void; - function back(): void; - const data?: PlayerState; - function decreaseVolume(): void; - function dispatchEvent(event: Event): void; - const eventListeners: { - [key: string]: Array<(event?: Event) => void> - }; - function formatTime(milisecond: number): string; - function getDuration(): number; - function getMute(): boolean; - function getProgress(): number; - function getProgressPercent(): number; - function getRepeat(): number; - function getShuffle(): boolean; - function getHeart(): boolean; - function getVolume(): number; - function increaseVolume(): void; - function isPlaying(): boolean; - function next(): void; - function pause(): void; - function play(): void; - function playUri(uri: string, context?: any, options?: any): Promise; - function removeEventListener(type: string, callback: (event?: Event) => void): void; - function seek(position: number): void; - function setHeart(status: boolean): void; - function setMute(state: boolean): void; - function setRepeat(mode: number): void; - function setShuffle(state: boolean): void; - function setVolume(level: number): void; - function skipBack(amount?: number): void; - function skipForward(amount?: number): void; - function toggleHeart(): void; - function toggleMute(): void; - function togglePlay(): void; - function toggleRepeat(): void; - function toggleShuffle(): void; -} -``` - -## Properties - -### data - -An object contains all information about current track and player. - -:::caution - -If the current player doesn't have any track, `data` will be `null`. Always check for `null` before using `data` to avoid errors. - -::: - -```ts -Spicetify.Player.data; -``` - -#### Return - -[`PlayerState`](/docs/development/api-wrapper/types/player-state.md) - - -#### Example - -```ts -// Get current track URI -const currentURI = Spicetify.Player.data?.item.uri; -if (currentURI) { - console.log(currentURI); -} -``` - -### eventListeners - -An object containing all registered event listeners. - -```ts -Spicetify.Player.eventListeners; -``` - -#### Return - -```ts -{ - [key: string]: Array<(event?: Event) => void> -} -``` - -| Key | Description | -| --- | ----------- | -| `key` | Event type | -| `value` | Array of registered event listeners | - -## Methods - -### addEventListener - -Register a listener of `type` on Spicetify.Player. You can use this method to listen to events that are fired throughout the app, including: - - * `songchange` type when player changes track. - * `onplaypause` type when player plays or pauses. - * `onprogress` type when track progress changes. - -```ts -// Register a listener that will be called when player changes track -Spicetify.Player.addEventListener("songchange", (event) => { - // Do something - console.log(event.data); -}); -``` - -#### Parameters - -| Name | Type | Description | -| ---- | ---- | ----------- | -| `type` | `string` | Event type | -| `callback` | `(event?: Event) => void` | Event listener. Includes relevant information about the event (e.g. `data` for `songchange` event) | - -### dispatchEvent - -Dispatches an event at `Spicetify.Player`. - -By default, `Spicetify.Player` always dispatch - - * `songchange` type when player changes track. - * `onplaypause` type when player plays or pauses. - * `onprogress` type when track progress changes. - -```ts -Spicetify.Player.dispatchEvent(event); -``` - -#### Parameters - -| Name | Type | Description | -| ---- | ---- | ----------- | -| `event` | `Event` | Event to dispatch. Includes relevant information about the event (e.g. `data` for `songchange` event) | - -### back - -Skip to previous track. - -```ts -Spicetify.Player.back(); -``` - -### decreaseVolume - -Decrease a small amount of volume. The value is automatically determined by the client. - -```ts -Spicetify.Player.decreaseVolume(); -``` - -### formatTime - -Format a time in milliseconds to a string in `mm:ss` format. - -```ts -Spicetify.Player.formatTime(time); -``` - -#### Parameters - -| Name | Type | Description | -| ---- | ---- | ----------- | -| `time` | `number` | Time in milliseconds | - -#### Return - -`string` - -#### Example - -```ts -Spicetify.Player.formatTime(1000); // "00:01" - -// Get current track duration -const duration = Spicetify.Player.getDuration(); -const formattedDuration = Spicetify.Player.formatTime(duration); -console.log(formattedDuration); // "03:45" -``` - -### getDuration - -Return the duration of current track in milliseconds. - -```ts -Spicetify.Player.getDuration(); -``` - -#### Return - -`number` - -#### Example - -```ts -// Get current track duration -const duration = Spicetify.Player.getDuration(); -console.log(duration); // 225000 -``` - -### getMute - -Return the mute state of player. - -```ts -Spicetify.Player.getMute(); -``` - -#### Return - -`boolean` - -### getProgress - -Return the progress of current track in milliseconds. - -```ts -Spicetify.Player.getProgress(); -``` - -#### Return - -`number` - -#### Example - -```ts -// Get current track progress -const progress = Spicetify.Player.getProgress(); -console.log(progress); // 10000 -``` - -### getProgressPercent - -Return the progress of current track in percentage, from 0 to 1. - -```ts -Spicetify.Player.getProgressPercent(); -``` - -#### Return - -`number` - -#### Example - -```ts -// Get current track progress -const progress = Spicetify.Player.getProgressPercent(); -console.log(progress); // 0.04 -``` - -### getRepeat - -Return the repeat mode of player. The value can be: - - * `0` for no repeat. - * `1` for repeat all. - * `2` for repeat one. - -```ts -Spicetify.Player.getRepeat(); -``` - -#### Return - -`number` - -### getShuffle - -Return the shuffle state of player. - -```ts -Spicetify.Player.getShuffle(); -``` - -#### Return - -`boolean` - -### getHeart - -Return the heart state of player. - -```ts -Spicetify.Player.getHeart(); -``` - -#### Return - -`boolean` - -### getVolume - -Return the volume of player. The value is from 0 to 1. - -```ts -Spicetify.Player.getVolume(); -``` - -#### Return - -`number` - -### increaseVolume - -Increase a small amount of volume. The value is automatically determined by the client. - -```ts -Spicetify.Player.increaseVolume(); -``` - -### next - -Skip to next track. - -```ts -Spicetify.Player.next(); -``` - -### pause - -Pause the player. - -```ts -Spicetify.Player.pause(); -``` - -### play - -Resume the player. - -```ts -Spicetify.Player.play(); -``` - -### playUri - -Start playback of the specified track. - -```ts -Spicetify.Player.playUri(uri, context?: any, options?: any); -``` - -#### Parameters - -| Name | Type | Description | -| ---- | ---- | ----------- | -| `uri` | `string` | Track URI string | -| `context` | `any` | Context of the track. Default is `{}` | -| `options` | `any` | Options of the track. Default is `{}` | - -#### Example - -```ts -// 505 - Arctic Monkeys -const trackURI = "spotify:track:0BxE4FqsDD1Ot4YuBXwAPp"; - -await Spicetify.Player.playUri(trackURI); -``` - -### removeEventListener - -Unregister added event listener `type`. - -```ts -Spicetify.Player.removeEventListener(type, callback); -``` - -#### Parameters - -| Name | Type | Description | -| ---- | ---- | ----------- | -| `type` | `string` | Event type | -| `callback` | `(event?: Event) => void` | Event listener | - -### seek - -Seek track to position. Position can be in percentage (0 to 1) or in milliseconds. - -```ts -Spicetify.Player.seek(position); -``` - -#### Parameters - -| Name | Type | Description | -| ---- | ---- | ----------- | -| `position` | `number` | Position to seek. Can be in percentage (0 to 1) or in milliseconds | - -#### Example - -```ts -// Seek to 50% of track -Spicetify.Player.seek(0.5); - -// Seek to 1 minute of track -Spicetify.Player.seek(60000); -``` - -### setHeart - -Set the heart status of the currently playing track. - -```ts -Spicetify.Player.setHeart(status); -``` - -#### Parameters - -| Name | Type | Description | -| ---- | ---- | ----------- | -| `status` | `boolean` | Heart status | - -### setMute - -Set the mute state of player. - -```ts -Spicetify.Player.setMute(state); -``` - -#### Parameters - -| Name | Type | Description | -| ---- | ---- | ----------- | -| `state` | `boolean` | Mute state | - -### setRepeat - -Set the repeat mode of player. The value can be: - - * `0` for no repeat. - * `1` for repeat all. - * `2` for repeat one. - -```ts -Spicetify.Player.setRepeat(mode); -``` - -#### Parameters - -| Name | Type | Description | -| ---- | ---- | ----------- | -| `mode` | `number` | Repeat mode | - -### setShuffle - -Set the shuffle state of player. - -```ts -Spicetify.Player.setShuffle(state); -``` - -#### Parameters - -| Name | Type | Description | -| ---- | ---- | ----------- | -| `state` | `boolean` | Shuffle state | - -### setVolume - -Set the volume of player. The value is from 0 to 1. - -```ts -Spicetify.Player.setVolume(level); -``` - -#### Parameters - -| Name | Type | Description | -| ---- | ---- | ----------- | -| `level` | `number` | Volume | - -### toggleHeart - -Toggle the heart state of player / save / unsave the current track from user's library. - -```ts -Spicetify.Player.toggleHeart(); -``` - -### toggleMute - -Toggle the mute state of player. - -```ts -Spicetify.Player.toggleMute(); -``` - -### togglePlay - -Toggle the play state of player. - -```ts -Spicetify.Player.togglePlay(); -``` - -### toggleRepeat - -Toggle the repeat mode of player. The value switches between: No repeat, Repeat all, Repeat one. - -```ts -Spicetify.Player.toggleRepeat(); -``` - -### toggleShuffle - -Toggle the shuffle state of player. - -```ts -Spicetify.Player.toggleShuffle(); -``` diff --git a/docs/development/api-wrapper/methods/popup-modal.md b/docs/development/api-wrapper/methods/popup-modal.md deleted file mode 100644 index 86f23b69..00000000 --- a/docs/development/api-wrapper/methods/popup-modal.md +++ /dev/null @@ -1,68 +0,0 @@ ---- -title: PopupModal -description: Set of methods to create and control popup modals. ---- - -Spicetify provides a set of methods to create and control popup modals. This will display a modal on top of the client, which can be used to display information or ask for user input. - -```ts -namespace PopupModal { - interface Content { - title: string; - content: string | Element; - isLarge?: boolean; - } - - function display(content: Content): void; - function hide(): void; -}; -``` - -## Interface - -### `Content` - -`Content` is an object that contains the information needed to display the modal. - -| Property | Type | Description | -| --- | --- | --- | -| `title` | `string` | Title of the modal. | -| `content` | `string` | Content of the modal. You can specify a string for simple text display or an HTML element for interactive config/setting menu. | -| `isLarge` | `boolean` | `undefined` | Bigger modal. | - -## Methods - -### `display` - -Displays a modal on top of the client. - -:::note - -This method will replace the current modal if there is one. - -::: - -| Parameter | Type | Description | -| --- | --- | --- | -| `content` | [`Content`](#content) | Information about the modal. | - -```ts -Spicetify.PopupModal.display({ - title: 'Hello World', - content: 'This is a simple text', -}); -``` - -### `hide` - -Hides the current modal. - -:::note - -This method will hide *any* modal currently displayed via `Spicetify.PopupModal.display`. - -::: - -```ts -Spicetify.PopupModal.hide(); -``` diff --git a/docs/development/api-wrapper/methods/uri.md b/docs/development/api-wrapper/methods/uri.md deleted file mode 100644 index e56e08cc..00000000 --- a/docs/development/api-wrapper/methods/uri.md +++ /dev/null @@ -1,317 +0,0 @@ ---- -title: URI -description: Set of API methods to parse and validate URIs. ---- - -Spicetify provides a set of API methods to parse and validate URIs. These are internal APIs used by Spotify in almost every component in the client. - -This would be useful if you want to develop extensions that need to interact with Spotify components (e.g. open a track in the player, get playlist tracks, etc.). - -:::warning - -Because these APIs are internal, they are not guaranteed to be stable and may _drastically_ change in the future. Use them at your own risk. - -::: - -:::note - -Spotify has had a major rework of this method in version `1.2.4`. - -In this documentation, we will be covering the new method, used from version `1.2.4` onwards. - -::: - -## Introduction - -Almost every component in the client has its own URI (Uniform Resource Indicator), whether it be a track, an album, a playlist, a user, etc. The URI is used to identify the resource and to interact with it. - -They all follow the same format in the form of `spotify::`, where `` is the type of the resource and `` is the `base62` unique identifier of the resource. - -``` -spotify:: -``` - -For example, a track URI looks like this: - -``` -spotify:track:6rqhFgbbKwnb9MLmUQDhG6 -``` - -When you share a track, album, playlist, etc. on Spotify, you are actually sharing the URI of the resource. - -It follows a similar format to the URI, but with a few differences: - -``` -https://open.spotify.com//?query=parameters -``` - -For example, a track URL looks like this: - -``` -https://open.spotify.com/track/6rqhFgbbKwnb9MLmUQDhG6?si=2a3b4c5d6e7f8g9h -``` - -To get a URI of an item using the client interface, simply right-click on it, hover over `Share` and hold your Ctrl key. You will see a new option called `Copy Spotify URI` appear in place of `Copy Link`. Click on it to copy the URI to your clipboard. - -## Usage - -`Spicetify.URI` itself is a class that helps you create a URI object, as well as provide static methods to parse and validate URIs. - -:::note - -This type is deducted from Spotify's internal usage. It may not be accurate and may change in the future. - -::: - -```ts -class URI { - constructor(type: string, props: any); - public type: string; - public hasBase62Id: boolean; - - public id?: string; - public disc?: any; - public args?: any; - public category?: string; - public username?: string; - public artist?: string; - public album?: string; - public query?: string; - public country?: string; - public global?: boolean; - public context?: string | typeof URI | null; - public anchor?: string; - public play?: any; - public toplist?: any; - - toURI(): string; - toString(): string; - toURLPath(opt_leadingSlash: boolean): string; - toURL(origin?: string): string; - clone(): URI | null; - getPath(): string; -} -``` - -Almost all properties are optional, except for `type` and `hasBase62Id`. The `type` property is the type of the URI, and the `hasBase62Id` property is a boolean that indicates whether the URI has a `base62` identifier. - -All other properties are dependent on the type of the URI. For example, a track URI _may_ include a `context` property, while a playlist URI _may_ include a `username` property. - -The `type` property matches one of the values in the [`Spicetify.URI.Type`](/docs/development/api-wrapper/types/uri/type) enum. - -### Example - -```ts -const uri = new Spicetify.URI('track', { - id: '6rqhFgbbKwnb9MLmUQDhG6', - context: 'spotify:album:1Je1IMUlBXcx1Fz0WE7oPT', -}); -``` - -### Dynamic methods - -These methods are included in every instance of `Spicetify.URI`. - -#### toURI - -Creates a URI string from the URI object. - -```ts -const uri = new Spicetify.URI('track', { - id: '6rqhFgbbKwnb9MLmUQDhG6', - context: 'spotify:album:1Je1IMUlBXcx1Fz0WE7oPT', -}); - -uri.toURI(); // spotify:track:6rqhFgbbKwnb9MLmUQDhG6?context=spotify:album:1Je1IMUlBXcx1Fz0WE7oPT -``` - -#### toString - -Alias of [`toURI`](#touri) - -```ts -const uri = new Spicetify.URI('track', { - id: '6rqhFgbbKwnb9MLmUQDhG6', - context: 'spotify:album:1Je1IMUlBXcx1Fz0WE7oPT', -}); - -uri.toString(); // spotify:track:6rqhFgbbKwnb9MLmUQDhG6?context=spotify:album:1Je1IMUlBXcx1Fz0WE7oPT -``` - -#### toURLPath - -Creates a URL path from the URI object. - -| Parameter | Type | Description | -| ---------------- | ---------------------------- | ----------------------------------------------- | -| opt_leadingSlash | `boolean` | `undefined` | Whether to prepend a leading slash to the path. | - -```ts -const uri = new Spicetify.URI('track', { - id: '6rqhFgbbKwnb9MLmUQDhG6', - context: 'spotify:album:1Je1IMUlBXcx1Fz0WE7oPT', -}); - -uri.toURLPath(); // /track/6rqhFgbbKwnb9MLmUQDhG6?context=spotify:album:1Je1IMUlBXcx1Fz0WE7oPT - -uri.toURLPath(true); // /track/6rqhFgbbKwnb9MLmUQDhG6?context=spotify:album:1Je1IMUlBXcx1Fz0WE7oPT -``` - -#### toURL - -Creates a URL from the URI object. - -| Parameter | Type | Description | -| --------- | --------------------------- | ---------------------------------------------------------------------- | -| origin | `string` | `undefined` | The origin to use for the URL. Defaults to `https://open.spotify.com`. | - -```ts -const uri = new Spicetify.URI('track', { - id: '6rqhFgbbKwnb9MLmUQDhG6', - context: 'spotify:album:1Je1IMUlBXcx1Fz0WE7oPT', -}); - -uri.toURL(); // https://open.spotify.com/track/6rqhFgbbKwnb9MLmUQDhG6?context=spotify:album:1Je1IMUlBXcx1Fz0WE7oPT - -uri.toURL('https://example.com/'); // https://example.com/track/6rqhFgbbKwnb9MLmUQDhG6?context=spotify:album:1Je1IMUlBXcx1Fz0WE7oPT -``` - -#### clone - -Clones the URI object. - -```ts -const uri = new Spicetify.URI('track', { - id: '6rqhFgbbKwnb9MLmUQDhG6', - context: 'spotify:album:1Je1IMUlBXcx1Fz0WE7oPT', -}); - -const clone = uri.clone(); - -clone.toURI(); // spotify:track:6rqhFgbbKwnb9MLmUQDhG6?context=spotify:album:1Je1IMUlBXcx1Fz0WE7oPT -``` - -#### getPath - -Gets the path of the URI object by removing all hash and query parameters. - -```ts -const uri = new Spicetify.URI('track', { - id: '6rqhFgbbKwnb9MLmUQDhG6', - context: 'spotify:album:1Je1IMUlBXcx1Fz0WE7oPT', -}); - -uri.getPath(); // spotify:track:6rqhFgbbKwnb9MLmUQDhG6 -``` - -### Static methods - -These methods are included in the `Spicetify.URI` class. - -#### Type - -An enum of all URI types. - -```ts -const { Type } = Spicetify.URI; - -Type.ALBUM; // album -``` - -For a list of all URI types, see the [`Spicetify.URI.Type`](/docs/development/api-wrapper/types/uri/type) enum and explore in DevTools. - -##### Usage - -You can use this enum to check and validate the type of a URI. - -```ts -const uri = new Spicetify.URI('track', { - id: '6rqhFgbbKwnb9MLmUQDhG6', - context: 'spotify:album:1Je1IMUlBXcx1Fz0WE7oPT', -}); - -uri.type === Spicetify.URI.Type.TRACK; // true -``` - -#### from - -Parses a given argument into a [`URI`](#usage) instance. - -Unlike [`URI.fromString`](#fromstring), this function could receive any kind of value. - -If the value is already a URI instance, it is simply returned. Otherwise the value will be stringified before parsing. - -This function also does not throw an error like [`URI.fromString`](#fromstring), but -instead simply returns null if it can't parse the value. - -```ts -class URI { - static from(value: any): URI | null; -} -``` - -| Parameter | Type | Description | -| --------- | ----- | ------------------- | -| value | `any` | The value to parse. | - -```ts -Spicetify.URI.from('spotify:track:6rqhFgbbKwnb9MLmUQDhG6'); // URI instance - -Spicetify.URI.from('https://open.spotify.com/track/6rqhFgbbKwnb9MLmUQDhG6'); // URI instance - -Spicetify.URI.from(new Spicetify.URI('track', { id: '6rqhFgbbKwnb9MLmUQDhG6' })); // URI instance - -Spicetify.URI.from(Spicetify.URI.from('spotify:track:6rqhFgbbKwnb9MLmUQDhG6')); // URI instance - -Spicetify.URI.from({ id: '6rqhFgbbKwnb9MLmUQDhG6' }); // null -``` - -#### fromString - -Parses a given string into a [`URI`](#usage) instance. - -:::caution - -This function will throw a `TypeError` if the argument passed is not a string. Use [`URI.from`](#from) if you want to parse any kind of value. - -::: - -```ts -class URI { - static fromString(uri: string): URI; -} -``` - -| Parameter | Type | Description | -| --------- | -------- | ----------------- | -| uri | `string` | The URI to parse. | - -```ts -Spicetify.URI.fromString('spotify:track:6rqhFgbbKwnb9MLmUQDhG6'); // URI instance - -Spicetify.URI.fromString('https://open.spotify.com/track/6rqhFgbbKwnb9MLmUQDhG6'); // URI instance - -Spicetify.URI.fromString('spotify:track:6rqhFgbbKwnb9MLmUQDhG6?context=spotify:album:1Je1IMUlBXcx1Fz0WE7oPT'); // URI instance - -Spicetify.URI.fromString(new Spicetify.URI('track', { id: '6rqhFgbbKwnb9MLmUQDhG6' })); // TypeError: Argument `uri` must be a string. -``` - -#### Validation functions - -Each URI type has a validation function that can be used to check if a given string is a valid URI of that type. - -For a list of all validation functions, see [`Validation functions`](/docs/development/api-wrapper/types/uri/validation-functions). - -:::caution - -Almost all playlists use the `playlist-v2` type, so use `Spicetify.URI.isPlaylistV2URI` instead. - -Similarly, you can use `Spicetify.URI.isPlaylistV1OrV2` to check if a URI is a playlist of any version. - -::: - -```ts -const { isAlbumURI } = Spicetify.URI; - -isAlbumURI('spotify:album:1Je1IMUlBXcx1Fz0WE7oPT'); // true -``` diff --git a/docs/development/api-wrapper/modules.md b/docs/development/api-wrapper/modules.md deleted file mode 100644 index 8300d115..00000000 --- a/docs/development/api-wrapper/modules.md +++ /dev/null @@ -1,98 +0,0 @@ ---- -title: Modules -description: 🧩 Modules exposed via Spicetify object. ---- - -Spicetify exposes some modules via `Spicetify` object. - -You can access them by typing `Spicetify.` in the DevTools console, inside your extension, or `window.top.Spicetify.` if you're developing an app inside an `iframe`. - -Utilizing these modules can help you create more powerful extensions without having to include the whole module in your extension. - -```js -Spicetify.React; -``` - -For usage of these modules, please refer to their official documentation. - -### React - -[React](https://reactjs.org/) is a JavaScript library for building user interfaces. It is used by Spotify to build their UI. - -:::note - -Spotify versions *below* 1.2.26 use version **17.0.2**, *after* - **18.2.0**. - -::: - -```js -Spicetify.React; -``` - -### ReactDOM - -[ReactDOM](https://reactjs.org/docs/react-dom.html) is a package that provides DOM-specific methods that can be used at the top level of your app and as an escape hatch to get outside of the React model if you need to. It is used by Spotify to render React components to the DOM. - -```js -Spicetify.ReactDOM; -``` - -### Tippy.js - -[Tippy.js](https://atomiks.github.io/tippyjs/) is a highly customizable tooltip and popover library powered by Popper. - -```js -Spicetify.Tippy; -``` - -### Mousetrap - -[Mousetrap](https://craig.is/killing/mice) is a simple library for handling keyboard shortcuts in JavaScript. - -```js -Spicetify.Mousetrap; -``` - -### React Flip Toolkit - -[React Flip Toolkit](https://github.com/aholachek/react-flip-toolkit) is a collection of easy-to-use animation effects and utilities that can be used to enhance your React project. - -```js -Spicetify.ReactFlipToolkit; -``` - -### React Query (v3) - -[React Query](https://react-query.tanstack.com/) is a library for managing, caching, syncing, and refetching server state in React. - -:::note - -Spotify uses React Query v3, instead of the current latest version (v4). As such, the API may be different from the official documentation. - -::: - -```js -Spicetify.ReactQuery; -``` - -### classnames - -[classnames](https://github.com/JedWatson/classnames) is a simple JavaScript utility for conditionally joining class names together. - -```js -Spicetify.classnames; -``` - -### Snackbar - -[Notistack](https://github.com/iamhosseindhv/notistack) is a JavaScript library for creating highly customizable notification snackbars (toasts) that can be stacked on top of each other. - -:::note - -Be aware that only `SnackbarProvider` and `useSnackbar` work as described in the official Notistack documentation. - -::: - -```js -Spicetify.Snackbar; -``` diff --git a/docs/development/api-wrapper/properties/config.md b/docs/development/api-wrapper/properties/config.md deleted file mode 100644 index 571ad9c0..00000000 --- a/docs/development/api-wrapper/properties/config.md +++ /dev/null @@ -1,38 +0,0 @@ ---- -title: Config -description: 🛠️ Accessing a copy of Spicetify's `config-xpui.ini` file inside your extension. ---- - -To make it easier for you to validate and debug your extensions, Spicetify provides a filtered copy of the user's `config-xpui.ini` in the `Spicetify` object. - -```ts -interface Config { - version: string; - current_theme: string; - color_scheme: string; - extensions: string[]; - custom_apps: string[]; -} -``` - -| Property | Type | Description | -| --- | --- | --- | -| `version` | `string` | Spicetify version. | -| `current_theme` | `string` | Current theme name. | -| `color_scheme` | `string` | Current color scheme name. | -| `extensions` | `string[]` | List of enabled extensions. | -| `custom_apps` | `string[]` | List of enabled custom apps. | - -## Usage - -You can validate if the user currently has a custom app or a theme enabled by checking if the app or theme's name is included in the `custom_apps` or `current_theme` property of the `Config` object. - -```ts -const { Config } = Spicetify; - -if (Config.custom_apps.includes("lyrics-plus")) { - // Do something -} -``` - -This can ensure that your extension doesn't break if the user doesn't have the required app or theme installed. diff --git a/docs/development/api-wrapper/properties/queue.md b/docs/development/api-wrapper/properties/queue.md deleted file mode 100644 index a257dfa4..00000000 --- a/docs/development/api-wrapper/properties/queue.md +++ /dev/null @@ -1,43 +0,0 @@ ---- -title: Queue -description: An object containing information about the current queue. ---- - -:::note - -This type is deducted from Spotify's internal usage. It may not be accurate and may change in the future. - -::: - -The `Queue` object contains a list of queuing tracks, history of played tracks, and current track metadata. - -```ts -Spicetify.Queue -``` - -## Return - -```ts -const Queue: { - nextTracks: ProvidedTrack[]; - prevTracks: ProvidedTrack[]; - queueRevision: string; - track: ProvidedTrack; -}; -``` - -| Property | Type | Description | -| --- | --- | --- | -| `nextTracks` | [`ProvidedTrack[]`](/docs/development/api-wrapper/types/provided-track) | List of next tracks. | -| `prevTracks` | [`ProvidedTrack[]`](/docs/development/api-wrapper/types/provided-track) | List of previous tracks. | -| `queueRevision` | `string` | Queue revision ID used internally by Spotify. | -| `track` | [`ProvidedTrack`](/docs/development/api-wrapper/types/provided-track) | Current track. | - -## Usage - -If you plan on developing extensions that need to access the current queue, you can use the `Spicetify.Queue` object. - -```ts -const queue = Spicetify.Queue; -const currentTrack = queue.track; -``` diff --git a/docs/development/api-wrapper/properties/react-components.md b/docs/development/api-wrapper/properties/react-components.md deleted file mode 100644 index eb8112a8..00000000 --- a/docs/development/api-wrapper/properties/react-components.md +++ /dev/null @@ -1,375 +0,0 @@ ---- -title: ReactComponent -description: Set of stock React components used by Spotify. ---- - -Spicetify provides a set of stock React components used by Spotify. You can use these components to create your own custom UI. - -:::note - -It is recommended that you be familiar with [`React`](https://react.dev/) and [`spicetify-creator`](/docs/development/spicetify-creator/the-basics) before using these components. - -::: - -```ts -namespace ReactComponent { - const ContextMenu: any; - const RightClickMenu: any; - const Menu: any; - const MenuItem: any; - const AlbumMenu: any; - const PodcastShowMenu: any; - const ArtistMenu: any; - const PlaylistMenu: any; - const TooltipWrapper: any; - const IconComponent: any; - const TextComponent: any; - const ConfirmDialog: any; - const PanelSkeleton: any; - const PanelContent: any; - const PanelHeader: any; - const Toggle: any; - const Slider: any -}; -``` - -## Components - -:::info - -These components may be wrappers for other components such as [`Tippy`](https://atomiks.github.io/tippyjs/) or [`styled-components`](https://styled-components.com/). They may accept additional props that are not listed here. - -As such, type definitions are not forced for these components but they act as a guideline for what you can use and what Spotify uses. - -Refer to the underlying library's documentation for more information. - -::: - -### `ContextMenu` - -Generic context menu provider. It is used by Spotify on a variety of elements, such as right-click menu, dropdown menu, etc. - -#### Props - -See [`ContextMenuProps`](/docs/development/api-wrapper/types/react-component/context-menu-props). - -#### Example - -```tsx -// See Menu section for more details -const menuWrapper = React.memo((props: MenuProps) => - - Spicetify.showNotification('Hello World')} - /> - -}); - -const contextMenu = React.memo((props: ContextMenuProps) => { - return ( - } - > -
Click me
-
- ); -}); -``` - -### `RightClickMenu` - -Wrapper of [`ContextMenu`](#contextmenu) with predefined props: `action = 'toggle'` and `trigger = 'right-click'`. - -#### Props - -See [`ContextMenuProps`](/docs/development/api-wrapper/types/react-component/context-menu-props). - -#### Example - -```tsx -const menuWrapper = React.memo((props: MenuProps) => - - Spicetify.showNotification('Hello World')} - /> - -}); -// Same as ContextMenu example, but appears on right-click -const contextMenu = React.memo((props: ContextMenuProps) => { - return ( - } - > -
Right-click me
-
- ); -}); -``` - -### `Menu` - -Outer layer containing [`MenuItem`](#menuitem)s. - -#### Props - -See [`MenuProps`](/docs/development/api-wrapper/types/react-component/menu-props). - -#### Example - -```tsx -const menuWrapper = React.memo((props: MenuProps) => - Spicetify.showNotification('Menu closed')}> - Spicetify.showNotification('Hello World')} - /> - -}); -``` - -### `MenuItem` - -Component to construct menu item. Used as [`Menu`](#menu) children. - -#### Props - -See [`MenuItemProps`](/docs/development/api-wrapper/types/react-component/menu-item-props). - -#### Example - -```tsx -const icon = React.memo((props: IconComponentProps) => - -); - -const menuItem = React.memo((props: MenuItemProps) => - Spicetify.showNotification('Hello World')} - disabled={false} - divider="after" - {/* It is recommended that you use both `icon` and `trailingIcon` for compatibility between older versions */} - icon={} - trailingIcon={} - > - Hello World - -); -``` - -### `AlbumMenu`, `PodcastShowMenu`, `ArtistMenu`, `PlaylistMenu` - -Tailored [`Menu`](#menu) for specific type of object. - -#### Props - -Accepts `uri` and `onRemoveCallback` props along with [`MenuProps`](/docs/development/api-wrapper/types/react-component/menu-props). - -```ts -interface AlbumMenuProps extends MenuProps { - uri: string; - onRemoveCallback?: (uri: string) => void; -}; -``` - -#### Example - -```tsx -const currentAlbumURI = Spicetify.Player.data.item.metadata.album_uri; - -const albumMenu = React.memo((props: AlbumMenuProps) => - Spicetify.showNotification('Menu closed')} - uri={currentAlbumURI} - /> -); -``` - -### `TooltipWrapper` - -Component to display tooltip when hovering over element. Useful for accessibility. - -:::info - -This component is a wrapper for [`Tippy`](https://atomiks.github.io/tippyjs/). It may accept additional props that are not listed here. - -::: - -#### Props - -See [`TooltipProps`](/docs/development/api-wrapper/types/react-component/tooltip-props). - -#### Example - -```tsx -const elementHasTooltip = React.memo((props: TooltipProps) => - -
Hover me
-
-); -``` - -### `IconComponent` - -Component to render Spotify-style icon. It is used by Spotify on a variety of elements, such as buttons, icons, etc. - -:::info - -This component is a wrapper for [`styled-components`](https://styled-components.com/). It may accept additional props that are not listed here. - -::: - -#### Props - -See [`IconComponentProps`](/docs/development/api-wrapper/types/react-component/icon-component-props). - -#### Example - -```tsx -const icon = React.memo((props: IconComponentProps) => - -); -``` - -### `TextComponent` - -Component to render text. It is used by Spotify on a variety of elements, such as buttons, text, etc. - -:::info - -This component is a wrapper for [`styled-components`](https://styled-components.com/). It may accept additional props that are not listed here. - -::: - -#### Props - -See [`TextComponentProps`](/docs/development/api-wrapper/types/react-component/text-component-props). - -#### Example - -```tsx -const text = React.memo((props: TextComponentProps) => - - Hello World - -); -``` - -### `ConfirmDialog` - -Component to display Spotify-style confirmation dialog. Used by Spotify on playlist, track removal, etc. - -:::info - -For each of the `onConfirm`, `onCancel`, and `onOutsideClick` props, the dialog will not close automatically. You must manually handle the state of the dialog. - -::: - -#### Props - -See [`ConfirmDialogProps`](/docs/development/api-wrapper/types/react-component/confirm-dialog-props). - -#### Example - -```tsx -const ConfirmButton = () => { - // Modal open state must be handled manually - const [showModal, setShowModal] = React.useState(false); - - return ( - { - setShowModal(false); - Spicetify.showNotification('Confirmed'); - }} - onCancel={() => { - setShowModal(false); - Spicetify.showNotification('Cancelled'); - }} - onOutsideClick={() => { - setShowModal(false); - Spicetify.showNotification('Clicked outside'); - }} - titleText="Confirm Modal" - descriptionText="Are you sure you want to confirm?" - confirmText="Confirm" - cancelText="Cancel" - /> - - ); -} -``` - -### `Toggle` - -Component to display Spotify-style toggle. Used by Spotify on the settings page. - -#### Props - -See [`ToggleProps`](/docs/development/api-wrapper/types/react-component/toggle-props). - -```tsx -const Toggle = () => { - const [enabled, setEnabled] = React.useState(false); - - return ( - - ); -} -``` - -### `Slider` - -Component to render sliders. It is used by Spotify for the volume/playing bars and on the settings page. - -#### Props - -See [`SliderProps`](/docs/development/api-wrapper/types/react-component/slider-props). - -#### Example - -```tsx -const Slider = () => { - const [value, setValue] = useState(0); - - return ( - {}} - onDragMove={setValue} - onDragEnd={(value) => {console.log(`final value is ${value}`)}} - > - ); -} -``` - -### `PanelSkeleton`, `PanelContent`, `PanelHeader` - -Components to render Spotify-style panel. Used by Spotify on their right sidebar panels (e.g. BuddyFeed, Now Playing, etc). - -Refer to [`Panel.Components`](/docs/development/api-wrapper/methods/panel#components) for more details. \ No newline at end of file diff --git a/docs/development/api-wrapper/properties/react-hook.md b/docs/development/api-wrapper/properties/react-hook.md deleted file mode 100644 index c1737887..00000000 --- a/docs/development/api-wrapper/properties/react-hook.md +++ /dev/null @@ -1,184 +0,0 @@ ---- -title: ReactHook -description: Set of React hooks used by the Spotify client. ---- - -Spicetify provides a set of React hooks used by the Spotify client. You can use these hooks to create a React component interactive with the client. - -:::note - -It is recommended that you be familiar with [`React`](https://react.dev/) before using these hooks. - -::: - -```ts -namespace ReactHook { - function DragHandler( - uris?: string[], - label?: string, - contextUri?: string, - sectionIndex?: number, - dropOriginUri?: string - ): (event: React.DragEvent, uris?: string[], label?: string, contextUri?: string, sectionIndex?: number) => void; - function usePanelState(id: number): { toggle: () => void, isActive: boolean }; - function useExtractedColor(uri: string, fallbackColor?: string, variant?: "colorRaw" | "colorLight" | "colorDark"): string; -} -``` - -## Hooks - -### `DragHandler` - -React Hook to create interactive drag-and-drop element. - -Used to create a draggable element that can be dropped into Spotify's components (e.g. Playlist, Folder, Sidebar, Queue) - -```ts -function DragHandler( - uris?: string[], - label?: string, - contextUri?: string, - sectionIndex?: number, - dropOriginUri?: string -): (event: React.DragEvent, uris?: string[], label?: string, contextUri?: string, sectionIndex?: number) => void; -``` - -#### Parameters - -| Parameter | Type | Description | -| :--- | :--- | :--- | -| uris | `string[]` | `undefined` | List of URIs to be dragged. | -| label | `string` | `undefined` | Label to be displayed when dragging. | -| contextUri | `string` | `undefined` | Context URI of the element from which the drag originated (e.g. Playlist URI). | -| sectionIndex | `number` | `undefined` | Index of the section in which the drag originated. | -| dropOriginUri | `string` | `undefined` | URI of the desired drop target. Leave empty to allow drop anywhere. | - -#### Returns - -Function to handle drag event. Should be passed to `onDragStart` prop of the element. All parameters passed onto the hook will be passed onto the handler unless declared otherwise. - -#### Example - -```tsx -const DraggableComponent = () => { - // Do I Wanna Know? by Arctic Monkeys - const uri = "spotify:track:5FVd6KXrgO9B3JPmC8OPst"; - const label = "Do I Wanna Know? - Arctic Monkeys"; - - const handleDragStart = Spicetify.ReactHook.DragHandler([uri], label); - - return ( -
- {label} -
- ); -} -``` - -### `usePanelState` - -React Hook to use panel state. - -```ts -function usePanelState(id: number): { toggle: () => void, isActive: boolean }; -``` - -#### Parameters - -| Parameter | Type | Description | -| :--- | :--- | :--- | -| id | `number` | ID of the panel to use. | - -#### Returns - -Object with methods of the panel. - -| Property | Type | Description | -| :--- | :--- | :--- | -| toggle | `() => void` | Toggle the panel. | -| isActive | `boolean` | Whether the panel is active. | - -#### Example - -```tsx -const PanelComponent = () => { - // The ID can be either Spotify's default panel IDs or your custom panel ID registered via `Spicetify.Panel.registerPanel` - const { toggle, isActive } = Spicetify.ReactHook.usePanelState(5); - - return ( -
- -
- ); -} -``` - -### `useExtractedColor` - -React Hook to use extracted color from GraphQL. - -```ts -function useExtractedColor(uri: string, fallbackColor?: string, variant?: "colorRaw" | "colorLight" | "colorDark"): string; -``` - -:::note - -This is a wrapper of ReactQuery's `useQuery` hook. The component using this hook must be wrapped in a `QueryClientProvider` component. - -Look into the example below for more information. - -::: - -#### Parameters - -| Parameter | Type | Description | -| :--- | :--- | :--- | -| uri | `string` | URI of the Spotify image to extract color from. | -| fallbackColor | `string` | `undefined` | Fallback color to use if the image is not available. Defaults to `#535353`. | -| variant | `"colorRaw"` | `"colorLight"` | `"colorDark"` | `undefined` | Variant of the color to use. Defaults to `colorRaw`. | - -#### Returns - -Extracted color hex code. - -#### Example - -```tsx -import { useEffect, useState } from "react"; - -const { QueryClient, QueryClientProvider } = Spicetify.ReactQuery; -const { useExtractedColor } = Spicetify.ReactHook; - -const queryClient = new QueryClient(); - -const Component = () => { - const [imageUri, setImageUri] = useState(Spicetify.Player.data?.item?.metadata?.image_xlarge_url ?? ""); - const color = useExtractedColor(imageUri); - - useEffect(() => { - // Listen to track change - const listener = () => { - setImageUri(Spicetify.Player.data?.item?.metadata?.image_xlarge_url ?? ""); - }; - Spicetify.Player.addEventListener("songchange", listener); - - return () => Spicetify.Player.removeEventListener("songchange", listener); - }, []); - - return ( -
- Hello World -
- ); -} - -const App = () => { - return ( - - - - ); -} -``` diff --git a/docs/development/api-wrapper/properties/svgicons.md b/docs/development/api-wrapper/properties/svgicons.md deleted file mode 100644 index a95713e7..00000000 --- a/docs/development/api-wrapper/properties/svgicons.md +++ /dev/null @@ -1,43 +0,0 @@ ---- -title: SVGIcons -description: A set of SVG icons used throughout the Spotify client. ---- - -Spicetify has a predefined set of SVG icons that are used by Spotify throughout the client. These are strings of SVG `innerHTML` that are used to create `` elements. - -```ts -const SVGIcons = Record; -``` - -| Property | Type | Description | -| --- | --- | --- | -| `key` | [`SVGIcon`](/docs/development/api-wrapper/types/svgicon) | SVG icon name. | - -## Usage - -You can use these icons to create custom menu items or other custom components. - -In vanilla JavaScript, you can create an `` element and set its `innerHTML` to the SVG icon string. - -```ts -const icon = document.createElement("svg"); -icon.innerHTML = Spicetify.SVGIcons["play"]; -``` - -In React, you can use the `dangerouslySetInnerHTML` prop to set the SVG icon string as the inner HTML of the `` element. - -```tsx -const icon = ; -``` - -In Spicetify's own methods, you can simply pass an [`SVGIcon`](/docs/development/api-wrapper/types/svgicon) to the `icon` parameter. - -```ts -new Spicetify.ContextMenu.Item( - name: "My Custom Item". - onClick: () => Spicetify.showNotification("Hello World!"), - shouldAdd: () => true, - icon: "play", - disabled: false, -) -``` diff --git a/docs/development/api-wrapper/properties/tippy-props.md b/docs/development/api-wrapper/properties/tippy-props.md deleted file mode 100644 index 6eaeb8c1..00000000 --- a/docs/development/api-wrapper/properties/tippy-props.md +++ /dev/null @@ -1,64 +0,0 @@ ---- -title: TippyProps -description: Predefined props for Tippy.js tooltips. ---- - -Spicetify provides a set of predefined props for Tippy.js tooltips. This is aimed to create tooltips that mimic the style of Spotify's tooltips. - -This is utilized for [`Topbar`](/docs/development/api-wrapper/classes/topbar) and [`Playbar`](/docs/development/api-wrapper/classes/playbar) tooltips. - -```ts -Spicetify.TippyProps = { - delay: [200, 0], - animation: true, - render(instance) { - const popper = document.createElement('div'); - const box = document.createElement('div'); - - popper.id = "context-menu"; - popper.appendChild(box); - - box.className = "main-contextMenu-tippy" - box.textContent = instance.props.content; - - function onUpdate(prevProps, nextProps) { - if (prevProps.content !== nextProps.content) { - if (nextProps.allowHTML) box.innerHTML = nextProps.content; - else box.textContent = nextProps.content; - } - } - - return { popper, onUpdate } - }, - onShow(instance) { - instance.popper.firstChild.classList.add("main-contextMenu-tippyEnter"); - }, - onMount(instance) { - requestAnimationFrame(() => { - instance.popper.firstChild.classList.remove("main-contextMenu-tippyEnter"); - instance.popper.firstChild.classList.add("main-contextMenu-tippyEnterActive"); - }); - }, - onHide(instance) { - requestAnimationFrame(() => { - instance.popper.firstChild.classList.remove("main-contextMenu-tippyEnterActive"); - instance.unmount(); - }); - }, -}, -``` - -#### Usage - -If you want to use this set of props for your own Tippy.js tooltips, you can simply spread the `Spicetify.TippyProps` object into your Tippy.js instance. - -```ts -const element = document.createElement("div"); - -const tooltip = tippy(element, { - ...Spicetify.TippyProps, - content: "Tooltip content", - // For example, if you want to override the delay - delay: [100, 0], -}); -``` diff --git a/docs/development/api-wrapper/types/context-menu/onclick-callback.md b/docs/development/api-wrapper/types/context-menu/onclick-callback.md deleted file mode 100644 index 2cf44f7b..00000000 --- a/docs/development/api-wrapper/types/context-menu/onclick-callback.md +++ /dev/null @@ -1,20 +0,0 @@ ---- -title: OnClickCallback -description: Type definition for callback function when menu item is clicked. ---- - -```ts -type OnClickCallback = ( - uris: string[], - uids?: string[], - contextUri?: string -) => void; -``` - -#### Parameters - -| Parameter | Type | Description | -| :--- | :--- | :--- | -| uris | `string[]` | List of URIs of the selected items. | -| uids | `string[]` | `undefined` | List of UIDs of the selected items. **Note:** Not all context menu items have UIDs. | -| contextUri | `string` | `undefined` | URI of the context menu where the item was called from. This could be a playlist, album, artist, or a track. | diff --git a/docs/development/api-wrapper/types/context-menu/should-add-callback.md b/docs/development/api-wrapper/types/context-menu/should-add-callback.md deleted file mode 100644 index ecea009d..00000000 --- a/docs/development/api-wrapper/types/context-menu/should-add-callback.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: ShouldAddCallback -description: Type definition for callback function to determine if menu item should be added. ---- - -```ts -type ShouldAddCallback = ( - uris: string[], - uids?: string[], - contextUri?: string -) => boolean; -``` - -#### Parameters - -| Parameter | Type | Description | -| :--- | :--- | :--- | -| uris | `string[]` | List of URIs of the selected items. | -| uids | `string[]` | `undefined` | List of UIDs of the selected items. **Note:** Not all context menu items have UIDs. | -| contextUri | `string` | `undefined` | URI of the context menu where the item was called from. This could be a playlist, album, artist, or a track. | - -#### Return value - -`boolean` - Whether the menu item should be added. diff --git a/docs/development/api-wrapper/types/context-option.md b/docs/development/api-wrapper/types/context-option.md deleted file mode 100644 index 7431e647..00000000 --- a/docs/development/api-wrapper/types/context-option.md +++ /dev/null @@ -1,46 +0,0 @@ ---- -title: ContextOption -description: ContextOption type definition. ---- - -:::note - -This type is deducted from Spotify's internal usage. It may not be accurate and may change in the future. - -::: - -```ts -type ContextOption = { - contextURI?: string; - index?: number; - trackUri?: string; - page?: number; - trackUid?: string; - sortedBy?: string; - filteredBy?: string; - shuffleContext?: boolean; - repeatContext?: boolean; - repeatTrack?: boolean; - offset?: number; - next_page_url?: string; - restrictions?: Record; - referrer?: string; -}; -``` - -| Property | Type | Description | -| --- | --- | --- | -| `contextURI` | `string` | `undefined` | Context URI. | -| `index` | `number` | `undefined` | Track index. | -| `trackUri` | `string` | `undefined` | Track URI. | -| `page` | `number` | `undefined` | Page number. | -| `trackUid` | `string` | `undefined` | Track UID. | -| `sortedBy` | `string` | `undefined` | Sorted by timestamp. | -| `filteredBy` | `string` | `undefined` | Filtered by timestamp. | -| `shuffleContext` | `boolean` | `undefined` | Shuffle context URI. | -| `repeatContext` | `boolean` | `undefined` | Repeat context URI. | -| `repeatTrack` | `boolean` | `undefined` | Repeat track URI. | -| `offset` | `number` | `undefined` | Offset. | -| `next_page_url` | `string` | `undefined` | Next page URL. | -| `restrictions` | `Record` | `undefined` | Restrictions. | -| `referrer` | `string` | `undefined` | Referrer. | diff --git a/docs/development/api-wrapper/types/context-track.md b/docs/development/api-wrapper/types/context-track.md deleted file mode 100644 index 63f6fb39..00000000 --- a/docs/development/api-wrapper/types/context-track.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: ContextTrack -description: ContextTrack type definition. ---- - -:::note - -This type is deducted from Spotify's internal usage. It may not be accurate and may change in the future. - -::: - -```ts -type ContextTrack = { - uri: string; - uid?: string | null; - metadata?: Metadata; -} -``` - -| Property | Type | Description | -| --- | --- | --- | -| `uri` | `string` | Track URI. | -| `uid` | `string` | `undefined` | `null` | Track UID. | -| `metadata` | [`Metadata`](/docs/development/api-wrapper/types/metadata) | `undefined` | Track metadata. | diff --git a/docs/development/api-wrapper/types/cosmos-async/body.md b/docs/development/api-wrapper/types/cosmos-async/body.md deleted file mode 100644 index 87461f0b..00000000 --- a/docs/development/api-wrapper/types/cosmos-async/body.md +++ /dev/null @@ -1,10 +0,0 @@ ---- -title: Body -description: CosmosAsync Body type definition. ---- - -Parsed JSON response body. - -```ts -type Body = Record; -``` diff --git a/docs/development/api-wrapper/types/cosmos-async/error.md b/docs/development/api-wrapper/types/cosmos-async/error.md deleted file mode 100644 index 0b65708e..00000000 --- a/docs/development/api-wrapper/types/cosmos-async/error.md +++ /dev/null @@ -1,26 +0,0 @@ ---- -title: Error -description: CosmosAsync Error type definition. ---- - -:::note - -This type is deducted from Spotify's internal usage. It may not be accurate and may change in the future. - -::: - -```ts -interface Error { - code: number; - error: string; - message: string; - stack?: string; -}; -``` - -| Property | Type | Description | -| --- | --- | --- | -| `code` | `number` | HTML error code. | -| `error` | `string` | HTML error name. | -| `message` | `string` | Error message. | -| `stack` | `string` | `undefined` | Error stack. | diff --git a/docs/development/api-wrapper/types/cosmos-async/headers.md b/docs/development/api-wrapper/types/cosmos-async/headers.md deleted file mode 100644 index fd8b1cec..00000000 --- a/docs/development/api-wrapper/types/cosmos-async/headers.md +++ /dev/null @@ -1,10 +0,0 @@ ---- -title: Headers -description: CosmosAsync Headers type definition. ---- - -Equivalent to `XMLHttpRequest`'s `headers` property. - -```ts -type Headers = Record; -``` diff --git a/docs/development/api-wrapper/types/cosmos-async/method.md b/docs/development/api-wrapper/types/cosmos-async/method.md deleted file mode 100644 index 9aac323d..00000000 --- a/docs/development/api-wrapper/types/cosmos-async/method.md +++ /dev/null @@ -1,10 +0,0 @@ ---- -title: Method -description: CosmosAsync Method type definition. ---- - -Equivalent to `XMLHttpRequest`'s `method` property. - -```ts -type Method = "DELETE" | "GET" | "HEAD" | "PATCH" | "POST" | "PUT" | "SUB"; -``` diff --git a/docs/development/api-wrapper/types/cosmos-async/response.md b/docs/development/api-wrapper/types/cosmos-async/response.md deleted file mode 100644 index 1181cb8d..00000000 --- a/docs/development/api-wrapper/types/cosmos-async/response.md +++ /dev/null @@ -1,28 +0,0 @@ ---- -title: Response -description: CosmosAsync Response type definition. ---- - -Represents a response from a CosmosAsync request. - -:::note - -This type is deducted from Spotify's internal usage. It may not be accurate and may change in the future. - -::: - -```ts -interface Response { - body: any; - headers: Headers; - status: number; - uri?: string; -} -``` - -| Property | Type | Description | -| --- | --- | --- | -| `body` | [`Body`](./body.md) | Parsed JSON response body. | -| `headers` | [`Headers`](./headers.md) | Response headers. | -| `status` | `number` | HTTP status code. | -| `uri` | `string` | `undefined` | Request URI. | diff --git a/docs/development/api-wrapper/types/graphql/query.md b/docs/development/api-wrapper/types/graphql/query.md deleted file mode 100644 index 0c09de00..00000000 --- a/docs/development/api-wrapper/types/graphql/query.md +++ /dev/null @@ -1,104 +0,0 @@ ---- -title: Query -description: List of GraphQL definitions used by Spotify. ---- - -:::note - -This type is deducted from Spotify's internal usage. It may not be accurate and may change in the future. - -::: - -```ts -type Query = "decorateItemsForEnhance" | - "imageURLAndSize" | - "imageSources" | - "audioItems" | - "creator" | - "extractedColors" | - "extractedColorsAndImageSources" | - "fetchExtractedColorAndImageForAlbumEntity" | - "fetchExtractedColorAndImageForArtistEntity" | - "fetchExtractedColorAndImageForEpisodeEntity" | - "fetchExtractedColorAndImageForPlaylistEntity" | - "fetchExtractedColorAndImageForPodcastEntity" | - "fetchExtractedColorAndImageForTrackEntity" | - "fetchExtractedColorForAlbumEntity" | - "fetchExtractedColorForArtistEntity" | - "fetchExtractedColorForEpisodeEntity" | - "fetchExtractedColorForPlaylistEntity" | - "fetchExtractedColorForPodcastEntity" | - "fetchExtractedColorForTrackEntity" | - "getAlbumNameAndTracks" | - "getEpisodeName" | - "getTrackName" | - "queryAlbumTrackUris" | - "queryTrackArtists" | - "decorateContextEpisodesOrChapters" | - "decorateContextTracks" | - "fetchTracksForRadioStation" | - "decoratePlaylists" | - "playlistUser" | - "FetchPlaylistMetadata" | - "playlistContentsItemTrackArtist" | - "playlistContentsItemTrackAlbum" | - "playlistContentsItemTrack" | - "playlistContentsItemLocalTrack" | - "playlistContentsItemEpisodeShow" | - "playlistContentsItemEpisode" | - "playlistContentsItemResponse" | - "playlistContentsItem" | - "FetchPlaylistContents" | - "episodeTrailerUri" | - "podcastEpisode" | - "podcastMetadataV2" | - "minimalAudiobook" | - "audiobookChapter" | - "audiobookMetadataV2" | - "fetchExtractedColors" | - "queryFullscreenMode" | - "queryNpvEpisode" | - "queryNpvArtist" | - "albumTrack" | - "getAlbum" | - "queryAlbumTracks" | - "queryArtistOverview" | - "queryArtistAppearsOn" | - "discographyAlbum" | - "albumMetadataReleases" | - "albumMetadata" | - "queryArtistDiscographyAlbums" | - "queryArtistDiscographySingles" | - "queryArtistDiscographyCompilations" | - "queryArtistDiscographyAll" | - "queryArtistDiscographyOverview" | - "artistPlaylist" | - "queryArtistPlaylists" | - "queryArtistDiscoveredOn" | - "queryArtistFeaturing" | - "queryArtistRelated" | - "queryArtistMinimal" | - "searchModalResults" | - "queryWhatsNewFeed" | - "whatsNewFeedNewItems" | - "SetItemsStateInWhatsNewFeed" | - "browseImageURLAndSize" | - "browseImageSources" | - "browseAlbum" | - "browseArtist" | - "browseEpisode" | - "browseChapter" | - "browsePlaylist" | - "browsePodcast" | - "browseAudiobook" | - "browseTrack" | - "browseUser" | - "browseMerch" | - "browseArtistConcerts" | - "browseContent" | - "browseSectionContainer" | - "browseClientFeature" | - "browseItem" | - "browseAll" | - "browsePage"; -``` \ No newline at end of file diff --git a/docs/development/api-wrapper/types/keyboard/keysdefine.md b/docs/development/api-wrapper/types/keyboard/keysdefine.md deleted file mode 100644 index abef33c9..00000000 --- a/docs/development/api-wrapper/types/keyboard/keysdefine.md +++ /dev/null @@ -1,28 +0,0 @@ ---- -title: KeysDefine -description: Keyboard KeyDefine type definition. ---- - -```ts -type KeysDefine = string | { - key: string; - ctrl?: boolean; - shift?: boolean; - alt?: boolean; - meta?: boolean; -}; -``` - -`KeysDefine` is a type that defines a keyboard shortcut. It can be a string or an object. - -In the string format, it should be a list of keys separated by `+`. For example, `ctrl+shift+p` is a valid shortcut. - -In the object format, it should be an object with the following properties: - -| Property | Type | Description | -| --- | --- | --- | -| `key` | `string` | Key name. Refer to [this table](/docs/development/api-wrapper/types/keyboard/validkey) for a list of valid keys. | -| `ctrl` | `boolean` | `undefined` | Whether to require `CTRL` key. | -| `shift` | `boolean` | `undefined` | Whether to require `SHIFT` key. | -| `alt` | `boolean` | `undefined` | Whether to require `ALT` key. | -| `meta` | `boolean` | `undefined` | Whether to require the meta key. This could be the 🪟 key on Windows or the key on Mac. | diff --git a/docs/development/api-wrapper/types/keyboard/validkey.md b/docs/development/api-wrapper/types/keyboard/validkey.md deleted file mode 100644 index b607758a..00000000 --- a/docs/development/api-wrapper/types/keyboard/validkey.md +++ /dev/null @@ -1,229 +0,0 @@ ---- -title: ValidKey -description: A list of valid keys for keyboard shortcuts. ---- - -:::tip - -For the list of valid keys, refer to the [type definition](#type-definition). - -::: - -### Key/value pairs - -|Key|Value| -|---|---| -|BACKSPACE|backspace| -|TAB|tab| -|ENTER|enter| -|SHIFT|shift| -|CTRL|ctrl| -|ALT|alt| -|CAPS|capslock| -|ESCAPE|esc| -|SPACE|space| -|PAGE_UP|pageup| -|PAGE_DOWN|pagedown| -|END|end| -|HOME|home| -|ARROW_LEFT|left| -|ARROW_UP|up| -|ARROW_RIGHT|right| -|ARROW_DOWN|down| -|INSERT|ins| -|DELETE|del| -|A|a| -|B|b| -|C|c| -|D|d| -|E|e| -|F|f| -|G|g| -|H|h| -|I|i| -|J|j| -|K|k| -|L|l| -|M|m| -|N|n| -|O|o| -|P|p| -|Q|q| -|R|r| -|S|s| -|T|t| -|U|u| -|V|v| -|W|w| -|X|x| -|Y|y| -|Z|z| -|WINDOW_LEFT|meta| -|WINDOW_RIGHT|meta| -|SELECT|meta| -|NUMPAD_0|0| -|NUMPAD_1|1| -|NUMPAD_2|2| -|NUMPAD_3|3| -|NUMPAD_4|4| -|NUMPAD_5|5| -|NUMPAD_6|6| -|NUMPAD_7|7| -|NUMPAD_8|8| -|NUMPAD_9|9| -|MULTIPLY|*| -|ADD|+| -|SUBTRACT|-| -|DECIMAL_POINT|.| -|DIVIDE|/| -|F1|f1| -|F2|f2| -|F3|f3| -|F4|f4| -|F5|f5| -|F6|f6| -|F7|f7| -|F8|f8| -|F9|f9| -|F10|f10| -|F11|f11| -|F12|f12| -|;|;| -|=|=| -|,|,| -|-|-| -|.|.| -|/|/| -|`|`| -|[|[| -|\\|\\| -|]|]| -|"|'| -|~|`| -|!|1| -|@|2| -|#|3| -|$|4| -|%|5| -|^|6| -|&|7| -|*|8| -|(|9| -|)|0| -|_|-| -|+|=| -|:|;| -|\<|,| -|\>|.| -|?|/| -|||\\| - -### Type definition - -```ts -type ValidKey = "BACKSPACE" | - "TAB" | - "ENTER" | - "SHIFT" | - "CTRL" | - "ALT" | - "CAPS" | - "ESCAPE" | - "SPACE" | - "PAGE_UP" | - "PAGE_DOWN" | - "END" | - "HOME" | - "ARROW_LEFT" | - "ARROW_UP" | - "ARROW_RIGHT" | - "ARROW_DOWN" | - "INSERT" | - "DELETE" | - "A" | - "B" | - "C" | - "D" | - "E" | - "F" | - "G" | - "H" | - "I" | - "J" | - "K" | - "L" | - "M" | - "N" | - "O" | - "P" | - "Q" | - "R" | - "S" | - "T" | - "U" | - "V" | - "W" | - "X" | - "Y" | - "Z" | - "WINDOW_LEFT" | - "WINDOW_RIGHT" | - "SELECT" | - "NUMPAD_0" | - "NUMPAD_1" | - "NUMPAD_2" | - "NUMPAD_3" | - "NUMPAD_4" | - "NUMPAD_5" | - "NUMPAD_6" | - "NUMPAD_7" | - "NUMPAD_8" | - "NUMPAD_9" | - "MULTIPLY" | - "ADD" | - "SUBTRACT" | - "DECIMAL_POINT" | - "DIVIDE" | - "F1" | - "F2" | - "F3" | - "F4" | - "F5" | - "F6" | - "F7" | - "F8" | - "F9" | - "F10" | - "F11" | - "F12" | - ";" | - "=" | - " | - " | - "-" | - "." | - "/" | - "`" | - "[" | - "\\" | - "]" | - "\"" | - "~" | - "!" | - "@" | - "#" | - "$" | - "%" | - "^" | - "&" | - "*" | - "(" | - ")" | - "_" | - "+" | - ":" | - "<" | - ">" | - "?" | - "|"; -``` diff --git a/docs/development/api-wrapper/types/metadata.md b/docs/development/api-wrapper/types/metadata.md deleted file mode 100644 index f317a6b6..00000000 --- a/docs/development/api-wrapper/types/metadata.md +++ /dev/null @@ -1,66 +0,0 @@ ---- -title: Metadata -description: Type of metadata object. ---- - -:::note - -This type is deducted from Spotify's internal usage. It may not be accurate and may change in the future. - -::: - -```ts -type Metadata = Partial>; -``` - -An example of metadata object, **not** a type definition, may not include all properties. - -```ts -type Metadata = { - actions.skipping_next_past_track: string; - actions.skipping_prev_past_track: string; - added_at: `${bigint}`; - album_artist_name: string; - album_disc_count: `${number}`; - album_disc_number: `${number}`; - album_title: string; - album_track_count: `${number}`; - album_track_number: `${number}`; - album_uri: string; - artist_name: string; - artist_uri: string; - // URL - canvas.artist.avatar: string; - canvas.artist.name: string; - canvas.artist.uri: string; - canvas.canvasUri: string; - canvas.entityUri: string; - canvas.explicit: "true" | "false"; - canvas.fileId: string; - canvas.id: string; - canvas.type: string; - canvas.uploadedBy: string; - // URL - canvas.url: string; - collection.can_add: "true" | "false"; - collection.can_ban: "true" | "false"; - collection.in_collection: "true" | "false"; - collection.is_banned: "true" | "false"; - context_uri: string; - duration: `${bigint}`; - entity_uri: string; - has_lyrics: "true" | "false"; - // Internal URL paths, not URLs - image_large_url: string; - image_small_url: string; - image_url: string; - image_xlarge_url: string; - interaction_id: string; - iteration: `${number}`; - marked_for_download: "true" | "false"; - page_instance_id: string; - popularity: `${number}`; - title: string; - track_player: string; -} -``` diff --git a/docs/development/api-wrapper/types/panel/panel-props.md b/docs/development/api-wrapper/types/panel/panel-props.md deleted file mode 100644 index 697610b3..00000000 --- a/docs/development/api-wrapper/types/panel/panel-props.md +++ /dev/null @@ -1,38 +0,0 @@ ---- -title: PanelProps -description: Properties that are used by the `registerPanel` function. ---- - -```ts -type PanelProps = { - label?: string; - children: React.ReactNode; - isCustom?: boolean; - style?: React.CSSProperties; - wrapperClassname?: string; - headerClassname?: string; - headerVariant?: Variant; - headerSemanticColor?: SemanticColor; - headerLink?: string; - headerActions?: React.ReactNode; - headerOnClose?: () => void; - headerPreventDefaultClose?: boolean; - headerOnBack?: (event: React.MouseEvent) => void; -}; -``` - -| Property | Type | Description | -| --- | --- | --- | -| `label` | `string` | `undefined` | Label of the Panel. | -| `children` | `React.ReactNode` | Children to render inside the Panel.
Must be a React Component. | -| `isCustom` | `boolean` | `undefined` | Determine if the children passed is a custom Panel.
If true, the children will be rendered as is.
**Note**: All passed props except `children` will be ignored if enabled. | -| `style` | `React.CSSProperties` | `undefined` | Inline styles to apply to the Panel skeleton. | -| `wrapperClassname` | `string` | `undefined` | Additional class name to apply to the Panel content wrapper. | -| `headerClassname` | `string` | `undefined` | Additional class name to apply to the Panel header. | -| `headerVariant` | [`Variant`](/docs/development/api-wrapper/types/variant) | `undefined` | Font variant for the Panel header title. | -| `headerSemanticColor` | [`SemanticColor`](/docs/development/api-wrapper/types/semantic-color) | `undefined` | Semantic color name for the Panel header title. | -| `headerLink` | `string` | `undefined` | Href for the header link.
Can be either a URI, a path within the app, or a URL for an external link. | -| `headerActions` | `React.ReactNode` | `undefined` | Additional actions to render in the header.
Will be rendered next to the close button. | -| `headerOnClose` | `() => void` | `undefined` | Function to call when clicking on the header close button.
Called before the panel is closed. | -| `headerPreventDefaultClose` | `boolean` | `undefined` | Prevent the panel from closing when clicking on the header close button. | -| `headerOnBack` | `(event: React.MouseEvent) => void` | `undefined` | Function to call when clicking on the header back button.
If not provided, the back button will not be rendered. | \ No newline at end of file diff --git a/docs/development/api-wrapper/types/player-state.md b/docs/development/api-wrapper/types/player-state.md deleted file mode 100644 index 9734d3cb..00000000 --- a/docs/development/api-wrapper/types/player-state.md +++ /dev/null @@ -1,92 +0,0 @@ ---- -title: PlayerState -description: PlayerState type definition. ---- - -:::note - -This type is deducted from Spotify's internal usage. It may not be accurate and may change in the future. - -::: - -```ts -type PlayerState = { - timestamp: number; - context_uri: string; - context_url: string; - context_restrictions: Record; - index?: { - page: number; - track: number; - }; - track?: ProvidedTrack; - playback_id?: string; - playback_quality?: { - bitrate_level: string; - hifi_status?: string; - strategy?: string; - target_bitrate_available?: boolean; - target_bitrate_level?: string; - } - playback_speed?: number; - position_as_of_timestamp: number; - duration: number; - is_playing: boolean; - is_paused: boolean; - is_buffering: boolean; - play_origin: { - feature_identifier: string; - feature_version: string; - view_uri?: string; - external_referrer?: string; - referrer_identifier?: string; - device_identifier?: string; - }; - options: { - shuffling_context?: boolean; - repeating_context?: boolean; - repeating_track?: boolean; - }; - restrictions: Record; - suppressions: { - providers: string[]; - }; - debug?: { - log: string[]; - }; - prev_tracks?: ProvidedTrack[]; - next_tracks?: ProvidedTrack[]; - context_metadata: Metadata; - page_metadata: Metadata; - session_id: string; - queue_revision?: string; -}; -``` - -| Property | Type | Description | -| --- | --- | --- | -| `timestamp` | `number` | Timestamp. | -| `context_uri` | `string` | Context URI from which the track was played. | -| `context_url` | `string` | Context internal URL. | -| `context_restrictions` | `Record` | Context restrictions. | -| `index` | `object` | `undefined` | Track index. | -| `track` | [`ProvidedTrack`](/docs/development/api-wrapper/types/provided-track) | `undefined` | Current track. | -| `playback_id` | `string` | `undefined` | Playback ID. | -| `playback_quality` | `object` | `undefined` | Playback quality. | -| `playback_speed` | `number` | `undefined` | Playback speed. | -| `position_as_of_timestamp` | `number` | Position as of timestamp. Relative to the track's start. | -| `duration` | `number` | Track duration. | -| `is_playing` | `boolean` | Whether the track is playing. | -| `is_paused` | `boolean` | Whether the track is paused. | -| `is_buffering` | `boolean` | Whether the track is buffering. | -| `play_origin` | `object` | Play origin (client info). | -| `options` | `object` | Repeat and shuffle state. | -| `restrictions` | `Record` | Restrictions. | -| `suppressions` | `object` | Suppressions from providers. | -| `debug` | `object` | `undefined` | Debug info. | -| `prev_tracks` | [`ProvidedTrack[]`](/docs/development/api-wrapper/types/provided-track) | `undefined` | Previous tracks. | -| `next_tracks` | [`ProvidedTrack[]`](/docs/development/api-wrapper/types/provided-track) | `undefined` | Next tracks. | -| `context_metadata` | [`Metadata`](/docs/development/api-wrapper/types/metadata) | Context metadata. | -| `page_metadata` | [`Metadata`](/docs/development/api-wrapper/types/metadata) | Page metadata. | -| `session_id` | `string` | Session ID. | -| `queue_revision` | `string` | `undefined` | Queue revision. | diff --git a/docs/development/api-wrapper/types/provided-track.md b/docs/development/api-wrapper/types/provided-track.md deleted file mode 100644 index 78604811..00000000 --- a/docs/development/api-wrapper/types/provided-track.md +++ /dev/null @@ -1,26 +0,0 @@ ---- -title: ProvidedTrack -description: ProvidedTrack type definition. ---- - -:::note - -This type is deducted from Spotify's internal usage. It may not be accurate and may change in the future. - -::: - -```ts -type ProvidedTrack = ContextTrack & { - removed?: string[]; - blocked?: string[]; - provider?: string; -}; -``` - -Extends [`ContextTrack`](/docs/development/api-wrapper/types/context-track). - -| Property | Type | Description | -| --- | --- | --- | -| `removed` | `string[]` | `undefined` | List of removed providers. | -| `blocked` | `string[]` | `undefined` | List of blocked providers. | -| `provider` | `string` | Current provider, eg. from `context` | diff --git a/docs/development/api-wrapper/types/react-component/confirm-dialog-props.md b/docs/development/api-wrapper/types/react-component/confirm-dialog-props.md deleted file mode 100644 index a7c2fb5e..00000000 --- a/docs/development/api-wrapper/types/react-component/confirm-dialog-props.md +++ /dev/null @@ -1,42 +0,0 @@ ---- -title: ConfirmDialogProps -description: Type definition for props of ReactComponent.ConfirmDialog ---- - -:::note - -This type is deducted from Spotify's internal usage. It may not be accurate and may change in the future. - -::: - -The `ConfirmDialogProps` object is used to create a confirm dialog. - -```ts -type ConfirmDialogProps = { - isOpen?: boolean; - allowHTML?: boolean; - titleText: string; - descriptionText?: string; - confirmText?: string; - cancelText?: string; - confirmLabel?: string; - onConfirm?: (event: React.MouseEvent) => void; - onClose?: (event: React.MouseEvent) => void; - onOutside?: (event: React.MouseEvent) => void; -}; -``` - -#### Properties - -| Property | Type | Description | -| :--- | :--- | :--- | -| `isOpen` | `boolean` | `undefined` | Boolean to determine if the dialog should be opened. Defaults to `true` | -| `allowHTML` | `boolean` | `undefined` | Whether to allow inline HTML in component text. Defaults to `false` | -| `titleText` | `string` | Dialog title. Can be inline HTML if `allowHTML` is true | -| `descriptionText` | `string` | `undefined` | Dialog description. Can be inline HTML if `allowHTML` is true | -| `confirmText` | `string` | `undefined` | Confirm button text | -| `cancelText` | `string` | `undefined` | Cancel button text | -| `confirmLabel` | `string` | `undefined` | Confirm button `aria-label` | -| `onConfirm` | `(event: React.MouseEvent) => void` | `undefined` | Function to run when confirm button is clicked.
The dialog does not close automatically, a handler must be included. | -| `onClose` | `(event: React.MouseEvent) => void` | `undefined` | Function to run when cancel button is clicked.
The dialog does not close automatically, a handler must be included. | -| `onOutside` | `(event: React.MouseEvent) => void` | `undefined` | Function to run when dialog is clicked outside of.
By default, this will run `onClose`.
A handler must be included to close the dialog. | diff --git a/docs/development/api-wrapper/types/react-component/context-menu-props.md b/docs/development/api-wrapper/types/react-component/context-menu-props.md deleted file mode 100644 index e489775c..00000000 --- a/docs/development/api-wrapper/types/react-component/context-menu-props.md +++ /dev/null @@ -1,37 +0,0 @@ ---- -title: ContextMenuProps -description: Type definition for props of ReactComponent.ContextMenu ---- - -:::note - -This type is deducted from Spotify's internal usage. It may not be accurate and may change in the future. - -::: - -The `ContextMenuProps` object is used to create a context menu. - -```ts -type ContextMenuProps = { - renderInline?: boolean; - trigger?: 'click' | 'right-click'; - action?: 'toggle' | 'open'; - placement?: 'top' | 'top-start' | 'top-end' | 'right' | 'right-start' | 'right-end' | 'bottom' | 'bottom-start' | 'bottom-end' | 'left' | 'left-start' | 'left-end'; - offset?: [number, number]; - preventScrollingWhileOpen?: boolean; - menu: typeof Spicetify.ReactComponent.Menu; - children: Element | ((isOpen?: boolean, handleContextMenu?: (e: MouseEvent) => void, ref?: (e: Element) => void) => Element); -}; -``` - -#### Properties - -| Property | Type | Description | -| :--- | :--- | :--- | -| `renderInline` | `boolean` | Decide whether to use the global singleton context menu (rendered in ``) or a new inline context menu (rendered in a sibling element to `children`) | -| `trigger` | `'click'` | `'right-click'` | Determines what will trigger the context menu. For example, a click, or a right-click | -| `action` | `'toggle'` | `'open'` | Determines if the context menu should open or toggle when triggered | -| `placement` | `'top'` | `'top-start'` | `'top-end'` | `'right'` | `'right-start'` | `'right-end'` | `'bottom'` | `'bottom-start'` | `'bottom-end'` | `'left'` | `'left-start'` | `'left-end'` | The preferred placement of the context menu when it opens. Relative to trigger element. | -| `offset` | `[number, number]` | The x and y offset distances at which the context menu should open. Relative to trigger element and `position`. | -| `preventScrollingWhileOpen` | `boolean` | Will stop the client from scrolling while the context menu is open | -| `menu` | `typeof Spicetify.ReactComponent.Menu` | The menu UI to render inside of the context menu. | diff --git a/docs/development/api-wrapper/types/react-component/icon-component-props.md b/docs/development/api-wrapper/types/react-component/icon-component-props.md deleted file mode 100644 index 798ae4ec..00000000 --- a/docs/development/api-wrapper/types/react-component/icon-component-props.md +++ /dev/null @@ -1,38 +0,0 @@ ---- -title: IconComponentProps -description: Type definition for props of ReactComponent.IconComponent. ---- - -:::note - -This type is deducted from Spotify's internal usage. It may not be accurate and may change in the future. - -::: - -The `IconComponentProps` object is used to create an icon component. - -```ts -type IconComponentProps = { - iconSize?: number; - color?: string; - semanticColor?: SemantiColor; - title?: string; - titleId?: string; - desc?: string; - descId?: string; - autoMirror?: boolean; -}; -``` - -#### Properties - -| Property | Type | Description | -| :--- | :--- | :--- | -| iconSize | `number` | `undefined` | Icon size | -| color | `string` | `undefined` | Icon color. Might not be used by component | -| semanticColor | [`SemanticColor`](../semantic-color) | `undefined` | Semantic color name. Matches color variables used in xpui | -| title | `string` | `undefined` | Icon title | -| titleId | `string` | `undefined` | Title ID (internal) | -| desc | `string` | `undefined` | Icon description | -| descId | `string` | `undefined` | Description ID (internal) | -| autoMirror | `boolean` | `undefined` | Whether the icon can be auto mirrored | diff --git a/docs/development/api-wrapper/types/react-component/menu-item-props.md b/docs/development/api-wrapper/types/react-component/menu-item-props.md deleted file mode 100644 index b408005b..00000000 --- a/docs/development/api-wrapper/types/react-component/menu-item-props.md +++ /dev/null @@ -1,34 +0,0 @@ ---- -title: MenuItemProps -description: Type definition for props of ReactComponent.MenuItem. ---- - -:::note - -This type is deducted from Spotify's internal usage. It may not be accurate and may change in the future. - -::: - -The `MenuItemProps` object is used to create a menu item. - -```ts -type MenuItemProps = { - onClick?: React.MouseEventHandler; - disabled?: boolean; - divider?: 'before' | 'after' | 'both'; - icon?: React.ReactNode; - leadingIcon?: React.ReactNode; - trailingIcon?: React.ReactNode; -}; -``` - -#### Properties - -| Property | Type | Description | -| :--- | :--- | :--- | -| `onClick` | `React.MouseEventHandler` | `undefined` | Function that runs when `MenuItem` is clicked | -| `disabled` | `boolean` | `undefined` | Indicates if `MenuItem` is disabled. Disabled items will not cause the `Menu` to close when clicked. | -| `divider` | `'before' | 'after' | 'both'` | `undefined` | Indicate that a divider line should be added `before` or `after` this `MenuItem` | -| `icon` | `React.ReactNode` | `undefined` | React component icon that will be rendered at the end of the `MenuItem`. **Deprecated**: Since Spotify `1.2.8`. Use `leadingIcon` or `trailingIcon` instead | -| `leadingIcon` | `React.ReactNode` | `undefined` | React component icon that will be rendered at the start of the `MenuItem`. **Since Spotify `1.2.8`** | -| `trailingIcon` | `React.ReactNode` | `undefined` | React component icon that will be rendered at the start of the `MenuItem`. **Since Spotify `1.2.8`** | diff --git a/docs/development/api-wrapper/types/react-component/menu-props.md b/docs/development/api-wrapper/types/react-component/menu-props.md deleted file mode 100644 index 6d470128..00000000 --- a/docs/development/api-wrapper/types/react-component/menu-props.md +++ /dev/null @@ -1,26 +0,0 @@ ---- -title: MenuProps -description: Type definition for props of ReactComponent.Menu. ---- - -:::note - -This type is deducted from Spotify's internal usage. It may not be accurate and may change in the future. - -::: - -The `MenuProps` object is used to create a menu. - -```ts -type MenuProps = { - onClose?: () => void; - getInitialFocusElement?: (el: HTMLElement | null) => HTMLElement | undefined | null; -}; -``` - -#### Properties - -| Property | Type | Description | -| :--- | :--- | :--- | -| onClose | `() => void` | `undefined` | Function that is called when the menu is closed | -| getInitialFocusElement | `(el: HTMLElement | null) => HTMLElement | undefined | null` | `undefined` | Function that provides the element that focus should jump to when the menu is opened | diff --git a/docs/development/api-wrapper/types/react-component/panel-content-props.md b/docs/development/api-wrapper/types/react-component/panel-content-props.md deleted file mode 100644 index c36acfec..00000000 --- a/docs/development/api-wrapper/types/react-component/panel-content-props.md +++ /dev/null @@ -1,26 +0,0 @@ ---- -title: PanelContentProps -description: Type definition for props of ReactComponent.PanelContent. ---- - -The `PanelContentProps` object is used to render a panel content wrapper. - -:::note - -This type is deducted from Spotify's internal usage. It may not be accurate and may change in the future. - -::: - -```ts -type PanelContentProps = { - className?: string; - children?: React.ReactNode; -}; -``` - -#### Properties - -| Property | Type | Description | -| :--- | :--- | :--- | -| className | `string` | `undefined` | Additional class name to apply to the panel. | -| children | `React.ReactNode` | `undefined` | Children to render inside the panel. | \ No newline at end of file diff --git a/docs/development/api-wrapper/types/react-component/panel-header-props.md b/docs/development/api-wrapper/types/react-component/panel-header-props.md deleted file mode 100644 index ca2cdb20..00000000 --- a/docs/development/api-wrapper/types/react-component/panel-header-props.md +++ /dev/null @@ -1,42 +0,0 @@ ---- -title: PanelHeaderProps -description: Type definition for props of ReactComponent.PanelHeader. ---- - -The `PanelHeaderProps` object is used to render a panel header. - -:::note - -This type is deducted from Spotify's internal usage. It may not be accurate and may change in the future. - -::: - -```ts -type PanelHeaderProps = { - link?: string; - title?: string; - panel: number; - isAdvert?: boolean; - actions?: React.ReactNode; - onClose?: () => void; - preventDefaultClose?: boolean; - onBack?: (event: React.MouseEvent) => void; - titleVariant?: Variant; - titleSemanticColor?: SemanticColor; -}; -``` - -#### Properties - -| Property | Type | Description | -| :--- | :--- | :--- | -| link | `string` | `undefined` | Href for the header link.
Can be either a URI, a path within the app, or a URL for an external link. | -| title | `string` | `undefined` | Title of the header. | -| panel | `number` | Panel ID. Used to toggle panel open/closed state. | -| isAdvert | `boolean` | `undefined` | Whether or not the panel contains advertisements. Defaults to `false` | -| actions | `React.ReactNode` | `undefined` | Actions to render in the header. | -| onClose | `() => void` | `undefined` | Function to call when clicking on the close button.
Called before the panel is closed. | -| preventDefaultClose | `boolean` | `undefined` | Prevent the panel from closing when clicking on the header close button. Defaults to `false` | -| onBack | `(event: React.MouseEvent) => void` | `undefined` | Function to call when clicking on the header back button.
If not provided, the back button will not be rendered. | -| titleVariant | [`Variant`](/docs/development/api-wrapper/types/variant) | `undefined` | Font variant for the header title. Defaults to `"balladBold"` | -| titleSemanticColor | [`SemanticColor`](/docs/development/api-wrapper/types/semantic-color) | `undefined` | Semantic color name for the header title. Defaults to `"textBase"` | \ No newline at end of file diff --git a/docs/development/api-wrapper/types/react-component/panel-skeleton-props.md b/docs/development/api-wrapper/types/react-component/panel-skeleton-props.md deleted file mode 100644 index 19ca30cc..00000000 --- a/docs/development/api-wrapper/types/react-component/panel-skeleton-props.md +++ /dev/null @@ -1,32 +0,0 @@ ---- -title: PanelSkeletonProps -description: Type definition for props of ReactComponent.PanelSkeleton. ---- - -The `PanelSkeletonProps` object is used to render a panel skeleton. - -:::note - -This type is deducted from Spotify's internal usage. It may not be accurate and may change in the future. - -::: - -```ts -type PanelSkeletonProps = { - label?: string; - itemUri?: string; - className?: string; - style?: React.CSSProperties; - children?: React.ReactNode; -}; -``` - -#### Properties - -| Property | Type | Description | -| :--- | :--- | :--- | -| label | `string` | `undefined` | Aria label for the panel. Does not set the panel header content. | -| itemUri | `string` | `undefined` | Item URI of the panel. Used as reference for Spotify's internal Event Factory. | -| className | `string` | `undefined` | Additional class name to apply to the panel. **Deprecated**: Since Spotify `1.2.12` | -| style | `React.CSSProperties` | `undefined` | Additional styles to apply to the panel. | -| children | `React.ReactNode` | `undefined` | Children to render inside the panel. | \ No newline at end of file diff --git a/docs/development/api-wrapper/types/react-component/slider-props.md b/docs/development/api-wrapper/types/react-component/slider-props.md deleted file mode 100644 index 1c040fdc..00000000 --- a/docs/development/api-wrapper/types/react-component/slider-props.md +++ /dev/null @@ -1,44 +0,0 @@ ---- -title: SliderProps -description: Type definition for props of ReactComponent.PanelSkeleton. ---- - -The `SliderProps` object is used to render a slider. - -:::note - -This type is deducted from Spotify's internal usage. It may not be accurate and may change in the future. - -::: - -```ts -type SliderProps = { - value: number; - max: number; - step: number; - labelText?: string; - isInteractive?: boolean; - forceActiveStyles?: boolean; - onDragStart: (value: number) => void; - onDragMove: (value: number) => void; - onDragEnd: (value: number) => void; - onStepForward?: (value: number) => void; - onStepBackward?: (value: number) => void; -} -``` - -#### Properties - -| Property | Type | Description | -| :--- | :--- | :--- | -| value | `number` | The current value of the slider. | -| max | `number` | The maximum value the slider can have. | -| step | `number` | The increment/decrement value when the slider is moved. | -| labelText | `string` | `undefined` | The label text displayed for the slider. | -| isInteractive | `boolean` | `undefined` | Determines if the slider is interactive. | -| forceActiveStyles | `boolean` | `undefined` | Forces the active styles regardless of interaction state. | -| onDragStart | `(value: number) => void` | Callback function when dragging starts. | -| onDragMove | `(value: number) => void` | Callback function when the slider is being dragged. | -| onDragEnd | `(value: number) => void` | Callback function when dragging ends. | -| onStepForward | `(value: number) => void` | `undefined` | Callback function when the slider steps forward. **Deprecated.** | -| onStepBackward | `(value: number) => void` | `undefined` | Callback function when the slider steps backward. **Deprecated.** | diff --git a/docs/development/api-wrapper/types/react-component/text-component-props.md b/docs/development/api-wrapper/types/react-component/text-component-props.md deleted file mode 100644 index 547f1665..00000000 --- a/docs/development/api-wrapper/types/react-component/text-component-props.md +++ /dev/null @@ -1,32 +0,0 @@ ---- -title: TextComponentProps -description: Type definition for props of ReactComponent.TextComponent. ---- - -:::note - -This type is deducted from Spotify's internal usage. It may not be accurate and may change in the future. - -::: - -The `TextComponentProps` object is used to create a text component. - -```ts -type TextComponentProps = { - color?: string; - semanticColor?: SemanticColor; - variant?: Variant; - paddingBottom?: string; - weight?: 'book' | 'bold' | 'black'; -}; -``` - -#### Properties - -| Property | Type | Description | -| -------- | ---- | ----------- | -| color | `string` | `undefined` | Text color. Might not be used by component. | -| semanticColor | [`SemanticColor`](../semantic-color) | `undefined` | Semantic color name. Matches color variables used in xpui | -| variant | [`Variant`](../variant) | `undefined` | Font variant | -| paddingBottom | `string` | `undefined` | Bottom padding size | -| weight | `'book'` | `'bold'` | `'black'` | `undefined` | Font weight | diff --git a/docs/development/api-wrapper/types/react-component/toggle-props.md b/docs/development/api-wrapper/types/react-component/toggle-props.md deleted file mode 100644 index 97276b40..00000000 --- a/docs/development/api-wrapper/types/react-component/toggle-props.md +++ /dev/null @@ -1,32 +0,0 @@ ---- -title: ToggleProps -description: Type definition for props of ReactComponent.Toggle. ---- - -The `ToggleProps` object is used to render a toggle. - -:::note - -This type is deducted from Spotify's internal usage. It may not be accurate and may change in the future. - -::: - -```ts -type ToggleProps = { - value: boolean; - disabled?: boolean; - onSelected: (value: boolean) => void; - id?: string; - className?: string; -} -``` - -#### Properties - -| Property | Type | Description | -| :--- | :--- | :--- | -| value | `boolean` | The current state of the toggle. `true` means it's on, `false` means it's off. | -| disabled | `boolean` | `undefined` | Determines if the toggle is disabled. If `true`, the toggle is not interactive. | -| onSelected | `(value: boolean) => void` | Callback function that is called when the toggle is clicked. The function receives the new state of the toggle. | -| id | `string` | `undefined` | The ID for the toggle, useful for associating with a label for accessibility. | -| className | `string` | `undefined` | Additional CSS class name to apply to the toggle. | diff --git a/docs/development/api-wrapper/types/react-component/tooltip-props.md b/docs/development/api-wrapper/types/react-component/tooltip-props.md deleted file mode 100644 index 8e18bab0..00000000 --- a/docs/development/api-wrapper/types/react-component/tooltip-props.md +++ /dev/null @@ -1,36 +0,0 @@ ---- -title: TooltipProps -description: Type definition for props of ReactComponent.TooltipWrapper. ---- - -:::note - -This type is deducted from Spotify's internal usage. It may not be accurate and may change in the future. - -::: - -The `TooltipProps` object is used to create a tooltip. - -```ts -type TooltipProps = { - label: string; - children: React.ReactNode; - renderInline?: boolean; - showDelay?: number; - disabled?: boolean; - placement?: 'top' | 'top-start' | 'top-end' | 'right' | 'right-start' | 'right-end' | 'bottom' | 'bottom-start' | 'bottom-end' | 'left' | 'left-start' | 'left-end'; - labelClassName?: string; -}; -``` - -#### Properties - -| Property | Type | Description | -| :--- | :--- | :--- | -| `label` | `string` | Label to display in the tooltip | -| `children` | `React.ReactNode` | The child element that the tooltip will be attached to and will display when hovered over | -| `renderInline` | `boolean` | Decide whether to use the global singleton tooltip (rendered in ``) or a new inline tooltip (rendered in a sibling element to `children`) | -| `showDelay` | `number` | Delay in milliseconds before the tooltip is displayed after the user hovers over the child element | -| `disabled` | `boolean` | Determine whether the tooltip should be displayed | -| `placement` | `'top'` | `'top-start'` | `'top-end'` | `'right'` | `'right-start'` | `'right-end'` | `'bottom'` | `'bottom-start'` | `'bottom-end'` | `'left'` | `'left-start'` | `'left-end'` | The preferred placement of the context menu when it opens. Relative to trigger element. | -| `labelClassName` | `string` | Class name to apply to the tooltip | diff --git a/docs/development/api-wrapper/types/semantic-color.md b/docs/development/api-wrapper/types/semantic-color.md deleted file mode 100644 index a1921367..00000000 --- a/docs/development/api-wrapper/types/semantic-color.md +++ /dev/null @@ -1,41 +0,0 @@ ---- -title: SemanticColor -description: Semantic color names used in the Spotify app. ---- - -:::note - -This type is deducted from Spotify's internal usage. It may not be accurate and may change in the future. - -::: - -```ts -type SemanticColor = "textBase" | - "textSubdued" | - "textBrightAccent" | - "textNegative" | - "textWarning" | - "textPositive" | - "textAnnouncement" | - "essentialBase" | - "essentialSubdued" | - "essentialBrightAccent" | - "essentialNegative" | - "essentialWarning" | - "essentialPositive" | - "essentialAnnouncement" | - "decorativeBase" | - "decorativeSubdued" | - "backgroundBase" | - "backgroundHighlight" | - "backgroundPress" | - "backgroundElevatedBase" | - "backgroundElevatedHighlight" | - "backgroundElevatedPress" | - "backgroundTintedBase" | - "backgroundTintedHighlight" | - "backgroundTintedPress" | - "backgroundUnsafeForSmallTextBase" | - "backgroundUnsafeForSmallTextHighlight" | - "backgroundUnsafeForSmallTextPress"; -``` diff --git a/docs/development/api-wrapper/types/svgicon.md b/docs/development/api-wrapper/types/svgicon.md deleted file mode 100644 index 271c61c3..00000000 --- a/docs/development/api-wrapper/types/svgicon.md +++ /dev/null @@ -1,87 +0,0 @@ ---- -title: SVGIcon -description: SVGIcon type definition. ---- - -```ts -type SVGIcon = "album" | - "artist" | - "block" | - "brightness" | - "car" | - "chart-down" | - "chart-up" | - "check" | - "check-alt-fill" | - "chevron-left" | - "chevron-right" | - "chromecast-disconnected" | - "clock" | - "collaborative" | - "computer" | - "copy" | - "download" | - "downloaded" | - "edit" | - "enhance" | - "exclamation-circle" | - "external-link" | - "facebook" | - "follow" | - "fullscreen" | - "gamepad" | - "grid-view" | - "heart" | - "heart-active" | - "instagram" | - "laptop" | - "library" | - "list-view" | - "location" | - "locked" | - "locked-active" | - "lyrics" | - "menu" | - "minimize" | - "minus" | - "more" | - "new-spotify-connect" | - "offline" | - "pause" | - "phone" | - "play" | - "playlist" | - "playlist-folder" | - "plus-alt" | - "plus2px" | - "podcasts" | - "projector" | - "queue" | - "repeat" | - "repeat-once" | - "search" | - "search-active" | - "shuffle" | - "skip-back" | - "skip-back15" | - "skip-forward" | - "skip-forward15" | - "soundbetter" | - "speaker" | - "spotify" | - "subtitles" | - "tablet" | - "ticket" | - "twitter" | - "visualizer" | - "voice" | - "volume" | - "volume-off" | - "volume-one-wave" | - "volume-two-wave" | - "watch" | - "x"; -``` - -### Examples -![svg-examples](/images/spicetify-svg-examples.png) diff --git a/docs/development/api-wrapper/types/uri/type.md b/docs/development/api-wrapper/types/uri/type.md deleted file mode 100644 index ba26b4c4..00000000 --- a/docs/development/api-wrapper/types/uri/type.md +++ /dev/null @@ -1,79 +0,0 @@ ---- -title: Type -description: Type of a URI. ---- - -Type of a URI. - -:::note - -This type is deducted from Spotify's internal usage. It may not be accurate and may change in the future. - -::: - -```ts -class URI { - static Type: { - AD: string; - ALBUM: string; - GENRE: string; - QUEUE: string; - APPLICATION: string; - ARTIST: string; - ARTIST_TOPLIST: string; - ARTIST_CONCERTS: string; - AUDIO_FILE: string; - COLLECTION: string; - COLLECTION_ALBUM: string; - COLLECTION_ARTIST: string; - COLLECTION_MISSING_ALBUM: string; - COLLECTION_TRACK_LIST: string; - CONCERT: string; - CONTEXT_GROUP: string; - DAILY_MIX: string; - EMPTY: string; - EPISODE: string; - /** URI particle; not an actual URI. */ - FACEBOOK: string; - FOLDER: string; - FOLLOWERS: string; - FOLLOWING: string; - IMAGE: string; - INBOX: string; - INTERRUPTION: string; - LIBRARY: string; - LIVE: string; - ROOM: string; - EXPRESSION: string; - LOCAL: string; - LOCAL_TRACK: string; - LOCAL_ALBUM: string; - LOCAL_ARTIST: string; - MERCH: string; - MOSAIC: string; - PLAYLIST: string; - PLAYLIST_V2: string; - PRERELEASE: string; - PROFILE: string; - PUBLISHED_ROOTLIST: string; - RADIO: string; - ROOTLIST: string; - SEARCH: string; - SHOW: string; - SOCIAL_SESSION: string; - SPECIAL: string; - STARRED: string; - STATION: string; - TEMP_PLAYLIST: string; - TOPLIST: string; - TRACK: string; - TRACKSET: string; - USER_TOPLIST: string; - USER_TOP_TRACKS: string; - UNKNOWN: string; - MEDIA: string; - QUESTION: string; - POLL: string; - }; -}; -``` diff --git a/docs/development/api-wrapper/types/uri/validation-functions.md b/docs/development/api-wrapper/types/uri/validation-functions.md deleted file mode 100644 index 633f4232..00000000 --- a/docs/development/api-wrapper/types/uri/validation-functions.md +++ /dev/null @@ -1,69 +0,0 @@ ---- -title: Validation functions -description: Functions that can be used to validate a URI type. ---- - -```ts -class URI { - static isAd(uri: any): boolean; - static isAlbum(uri: any): boolean; - static isGenre(uri: any): boolean; - static isQueue(uri: any): boolean; - static isApplication(uri: any): boolean; - static isArtist(uri: any): boolean; - static isArtistToplist(uri: any): boolean; - static isArtistConcerts(uri: any): boolean; - static isAudioFile(uri: any): boolean; - static isCollection(uri: any): boolean; - static isCollectionAlbum(uri: any): boolean; - static isCollectionArtist(uri: any): boolean; - static isCollectionMissingAlbum(uri: any): boolean; - static isCollectionTrackList(uri: any): boolean; - static isConcert(uri: any): boolean; - static isContextGroup(uri: any): boolean; - static isDailyMix(uri: any): boolean; - static isEmpty(uri: any): boolean; - static isEpisode(uri: any): boolean; - static isFacebook(uri: any): boolean; - static isFolder(uri: any): boolean; - static isFollowers(uri: any): boolean; - static isFollowing(uri: any): boolean; - static isImage(uri: any): boolean; - static isInbox(uri: any): boolean; - static isInterruption(uri: any): boolean; - static isLibrary(uri: any): boolean; - static isLive(uri: any): boolean; - static isRoom(uri: any): boolean; - static isExpression(uri: any): boolean; - static isLocal(uri: any): boolean; - static isLocalTrack(uri: any): boolean; - static isLocalAlbum(uri: any): boolean; - static isLocalArtist(uri: any): boolean; - static isMerch(uri: any): boolean; - static isMosaic(uri: any): boolean; - static isPlaylist(uri: any): boolean; - static isPlaylistV2(uri: any): boolean; - static isPrerelease(uri: any): boolean; - static isProfile(uri: any): boolean; - static isPublishedRootlist(uri: any): boolean; - static isRadio(uri: any): boolean; - static isRootlist(uri: any): boolean; - static isSearch(uri: any): boolean; - static isShow(uri: any): boolean; - static isSocialSession(uri: any): boolean; - static isSpecial(uri: any): boolean; - static isStarred(uri: any): boolean; - static isStation(uri: any): boolean; - static isTempPlaylist(uri: any): boolean; - static isToplist(uri: any): boolean; - static isTrack(uri: any): boolean; - static isTrackset(uri: any): boolean; - static isUserToplist(uri: any): boolean; - static isUserTopTracks(uri: any): boolean; - static isUnknown(uri: any): boolean; - static isMedia(uri: any): boolean; - static isQuestion(uri: any): boolean; - static isPoll(uri: any): boolean; - static isPlaylistV1OrV2(uri: any): boolean; -} -``` diff --git a/docs/development/api-wrapper/types/variant.md b/docs/development/api-wrapper/types/variant.md deleted file mode 100644 index 52106e18..00000000 --- a/docs/development/api-wrapper/types/variant.md +++ /dev/null @@ -1,32 +0,0 @@ ---- -title: Variant -description: Font variants used in the Spotify app. ---- - -:::note - -This type is deducted from Spotify's internal usage. It may not be accurate and may change in the future. - -::: - -```ts -type Variant = "bass" | - "forte" | - "brio" | - "altoBrio" | - "alto" | - "canon" | - "celloCanon" | - "cello" | - "ballad" | - "balladBold" | - "viola" | - "violaBold" | - "mesto" | - "mestoBold" | - "metronome" | - "finale" | - "finaleBold" | - "minuet" | - "minuetBold"; -``` diff --git a/docs/development/compiling.md b/docs/development/compiling.md deleted file mode 100644 index bdd65230..00000000 --- a/docs/development/compiling.md +++ /dev/null @@ -1,33 +0,0 @@ ---- -title: Compiling -description: 🧰 Compiling Spicetify. ---- - -### Requirements - -- [Go](https://golang.org/dl/) - -Clone repo and download dependencies: - -```bash -cd $HOME -mkdir spicetify -cd spicetify -git clone https://github.com/spicetify/cli -``` - -### Build - -#### Windows - -```powershell -cd $HOME\spicetify\cli -go build -o spicetify.exe -``` - -#### Linux and MacOS - -```bash -cd ~/spicetify/cli -go build -o spicetify -``` diff --git a/docs/development/index.md b/docs/development/index.md deleted file mode 100644 index 57d79ac0..00000000 --- a/docs/development/index.md +++ /dev/null @@ -1,10 +0,0 @@ ---- -title: Development ---- - -```mdx-code-block -import DocCardList from '@theme/DocCardList'; -import {useCurrentSidebarCategory} from '@docusaurus/theme-common'; - - -``` diff --git a/docs/development/react-devtools.md b/docs/development/react-devtools.md deleted file mode 100644 index b04f44b7..00000000 --- a/docs/development/react-devtools.md +++ /dev/null @@ -1,19 +0,0 @@ ---- -title: React Developer Tools -description: Installing and using React Developer Tools in Spotify. ---- - -To install React Developer Tools in Spotify: - -1. Launch Spotify in the developer mode (and with the `--enable-chrome-runtime` flag if you are using **Spotify versions older than 1.2.34**; without it, React Developer Tools won't work) -2. Press *Ctrl + Shift + T* -3. Press *Ctrl + N* -4. Navigate to the React Developer Tools page on Chrome Web Store using the address bar: `https://chromewebstore.google.com/detail/react-developer-tools/fmkadmapgofadopljbjfkapdkoienihi` -5. Press the "Add to Chrome" button -6. Confirm your choice - -You may need to press *F5* to get the extension working. - -If you get an error saying that you're not allowed to install extensions, try using this switch: `--allowlisted-extension-id=fmkadmapgofadopljbjfkapdkoienihi`. - -You can find more info about React Developer Tools on the [official React website](https://react.dev/learn/react-developer-tools). diff --git a/docs/development/spicetify-creator/building-and-testing.md b/docs/development/spicetify-creator/building-and-testing.md deleted file mode 100644 index cec78312..00000000 --- a/docs/development/spicetify-creator/building-and-testing.md +++ /dev/null @@ -1,115 +0,0 @@ ---- -title: Building And Testing -description: 🛠 Ensuring the quality of your creations. ---- - -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; - -## Building - -Open a terminal in the project's directory and run this: - - - - ```shell - npm run build - ``` - - - ```shell - yarn run build - ``` - - - ```shell - pnpm build - ``` - - - -Then make sure you've added your app to Spicetify's config by running this: - - - - ```shell - spicetify config extensions my-app.js - ``` - - - ```shell - spicetify config custom_apps my-app - ``` - - - -Finally, do - -```shell -spicetify apply -``` - -and you'll see your app in Spotify. - -## Watching - -Please first [build your app](#building) at least once before watching. - -Watching means that it'll rebuild the app every time the code changes. -Go into your project's directory and enter the following command: - - - ```shell - npm run watch - ``` - - - ```shell - yarn run watch - ``` - - - ```shell - pnpm watch - ``` - - - -Then, run Spotify in watch mode: - - - - ```shell - spicetify watch -le - ``` - - - ```shell - spicetify watch -la - ``` - - - -## Building locally - -If you want to upload the build files with your repository or just see them, you can do: - - - - ```shell - npm run build-local - ``` - - - ```shell - yarn run build-local - ``` - - - ```shell - pnpm build-local - ``` - - - -And the compiled files will be created in a local `dist` folder. diff --git a/docs/development/spicetify-creator/create-custom-apps.md b/docs/development/spicetify-creator/create-custom-apps.md deleted file mode 100644 index 47763423..00000000 --- a/docs/development/spicetify-creator/create-custom-apps.md +++ /dev/null @@ -1,72 +0,0 @@ ---- -title: Create Custom Apps -description: 🔧 Creating single-page apps for Spicetify. ---- - -Notes: - -- It is recommended to learn React before starting to create Custom Apps. -- This tutorial assumes you have chosen to generate an example using Create Spicetify App. - -After creating a new Spicetify Creator project and choosing "Custom App" as your app's type, your project's structure should look like this (With the generated example): - -``` -my-app/ - .gitattributes - .gitignore - package.json - README.md - tsconfig.json - yarn.lock - src/ - ... - node_modules/ - ... -``` - -For now, we only care about the `src/` folder, whose structure looks like this - -``` -src/ - app.tsx - settings.json - extensions/ - extension.tsx - css/ - icon.svg - app.module.scss - types/ - ... -``` - -`app.tsx` exports a React Component that will be mounted to Spotify every time the user enters your custom app. -It comes with an example of a simple counter using React's logic, and usages for SCSS modules. -`settings.json` is a simple JSON file containing 4 keys: - -```json -{ - "displayName": "My App", // The name of your app in the left sidebar - "nameId": "my-app", // The id of your app - "icon": "css/icon.svg", // The icon that will be displayed in the sidebar - "activeIcon": "css/icon.svg" // The icon that will be displayed upon selecting the app in the sidebar. -} -``` - -The `extensions/` folder takes all the files inside it and transforms them into extensions that will run on Spotify's startup. -The `extensions/extension.tsx` file is an example that says "Welcome!" whenever Spotify starts. - -
-extension.tsx's content - -```ts -(async () => { - while (!Spicetify?.showNotification) { - await new Promise((resolve) => setTimeout(resolve, 100)); - } - - // Show message on start. - Spicetify.showNotification('Welcome!'); -})(); -``` - -
diff --git a/docs/development/spicetify-creator/the-basics.md b/docs/development/spicetify-creator/the-basics.md deleted file mode 100644 index 97f278c2..00000000 --- a/docs/development/spicetify-creator/the-basics.md +++ /dev/null @@ -1,152 +0,0 @@ ---- -title: The Basics -description: 🤠 Spicetify Creator 101. ---- - -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; - -## Introduction - -[Spicetify Creator](https://github.com/FlafyDev/spicetify-creator) is a tool to compile modern TypeScript/JavaScript code to Spicetify extensions and custom apps. - -Its built-in features include: - -- TypeScript and React syntax -- [Import node packages](#node-packages) -- [CSS/SCSS with PostCSS support](#css) -- Extremely fast compile time with esbuild. -- [Plugins](#plugins) - -## Getting Started - -The easiest way to start using Spicetify Creator is with Create Spicetify App. -Create Spicetify App allows you to effortlessly create new Spicetify Creator projects through the terminal. - -Install Node.js and either npm or Yarn. -Open the terminal in your desired directory and enter the following command - - - - ```shell - npx create-spicetify-app - ``` - - - ```shell - yarn create spicetify-app - ``` - - - ```shell - pnpm create spicetify-app - ``` - - - -The command will ask you 3-4 simple questions about the app you plan to create and generate a Spicetify Creator project accordingly. -After creation, read one of the following pages depending on what type of app you chose to create. - - - -## CSS - -To apply a CSS/SCSS file to your app you have to import it like this: - -```ts -import './my-css-file.css'; // For CSS -import './my-scss-file.scss'; // For SCSS -``` - -There is also support for [CSS Modules](https://github.com/css-modules/css-modules) and you import them like this: - -```ts -import styles from './item-list.module.css'; // For CSS -import styles from './item-list.module.scss'; // For SCSS -``` - -## Node packages - -You can use node packages in your app by installing them with your package manager. - - - - ```shell - npm install - ``` - - - ```shell - yarn add - ``` - - - ```shell - pnpm add - ``` - - - -then simply import the package in the code and you're good to go. - -```ts -import packageName from ''; -``` - -## Plugins - -Plugins are node packages designed for Spicetify Creator projects, and they support either extensions, custom apps, or both. -The convention is to name every plugin like so: `spcr-`. - -For a list of plugins: https://github.com/FlafyDev/spicetify-creator-plugins - -To install and import a plugin: - - - ```shell - npm install spcr- - ``` - - - ```shell - yarn add spcr- - ``` - - - ```shell - pnpm add spcr- - ``` - - - -```ts -import plugin from 'spcr-'; -``` - -#### Example of 2 plugins you can already use in your own apps: - -- [spcr-settings](https://github.com/FlafyDev/spicetify-creator-plugins/tree/main/packages/spcr-settings) -- [spcr-navigation-bar](https://github.com/FlafyDev/spicetify-creator-plugins/tree/main/packages/spcr-navigation-bar) - -## Update Spicetify Creator - - - - ```shell - npm update spicetify-creator - ``` - - - ```shell - yarn upgrade spicetify-creator - ``` - - - ```shell - pnpm update spicetify-creator - ``` - - diff --git a/docs/development/spotify-cli-flags.md b/docs/development/spotify-cli-flags.md deleted file mode 100644 index 7db74814..00000000 --- a/docs/development/spotify-cli-flags.md +++ /dev/null @@ -1,106 +0,0 @@ ---- -title: Spotify CLI Flags -description: 🚩 Flags to alter the behavior of the Spotify. ---- - -## List of flags - -**When adding flags to your spicetify config, separate each one with `|`.** - -| Flag | Description | -| -------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `--allow-upgrades` | | -| `--append-log-file` | | -| `--app-directory=` | Specify the "Apps" directory path. Used by spicetify for modifying Spotify from Microsoft Store. | -| `--app-icon-overlay` | | -| `--apr` | | -| `--audio-api` | | -| `--autostart` | | -| `--bridge-log-filename` | | -| `--cache-path=` | Use as root for the cache directory. | -| `--campaign-id` | | -| `--connect-debug-level` | | -| `--disable-cef-views` | | -| `--disable-crash-reporting` | | -| `--disable-update-restarts` | | -| `--disallow-multiple-instances` | | -| `--enable-audio-graph` | | -| `--enable-cef-views` | | -| `--enable-chrome-runtime` | Switches runtime from Alloy to Chrome on Spotify versions below 1.2.34. See [this issue on GitHub](https://github.com/chromiumembedded/cef/issues/3685) for details about the differences. | -| `--enable-developer-mode` | Used to enable the developer mode like `spicetify enable-devtools`. Stopped working long time ago. | -| `--event-sender-send-interval` | | -| `--experimental-languages` | | -| `--experimental-network` | | -| `--force-auto-update` | | -| `--force-cef-http` | | -| `--immediate-widevine-cdm-download` | | -| `--log-detailed-request-account` | | -| `--log-file=` | Save log output to file (extension needs to be '.log'). | -| `--maximized` | | -| `--minimum-update-request-interval` | | -| `--minimized` | Start the app with the window minimized. Only works on Windows. | -| `--mu=` | Start with a special cache directory. Allows you to run multiple clients at the same time. Value can be anything (will be used as part of the cache name). | -| `--password=` | Use to automatically sign in on startup. Use together with `--username`. No longer works. | -| `--performance-tracing` | | -| `--product-version` | | -| `--protocol-uri=` | Identical to --uri, but only used from the Windows protocol handler, so we can apply extra security restrictions. | -| `--remote-allow-origins=` | Required to use remote debugging since Spotify 1.2.8 due to security changes in Chromium 111. Example configuration: `--remote-debugging-port=8088 \| --remote-allow-origins=http://localhost:8088`. | -| `--remote-app-config` | | -| `--remote-debugging-port=` | Enable remote debugging. Use together with `--remote-allow-origins`. | -| `--remember-cmd-login` | | -| `--show-console` | Show more log output. | -| `--startup-success-file-path` | | -| `--test-auto-update-success-file-path` | | -| `--trace-file=` | Save a trace file to this path. | -| `--trigger-ta-crash` | | -| `--update-endpoint-override=` | Can be used to disable Spotify updates. Example configuration: `--update-endpoint-override=http://localhost`. | -| `--update-immediately` | | -| `--upgrade-failed` | | -| `--uri=` | Start the client normally, but automatically navigate to the URI when initialized. | -| `--use-event-sender-test-transport` | | -| `--user-agent-product` | | -| `--username=` | Use to automatically sign in on startup. Use together with `--password`. No longer works. | -| `--weblogin-endpoint` | | - -**When added to spicetify config, the flags will only be applied when you launch Spotify using spicetify.** But you can also add them to the Spotify shortcut (on Windows) or `.desktop` file (on Linux) and use it to launch Spotify. - -Most of the flags with some descriptions are taken directly from the Spotify executable. - -## See also - -- [General documentation on Chromium command-line switches](https://www.chromium.org/developers/how-tos/run-chromium-with-flags) - -- [List of Chromium command-line switches](https://peter.sh/experiments/chromium-command-line-switches) - -- List of CEF command-line switches in the source code: [1](https://github.com/chromiumembedded/cef/blob/master/tests/shared/common/client_switches.cc), [2](https://github.com/chromiumembedded/cef/blob/master/libcef/common/cef_switches.cc) - -Don't expect every switch to work. - -## Experimental features - -Some Chromium experimental features can be enabled with `--enable-features=`, some require both a switch and a feature. Smooth scrolling is an example: `--enable-smooth-scrolling | --enable-features=WindowsScrollingPersonality`. -There is no list of experimental features and they vary from version to version. - -To enable experimental features in **Spotify newer than 1.2.33**: - -1. Launch it in the developer mode -2. Press *Ctrl + Shift + T* -3. Press *Ctrl + N* -4. Navigate to the `chrome://flags` page using the address bar -5. Enable the ones you want -6. Press the "Relaunch" button - -If you are using **Spotify older than 1.2.34**: - -1. Launch it with the `--enable-chrome-runtime` switch and developer mode enabled -2. Press *Ctrl + Shift + T* -3. Press *Ctrl + N* -4. Navigate to the `chrome://flags` page using the address bar -5. Enable the ones you want -6. Press the "Relaunch" button -7. Restart Spotify with the `--enable-chrome-runtime` switch and developer mode enabled -8. Press *Ctrl + Shift + T* -9. Click the `chrome://version` link -10. Copy and paste the flags between `--flag-switches-begin` `--flag-switches-end` into your spicetify config and/or the shortcut/`.desktop` file you use to launch Spotify - -The `--enable-chrome-runtime` switch and developer mode are not required for experimental features to work. diff --git a/docs/development/themes.md b/docs/development/themes.md deleted file mode 100644 index 049d98b0..00000000 --- a/docs/development/themes.md +++ /dev/null @@ -1,58 +0,0 @@ ---- -title: Themes -description: ✨ Creating Themes for Spicetify. ---- - -## Theme Structure - -```text -~/.config/spicetify/Themes/ # Linux/macOS -%appdata%\spicetify\Themes\ # Windows -└── MyTheme/ - ├── color.ini # Required: color definitions - ├── user.css # Required: custom CSS rules - ├── theme.js # Optional: theme-specific JavaScript - └── assets/ # Optional: images, fonts, etc. - └── ... -``` - -## Theme Location - -There are 2 places you can put your themes: - -1. `Themes` folder in Home directory - -| Platform | Path | -| ------------------- | --------------------------------- | -| **Windows** | `%appdata%\spicetify\Themes` | -| **Linux**/**MacOS** | `~/.config/spicetify/Themes` | - -2. `Themes` folder in Spicetify executable directory - -If there are 2 themes with the same name, the theme in the Home directory is prioritized. - -Every theme should contain: - -- `color.ini`: stores colors values that later will be converted to CSS variables -- `user.css`: set of custom CSS rules to manipulate, hide, move UI elements. - -Color value can be in several formats and forms: - -- Hex: e.g `#FF0000`, `#1258F6`, `#F55` -- Decimal: e.g `255,255,255`, `50,80,120` -- Environment variables can be used in place of color. - - - Syntax: `${}` - - Example usage: `text = ${LIGHT_GREY}` - -- **[Linux]** You can use XResources variable in place of color. Extremely useful for who uses `pywal` to generate color scheme. - - Syntax: `${xrdb:}` or `${xrdb::}` - - Example usage: - -``` -[Base] -text = ${xrdb:color14} -subtext = ${xrdb:foreground:#FFF} -player = ${xrdb:background} -... -``` diff --git a/docs/getting-started.md b/docs/getting-started.md deleted file mode 100644 index 36989e1c..00000000 --- a/docs/getting-started.md +++ /dev/null @@ -1,406 +0,0 @@ ---- -title: Getting Started -sidebar_position: 1 ---- - -Spicetify is a multiplatform command-line tool to customize the official Spotify client. - -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; - -## Installation - -### Windows - - - - -```powershell -iwr -useb https://raw.githubusercontent.com/spicetify/cli/main/install.ps1 | iex -``` - - - - -```powershell -winget install Spicetify.Spicetify -``` - - - - -```powershell -scoop install spicetify-cli -``` - - - - -```powershell -choco install spicetify-cli -``` - -See the [Chocolatey package page](https://chocolatey.org/packages/spicetify-cli) for more details. - - - - -
-Spotify from Scoop - -If you installed Spotify via Scoop, find its location and set it in your config: - -```console -$ scoop prefix spotify -C:\Users\\scoop\apps\spotify\current -``` - -```powershell -spicetify config spotify_path "C:\Users\\scoop\apps\spotify\current" -``` - -
- -### Linux - - - - -```bash -curl -fsSL https://raw.githubusercontent.com/spicetify/cli/main/install.sh | sh -``` - - - - -```bash -brew install spicetify-cli -``` - - - - -```bash -yay -S spicetify-cli -``` - - - - -For NixOS and Home Manager users, use the [spicetify-nix](https://github.com/Gerg-L/spicetify-nix) flake. It provides declarative configuration and handles Spotify installation automatically. - -Add the flake input: - -```nix -{ - inputs.spicetify-nix.url = "github:Gerg-L/spicetify-nix"; -} -``` - -Then import the module and configure: - -```nix -# For NixOS: spicetify-nix.nixosModules.spicetify -# For Home Manager: spicetify-nix.homeManagerModules.spicetify - -{ pkgs, inputs, ... }: -let - spicePkgs = inputs.spicetify-nix.legacyPackages.${pkgs.system}; -in { - programs.spicetify = { - enable = true; - enabledExtensions = with spicePkgs.extensions; [ - adblockify - hidePodcasts - shuffle - ]; - theme = spicePkgs.themes.catppuccin; - colorScheme = "mocha"; - }; -} -``` - -See the [spicetify-nix documentation](https://gerg-l.github.io/spicetify-nix) for full configuration options. - -:::note -The module installs Spotify for you. Do not install `pkgs.spotify` separately. -::: - - - - -#### Linux-Specific Setup - -Depending on how you installed Spotify, you may need additional configuration. - -
-Adding Spicetify to PATH - -If you installed Spicetify via a package manager (Homebrew, AUR, etc.) and the `spicetify` command isn't found, you may need to add it to your PATH manually. - -First, determine your shell: - -```bash -echo $SHELL -``` - -Then add `~/.spicetify` to your PATH based on your shell: - - - - -```bash -echo 'export PATH=$PATH:~/.spicetify' >> ~/.zshrc -source ~/.zshrc -``` - - - - -```bash -echo 'export PATH=$PATH:~/.spicetify' >> ~/.bashrc -source ~/.bashrc -``` - - - - -```fish -fish_add_path ~/.spicetify -``` - - - - -
- -
-Spotify from AUR - -Grant write permissions to Spotify's directory: - -```bash -sudo chmod a+wr /opt/spotify -sudo chmod a+wr /opt/spotify/Apps -R -``` - -
- -
-Spotify from spotify-launcher (Arch) - -The `spotify-launcher` package installs Spotify to a user directory. Set the path in your config: - -```bash -spicetify config spotify_path "$HOME/.local/share/spotify-launcher/install/usr/share/spotify" -``` - -:::warning -When setting config values, use the full absolute path (e.g., `/home/username/...`). The `~` shortcut works in shell commands but not in config values. -::: - -
- -
-Spotify from APT - -Grant write permissions to Spotify's directory: - -```bash -sudo chmod a+wr /usr/share/spotify -sudo chmod a+wr /usr/share/spotify/Apps -R -``` - -
- -
-Spotify from Snap - -Snap apps cannot be modified. You'll need to switch to the apt version: - -1. Remove Snap Spotify: - - ```bash - snap remove spotify - ``` - -2. Install via apt: - - ```bash - curl -sS https://download.spotify.com/debian/pubkey_C85668DF69375001.gpg | sudo gpg --dearmor --yes -o /etc/apt/trusted.gpg.d/spotify.gpg - echo "deb http://repository.spotify.com stable non-free" | sudo tee /etc/apt/sources.list.d/spotify.list - sudo apt-get update && sudo apt-get install spotify-client - ``` - -3. Grant permissions: - - ```bash - sudo chmod a+wr /usr/share/spotify - sudo chmod a+wr /usr/share/spotify/Apps -R - ``` - -
- -
-Spotify from Flatpak - -1. Find your Flatpak Spotify location: - - ```bash - flatpak --installations - ``` - - Common locations: - - `/var/lib/flatpak/app/com.spotify.Client/x86_64/stable/active/files/extra/share/spotify/` - - `~/.local/share/flatpak/app/com.spotify.Client/x86_64/stable/active/files/extra/share/spotify/` - -2. Set the path: - - ```bash - spicetify config spotify_path "/var/lib/flatpak/app/com.spotify.Client/x86_64/stable/active/files/extra/share/spotify" - ``` - -3. Find and set your prefs file (check both locations): - - ```bash - # Check which exists: - ls ~/.config/spotify/prefs - ls ~/.var/app/com.spotify.Client/config/spotify/prefs - - # Set whichever exists (use the full absolute path): - spicetify config prefs_path $HOME/.var/app/com.spotify.Client/config/spotify/prefs - ``` - -4. Grant permissions: - - ```bash - sudo chmod a+wr /var/lib/flatpak/app/com.spotify.Client/x86_64/stable/active/files/extra/share/spotify - sudo chmod a+wr -R /var/lib/flatpak/app/com.spotify.Client/x86_64/stable/active/files/extra/share/spotify/Apps - ``` - -
- -### macOS - - - - -```bash -curl -fsSL https://raw.githubusercontent.com/spicetify/cli/main/install.sh | sh -``` - - - - -```bash -brew install spicetify-cli -``` - -After installing via Homebrew, set the Spotify path: - -```bash -spicetify config spotify_path "/Applications/Spotify.app/Contents/Resources" -``` - - - - -For nix-darwin and Home Manager users on macOS, use the [spicetify-nix](https://github.com/Gerg-L/spicetify-nix) flake: - -```nix -{ - inputs.spicetify-nix.url = "github:Gerg-L/spicetify-nix"; -} -``` - -Import the module (`spicetify-nix.darwinModules.spicetify` for nix-darwin or `spicetify-nix.homeManagerModules.spicetify` for Home Manager) and configure `programs.spicetify`. See the [Linux Nix tab](#linux) for a configuration example. - - - - ---- - -## Before First Run - -:::tip -If this is a fresh Spotify install, open Spotify and log in for at least 60 seconds before running Spicetify. This allows Spotify to generate the files that Spicetify needs to function. -::: - ---- - -## Marketplace - -The [Spicetify Marketplace](https://github.com/spicetify/marketplace) gives you a tab in Spotify's sidebar to browse and install themes, extensions, and snippets. - - - - -```powershell -iwr -useb https://raw.githubusercontent.com/spicetify/marketplace/main/resources/install.ps1 | iex -``` - - - - -```bash -curl -fsSL https://raw.githubusercontent.com/spicetify/marketplace/main/resources/install.sh | sh -``` - - - - ---- - -## Basic Usage - -After installing Spicetify and Marketplace, you can customize Spotify using themes and extensions from the Marketplace tab in Spotify's sidebar. - -### Updating - -Spotify updates periodically. After a Spotify update, you'll need to re-apply Spicetify: - -```bash -spicetify backup apply -``` - -If that doesn't work, Spicetify itself may need an update: - -```bash -spicetify update -``` - -If you updated Spicetify via a package manager (Homebrew, AUR, etc.) or the update command didn't fully apply changes, run: - -```bash -spicetify restore backup apply -``` - -If no Spicetify upgrade is available yet, the team is likely still working on compatibility with the new Spotify version. - ---- - -## Legacy Versions - -If you need an older Spicetify version (e.g., for Spotify v1.1.56 or older): - - - - -```powershell -$v="1.2.1"; Invoke-WebRequest -UseBasicParsing "https://raw.githubusercontent.com/spicetify/cli/main/install.ps1" | Invoke-Expression -``` - - - - -```bash -curl -fsSL https://raw.githubusercontent.com/spicetify/cli/main/install.sh -o /tmp/install.sh -sh /tmp/install.sh 1.2.1 -``` - - - - -Legacy resources: -- [Spicetify v1 source (legacy branch)](https://github.com/spicetify/cli/tree/legacy) -- [Legacy themes](https://github.com/spicetify/spicetify-themes/tree/legacy) diff --git a/docs/uninstallation.md b/docs/uninstallation.md deleted file mode 100644 index c9854fbe..00000000 --- a/docs/uninstallation.md +++ /dev/null @@ -1,111 +0,0 @@ ---- -title: Uninstallation -description: How to remove Spicetify from your system. ---- - -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; - -## Quick Uninstall - - - - -```powershell -spicetify restore -rmdir -r -fo $env:APPDATA\spicetify -rmdir -r -fo $env:LOCALAPPDATA\spicetify -``` - - - - -```bash -spicetify restore -rm -rf ~/.spicetify -rm -rf ~/.config/spicetify -``` - - - - -## Step by Step - -### 1. Restore Spotify - -First, remove Spicetify modifications from Spotify: - -```bash -spicetify restore -``` - -This returns Spotify to its vanilla state. - -### 2. Remove Spicetify Files - - - - -Remove the config and data directories: - -```powershell -rmdir -r -fo $env:APPDATA\spicetify -rmdir -r -fo $env:LOCALAPPDATA\spicetify -``` - - - - -Remove the config and data directories: - -```bash -rm -rf ~/.spicetify -rm -rf ~/.config/spicetify -``` - - - - -### 3. Remove the Binary (Optional) - -If you installed via a package manager, use it to uninstall: - -| Method | Uninstall Command | -|--------|-------------------| -| Scoop | `scoop uninstall spicetify-cli` | -| Winget | `winget uninstall Spicetify.Spicetify` | -| Chocolatey | `choco uninstall spicetify-cli` | -| Homebrew | `brew uninstall spicetify-cli` | -| AUR | `yay -R spicetify-cli` | - -If you used the install script, the binary is in your PATH. You can find and remove it: - - - - -```powershell -where spicetify -# Remove the file shown -``` - - - - -```bash -which spicetify -# Remove the file shown, e.g.: -rm $(which spicetify) -``` - - - - -## Keeping Configuration - -If you might reinstall later and want to keep your settings: - -```bash -spicetify restore -``` - -This removes Spicetify from Spotify but preserves your config file, themes, and extensions. diff --git a/src/components/Footer.astro b/src/components/Footer.astro index 60f3bd7e..fe1e35b0 100644 --- a/src/components/Footer.astro +++ b/src/components/Footer.astro @@ -14,7 +14,7 @@ const year = new Date().getFullYear();

Docs

diff --git a/src/components/homepage/ThemesBento.astro b/src/components/homepage/ThemesBento.astro index c0a16e7f..32c69a30 100644 --- a/src/components/homepage/ThemesBento.astro +++ b/src/components/homepage/ThemesBento.astro @@ -36,11 +36,11 @@ const { themes, marketplaceItems } = Astro.props;
- + Browse all themes - {marketplaceItems} items in the Marketplace + {marketplaceItems} items in the store
diff --git a/src/config/sidebar.ts b/src/config/sidebar.ts index 79b24c2a..b7eb50a9 100644 --- a/src/config/sidebar.ts +++ b/src/config/sidebar.ts @@ -7,18 +7,15 @@ export interface SidebarItem { export const sidebar: SidebarItem[] = [ { label: 'Getting Started', href: '/docs/getting-started' }, + { label: 'What Changes in v3', href: '/docs/whats-new' }, { - label: 'Customization', - href: '/docs/customization', + label: 'Modules & the Store', + href: '/docs/modules', collapsed: false, items: [ - { label: 'Marketplace', href: '/docs/customization/marketplace' }, - { label: 'Themes', href: '/docs/customization/themes' }, - { label: 'Extensions', href: '/docs/customization/extensions' }, - { label: 'Custom Apps', href: '/docs/customization/custom-apps' }, { label: 'Configuration Reference', - href: '/docs/customization/config-file', + href: '/docs/modules/config-file', }, ], }, @@ -31,37 +28,17 @@ export const sidebar: SidebarItem[] = [ label: 'Development', href: '/docs/development', items: [ + { + label: 'Building a Module', + href: '/docs/development/building-a-module', + }, + { label: 'Publishing a Module', href: '/docs/development/publishing' }, { label: 'Compiling', href: '/docs/development/compiling' }, - { label: 'Themes', href: '/docs/development/themes' }, - { label: 'Extensions', href: '/docs/development/extensions' }, - { label: 'Custom Apps', href: '/docs/development/custom-apps' }, - { label: 'JS Modules', href: '/docs/development/js-modules' }, { label: 'React DevTools', href: '/docs/development/react-devtools' }, { label: 'Spotify CLI Flags', href: '/docs/development/spotify-cli-flags', }, - { - label: 'Spicetify Creator', - items: [ - { - label: 'The Basics', - href: '/docs/development/spicetify-creator/the-basics', - }, - { - label: 'Create Extensions', - href: '/docs/development/spicetify-creator/create-extensions', - }, - { - label: 'Create Custom Apps', - href: '/docs/development/spicetify-creator/create-custom-apps', - }, - { - label: 'Building & Testing', - href: '/docs/development/spicetify-creator/building-and-testing', - }, - ], - }, { label: 'API Wrapper', href: '/docs/development/api-wrapper', @@ -367,8 +344,79 @@ export const sidebar: SidebarItem[] = [ }, ], }, - { label: 'FAQ', href: '/docs/faq' }, { label: 'Uninstallation', href: '/docs/uninstallation' }, + { label: 'FAQ', href: '/docs/faq' }, + { + label: 'Spicetify v2 (legacy)', + href: '/docs/legacy', + collapsed: true, + items: [ + { label: 'Getting Started', href: '/docs/legacy/getting-started' }, + { + label: 'Customization', + href: '/docs/legacy/customization', + items: [ + { + label: 'Marketplace', + href: '/docs/legacy/customization/marketplace', + }, + { label: 'Themes', href: '/docs/legacy/customization/themes' }, + { + label: 'Extensions', + href: '/docs/legacy/customization/extensions', + }, + { + label: 'Custom Apps', + href: '/docs/legacy/customization/custom-apps', + }, + { + label: 'Configuration Reference', + href: '/docs/legacy/customization/config-file', + }, + ], + }, + { + label: 'CLI Reference', + href: '/docs/legacy/cli', + items: [{ label: 'Commands', href: '/docs/legacy/cli/commands' }], + }, + { + label: 'Development', + items: [ + { label: 'Themes', href: '/docs/legacy/development/themes' }, + { label: 'Extensions', href: '/docs/legacy/development/extensions' }, + { + label: 'Custom Apps', + href: '/docs/legacy/development/custom-apps', + }, + { label: 'JS Modules', href: '/docs/legacy/development/js-modules' }, + { + label: 'Spicetify Creator', + items: [ + { + label: 'The Basics', + href: '/docs/legacy/spicetify-creator/the-basics', + }, + { + label: 'Create Extensions', + href: '/docs/legacy/spicetify-creator/create-extensions', + }, + { + label: 'Create Custom Apps', + href: '/docs/legacy/spicetify-creator/create-custom-apps', + }, + { + label: 'Building & Testing', + href: '/docs/legacy/spicetify-creator/building-and-testing', + }, + ], + }, + ], + }, + { label: 'Uninstallation', href: '/docs/legacy/uninstallation' }, + { label: 'FAQ', href: '/docs/legacy/faq' }, + ], + }, ]; /** Flatten sidebar into ordered list of hrefs for prev/next navigation */ diff --git a/src/content/docs/cli/commands.md b/src/content/docs/cli/commands.md index c82ebab7..9c1f6c28 100644 --- a/src/content/docs/cli/commands.md +++ b/src/content/docs/cli/commands.md @@ -1,278 +1,177 @@ --- title: Commands -description: Complete Spicetify command reference. +description: Every command in the Spicetify v3 CLI. +sidebar_position: 2 --- -This page documents all Spicetify CLI commands. +Every command accepts the [global options](/docs/cli#global-options), so they are not repeated below. `spicetify --help` is the authority if this page ever falls behind the binary. -## Core Commands - -### `spicetify` (no arguments) - -Run with no arguments to generate the config file on first run, or verify your setup. - -```bash -spicetify -``` - -### `backup` - -Create a backup of vanilla Spotify files. Required before applying Spicetify for the first time. +--- -```bash -spicetify backup -``` +## Core ### `apply` -Apply Spicetify modifications to Spotify. - ```bash spicetify apply ``` -This injects your theme, extensions, custom apps, and other modifications into Spotify. +Patches Spotify. This is the whole setup on a fresh install, and the fix for almost anything that looks wrong afterwards. -### `restore` +It stops Spotify, unpacks the client, renames Spotify's own archive to `xpui.spa.backup` (that rename is the backup), injects Spicetify's payload, fetches the mapping for your exact Spotify version, stages every enabled module, installs and starts the daemon, registers the `spicetify://` handler, and starts Spotify again. -Remove all Spicetify modifications and restore Spotify to vanilla state. +Safe to run repeatedly. If the fetch for a new Spotify version fails, whatever is already cached still applies, so `apply` works offline. + +### `restore` ```bash spicetify restore ``` -Your config file and customization files are preserved. - -### `update` +Puts stock Spotify back from the backup taken at apply time. Restore with the same CLI that applied: v2 and v3 keep their backups differently. -Hot-reload theme changes without full restart. Use this during theme development. +### `restart` ```bash -spicetify update +spicetify restart ``` -After running, press Ctrl + Shift + R (or Cmd + Shift + R on macOS) in Spotify to see changes. +Restarts the Spotify client. No patching. -### `upgrade` - -Upgrade Spicetify to the latest version (only works with script-based installations). +### `init` ```bash -spicetify upgrade +spicetify init [--yes] ``` ---- - -## Configuration Commands - -### `config` +Writes a fresh `config.toml` from what it detects, and **deletes `hooks/`, `modules/` and `store/`**, so every installed module goes with it. It asks first unless you pass `--yes`. This is a clean slate, not a repair. -View or modify configuration values. +--- -**View all settings:** +## Modules -```bash -spicetify config -``` - -**View a specific setting:** +### `pkg list` ```bash -spicetify config current_theme +spicetify pkg list ``` -**Set a value:** - -```bash -spicetify config current_theme Sleek -``` +What is installed, read from disk, with each module's version. -**Set multiple values:** +### `pkg install` ```bash -spicetify config current_theme Sleek color_scheme Dark +spicetify pkg install +spicetify pkg install ``` -**Add to a list (extensions, custom_apps):** +Resolves the id in the registry, downloads the artifact, verifies it against the checksum the registry recorded, and unpacks it. A mismatch aborts the install. If the entry lists mirrors, a host that has gone away costs an attempt rather than the install. -```bash -spicetify config extensions fullAppDisplay.js -``` +With a URL, the registry is bypassed entirely: nothing verifies those bytes, and the CLI says so and prints the digest it got. -This appends to existing extensions, not replaces. +Installing does not enable. Follow with `pkg enable` and `apply`. -**Remove from a list:** +### `pkg enable` ```bash -spicetify config extensions fullAppDisplay.js- +spicetify pkg enable @ ``` -Note the trailing `-`. - -### `config-dir` +Points the client at that version, by linking it into the modules directory. This is also how you roll back: enable the older version you still have and re-apply. -Open the Spicetify config directory in your file manager. +### `pkg delete` ```bash -spicetify config-dir +spicetify pkg delete ``` -### `-c` / `--config` - -Print the config file path. - -```bash -spicetify -c -``` +Removes the module and its store entry. --- -## Utility Commands - -### `enable-devtools` +## Configuration -Enable Chromium DevTools in Spotify. Useful for debugging themes and extensions. +### `config` ```bash -spicetify enable-devtools +spicetify config # print the resolved configuration +spicetify config open # open the configuration folder ``` -Access DevTools with Ctrl + Shift + I. - -### `watch` +With no subcommand it prints what Spicetify actually resolved: mirror mode, the config file, the config root, and the Spotify data directory, executable and offline cache it is using. That is the first thing to check when Spicetify is patching a Spotify you did not expect. -Watch for theme changes and auto-apply. Useful during development. - -```bash -spicetify watch -``` - -Monitors `color.ini` and `user.css` in your current theme folder. +There is no `config ` in v3. Edit [`config.toml`](/docs/modules/config-file). ### `path` -Print various Spicetify-related paths. - ```bash spicetify path -spicetify path userdata # Config directory -spicetify path spotify # Spotify installation ``` -### `auto` +Prints the paths Spicetify uses. -Automatically backup (if needed) and apply, then launch Spotify. +### `support` ```bash -spicetify auto +spicetify support ``` -Useful as a shortcut target instead of the Spotify executable. +Prints diagnostics to paste into a bug report. Start here before opening an issue. --- -## Combined Commands +## Daemon -Commands can be combined in a single call: +The daemon is what re-applies Spicetify after Spotify updates itself, and it serves the local proxy the client uses for hosts it cannot fetch directly. `apply` installs and starts it unless `daemon = false` in your config. ```bash -# First-time setup -spicetify backup apply enable-devtools - -# After Spotify updates -spicetify backup apply - -# Full restore and reapply -spicetify restore backup apply +spicetify daemon status # running? which version? +spicetify daemon start +spicetify daemon stop # also unloads the service, so it does not come back on its own +spicetify daemon install # install the service +spicetify daemon uninstall # remove it ``` ---- - -## Flags - -### `--help` / `-h` - -Show help for a command. - -```bash -spicetify --help -spicetify --help config -``` - -### `--version` / `-v` - -Show Spicetify version. - -```bash -spicetify --version -``` - -### `--no-restart` - -Apply changes without restarting Spotify. +`daemon status` reports the version it is running. If you have just updated Spicetify and behaviour has not changed, check that first: an old daemon serving old behaviour looks exactly like a fix that did not work. -```bash -spicetify apply --no-restart -``` - -### `--quiet` / `-q` +--- -Suppress non-error output. +## Spotify updates ```bash -spicetify apply -q +spicetify spotify-updates block # keep Spotify on the build you have +spicetify spotify-updates unblock +spicetify spotify-updates status ``` -### `--extension` / `-e` - -Specify a single extension to apply (useful for testing). - -```bash -spicetify apply -e myExtension.js -``` +Blocking patches Spotify's own binary, so Spotify has to be stopped to do it. Run from the terminal it stops the client and leaves it stopped; run from inside the client (through the store) it starts it again for you. --- -## Examples +## Development -### Fresh Install Workflow +### `dev` ```bash -# Install Spicetify (see Installation page) -# Generate config -spicetify - -# First-time setup -spicetify backup apply enable-devtools +spicetify dev ``` -### Enable an Extension +Enables developer mode in the client, which is what gives you Inspect Element. -```bash -spicetify config extensions fullAppDisplay.js -spicetify apply -``` - -### Change Theme +### `protocol` ```bash -spicetify config current_theme Sleek color_scheme Dark -spicetify apply +spicetify protocol "spicetify::" ``` -### After Spotify Updates +Handles a `spicetify://` URI. You rarely type this: `apply` registers a handler so links and in-client actions reach it. Actions are `add`, `install`, `enable`, `fast-install`, `fast-enable`, `delete`, `remove`, `fast-delete`, `fast-remove`, `apply`, `block-updates` and `unblock-updates`. -```bash -spicetify backup apply -``` +On macOS the handler is a small app bundle, because macOS delivers URL activations as an Apple Event that a bare binary cannot receive. Its output goes to `protocol.log` in the config folder, which is the only place to see what an invocation did. -### Theme Development +### `self-update` ```bash -# One-time: apply your theme -spicetify config current_theme MyTheme -spicetify apply - -# During development: watch for changes -spicetify watch +spicetify self-update ``` + +Updates the CLI and TUI to the latest release. Downloads are checksum-verified. If you installed through a package manager, update through that instead. diff --git a/src/content/docs/cli/index.md b/src/content/docs/cli/index.md index 66d10a51..c3cb3ba0 100644 --- a/src/content/docs/cli/index.md +++ b/src/content/docs/cli/index.md @@ -1,36 +1,60 @@ --- title: CLI Reference -description: Command-line interface reference for Spicetify. +description: The Spicetify v3 command line, and the handful of commands you actually need. +sidebar_position: 1 +category_index: true --- -Spicetify is primarily a command-line tool. This section covers the command reference. +The v3 CLI patches Spotify, manages modules, and controls the daemon. Day to day you need two commands. -## Quick Start +```bash +spicetify apply # patch Spotify (also the fix for anything that looks wrong) +spicetify restore # put stock Spotify back +``` -After [installing Spicetify](/docs/getting-started), the basic workflow is: +Everything else is for a specific situation. [Commands](/docs/cli/commands) documents all of them. -```bash -# Generate config (first run only) -spicetify +--- -# Backup Spotify and apply Spicetify -spicetify backup apply +## First run -# After making changes, apply them +```bash spicetify apply ``` -## Getting Help +There is no backup step and no config generation step to remember. `apply` stops Spotify, patches it, sets up the daemon, registers the `spicetify://` handler and starts Spotify again. The backup is Spotify's own archive renamed in place, which is why `restore` needs nothing from you. -For help on any command: +--- + +## Getting help + +Every command prints its own usage, and that output is the authority if this page and the binary ever disagree: ```bash spicetify --help -spicetify --help +spicetify pkg --help +spicetify pkg install --help ``` -Example: +--- + +## Global options -```bash -spicetify --help config -``` +These work on any command: + +| Option | Meaning | +| --- | --- | +| `-m`, `--mirror [true\|false]` | Mirror mode for this invocation | +| `--spotify-data-dir ` | Where Spotify's data folder is | +| `--spotify-exec ` | Which Spotify binary to use | +| `--offline-bnk-dir ` | Where the offline cache is | +| `-h`, `--help` | Print help | +| `-V`, `--version` | Print the version | + +The path options exist because auto-detection can be wrong on unusual installs. They override [`config.toml`](/docs/modules/config-file) for that one command without changing it. + +--- + +## Do not mix v2 and v3 + +The two CLIs keep their backups differently and must never be pointed at the same client. Both refuse when they detect the other's work, but restore with whichever one you applied with before switching. diff --git a/src/content/docs/customization/themes.md b/src/content/docs/customization/themes.md deleted file mode 100644 index 097cc850..00000000 --- a/src/content/docs/customization/themes.md +++ /dev/null @@ -1,146 +0,0 @@ ---- -title: Themes -description: Change Spotify's visual appearance with themes. ---- - -Themes let you customize Spotify's colors, fonts, and visual styling. From subtle tweaks to complete visual overhauls, themes are one of the most popular Spicetify features. - -## Installing Themes - -### Via Marketplace - -The easiest way to install themes is through the **[Marketplace](./marketplace)**. Open Marketplace from Spotify's sidebar and browse the Themes tab. - -### Manual Installation - -For themes not in Marketplace: - -1. Download the theme folder (should contain `color.ini` and `user.css`) - -2. Place it in your Themes folder: - - | Platform | Path | - |----------|------| - | **Windows** | `%appdata%\spicetify\Themes\` | - | **Linux / macOS** | `~/.config/spicetify/Themes/` | - -3. Apply the theme: - - ```bash - spicetify config current_theme - spicetify apply - ``` - -### Color Schemes - -Many themes include multiple color schemes. To use a specific scheme: - -```bash -spicetify config current_theme color_scheme -spicetify apply -``` - -Check the theme's `color.ini` file to see available schemes (each `[SectionName]` is a scheme). - -## Removing Themes - -To go back to Spotify's default appearance, set the theme to an empty value: - -```bash -spicetify config current_theme "" -spicetify apply -``` - -Or set a different theme name. - ---- - -## Community Themes - -The Spicetify community has created many themes. Here are some popular repositories: - -### Official Repository - -The official collection, open for contributions: - -- **[spicetify/spicetify-themes](https://github.com/spicetify/spicetify-themes)** - -### Popular Third-Party Themes - -| Theme | Description | -|-------|-------------| -| [Comfy](https://github.com/NYRI4/Comfy-spicetify) | Comfortable, modern design | -| [Fluent](https://github.com/williamckha/spicetify-fluent) | Windows 11 Fluent Design style | -| [Catppuccin](https://github.com/catppuccin/spicetify) | Soothing pastel color palette | -| [Bloom](https://github.com/nimsandu/spicetify-bloom) | Gradient-heavy, vibrant look | -| [Lucid](https://github.com/sanoojes/spicetify-lucid) | Clean, minimal aesthetic | -| [Gruvify](https://github.com/Skaytacium/Gruvify) | Gruvbox color scheme | -| [Spotify Dark](https://github.com/SyndiShanX/Spotify-Dark) | Enhanced dark theme | -| [Throwback](https://github.com/bluedrift/Spicetify-Throwback) | Classic Spotify look | -| [SpicetifyCat](https://github.com/Adrien5902/SpicetifyCat) | Cat-themed customization | -| [RetroPlayer](https://github.com/Seglats/Spicetify-retro) | Retro aesthetic | -| [Dracula (m0squdev)](https://github.com/m0squdev/dracula-spicetify-theme) | Dracula color palette | - -### Unmaintained (May Not Work) - -These themes may not work with current Spotify versions: - -- [Nord-Spotify](https://github.com/Tetrax-10/Nord-Spotify) -- [Dribbblish Dynamic](https://github.com/JulienMaille/dribbblish-dynamic-theme) -- [Dracula (official)](https://github.com/dracula/spicetify) - ---- - -## Creating Themes - -Want to create your own theme? See the **[Theme Development Guide](/docs/development/themes)**. - -A basic theme needs two files: - -- `color.ini`: Color values that become CSS variables -- `user.css`: Custom CSS rules - -Example `color.ini`: - -```ini -[Base] -main = 121212 -sidebar = 000000 -player = 181818 -card = 282828 -shadow = 000000 -selected-row = 797979 -button = 1db954 -button-active = 1ed760 -text = ffffff -subtext = b3b3b3 -``` - ---- - -## Troubleshooting - -### Theme not applying - -1. Make sure the theme folder name matches what you used in `spicetify config` -2. Verify the folder contains both `color.ini` and `user.css` -3. Run `spicetify apply` after any config changes - -### Theme looks broken after Spotify update - -After Spotify updates, themes may need adjustment. Try: - -```bash -spicetify restore backup apply -``` - -If issues persist, check the theme's repository for updates or reported issues. - -### Colors not changing - -Make sure you're setting both the theme and color scheme: - -```bash -spicetify config current_theme MyTheme color_scheme Dark -spicetify apply -``` diff --git a/src/content/docs/development/api-wrapper/index.md b/src/content/docs/development/api-wrapper/index.md index 31db7c9b..c1fe3eb1 100644 --- a/src/content/docs/development/api-wrapper/index.md +++ b/src/content/docs/development/api-wrapper/index.md @@ -3,6 +3,11 @@ title: API Wrapper description: 🧰 Everything you need to know about the Spicetify object and API Wrapper. --- +:::note +This reference covers the `Spicetify` global, which both v2 extensions and v3 modules use: v3 serves the same surface through its compatibility wrapper. Pages carry a warning where v3 differs, checked against a running v3 client on Spotify 1.2.94. + +If you are writing a v3 module, [Building a module](/docs/development/building-a-module) is the place to start; this is the reference for what you can call once you are there. +::: Making an extension from scratch can be a daunting task. Luckily, Spicetify provides a powerful API Wrapper that makes it easy to interact with Spotify's internal APIs as well as provide out-of-the-box methods to help you easily create extensions. ## Spicetify Object diff --git a/src/content/docs/development/api-wrapper/methods/graphql.md b/src/content/docs/development/api-wrapper/methods/graphql.md index 7f0c3fcb..acf240d5 100644 --- a/src/content/docs/development/api-wrapper/methods/graphql.md +++ b/src/content/docs/development/api-wrapper/methods/graphql.md @@ -3,6 +3,12 @@ title: GraphQL description: GraphQL API Wrapper --- +:::warning +`Spicetify.GraphQL.Definitions` is empty in Spicetify v3, verified against a running client. v2 filled it by scanning the client bundle for query definitions; the v3 wrapper initialises it empty and nothing populates it, so the `const { someQuery } = Spicetify.GraphQL.Definitions` pattern below yields `undefined`. + +`Context`, `Handler` and `Request` are all present, so a definition you hold yourself still works. Until definitions are captured again, get the data through a native `Spicetify.Platform.*API` where one exists. +::: + GraphQL API Wrapper used throughout the Spotify client. It is used to communicate with their GraphQL API endpoints for tailored operations such as fetching data, or making mutations. :::caution diff --git a/src/content/docs/development/api-wrapper/methods/panel.md b/src/content/docs/development/api-wrapper/methods/panel.md index 87508a13..b00248e0 100644 --- a/src/content/docs/development/api-wrapper/methods/panel.md +++ b/src/content/docs/development/api-wrapper/methods/panel.md @@ -3,6 +3,10 @@ title: Panel description: API wrapper to interact with Spotify's panel/right sidebar. --- +:::warning +Not available in Spicetify v3. The v3 wrapper exposes no `Spicetify.Panel` and no `Platform.PanelAPI`, verified against a running client. Modules register a panel through stdlib's panel register instead: see [Building a module](/docs/development/building-a-module). This page documents the v2 API. +::: + Spicetify provides a wrapper for the Spotify `PanelAPI` method to make it easier to use, as well as providing a method to help you register your own panel. ```ts diff --git a/src/content/docs/development/api-wrapper/methods/platform.md b/src/content/docs/development/api-wrapper/methods/platform.md index 77dfdc16..29b60598 100644 --- a/src/content/docs/development/api-wrapper/methods/platform.md +++ b/src/content/docs/development/api-wrapper/methods/platform.md @@ -305,12 +305,11 @@ Contains data about the current platform, such as the current Spotify client ver interface PlatformData { app_platform: string; client_capabilities: Record; - client_version_triple: string; - client_version_quadruple: string; - client_version_quintuple: string; + client_variant: string; event_sender_context_information: Record; - os_name: string; + is_developer_mode: boolean; os_version: string; + remote_config_client_id: string; } ``` @@ -328,9 +327,13 @@ Spicetify.Platform.PlatformData.app_platform; // "win32" An object containing the current client capabilities. This usually contains information relating to functionality inside the Spotify client, such as whether or not the client can autostart. -#### `client_version_triple`, `client_version_quadruple`, `client_version_quintuple` +#### `client_variant` and `is_developer_mode` -The current Spotify client version. Usually in the format `1.2.8`, `1.2.8.923`, or `1.2.8.923.g4f94bf0d`. +Which build of the client is running, and whether developer mode is on (`spicetify dev` turns it on). + +:::note +The `client_version_triple`, `client_version_quadruple` and `client_version_quintuple` fields documented here previously are not on this object on current clients. Read `Spicetify.Platform.version` for the client version instead. Verified against a running client. +::: #### `event_sender_context_information` @@ -344,14 +347,19 @@ Spicetify.Platform.PlatformData.event_sender_context_information; // { "platform This could also help you diagnose issues with your custom apps. For example, if you're using a custom app on Windows and you're getting an error, you can check the `event_sender_context_information` object to see if the `platform_type` is `windows` or `macos`. -#### `os_name` +#### `os_version` and `app_platform` + +:::note +There is no `os_name`. The platform is carried by `app_platform` (`OSX_ARM64`, `WIN32_X86_64`, and so on), with `os_version` beside it. Verified against a running client. +::: The current operating system. Example: ```ts -Spicetify.Platform.PlatformData.os_name; // "windows" +Spicetify.Platform.PlatformData.os_version; // "26.5.2" +Spicetify.Platform.PlatformData.app_platform; // "OSX_ARM64" ``` #### `os_version` diff --git a/src/content/docs/development/api-wrapper/methods/player.md b/src/content/docs/development/api-wrapper/methods/player.md index aba421d3..cf103c46 100644 --- a/src/content/docs/development/api-wrapper/methods/player.md +++ b/src/content/docs/development/api-wrapper/methods/player.md @@ -70,7 +70,7 @@ Spicetify.Player.data; #### Return -[`PlayerState`](/docs/development/api-wrapper/types/player-state.md) +[`PlayerState`](/docs/development/api-wrapper/types/player-state) #### Example diff --git a/src/content/docs/development/api-wrapper/methods/uri.md b/src/content/docs/development/api-wrapper/methods/uri.md index e56e08cc..155be897 100644 --- a/src/content/docs/development/api-wrapper/methods/uri.md +++ b/src/content/docs/development/api-wrapper/methods/uri.md @@ -304,7 +304,7 @@ For a list of all validation functions, see [`Validation functions`](/docs/devel :::caution -Almost all playlists use the `playlist-v2` type, so use `Spicetify.URI.isPlaylistV2URI` instead. +Almost all playlists use the `playlist-v2` type, so use `Spicetify.URI.isPlaylistV2` instead. Similarly, you can use `Spicetify.URI.isPlaylistV1OrV2` to check if a URI is a playlist of any version. diff --git a/src/content/docs/development/api-wrapper/properties/react-components.md b/src/content/docs/development/api-wrapper/properties/react-components.md index eb8112a8..1b7e2645 100644 --- a/src/content/docs/development/api-wrapper/properties/react-components.md +++ b/src/content/docs/development/api-wrapper/properties/react-components.md @@ -370,6 +370,10 @@ const Slider = () => { ### `PanelSkeleton`, `PanelContent`, `PanelHeader` +:::warning +Not available in Spicetify v3, along with the rest of the panel surface. Verified against a running client. +::: + Components to render Spotify-style panel. Used by Spotify on their right sidebar panels (e.g. BuddyFeed, Now Playing, etc). Refer to [`Panel.Components`](/docs/development/api-wrapper/methods/panel#components) for more details. \ No newline at end of file diff --git a/src/content/docs/development/api-wrapper/properties/react-hook.md b/src/content/docs/development/api-wrapper/properties/react-hook.md index c1737887..2fbd8229 100644 --- a/src/content/docs/development/api-wrapper/properties/react-hook.md +++ b/src/content/docs/development/api-wrapper/properties/react-hook.md @@ -77,6 +77,10 @@ const DraggableComponent = () => { ### `usePanelState` +:::warning +Not available in Spicetify v3, which exposes only `DragHandler` and `useExtractedColor` on `Spicetify.ReactHook`. Verified against a running client. +::: + React Hook to use panel state. ```ts diff --git a/src/content/docs/development/api-wrapper/types/graphql/query.md b/src/content/docs/development/api-wrapper/types/graphql/query.md index 0c09de00..3844f20b 100644 --- a/src/content/docs/development/api-wrapper/types/graphql/query.md +++ b/src/content/docs/development/api-wrapper/types/graphql/query.md @@ -3,6 +3,10 @@ title: Query description: List of GraphQL definitions used by Spotify. --- +:::warning +The operation names listed here came from `Spicetify.GraphQL.Definitions`, which is empty in Spicetify v3. Treat this page as a record of what the client used to expose rather than a list you can index into today. See [GraphQL](/docs/development/api-wrapper/methods/graphql). +::: + :::note This type is deducted from Spotify's internal usage. It may not be accurate and may change in the future. diff --git a/src/content/docs/development/api-wrapper/types/panel/panel-props.md b/src/content/docs/development/api-wrapper/types/panel/panel-props.md index 697610b3..ce2d188f 100644 --- a/src/content/docs/development/api-wrapper/types/panel/panel-props.md +++ b/src/content/docs/development/api-wrapper/types/panel/panel-props.md @@ -3,6 +3,10 @@ title: PanelProps description: Properties that are used by the `registerPanel` function. --- +:::warning +Not available in Spicetify v3. The v3 wrapper exposes no `Spicetify.Panel` and no `Platform.PanelAPI`, verified against a running client. Modules register a panel through stdlib's panel register instead: see [Building a module](/docs/development/building-a-module). This page documents the v2 API. +::: + ```ts type PanelProps = { label?: string; diff --git a/src/content/docs/development/api-wrapper/types/react-component/panel-content-props.md b/src/content/docs/development/api-wrapper/types/react-component/panel-content-props.md index c36acfec..51e28c06 100644 --- a/src/content/docs/development/api-wrapper/types/react-component/panel-content-props.md +++ b/src/content/docs/development/api-wrapper/types/react-component/panel-content-props.md @@ -3,6 +3,10 @@ title: PanelContentProps description: Type definition for props of ReactComponent.PanelContent. --- +:::warning +Not available in Spicetify v3. The v3 wrapper exposes no `Spicetify.Panel` and no `Platform.PanelAPI`, verified against a running client. Modules register a panel through stdlib's panel register instead: see [Building a module](/docs/development/building-a-module). This page documents the v2 API. +::: + The `PanelContentProps` object is used to render a panel content wrapper. :::note diff --git a/src/content/docs/development/api-wrapper/types/react-component/panel-header-props.md b/src/content/docs/development/api-wrapper/types/react-component/panel-header-props.md index ca2cdb20..f6c9095a 100644 --- a/src/content/docs/development/api-wrapper/types/react-component/panel-header-props.md +++ b/src/content/docs/development/api-wrapper/types/react-component/panel-header-props.md @@ -3,6 +3,10 @@ title: PanelHeaderProps description: Type definition for props of ReactComponent.PanelHeader. --- +:::warning +Not available in Spicetify v3. The v3 wrapper exposes no `Spicetify.Panel` and no `Platform.PanelAPI`, verified against a running client. Modules register a panel through stdlib's panel register instead: see [Building a module](/docs/development/building-a-module). This page documents the v2 API. +::: + The `PanelHeaderProps` object is used to render a panel header. :::note diff --git a/src/content/docs/development/api-wrapper/types/react-component/panel-skeleton-props.md b/src/content/docs/development/api-wrapper/types/react-component/panel-skeleton-props.md index 19ca30cc..635d2f3a 100644 --- a/src/content/docs/development/api-wrapper/types/react-component/panel-skeleton-props.md +++ b/src/content/docs/development/api-wrapper/types/react-component/panel-skeleton-props.md @@ -3,6 +3,10 @@ title: PanelSkeletonProps description: Type definition for props of ReactComponent.PanelSkeleton. --- +:::warning +Not available in Spicetify v3. The v3 wrapper exposes no `Spicetify.Panel` and no `Platform.PanelAPI`, verified against a running client. Modules register a panel through stdlib's panel register instead: see [Building a module](/docs/development/building-a-module). This page documents the v2 API. +::: + The `PanelSkeletonProps` object is used to render a panel skeleton. :::note diff --git a/src/content/docs/development/building-a-module.md b/src/content/docs/development/building-a-module.md new file mode 100644 index 00000000..9ae28059 --- /dev/null +++ b/src/content/docs/development/building-a-module.md @@ -0,0 +1,153 @@ +--- +title: Building a module +description: Scaffold, run and ship a Spicetify v3 module. +sidebar_position: 2 +--- + +A module is the one thing you build for Spicetify v3. It can add a button, register a whole page, restyle the client, or all three. This page is the path from nothing to something running in your client; [the module standard](https://github.com/spicetify/modules/blob/main/docs/module-standard.md) is the contract it has to meet. + +--- + +## Scaffold and run + +```bash +npm create spicetify-module my-module +cd my-module +npm run dev -- --launch +``` + +`dev` rebuilds on every save and pushes the result into a running Spotify in about a second, with no re-apply and no restart. `--launch` starts (or reuses) Spotify with the remote debugging port; without it, start Spotify yourself with `--remote-debugging-port=9229`. + +Drop the pushed override when you are done: + +```js +Spicetify.Modules.removeLocal('my-module'); +``` + +Templates: `--template basic` (a button and a route), `extension` (behaviour only), `app` (a nav entry and a full page), `theme` (CSS only, no TypeScript). + +--- + +## What the scaffold gives you + +| File | Role | +| --- | --- | +| `metadata.json` | id, version, entries, dependencies, and the store card's data | +| `index.ts` | the loader entry shim, leave it alone | +| `mod.tsx` | your module | +| `logic.ts` | dependency-free logic, unit testable in Node | +| `index.scss` | styles, adopted as a stylesheet and removed on unload | + +--- + +## The entry point + +A module default-exports one function, and the loader awaits it: + +```ts +import { createRegistrar } from '/modules/stdlib/mod.ts'; +import type { ModuleRuntimeContext } from '/modules/stdlib/mod.ts'; + +export default async function (ctx: ModuleRuntimeContext) { + const registrar = createRegistrar(ctx); + // register your UI here + ctx.defer(() => { + // clear the timers, listeners and overlays you own + }); +} +``` + +Two rules that matter more than they look: + +:::danger +**Bound every wait.** The loader awaits this function, so an unbounded `await` (polling for a DOM node that never appears, for instance) hangs the loader and every other module silently fails to load with it. Cap the tries and degrade. +::: + +:::warning +**Undo everything on unload.** Modules load and unload at runtime. The registrar removes what you registered and the stylesheet it adopted; the timers, subscriptions and overlays you created yourself are yours to clear through `ctx.defer`. A module that lingers after a reload is a bug. +::: + +--- + +## Adding UI + +**Buttons** go through `placeButton`, which handles ordering and placement for you: + +```ts +registrar.placeButton('playbar', { + label: 'Loop section', + icon: LOOP_ICON, + onClick: toggleLoop, + near: { anchor: 'playbar:queue', side: 'before' }, +}); +``` + +Locations are `topbar-left`, `topbar-right` and `playbar`. `near` places the button next to one of Spotify's own controls by a stable name (`playbar:lyrics`, `playbar:queue`, `playbar:mute`, `playbar:miniplayer`, `playbar:fullscreen`) rather than a selector that changes with every client build. If the anchor cannot be found the button falls back to ordinary placement, so it is never hidden. + +**A page** is a nav entry plus a route: + +```ts +registrar.register( + 'navlink', + , +); +registrar.registerRoute(ROUTE, ); +``` + +**Settings** rows from every module render together under one Spicetify section in Spotify's own settings page, so a module with a single toggle does not need a page of its own. + +--- + +## The Spicetify global + +`Spicetify` is typed and available without importing anything, and it is the same surface v2 extensions used: `Player`, `Platform`, `URI`, `React`, `SVGIcons`, `CosmosAsync`, `GraphQL`, `Menu`, `PopupModal`, `LocalStorage` and the rest. See the [API reference](/docs/development/api-wrapper). + +Reach for a native `Platform.*API` before making an HTTP call of your own. + +--- + +## Class names + +Spotify's own class names are hashed and change with every client build, so never hardcode one. Reference them through `MAP`: + +```ts +const cls = MAP.main.topbar.right.button_t.wrapper; +``` + +Modules ship with those references intact and the CLI resolves them at apply time against the exact Spotify version installed, which is why one build of your module works on every supported client. `classmap.d.ts` is generated for you, so the paths autocomplete. + +--- + +## Testing + +Put anything worth testing in `logic.ts`, free of client imports, and inject the client objects from `mod.tsx`. Then: + +```bash +npm run check # typecheck +npm run test # unit tests +``` + +UI is verified live through the dev loop, because JSX and the client's runtime URLs do not resolve in Node. + +--- + +## Build and pack + +```bash +spicetify-kit build # bundles TS/TSX and compiles index.scss +spicetify-kit pack dist/my-module@1.0.0 # zips it and prints the sha256 +``` + +`build` enforces the standard's error tier: bad metadata or a missing loader shim aborts the build rather than producing something that fails at boot. `spicetify-kit check` runs the same audit on its own. + +To sideload a packed build into a running client without publishing: + +```bash +spicetify-kit install my-module@1.0.0.zip +``` + +--- + +## Then publish + +[Publishing](/docs/development/publishing) covers getting it into the store. diff --git a/src/content/docs/development/compiling.md b/src/content/docs/development/compiling.md index bdd65230..012c3af1 100644 --- a/src/content/docs/development/compiling.md +++ b/src/content/docs/development/compiling.md @@ -1,33 +1,48 @@ --- title: Compiling -description: 🧰 Compiling Spicetify. +description: Build the Spicetify CLI from source. --- -### Requirements +v3 is written in Rust. Most people should [install a release](/docs/getting-started); build from source to work on Spicetify itself, or to run it on a platform with no published build. -- [Go](https://golang.org/dl/) +## Requirements -Clone repo and download dependencies: +- [Rust](https://rustup.rs) (stable) +- [Node](https://nodejs.org) 24 and pnpm, for the browser-side payload + +## Build ```bash -cd $HOME -mkdir spicetify -cd spicetify git clone https://github.com/spicetify/cli +cd cli +pnpm install +pnpm build:payload +cargo build --release -p cli -p daemon ``` -### Build +The binaries land in `rust/target/release/` as `spicetify` and `spicetify-daemon`. Put that directory on your `PATH`. + +:::warning +`pnpm build:payload` is not optional, and it has to run before `cargo build`. The payload that runs inside Spotify is compiled into the binary, so a binary built without it refuses to apply rather than patching the client with nothing in it. After changing anything under `src/jsHelper/`, run both again. +::: -#### Windows +## Working on the daemon -```powershell -cd $HOME\spicetify\cli -go build -o spicetify.exe +The daemon is its own crate, so `cargo build -p cli` does not rebuild it, and `apply` leaves an already-running daemon alone when the version has not changed, which it will not have during local work. After touching the daemon, restart it explicitly: + +```bash +cargo build --release -p cli -p daemon +spicetify daemon stop && spicetify daemon start ``` -#### Linux and MacOS +Skipping the restart means testing the previous build. + +## Building v2 + +v2 is written in Go and lives on the same repository's `main` branch: ```bash -cd ~/spicetify/cli +git clone https://github.com/spicetify/cli +cd cli go build -o spicetify ``` diff --git a/src/content/docs/development/custom-apps.md b/src/content/docs/development/custom-apps.md deleted file mode 100644 index f7980483..00000000 --- a/src/content/docs/development/custom-apps.md +++ /dev/null @@ -1,156 +0,0 @@ ---- -title: Custom Apps -description: 🔧 Creating Custom Apps. ---- - -## App Structure - -```text -~/.config/spicetify/CustomApps/ # Linux/macOS -%appdata%\spicetify\CustomApps\ # Windows -└── my-app/ - ├── index.js # Required: main entry with render() - ├── manifest.json # Required: app metadata and icons - ├── style.css # Optional: custom styles - └── src/ # Optional: additional JS files - ├── Component.js - └── ... -``` - -## How to Start - -- Make a new folder for your custom app in your [CustomApps folder](/docs/customization/custom-apps). You'll install it like any other custom app. -- Create an `index.js` and a `manifest.json` inside that folder. -- The `index.js` file is the main file for the custom app. -- The manifest includes some important information to make the custom app work. - -## Manifest file - -- Your custom app needs a `manifest.json` file in the root folder with the following keys: -- `name`: The name of the custom app. -- `icon`: The escaped SVG markup for the sidebar icon. -- `active-icon`: The escaped SVG markup for the active status of the sidebar icon (when your custom app is open). -- `subfiles`: You can optionally include other JS files. These files will be concatenated together in the order defined here. Any variables you declare in the main `index.js`, or any subfiles will be accessible from all. This is useful for organizational purposes for more complex custom apps. -- `subfiles_extension`: You can optionally include one or more extensions with your custom app. These are treated as regular extensions, and will run when Spotify starts. - -_Note: The `subfiles` can be in nested folders, while any `subfiles_extension` can not._ - -```json -{ - "name": "My Custom App", - "icon": "", - "active-icon": "", - "subfiles": ["src/Subfile.js", "src/Subfile2.js"], - "subfiles_extension": ["my_extension.js"] -} -``` - -## Creating your index.js - -Custom apps are written in [React](https://reactjs.org). You'll need to grab some React references from the `Spicetify` object, set up a component for your page, and return that component from a `render()` function in the main body of the file. Sadly, this does not support [`jsx`](https://reactjs.org/docs/introducing-jsx.html), so you must use [`react.createElement`](https://reactjs.org/docs/react-api.html#createelement). - -Example: - -```js -// Grab any variables you need -const react = Spicetify.React; -const reactDOM = Spicetify.ReactDOM; -const { - URI, - React: { useState, useEffect, useCallback }, - Platform: { History }, -} = Spicetify; - -// The main custom app render function. The component returned is what is rendered in Spotify. -function render() { - return react.createElement(Grid, { title: "My Custom App" }); -} - -// Our main component -class Grid extends react.Component { - constructor(props) { - super(props); - Object.assign(this, props); - this.state = { - foo: "bar", - data: "etc" - }; - } - - render() { - return react.createElement("section", { - className: "contentSpacing", - }, - react.createElement("div", { - className: "marketplace-header", - }, react.createElement("h1", null, this.props.title), - ), - ), react.createElement("div", { - id: "marketplace-grid", - className: "main-gridContainer-gridContainer", - "data-tab": CONFIG.activeTab, - style: { - "--minimumColumnWidth": "180px", - }, - }, [...cardList]), - react.createElement("footer", { - style: { - margin: "auto", - textAlign: "center", - }, - }, !this.state.endOfList && (this.state.rest ? react.createElement(LoadMoreIcon, { onClick: this.loadMore.bind(this) }) : react.createElement(LoadingIcon)), - ), react.createElement(TopBarContent, { - switchCallback: this.switchTo.bind(this), - links: CONFIG.tabs, - activeLink: CONFIG.activeTab, - })); - } -} -``` - -## Common questions: - -### My custom app isn't running when Spotify starts - -Your custom app will only run when it is clicked on the sidebar and its page loads. In order to run code on startup, you need to include a separate JS file as `subfiles_extension` in your [manifest](#manifest-file). - -### My subfile extension can't read my variables from my custom app - -Any subfile extensions are loaded separately from the main custom app, and do not have access to variables. You can use `localStorage` to save/load data between the two. - -### How can I add a new sub page or path to my custom app? - -You can use `Spicetify.Platform.History.push(...)` to navigate to a new page. This can be a standard Spotify page, or a custom page for your app. You can include any data you need in the `state` key. - -```js -Spicetify.Platform.History.push({ - pathname: '/marketplace/readme', - state: { - data: { - title: 'My sub page title', - content: 'My sub page content', - }, - }, -}); -``` - -In order to render a different page, you can check the `pathname` of the current page within `index.js`'s main render method, and render a different page component for different paths. The main path for your custom app will be the name of the folder (which is the same that needs to be used in the [`config-xpui.ini`](/docs/development/themes) configuration file). -In this example, if our `pathname` is "/marketplace/readme", we load the `ReadmePage` component, otherwise we load our main page component, `Grid`. - -```js -function render() { - const { location } = Spicetify.Platform.History; - - // If page state set to display readme, render it - // (This location state data comes from your Spicetify.Platform.History.push() call - if (location.pathname === '/marketplace/readme') { - return react.createElement(ReadmePage, { - title: 'Spicetify Marketplace - Readme', - data: location.state.data, - }); - } // Otherwise, render the main Grid - else { - return react.createElement(Grid, { title: 'Spicetify Marketplace' }); - } -} -``` diff --git a/src/content/docs/development/extensions.md b/src/content/docs/development/extensions.md deleted file mode 100644 index 34a195d1..00000000 --- a/src/content/docs/development/extensions.md +++ /dev/null @@ -1,288 +0,0 @@ ---- -title: Extensions -description: Creating extensions for Spicetify. ---- - -Extensions are JavaScript files that run alongside Spotify's main code. They can add UI elements, modify behavior, integrate with external services, and more. - -## Extension Structure - -```text -~/.config/spicetify/Extensions/ # Linux/macOS -%appdata%\spicetify\Extensions\ # Windows -├── myExtension.js -├── anotherExtension.js -└── ... -``` - -Extensions are single JavaScript files. For complex extensions with multiple files, use [Spicetify Creator](/docs/development/spicetify-creator/the-basics). - -## Getting Started - -### Location - -Place your extension file in the Extensions folder: - -| Platform | Path | -|----------|------| -| **Windows** | `%appdata%\spicetify\Extensions\` | -| **Linux / macOS** | `~/.config/spicetify/Extensions/` | - -### Minimal Extension - -```js -// myExtension.js -(function myExtension() { - // Wait for Spicetify to be ready - if (!Spicetify.Player || !Spicetify.Platform) { - setTimeout(myExtension, 100); - return; - } - - console.log("My extension loaded!"); - - // Your code here -})(); -``` - -### Testing Your Extension - -1. Save your file to the Extensions folder -2. Enable it: - - ```bash - spicetify config extensions myExtension.js - spicetify apply - ``` - -3. Open DevTools in Spotify to see console output: - - **Windows/Linux**: Ctrl + Shift + I - - **macOS**: Cmd + Option + I - - ---- - -## The Spicetify API - -Extensions access Spotify through the global `Spicetify` object. Key namespaces: - -| Namespace | Purpose | -|-----------|---------| -| `Spicetify.Player` | Playback control (play, pause, skip, volume) | -| `Spicetify.Platform` | Core platform APIs (history, library, playlists) | -| `Spicetify.CosmosAsync` | HTTP requests to Spotify's internal API | -| `Spicetify.URI` | Parse and create Spotify URIs | -| `Spicetify.React` | React instance for UI components | -| `Spicetify.ReactDOM` | ReactDOM for rendering | -| `Spicetify.Topbar` | Add buttons to the top bar | -| `Spicetify.Playbar` | Add buttons to the player bar | -| `Spicetify.ContextMenu` | Add items to right-click menus | - -See the **[API Wrapper Reference](/docs/development/api-wrapper)** for complete documentation. - ---- - -## Common Patterns - -### Waiting for Spicetify - -Always wait for Spicetify to be ready before accessing its APIs: - -```js -(function init() { - if (!Spicetify.Player || !Spicetify.Platform) { - setTimeout(init, 100); - return; - } - - main(); -})(); - -function main() { - // Safe to use Spicetify here -} -``` - -### Adding a Top Bar Button - -```js -const button = new Spicetify.Topbar.Button( - "My Button", - `...`, // SVG icon - () => { - console.log("Button clicked!"); - } -); -``` - -### Adding a Context Menu Item - -```js -new Spicetify.ContextMenu.Item( - "My Menu Item", - (uris) => { - console.log("Selected URIs:", uris); - }, - (uris) => { - // Return true to show this item - return true; - }, - `...` // Optional icon -).register(); -``` - -### Listening to Player Events - -```js -Spicetify.Player.addEventListener("songchange", (event) => { - const track = Spicetify.Player.data?.item; - console.log("Now playing:", track?.name); -}); -``` - -### Making API Requests - -```js -// GET request -const response = await Spicetify.CosmosAsync.get( - "https://api.spotify.com/v1/me/player" -); - -// POST request -await Spicetify.CosmosAsync.post( - "https://api.spotify.com/v1/me/player/play", - { uris: ["spotify:track:..."] } -); -``` - -### Storing Data - -```js -// Save -Spicetify.LocalStorage.set("myExtension:setting", "value"); - -// Load -const value = Spicetify.LocalStorage.get("myExtension:setting"); -``` - -### Showing Notifications - -```js -Spicetify.showNotification("Hello from my extension!"); -``` - ---- - -## UI Development - -### Using React - -Spicetify exposes React for building UI: - -```js -const { React, ReactDOM } = Spicetify; -const { useState, useEffect } = React; - -function MyComponent() { - const [count, setCount] = useState(0); - - return React.createElement("button", { - onClick: () => setCount(c => c + 1) - }, `Clicked ${count} times`); -} -``` - -### Using Spotify's Components - -Access Spotify's internal React components: - -```js -const { Button, Toggle } = Spicetify.ReactComponent; - -// Use in your components -React.createElement(Button, { - onClick: () => console.log("clicked") -}, "Click me"); -``` - -### Creating Modals - -```js -Spicetify.PopupModal.display({ - title: "My Modal", - content: React.createElement("div", null, "Hello!"), -}); -``` - ---- - -## Spicetify Creator - -For a better development experience with TypeScript, JSX, and hot reloading, use **[Spicetify Creator](/docs/development/spicetify-creator/the-basics)**. - -```bash -npx spicetify-creator -``` - -Benefits: -- TypeScript support with full type definitions -- JSX syntax (no manual `React.createElement`) -- Hot reloading during development -- Build tooling (bundling, minification) - ---- - -## Best Practices - -### Performance - -- Avoid polling. Use event listeners when possible -- Debounce expensive operations -- Clean up listeners when no longer needed - -### Error Handling - -```js -try { - const data = await Spicetify.CosmosAsync.get("..."); -} catch (error) { - console.error("Request failed:", error); - Spicetify.showNotification("Something went wrong", true); -} -``` - -### Compatibility - -- Test with both light and dark themes -- Check for API existence before using (APIs can change between Spotify versions) -- Use feature detection: - -```js -if (Spicetify.Topbar?.Button) { - // Safe to use -} -``` - -### Naming - -- Use a unique prefix for localStorage keys: `myExtension:key` -- Use descriptive function and variable names - ---- - -## Publishing - -To share your extension: - -1. **GitHub**: Create a repository with your extension file(s) and installation instructions - -2. **Marketplace**: Submit to the [Spicetify Marketplace](https://github.com/spicetify/marketplace) for easy discovery and installation - ---- - -## Resources - -- **[API Wrapper Reference](/docs/development/api-wrapper)**: Complete API documentation -- **[Spicetify Creator](/docs/development/spicetify-creator/the-basics)**: TypeScript development tool -- **[Built-in Extensions](https://github.com/spicetify/cli/tree/main/Extensions)**: Reference implementations -- **[Marketplace Extensions](https://github.com/spicetify/marketplace)**: Community examples diff --git a/src/content/docs/development/index.md b/src/content/docs/development/index.md index 0792b0dc..e3a994e7 100644 --- a/src/content/docs/development/index.md +++ b/src/content/docs/development/index.md @@ -1,7 +1,29 @@ --- title: Development -description: Learn how to create themes, extensions, and custom apps for Spicetify. +description: Build modules for Spicetify v3, and work on Spicetify itself. category_index: true --- -Learn how to create themes, extensions, and custom apps for Spicetify. +Everything you can add to Spotify in v3 is a module: a theme, an extension, a whole page in the sidebar. One format, one toolchain. + +## Building modules + +- [Building a module](/docs/development/building-a-module) takes you from `npm create spicetify-module` to something running in your client, through a dev loop that pushes changes in about a second. +- [Publishing a module](/docs/development/publishing) covers submitting it to the store and what CI checks before it merges. +- [The API reference](/docs/development/api-wrapper) documents the `Spicetify` global your module talks to. + +The rules a module has to follow live in the [module standard](https://github.com/spicetify/modules/blob/main/docs/module-standard.md), next to the code that enforces them. + +## Debugging the client + +- [React DevTools](/docs/development/react-devtools) for inspecting the client's component tree. +- [Spotify CLI flags](/docs/development/spotify-cli-flags) for the switches Spotify itself understands, including the remote debugging port the dev loop uses. +- `spicetify dev` turns on Inspect Element in the client. + +## Working on Spicetify + +- [Compiling](/docs/development/compiling) builds the CLI from source, which is currently how you run v3. + +## Coming from v2 + +Extensions, custom apps and Spicetify Creator are v2 concepts. They still work with v2 and their guides are in the [legacy section](/docs/legacy); in v3 all three are modules. [What changes in v3](/docs/whats-new) maps the old model onto the new one. diff --git a/src/content/docs/development/js-modules.md b/src/content/docs/development/js-modules.md deleted file mode 100644 index e556bd29..00000000 --- a/src/content/docs/development/js-modules.md +++ /dev/null @@ -1,153 +0,0 @@ ---- -title: Javascript NPM Modules -description: 📦 Using NPM Modules for Spicetify. ---- - -Since v0.9.8, Spicetify injects extension with file extension `.mjs` as a script with type="module" and automatically symlink `node_modules` folder found in user's Extensions folder to `zlink` app. - -In Javascript module, Javascript would work just the same as normal script but now you can use `import` to include other Javascript files. [Click here for details](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules). - -Node Package Manager (NPM) is a commandline app bundled with NodeJS. You can use it to download and install hundreds of utility packages to ease your development process. Since Spicetify symlinks `node_modules` to Spotify main app folder, all packages files are mutually linked and available for you to use in your extension. Simply just use `import`: - -```js -import './node_modules/package_name/file.js'; -``` - -Careful! Javascript file you import has to be supported by Browser, not NodeJS. In other words, that Javascript should not use `require` function to include other packages or NodeJS API. Whenever you decide to use a package, check its readme page to see if its author already export distributed file as ES6 Module. - -### Example - -For Japanese studying purpose, I'm developing an extension that shows the Romaji form of Japanese track titles or artist names. - -The idea is when I right click at track name and choose Show Romaji: - -![img1](/images/development/js-modules-1.png) - -Result should show as a notification: - -![img2](/images/development/js-modules-2.png) - -To translate Japanese text to Romaji, I use a package named [kuroshiro](https://github.com/hexenq/kuroshiro). Luckily, this package will export distribution files as ES6 Module. This is quite important because package itself relies on other utilities packages too. When it is compiled as an ES6 module, everything is transpiled to Browser supported Javascript and combined in one file. Moreover, kuroshiro also needs [kuroshiro-analyzer-kuromoji](https://github.com/hexenq/kuroshiro-analyzer-kuromoji) package to be usable, which relies on dictionaries gzip files. You can see there is no easy way to utiltise both packages and their external files if we use traditional Javascript extension. - -**Following are steps to make and install this extension from scratch:** - -1. First, change director to the user's `Extensions` folder: - -- **Windows, in Powershell:** - -```powershell -cd "$(dirname "$(spicetify -c)")/Extensions" -``` - -- **Linux and MacOS**, in Bash: - -``` -cd "$(dirname "$(spicetify -c)")/Extensions" -``` - -2. Now install needed packages with NPM - -```bash -npm install kuroshiro kuroshiro-analyzer-kuromoji -``` - -Go to user's `Extensions` folder, you can see `node_modules` folder is created and contains all installed packages files. Next, go to the `kuroshiro` and `kuroshiro-analyzer-kuromoji`folder to locate ES6 Module distribution files. - -3. After that I can comfortably include both of them in my extension. Following is extension code: - -**romaji.mjs**: - -```javascript -import './node_modules/kuroshiro/dist/kuroshiro.min.js'; -import './node_modules/kuroshiro-analyzer-kuromoji/dist/kuroshiro-analyzer-kuromoji.min.js'; - -const kuroshiro = new Kuroshiro.default(); -kuroshiro.init(new KuromojiAnalyzer()); - -function converter(input) { - return kuroshiro.convert(input, { - to: 'romaji', - mode: 'spaced', - romajiSystem: 'passport', - }); -} - -const fetchAlbum = async (uri) => { - const res = await Spicetify.CosmosAsync.get(`hm://album/v1/album-app/album/${uri.split(':')[2]}/desktop`); - return res.name; -}; - -const fetchShow = async (uri) => { - const res = await Spicetify.CosmosAsync.get( - `sp://core-show/v1/shows/${uri.split(':')[2]}?responseFormat=protobufJson`, - { - policy: { list: { index: true } }, - } - ); - return res.header.showMetadata.name; -}; - -const fetchArtist = async (uri) => { - const res = await Spicetify.CosmosAsync.get(`hm://artist/v1/${uri.split(':')[2]}/desktop?format=json`); - return res.info.name; -}; - -const fetchTrack = async (uri) => { - const res = await Spicetify.CosmosAsync.get(`https://api.spotify.com/v1/tracks/${uri.split(':')[2]}`); - return res.name; -}; - -const fetchEpisode = async (uri) => { - const res = await Spicetify.CosmosAsync.get(`https://api.spotify.com/v1/episodes/${uri.split(':')[2]}`); - return res.name; -}; - -const fetchPlaylist = async (uri) => { - const res = await Spicetify.CosmosAsync.get(`sp://core-playlist/v1/playlist/${uri}/metadata`, { - policy: { name: true }, - }); - return res.metadata.name; -}; - -async function showRomaji([uri]) { - const type = uri.split(':')[1]; - let name; - switch (type) { - case Spicetify.URI.Type.TRACK: - name = await fetchTrack(uri); - break; - case Spicetify.URI.Type.ALBUM: - name = await fetchAlbum(uri); - break; - case Spicetify.URI.Type.ARTIST: - name = await fetchArtist(uri); - break; - case Spicetify.URI.Type.SHOW: - name = await fetchShow(uri); - break; - case Spicetify.URI.Type.EPISODE: - name = await fetchEpisode(uri); - break; - case Spicetify.URI.Type.PLAYLIST: - case Spicetify.URI.Type.PLAYLIST_V2: - name = await fetchPlaylist(uri); - break; - } - if (Kuroshiro.default.Util.hasJapanese(name)) { - name = await converter(name); - name = name.replace(/(^|\s)\S/g, (t) => t.toUpperCase()); - } - Spicetify.showNotification(name); -} - -new Spicetify.ContextMenu.Item(`Show Romaji`, showRomaji).register(); -``` - -Save this file to `Extensions` folder. - -4. Finally, push my extension to Spotify: - -```bash -spicetify config extensions romaji.mjs -spicetify apply -``` diff --git a/src/content/docs/development/publishing.md b/src/content/docs/development/publishing.md new file mode 100644 index 00000000..a3a79edf --- /dev/null +++ b/src/content/docs/development/publishing.md @@ -0,0 +1,101 @@ +--- +title: Publishing a module +description: Submit a module to the Spicetify store, and what gets checked before it merges. +sidebar_position: 3 +--- + +The store reads one registry: `vault.json` in [spicetify/modules](https://github.com/spicetify/modules). Your code stays in your own repository under your own license; what you submit is the entry that points at it. That is what makes every module in the store reviewable, checksummed and revocable. + +--- + +## Submit + +Build, pack, upload the zip to your own release, then record the entry: + +```bash +spicetify-kit build +spicetify-kit pack dist/my-module@1.0.0 +# upload my-module@1.0.0.zip to your release, then: +spicetify-kit vault add dist/my-module@1.0.0 --artifact --zip my-module@1.0.0.zip +``` + +That writes `vault/my-module.json`, one file holding your module and nothing else. Open a pull request with it. + +Your `metadata.json` needs four things the registry will not accept an entry without: + +| Field | Why | +| --- | --- | +| `preview` | an absolute https URL to a screenshot. Store cards are artwork-first, and an entry with no preview renders no card at all | +| `repository` | an https URL to the source | +| `license` | an SPDX identifier, shown next to the install button | +| `name`, `version` | must match the entry, and the id is permanent | + +--- + +## Automate it + +Call the publish action from your own release workflow instead: + +```yaml +- uses: spicetify/modules/.github/actions/submit@main + with: + dist: dist/my-module@1.0.0 + release-tag: ${{ github.ref_name }} + token: ${{ secrets.SPICETIFY_SUBMIT_TOKEN }} +``` + +The token is one with `public_repo` scope on your own account: the action pushes a branch to your fork and opens the pull request with it. Leave it out and the action prints the exact entry for you to submit by hand, which is a fine way to start. + +--- + +## What gets checked + +Nothing in the pull request is taken on trust. CI downloads the artifact and checks the entry against what is actually inside it: + +- the checksum matches the bytes, and the artifact is served over https +- the `metadata.json` inside the artifact declares the same id and version as the entry +- the store card matches the artifact: an entry cannot claim a description, repository or license the code does not declare, and cannot invent fields either +- the artifact was produced by the toolchain rather than assembled by hand +- every dependency it names is already in the registry +- the zip contains no absolute paths, no `..` traversal and no symlinks +- **published versions are immutable**: an existing version cannot be rewritten or removed, and a new one has to be higher than every published one +- **an id stays with the account that first published it**: later artifacts must come from the same owner + +A red check is something to fix, not a conversation to have. First submission gets a human review as well; after that a green check is the gate. + +--- + +## Updating + +Same flow with a new version key. The old versions stay exactly as they are, which is what makes rolling back possible for the people who installed them. + +--- + +## After the merge + +1. CI rebuilds the aggregate registry, and your module appears in the store. +2. Your artifact is copied to a mirror release in the registry repository and the mirror URL is appended to your entry. Installers try your host first and fall back to the mirror, so a release asset that disappears later does not break every install of that version. + +--- + +## Choosing an id + +Ids are global and permanent. The first submission binds the id to your account and nothing else can publish it afterwards, so pick something you would be happy to keep. Make it describe what the module does; suffix themes with `-theme` and snippet collections with `-snippets`. + +--- + +## Snippets + +A CSS-only module small enough to have no artifact can ship inline in the registry entry. Inline entries install with no download, so they are restricted to `.css` files: anything executable arrives as a checksummed zip. + +--- + +## Distributing outside the store + +You do not have to use the store. Anyone can install a packed module directly: + +```bash +spicetify pkg install my-module https://example.com/my-module@1.0.0.zip +``` + +That bypasses the registry, so nothing verifies those bytes and the CLI tells the user as much. It is the right path for private builds, betas and testing, and the wrong one for anything you want people to find. diff --git a/src/content/docs/development/spicetify-creator/create-extensions.md b/src/content/docs/development/spicetify-creator/create-extensions.md deleted file mode 100644 index d8a8be60..00000000 --- a/src/content/docs/development/spicetify-creator/create-extensions.md +++ /dev/null @@ -1,44 +0,0 @@ ---- -title: Create Extensions -description: 🔨 Creating small addons for Spicetify. ---- - -Notes: - -- This tutorial assumes you have chosen to generate an example using Create Spicetify App. - -After creating a new Spicetify Creator project and choosing "Extension" as your app's type, your project's structure should look like this (With the generated example): - -``` -my-app/ - .gitattributes - .gitignore - package.json - README.md - tsconfig.json - yarn.lock - src/ - ... - node_modules/ - ... -``` - -For now, we only care about the `src/` folder, whose structure looks like this - -``` -src/ - app.tsx - settings.json - types/ - ... -``` - -`app.tsx` exports a function that will be executed every time Spotify starts up. -It comes with an example that says "Hello!" to the user when Spotify starts up. -`settings.json` is a simple JSON file containing 1 key: - -```json -{ - "nameId": "my-app" // The id of your app -} -``` diff --git a/src/content/docs/faq.md b/src/content/docs/faq.md index b10de052..3b1a797d 100644 --- a/src/content/docs/faq.md +++ b/src/content/docs/faq.md @@ -1,90 +1,119 @@ --- title: FAQ +sidebar_position: 7 --- +Answers for v3. If you are running the released v2 CLI, see the [v2 FAQ](/docs/legacy/faq). + ## Where is the config file? -The config file is generally located at: +`config.toml`, in Spicetify's config folder. To see exactly where: -| Platform | Path | -| ------------------- | ------------------------------------------ | -| **Windows** | `%appdata%\spicetify\config-xpui.ini` | -| **Linux**/**macOS** | `~/.config/spicetify/config-xpui.ini` | +```bash +spicetify config # prints the resolved paths +spicetify config open # opens the folder +spicetify path +``` -However, you can know specifically where it is with: +Every key is documented in the [configuration reference](/docs/modules/config-file). v3 has no `spicetify config `: edit the file. -``` -spicetify -c -``` +## Spotify updated and my client looks stock again -Or, you can open the folder where it is located by entering the following in your terminal: +Usually it fixes itself. The daemon notices Spotify updating and re-applies afterwards, so give it a moment and restart Spotify. +If it does not: + +```bash +spicetify daemon status # running? which version? +spicetify apply ``` -spicetify config-dir -``` -For details about each config field, please run: +`daemon status` first is worth the extra second: a daemon that is not running, or one still on an old version after you updated Spicetify, looks exactly like an apply that did not work. + +To stay on the build you have: ```bash -spicetify --help config +spicetify spotify-updates block ``` -## Cannot find `pref_file` +## A new Spotify version came out. Do I have to wait for a Spicetify release? -### Windows +Usually not. v3 fetches the mapping for your exact Spotify version at apply time rather than baking it into the binary, so a new client build normally works with the Spicetify you already have. When something genuinely is not supported yet, the client tells you which part is degraded instead of looking silently wrong. -1. There is a great chance that you are using Microsoft Store Spotify. Please double check that in Spotify About page. -2. If you are actually using Microsoft Store Spotify, remove it completely. Go to Spotify website to download the normal version installer. -3. If you are not using the Microsoft Store Spotify, and are using the one from the Spotify website, check to see if you have a "prefs" file in `C:\Users\YOUR_USERNAME\AppData\Roaming\Spotify`. -4. If so, open your `config-xpui.ini` and set `prefs_path` to the absolute path of that prefs file. (e.g. `C:\Users\YOUR_USERNAME\AppData\Roaming\Spotify\prefs`) Then try running `spicetify` again. +## Spicetify cannot find Spotify -### Linux +Check what it resolved: -1. In `bash`, run `cd ~` and `find | grep "spotify/prefs$"` -2. If it returns a path to prefs file, copy its absolute path to `prefs_path` field in `config-xpui.ini`. +```bash +spicetify config +``` -## After Spotify's update, running `spicetify apply` or `spicetify update` breaks Spotify. +If the Spotify path is wrong, set `spotify_data_dir` and `spotify_exec` in `config.toml`, or pass `--spotify-exec` for one command. -After any Spotify update, always run `spicetify backup apply`. -Optionally, set the Spotify shortcut to run `spicetify auto` (instead of direct path to Spotify executable), so that Spicetify can backup and apply, when it needs to, then launch Spotify automatically. +If you installed Spotify from the Microsoft Store, Snap or Flatpak, that is the problem: those builds are sandboxed and Spicetify cannot patch them. Remove it and install Spotify from Spotify's own installer. -It may be the case that Spicetify does not yet support a new Spotify update. In that case, please check the Spicetify issue tracker. +## I installed a module and nothing happened -## I can't play some songs after downgrading Spotify +From the CLI, installing is only the first of three steps: -Delete all files in the following folder and launch spotify again. +```bash +spicetify pkg install +spicetify pkg enable @ +spicetify apply +``` -- **Windows**: `%LOCALAPPDATA%\Spotify` -- **Linux**: `~/.config/spotify` -- **macOS**: `~/Library/Application Support/Spotify` +`pkg install` unpacks the module, `pkg enable` points the client at it, and `apply` stages it. From the store inside Spotify all three happen for you. + +## A module broke my client -## Sometimes **Popup Lyrics** and/or **Lyrics Plus** seem to not work +Disable it from the store's Installed tab, or: -This problem happens in the extension [Popup Lyrics](https://github.com/spicetify/cli/wiki/Extensions#pop-up-lyrics) and custom app [Lyrics Plus](https://github.com/spicetify/cli/wiki/Custom-Apps#lyrics-plus) mostly because your Musixmatch token has been flagged for doing too many requests. This can be fixed by just waiting without skipping songs too much, however, if it is still a problem for you, all you need to do is to install the Musixmatch official app, which is a web-based app like Spotify. +```bash +spicetify pkg delete +spicetify apply +``` -1. **Linux:** find an archive online - **Windows:** go to [store.rg-adguard.net](https://store.rg-adguard.net/) and then select ProductID and enter `9wzdncrfj235` and click done. Download the .appxbundle and install. +If the client is too broken to reach the store, `spicetify restore` returns stock Spotify, and re-applying afterwards brings back the modules you kept. -2. **You don't need to log in!** +## Can I go back to an older version of a module? -3. Now in Musixmatch app, hit `Ctrl + Shift + i` to bring up DevTools. +Yes. Installed versions are kept side by side: -![mxm1](/images/faq/mxm1.png) +```bash +spicetify pkg enable my-module@1.2.0 +spicetify apply +``` -4. Switch to Network tab. Hit `Ctrl + R`. Filter results with "apic": +## Why did my theme stop when I enabled another one? -![mxm2](/images/faq/mxm2.png) +Exactly one theme is active at a time. Enabling a theme unloads the previous one, so you never end up with two fighting over the same client chrome. -5. Click on any result. Click on the Headers tab. Scroll all the way down. Note down `usertoken` +## Do my v2 themes and extensions work? -![mxm3](/images/faq/mxm3.png) +No. v3 modules are a different format, and v2's themes, extensions and custom apps are all modules now. Most popular ones already exist as modules in the store. See [what changes in v3](/docs/whats-new). -It should look like this: +## Can I install something that is not in the store? +Yes, by naming its artifact: + +```bash +spicetify pkg install my-module https://example.com/my-module@1.0.0.zip ``` -200501593b603a3fdc5c9b4a696389f6589dd988e5a1cf02dfdce1 -``` -6. You can open the config for Popup Lyrics by right clicking on the Popup Lyrics button. Or if you're using Lyrics Plus, open the config by clicking on Lyrics in the sidebar and clicking on the profile menu and then clicking 'Lyrics Plus config'. You can then paste your personal token in the input field in the Musixmatch section and turn the switch on. +Nothing verifies those bytes, because there is no registry entry with a checksum to hold them to, and the CLI says so. Prefer the store for anything you did not build yourself. + +## I can't play some songs after downgrading Spotify + +Delete everything in Spotify's own cache folder and start Spotify again: + +- **Windows**: `%LOCALAPPDATA%\Spotify` +- **Linux**: `~/.config/spotify` +- **macOS**: `~/Library/Application Support/Spotify` + +## How do I report a bug? + +```bash +spicetify support +``` -![mxm4](/images/faq/mxm4.png) +Paste that output into the issue. It carries the versions and paths that most questions would otherwise be about. diff --git a/src/content/docs/getting-started.md b/src/content/docs/getting-started.md new file mode 100644 index 00000000..14e37bc4 --- /dev/null +++ b/src/content/docs/getting-started.md @@ -0,0 +1,140 @@ +--- +title: Getting Started +description: Install Spicetify v3, patch Spotify, and add your first module. +sidebar_position: 1 +--- + +Spicetify customizes the official Spotify desktop client. v3 is a rewrite: everything you add to Spotify (themes, extensions, whole apps) is a **module**, and you browse and install modules from a store inside Spotify itself. + +If you are coming from v2, read [what changes in v3](/docs/whats-new) first: it is a reinstall rather than an upgrade, and the two must not share a client. + +--- + +## Requirements + +- The official Spotify desktop client, from Spotify's own installer. Sandboxed builds (Microsoft Store, Snap, Flatpak) hide the files Spicetify has to patch. +- If Spotify is a fresh install, open it and log in for a minute before running Spicetify, so it writes the files that get patched. + +--- + +## Install + +### macOS and Linux + +```bash +curl -fsSL https://raw.githubusercontent.com/spicetify/cli/main/install.sh | sh -s -- --v3 +``` + +The script installs into `~/.spicetify`, and prints the two lines to add to your shell profile if it cannot put itself on your `PATH`. It needs `zstd` to unpack the archive: `brew install zstd` or `apt install zstd`. + +Builds are published for macOS on x86_64 and arm64, and Linux on x86_64. On any other architecture, [build from source](#build-from-source). + +### Windows + +```powershell +$v3 = $true; iwr -useb https://raw.githubusercontent.com/spicetify/cli/main/install.ps1 | iex +``` + +Setting `$v3` before piping is what selects v3; without it the script installs the v2 release. It unpacks into `%LOCALAPPDATA%\spicetify` and adds that folder to your `PATH`. + +Windows builds are published for x86_64. On ARM, [build from source](#build-from-source). + +To install by hand instead, download `spicetify--windows-x86_64.zip` from the [releases page](https://github.com/spicetify/cli/releases), unpack it somewhere permanent, and add that folder to your `PATH`. Every asset ships a `.sha256` beside it if you want to check the download first. + +### Build from source + +Works on any platform Rust supports: + +```bash +git clone https://github.com/spicetify/cli +cd cli +pnpm install && pnpm build:payload +cargo build --release -p cli -p daemon +``` + +The binaries land in `rust/target/release/` as `spicetify` and `spicetify-daemon`. Put that directory on your `PATH`, or call the binary by its full path. + +:::note +`pnpm build:payload` is not optional. The browser-side payload is compiled into the binary, so a build without it refuses to apply rather than patching Spotify with nothing in it. +::: + +### Staying up to date + +```bash +spicetify self-update +``` + +Downloads are checksum-verified. If you installed through a package manager, update through that instead. + +--- + +## Apply + +```bash +spicetify apply +``` + +That is the whole setup. `apply` stops Spotify, patches the client, installs and starts the background daemon, registers the `spicetify://` handler, and starts Spotify again. + +There is no separate backup step. v3 renames Spotify's own `xpui.spa` to `xpui.spa.backup` in place, and that rename **is** the backup, which is why `spicetify restore` needs nothing from you. + +:::warning +Never point the v2 (Go) and v3 (Rust) binaries at the same client. They keep their backups differently, and running one over the other's state corrupts the install. Both detect a foreign apply and refuse, but restore with the same CLI that applied before switching. +::: + +--- + +## Add your first module + +Open Spotify and click **Module Store** in the sidebar. Browse, click install, and most modules take effect immediately. The few that need a restart say so. + +From the terminal instead: + +```bash +spicetify pkg install trashbin +spicetify pkg enable trashbin@0.2.0 +spicetify apply +``` + +:::warning +Installing does not enable. `pkg install` unpacks the module, `pkg enable` is what points the client at it, and `apply` stages it. An install with no enable sits on disk doing nothing. +::: + +--- + +## Keeping it working + +A Spotify update no longer breaks your client. The daemon notices Spotify updating itself and re-applies afterwards, so in the normal case there is nothing to do. + +If something does look wrong after an update: + +```bash +spicetify apply # re-patch +spicetify daemon status # is the daemon running, and which version +spicetify support # diagnostics to paste into a bug report +``` + +To keep Spotify on the build you have: + +```bash +spicetify spotify-updates block +``` + +--- + +## Where things live + +```bash +spicetify path +``` + +Configuration lives in `config.toml` under Spicetify's config folder, with `modules/` and `store/` beside it. `spicetify config open` opens that folder. + +--- + +## Next + +- [What changes in v3](/docs/whats-new) if you are coming from v2 +- [Modules and the store](/docs/modules) for what you can install and how to manage it +- [CLI reference](/docs/cli) for every command +- [Building a module](/docs/development/building-a-module) if you want to make one diff --git a/docs/cli/commands.md b/src/content/docs/legacy/cli/commands.md similarity index 98% rename from docs/cli/commands.md rename to src/content/docs/legacy/cli/commands.md index c82ebab7..ae1b50b8 100644 --- a/docs/cli/commands.md +++ b/src/content/docs/legacy/cli/commands.md @@ -3,6 +3,10 @@ title: Commands description: Complete Spicetify command reference. --- +:::note +These are **v2** commands. For v3, see [Commands](/docs/cli/commands). +::: + This page documents all Spicetify CLI commands. ## Core Commands diff --git a/docs/cli/index.md b/src/content/docs/legacy/cli/index.md similarity index 87% rename from docs/cli/index.md rename to src/content/docs/legacy/cli/index.md index 66d10a51..272bffdf 100644 --- a/docs/cli/index.md +++ b/src/content/docs/legacy/cli/index.md @@ -3,6 +3,10 @@ title: CLI Reference description: Command-line interface reference for Spicetify. --- +:::note +This is the **v2** CLI. For v3, see [the CLI reference](/docs/cli). +::: + Spicetify is primarily a command-line tool. This section covers the command reference. ## Quick Start diff --git a/src/content/docs/customization/config-file.md b/src/content/docs/legacy/customization/config-file.md similarity index 97% rename from src/content/docs/customization/config-file.md rename to src/content/docs/legacy/customization/config-file.md index 9e0e68b2..235beef0 100644 --- a/src/content/docs/customization/config-file.md +++ b/src/content/docs/legacy/customization/config-file.md @@ -3,6 +3,10 @@ title: Configuration Reference description: Complete reference for Spicetify's config file. --- +:::note +This documents **v2**'s `config-xpui.ini`. v3 uses [`config.toml`](/docs/modules/config-file). +::: + Spicetify stores its configuration in `config-xpui.ini`. This page documents all available options. ## File Location diff --git a/src/content/docs/customization/custom-apps.md b/src/content/docs/legacy/customization/custom-apps.md similarity index 97% rename from src/content/docs/customization/custom-apps.md rename to src/content/docs/legacy/customization/custom-apps.md index ae9629b0..5ad2a7ca 100644 --- a/src/content/docs/customization/custom-apps.md +++ b/src/content/docs/legacy/customization/custom-apps.md @@ -3,6 +3,10 @@ title: Custom Apps description: Add new pages to Spotify's sidebar with custom apps. --- +:::note +This is a **v2** custom app. In v3, apps are [modules](/docs/modules). +::: + Custom Apps are JavaScript applications that add entirely new pages to Spotify's sidebar. Unlike extensions (which modify existing behavior), custom apps create standalone experiences within Spotify. ## Installing Custom Apps diff --git a/src/content/docs/customization/extensions.md b/src/content/docs/legacy/customization/extensions.md similarity index 98% rename from src/content/docs/customization/extensions.md rename to src/content/docs/legacy/customization/extensions.md index 8a5b29b4..a5bf317b 100644 --- a/src/content/docs/customization/extensions.md +++ b/src/content/docs/legacy/customization/extensions.md @@ -3,6 +3,10 @@ title: Extensions description: Add features to Spotify with extensions. --- +:::note +This is a **v2** extension. In v3, extensions are [modules](/docs/modules). +::: + Extensions are JavaScript files that add new features to Spotify. They run alongside Spotify's main code and can add buttons, modify behavior, and integrate with external services. ## Installing Extensions diff --git a/src/content/docs/customization/index.md b/src/content/docs/legacy/customization/index.md similarity index 93% rename from src/content/docs/customization/index.md rename to src/content/docs/legacy/customization/index.md index 270751cb..3332550c 100644 --- a/src/content/docs/customization/index.md +++ b/src/content/docs/legacy/customization/index.md @@ -3,6 +3,10 @@ title: Customization description: Customize Spotify with themes, extensions, and custom apps. --- +:::note +This is **v2** customization. In v3, themes, extensions, custom apps and snippets are all [modules](/docs/modules). +::: + Spicetify lets you customize Spotify in three ways: | Type | What it does | diff --git a/src/content/docs/customization/marketplace.mdx b/src/content/docs/legacy/customization/marketplace.mdx similarity index 97% rename from src/content/docs/customization/marketplace.mdx rename to src/content/docs/legacy/customization/marketplace.mdx index 972baea5..17fe2a9c 100644 --- a/src/content/docs/customization/marketplace.mdx +++ b/src/content/docs/legacy/customization/marketplace.mdx @@ -3,6 +3,10 @@ title: Marketplace description: Browse and install themes, extensions, and snippets from Spotify. --- +:::note +The Marketplace is **v2**. v3 ships [a store inside Spotify](/docs/modules). +::: + The Spicetify Marketplace is a built-in store that lets you browse, install, and manage customizations directly from Spotify. ## Installation diff --git a/docs/customization/themes.md b/src/content/docs/legacy/customization/themes.md similarity index 97% rename from docs/customization/themes.md rename to src/content/docs/legacy/customization/themes.md index 097cc850..18748e6f 100644 --- a/docs/customization/themes.md +++ b/src/content/docs/legacy/customization/themes.md @@ -3,6 +3,10 @@ title: Themes description: Change Spotify's visual appearance with themes. --- +:::note +This is a **v2** theme. In v3, themes are [modules](/docs/modules#themes) you install from the store. +::: + Themes let you customize Spotify's colors, fonts, and visual styling. From subtle tweaks to complete visual overhauls, themes are one of the most popular Spicetify features. ## Installing Themes diff --git a/docs/development/custom-apps.md b/src/content/docs/legacy/development/custom-apps.md similarity index 98% rename from docs/development/custom-apps.md rename to src/content/docs/legacy/development/custom-apps.md index f7980483..63eecf94 100644 --- a/docs/development/custom-apps.md +++ b/src/content/docs/legacy/development/custom-apps.md @@ -3,6 +3,10 @@ title: Custom Apps description: 🔧 Creating Custom Apps. --- +:::note +This builds a **v2** custom app. In v3, see [Building a module](/docs/development/building-a-module). +::: + ## App Structure ```text diff --git a/docs/development/extensions.md b/src/content/docs/legacy/development/extensions.md similarity index 98% rename from docs/development/extensions.md rename to src/content/docs/legacy/development/extensions.md index 34a195d1..cd7f076e 100644 --- a/docs/development/extensions.md +++ b/src/content/docs/legacy/development/extensions.md @@ -3,6 +3,10 @@ title: Extensions description: Creating extensions for Spicetify. --- +:::note +This builds a **v2** extension. In v3, see [Building a module](/docs/development/building-a-module). +::: + Extensions are JavaScript files that run alongside Spotify's main code. They can add UI elements, modify behavior, integrate with external services, and more. ## Extension Structure diff --git a/docs/development/js-modules.md b/src/content/docs/legacy/development/js-modules.md similarity index 98% rename from docs/development/js-modules.md rename to src/content/docs/legacy/development/js-modules.md index e556bd29..fe95423a 100644 --- a/docs/development/js-modules.md +++ b/src/content/docs/legacy/development/js-modules.md @@ -3,6 +3,10 @@ title: Javascript NPM Modules description: 📦 Using NPM Modules for Spicetify. --- +:::note +This is **v2**. In v3, see [Building a module](/docs/development/building-a-module). +::: + Since v0.9.8, Spicetify injects extension with file extension `.mjs` as a script with type="module" and automatically symlink `node_modules` folder found in user's Extensions folder to `zlink` app. In Javascript module, Javascript would work just the same as normal script but now you can use `import` to include other Javascript files. [Click here for details](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules). diff --git a/src/content/docs/development/themes.md b/src/content/docs/legacy/development/themes.md similarity index 94% rename from src/content/docs/development/themes.md rename to src/content/docs/legacy/development/themes.md index 049d98b0..3427a356 100644 --- a/src/content/docs/development/themes.md +++ b/src/content/docs/legacy/development/themes.md @@ -3,6 +3,10 @@ title: Themes description: ✨ Creating Themes for Spicetify. --- +:::note +This builds a **v2** theme. In v3, see [Building a module](/docs/development/building-a-module). +::: + ## Theme Structure ```text diff --git a/docs/faq.md b/src/content/docs/legacy/faq.md similarity index 97% rename from docs/faq.md rename to src/content/docs/legacy/faq.md index b10de052..21d824e0 100644 --- a/docs/faq.md +++ b/src/content/docs/legacy/faq.md @@ -1,7 +1,12 @@ --- -title: FAQ +title: FAQ (v2) +sidebar_label: FAQ --- +:::note +This is the v2 FAQ. For Spicetify v3, see [the current FAQ](/docs/faq). +::: + ## Where is the config file? The config file is generally located at: diff --git a/src/content/docs/getting-started.mdx b/src/content/docs/legacy/getting-started.mdx similarity index 98% rename from src/content/docs/getting-started.mdx rename to src/content/docs/legacy/getting-started.mdx index b6343631..2f6d7992 100644 --- a/src/content/docs/getting-started.mdx +++ b/src/content/docs/legacy/getting-started.mdx @@ -3,6 +3,10 @@ title: Getting Started sidebar_position: 1 --- +:::note +This installs **Spicetify v2**, the current stable release. For the v3 beta, see [Getting started](/docs/getting-started). +::: + Spicetify is a multiplatform command-line tool to customize the official Spotify client. import Tabs from '@/components/Tabs.astro'; diff --git a/src/content/docs/legacy/index.md b/src/content/docs/legacy/index.md new file mode 100644 index 00000000..9de87edb --- /dev/null +++ b/src/content/docs/legacy/index.md @@ -0,0 +1,34 @@ +--- +title: Spicetify v2 +description: Documentation for the released v2 CLI, kept while v2 is the stable release. +category_index: true +--- + +This section documents **Spicetify v2**, the released, stable CLI. It is still what `install.sh` and `install.ps1` give you by default, and it is what most people are running today. + +[Spicetify v3](/docs/getting-started) is the rewrite: one kind of add-on instead of four, a store inside Spotify, and a client that repairs itself after Spotify updates. It is the current version; this section stays while v2 is still supported. + +:::note +The two are not interchangeable. They keep their backups differently and must never be pointed at the same client. If you are moving between them, restore with the one you applied with first. [What changes in v3](/docs/whats-new) has the full path. +::: + +## Using v2 + +- [Getting started](/docs/legacy/getting-started) installs v2 and applies it +- [Customization](/docs/legacy/customization) covers the Marketplace, themes, extensions and custom apps +- [Configuration reference](/docs/legacy/customization/config-file) documents `config-xpui.ini` +- [CLI reference](/docs/legacy/cli) documents the v2 commands +- [Uninstallation](/docs/legacy/uninstallation) +- [FAQ](/docs/legacy/faq) + +## Developing for v2 + +- [Themes](/docs/legacy/development/themes), [extensions](/docs/legacy/development/extensions) and [custom apps](/docs/legacy/development/custom-apps) +- [JS modules](/docs/legacy/development/js-modules) +- [Spicetify Creator](/docs/legacy/spicetify-creator/the-basics), the v2 authoring toolchain + +In v3 all of the above are one thing, a [module](/docs/development/building-a-module). + +## The API reference is shared + +The `Spicetify` global that v2 extensions use is the same surface v3 modules get, so [the API reference](/docs/development/api-wrapper) is not duplicated here. It applies to both. diff --git a/src/content/docs/development/spicetify-creator/building-and-testing.mdx b/src/content/docs/legacy/spicetify-creator/building-and-testing.mdx similarity index 95% rename from src/content/docs/development/spicetify-creator/building-and-testing.mdx rename to src/content/docs/legacy/spicetify-creator/building-and-testing.mdx index 9eb4cee5..d1e91d6b 100644 --- a/src/content/docs/development/spicetify-creator/building-and-testing.mdx +++ b/src/content/docs/legacy/spicetify-creator/building-and-testing.mdx @@ -3,6 +3,10 @@ title: Building And Testing description: 🛠 Ensuring the quality of your creations. --- +:::note +Spicetify Creator is the **v2** toolchain. v3 uses [`spicetify-kit`](/docs/development/building-a-module). +::: + import Tabs from '@/components/Tabs.astro'; import TabItem from '@/components/TabItem.astro'; diff --git a/src/content/docs/development/spicetify-creator/create-custom-apps.md b/src/content/docs/legacy/spicetify-creator/create-custom-apps.md similarity index 93% rename from src/content/docs/development/spicetify-creator/create-custom-apps.md rename to src/content/docs/legacy/spicetify-creator/create-custom-apps.md index 47763423..8b90e238 100644 --- a/src/content/docs/development/spicetify-creator/create-custom-apps.md +++ b/src/content/docs/legacy/spicetify-creator/create-custom-apps.md @@ -3,6 +3,10 @@ title: Create Custom Apps description: 🔧 Creating single-page apps for Spicetify. --- +:::note +Spicetify Creator is the **v2** toolchain. v3 uses [`spicetify-kit`](/docs/development/building-a-module). +::: + Notes: - It is recommended to learn React before starting to create Custom Apps. diff --git a/docs/development/spicetify-creator/create-extensions.md b/src/content/docs/legacy/spicetify-creator/create-extensions.md similarity index 88% rename from docs/development/spicetify-creator/create-extensions.md rename to src/content/docs/legacy/spicetify-creator/create-extensions.md index d8a8be60..5534d837 100644 --- a/docs/development/spicetify-creator/create-extensions.md +++ b/src/content/docs/legacy/spicetify-creator/create-extensions.md @@ -3,6 +3,10 @@ title: Create Extensions description: 🔨 Creating small addons for Spicetify. --- +:::note +Spicetify Creator is the **v2** toolchain. v3 uses [`spicetify-kit`](/docs/development/building-a-module). +::: + Notes: - This tutorial assumes you have chosen to generate an example using Create Spicetify App. diff --git a/src/content/docs/development/spicetify-creator/the-basics.mdx b/src/content/docs/legacy/spicetify-creator/the-basics.mdx similarity index 97% rename from src/content/docs/development/spicetify-creator/the-basics.mdx rename to src/content/docs/legacy/spicetify-creator/the-basics.mdx index 1fd5b947..4aef5a34 100644 --- a/src/content/docs/development/spicetify-creator/the-basics.mdx +++ b/src/content/docs/legacy/spicetify-creator/the-basics.mdx @@ -3,6 +3,10 @@ title: The Basics description: 🤠 Spicetify Creator 101. --- +:::note +Spicetify Creator is the **v2** toolchain. v3 uses [`spicetify-kit`](/docs/development/building-a-module). +::: + import Tabs from '@/components/Tabs.astro'; import TabItem from '@/components/TabItem.astro'; diff --git a/src/content/docs/uninstallation.mdx b/src/content/docs/legacy/uninstallation.mdx similarity index 95% rename from src/content/docs/uninstallation.mdx rename to src/content/docs/legacy/uninstallation.mdx index fbbad3ec..6f11bcd4 100644 --- a/src/content/docs/uninstallation.mdx +++ b/src/content/docs/legacy/uninstallation.mdx @@ -3,6 +3,10 @@ title: Uninstallation description: How to remove Spicetify from your system. --- +:::note +This removes **Spicetify v2**. For v3, see [Uninstallation](/docs/uninstallation). +::: + import Tabs from '@/components/Tabs.astro'; import TabItem from '@/components/TabItem.astro'; diff --git a/src/content/docs/modules/config-file.md b/src/content/docs/modules/config-file.md new file mode 100644 index 00000000..f07f6bd2 --- /dev/null +++ b/src/content/docs/modules/config-file.md @@ -0,0 +1,77 @@ +--- +title: Configuration reference +description: Every key in Spicetify v3's config.toml, and the flags that override them. +sidebar_position: 2 +--- + +v3 keeps its configuration in `config.toml`, in Spicetify's config folder. There is no `spicetify config ` command any more: open the file and edit it. + +```bash +spicetify config open # open the folder in your file manager +spicetify path # print the paths spicetify is using +``` + +The file is written for you by `spicetify init`, and every key has a working default, so an empty file is a valid file. + +--- + +## Keys + +```toml +# Re-apply automatically after Spotify updates itself. On by default: this is +# what keeps a Spotify update from leaving you with a stock client. +daemon = true + +# Mirror mode. Off by default. +mirror = false + +# Only set these when auto-detection gets it wrong. +# spotify_data_dir = "/path/to/spotify/data" +# spotify_exec = "/path/to/spotify/binary" +# offline_bnk_dir = "/path/to/offline/bnk" +``` + +### `daemon` + +Whether `apply` installs and starts the background daemon. The daemon watches for Spotify updating itself and re-applies afterwards, and it serves the local proxy the client uses to reach hosts it cannot fetch directly. + +Set it to `false` if you would rather re-apply by hand. `spicetify apply` then leaves the daemon alone. + +### `mirror` + +Mirror mode, off by default. Also settable per invocation with `--mirror`. + +### `spotify_data_dir`, `spotify_exec`, `offline_bnk_dir` + +Where Spotify's data folder, executable and offline cache are. Spicetify finds all three on its own; set them only when it cannot, which usually means a non-standard install location. `spicetify path` prints what it resolved. + +--- + +## Overriding for one command + +Every path key has a matching flag, which takes precedence over the file for that invocation and does not change it: + +```bash +spicetify --spotify-exec /opt/spotify/spotify apply +spicetify --spotify-data-dir ~/custom/spotify apply +spicetify --offline-bnk-dir ~/custom/bnk apply +spicetify --mirror true apply +``` + +--- + +## Resetting + +```bash +spicetify init +``` + +`init` writes a fresh `config.toml` from what it detects and **deletes `hooks/`, `modules/` and `store/`**, which means every installed module goes with it. It asks first; `--yes` skips the prompt. Use it to start clean, not to fix a config typo. + +--- + +## What happened to `config-xpui.ini` + +That is v2's configuration, and v3 does not read it. It stays on disk untouched, so switching back to v2 finds it exactly as it was. The v2 keys that listed your themes and extensions have no v3 equivalent by design: what is installed is now recorded in the store, not in a config file you hand-edit. + +See [the v2 reference](/docs/legacy/customization/config-file) if you are still running v2. diff --git a/src/content/docs/modules/index.md b/src/content/docs/modules/index.md new file mode 100644 index 00000000..5350a555 --- /dev/null +++ b/src/content/docs/modules/index.md @@ -0,0 +1,93 @@ +--- +title: Modules and the store +description: Find, install and manage modules, the one kind of add-on in Spicetify v3. +sidebar_position: 1 +category_index: true +--- + +Everything you add to Spotify in v3 is a **module**. A theme, a small tweak like skipping explicit tracks, a whole extra page in the sidebar: same format, same install path, same lifecycle. Modules are tagged so you can still tell them apart in the store, but there is nothing different about installing one over another. + +--- + +## The store + +The store ships with Spicetify. Open Spotify and click **Module Store** in the sidebar. + +- **Browse** by tab (extensions, themes, snippets, apps), search, or sort by installs. +- **Install** with one click. Most modules load immediately; a module that has to run before the client boots says it needs a restart. +- **Manage** what you have from the Installed tab: enable, disable, update or remove. +- **Details** shows the description, authors, the source repository, and the license the code is published under. + +Every module in the store comes from a single registry, and every entry in it was validated before it merged: the artifact is downloaded and re-hashed, the card is checked against the module's own metadata, and a published version can never be repointed at different bytes afterwards. The store verifies the checksum again when it installs, and refuses the install on a mismatch. + +### Updates + +The store shows what has a newer version, and **Update all** installs them in dependency order. Nothing updates behind your back. + +### Backups + +Export writes a small file listing your preferences and which modules you have installed. Import restores the preferences and reinstalls those modules from the registry, verified the same way as any other install. The file never contains module code, so importing one cannot install something the registry does not carry. + +--- + +## From the terminal + +The same catalog, without leaving the shell: + +```bash +spicetify pkg install trashbin # unpack it +spicetify pkg enable trashbin@0.2.0 # point the client at that version +spicetify apply # stage it into the client +``` + +`pkg install` on its own is inert. It unpacks the module and marks it installed, but nothing points at it until `pkg enable`, and nothing reaches the client until `apply`. + +```bash +spicetify pkg list # what is installed, with versions +spicetify pkg delete trashbin # remove it +``` + +To install something that is not in the store, name the artifact directly: + +```bash +spicetify pkg install my-module https://example.com/my-module@1.0.0.zip +``` + +That bypasses the registry, so there is no checksum to hold it to and no review behind it. The CLI says so, and prints the digest it got. + +--- + +## Themes + +Themes are modules tagged `theme`, with one rule of their own: **exactly one theme is active at a time**. Enabling a theme unloads the previous one, so there is never an overlap and never a half-applied look. + +Many themes ship several colour **schemes**. Switch scheme from the theme's entry in the store, or from the Spicetify section of Spotify's own settings page. Scheme changes apply immediately, with no re-apply and no restart. + +If a theme leaves the client looking wrong, disable it from the Installed tab and the client returns to its stock appearance straight away. + +--- + +## Rolling back + +Installed versions are kept side by side, so going back to a version that worked is one command: + +```bash +spicetify pkg enable my-module@1.2.0 +spicetify apply +``` + +--- + +## When a module is withdrawn + +The registry can revoke a module (a security problem, a takedown). A revoked module stops being offered, and the store disables it in your client and tells you why rather than leaving it running quietly. + +--- + +## Where modules live on disk + +```bash +spicetify path +``` + +Under the config folder, `store///` holds the unpacked releases and `modules/` points at the one that is enabled. `config.toml` sits beside them. If `modules/` is a real directory rather than a link, it is a local build you staged yourself, and `apply` uses that instead of anything the store installed. diff --git a/src/content/docs/uninstallation.md b/src/content/docs/uninstallation.md new file mode 100644 index 00000000..ea5e9cf2 --- /dev/null +++ b/src/content/docs/uninstallation.md @@ -0,0 +1,62 @@ +--- +title: Uninstallation +description: Remove Spicetify v3 and put stock Spotify back. +sidebar_position: 6 +--- + +## Put Spotify back + +```bash +spicetify restore +``` + +That undoes the patch and returns the stock client. Restore with the same CLI that applied: v2 and v3 keep their backups differently, so the v2 binary cannot undo a v3 apply. + +If you only wanted to stop a module misbehaving, you do not need any of this: disable it from the store's Installed tab, or `spicetify pkg delete `. + +--- + +## Stop the daemon + +```bash +spicetify daemon stop +spicetify daemon uninstall +``` + +`stop` also unloads the service, so it does not come back on its own. `uninstall` removes it entirely. + +--- + +## Remove the files + +`spicetify path` prints where everything is. Removing the config folder removes your configuration, every installed module and the store's record of them. + +```bash +rm -rf ~/.config/spicetify # Linux and macOS, if that is what `spicetify path` reported +``` + +On Windows, delete the folder `spicetify path` names. + +:::note +If you plan to reinstall, keep the folder. Reinstalling with it intact brings back every module you had, at the versions you had. +::: + +--- + +## Remove the binary + +If you installed with the script, everything lives in one directory: + +```bash +rm -rf ~/.spicetify +``` + +Then remove the `PATH` entry the installer added to your shell profile. If you installed through a package manager, uninstall through it instead. + +On macOS, `apply` also created `~/Applications/Spicetify.app`, the small bundle that receives `spicetify://` links. Delete it too. + +--- + +## Going back to v2 + +Restore with v3 first, then install v2 and apply with it. Your v2 configuration (`config-xpui.ini`) was never touched, so it is exactly as you left it. See the [v2 guide](/docs/legacy/getting-started). diff --git a/src/content/docs/whats-new.md b/src/content/docs/whats-new.md new file mode 100644 index 00000000..073d6827 --- /dev/null +++ b/src/content/docs/whats-new.md @@ -0,0 +1,87 @@ +--- +title: What changes in v3 +description: How Spicetify v3 differs from v2, and what to do with an existing v2 install. +sidebar_position: 2 +--- + +v3 is a different model, not a faster v2. The short version: one kind of add-on instead of four, a store inside Spotify instead of a CLI-only flow, and a client that repairs itself after Spotify updates. + +--- + +## Modules replace themes, extensions, custom apps and snippets + +In v2 you managed four things in four ways: a theme folder, an extension `.js`, a custom app directory, and CSS snippets, each wired up through `config-xpui.ini` and re-applied by hand. + +In v3 all four are the same thing, a **module**: a directory with a `metadata.json`, optionally some JavaScript, optionally some CSS, and a declared dependency on the standard library. One format, one install path, one lifecycle. A module can add a button, register a whole route, restyle the client, or all three. + +What that buys you: modules load and unload at runtime, so most installs take effect without restarting Spotify, and a module that fails is contained instead of taking the client with it. + +--- + +## A store inside Spotify + +The [Marketplace](/docs/legacy/customization/marketplace) was a custom app you installed separately. In v3 the store ships with Spicetify and is the normal way to find and install things, with the CLI (`spicetify pkg`) as the equivalent path for people who prefer a terminal. + +Every module in the store comes from one registry, and every entry in it was checked before it merged: the artifact is downloaded and re-hashed, published versions can never be rewritten, and a module id stays with the account that first published it. Installs verify the checksum before unpacking. + +--- + +## Spotify updates stop breaking things + +In v2, Spotify updating itself left you with a stock client until you re-ran `spicetify backup apply`, and often waiting for a new Spicetify release that understood the new build. + +v3 installs a small daemon that notices the update and re-applies afterwards. Support for a new Spotify build no longer needs a new Spicetify release either: the mapping between Spicetify and Spotify's internals is fetched per apply, so a new client version usually just works. When something genuinely is not supported yet, the client says which part is degraded rather than looking silently wrong. + +--- + +## Going back is cheap + +Installed versions are kept side by side. If an update misbehaves: + +```bash +spicetify pkg enable my-module@1.2.0 +spicetify apply +``` + +No hunting for an old download. + +--- + +## Command changes + +| v2 | v3 | +| --- | --- | +| `spicetify backup apply` | `spicetify apply` | +| `spicetify restore backup apply` | `spicetify apply` | +| `spicetify update` | `spicetify self-update` | +| `spicetify upgrade` | `spicetify self-update` | +| `spicetify config ` | edit `config.toml` | +| `spicetify config-dir` | `spicetify config open` | +| `spicetify enable-devtools` | `spicetify dev` | +| `spicetify watch` | `spicetify-kit dev ` | +| `spicetify auto` | not needed; the daemon re-applies | +| Marketplace | the built-in store, or `spicetify pkg` | + +`spicetify restore` still restores stock Spotify, and `spicetify path` still prints where things live. + +--- + +## Upgrading from v2 + +v3 is a reinstall, not an in-place upgrade, and the two must never share a client. + +1. **Restore with v2 first.** Run `spicetify restore` with the v2 binary you have installed. This matters: v2 consumes `xpui.spa` while v3 renames it, so v3 cannot undo a v2 apply and vice versa. +2. **Install v3** ([getting started](/docs/getting-started)). +3. **Apply**: `spicetify apply`. +4. **Reinstall what you had** from the store. Your v2 themes and extensions do not carry over: they are a different format, and most popular ones already exist as modules. + +Your v2 config (`config-xpui.ini`) is left alone. v3 reads `config.toml` and ignores it, so nothing is lost if you go back. + +--- + +## What is not in v3 yet + +- **Source transforms.** v2 extensions that rewrote the client bundle are off by default; features built on that degrade. +- **Spicetify Creator.** Superseded by [`spicetify-kit`](/docs/development/building-a-module). The [old guides](/docs/legacy/spicetify-creator/the-basics) stay for v2. + +The [v2 documentation](/docs/legacy) remains available for as long as v2 is the released binary. diff --git a/src/pages/index.astro b/src/pages/index.astro index 258e468c..13d5d737 100644 --- a/src/pages/index.astro +++ b/src/pages/index.astro @@ -60,13 +60,13 @@ const themes = [ const heroShowcase = [ { src: heroMarketplace, - alt: 'Spicetify Marketplace — browse and install themes, extensions, and custom apps', - label: 'Marketplace', + alt: 'The Spicetify store, where themes, extensions and apps are browsed and installed inside Spotify', + label: 'Store', }, { src: heroLyrics, alt: 'Lyrics Plus — a custom app that displays synced lyrics inside Spotify', - label: 'Custom Apps', + label: 'Apps', }, { src: heroFullApp, @@ -81,27 +81,27 @@ const features = [ title: 'Themes', description: 'Transform your Spotify with community color schemes, layouts, and visual overhauls.', - href: '/docs/customization/themes', + href: '/docs/modules#themes', }, { icon: 'puzzle', title: 'Extensions', description: - 'Add keyboard shortcuts, playback controls, and integrations with other services.', - href: '/docs/customization/extensions', + 'Add keyboard shortcuts, playback controls, and integrations with other services. Install them from the store inside Spotify.', + href: '/docs/modules', }, { icon: 'grid', - title: 'Custom Apps', + title: 'Apps', description: - 'Build entirely new pages inside Spotify for lyrics, stats, and more.', - href: '/docs/customization/custom-apps', + 'Add entirely new pages inside Spotify for lyrics, stats, and more.', + href: '/docs/modules', }, { icon: 'terminal', title: 'Powerful CLI', description: - 'Full control from the command line. Apply, backup, restore, and update with ease.', + 'Full control from the command line. Apply, restore, and manage modules with one tool.', href: '/docs/getting-started', }, ]; From 78b15fcb86e95fa5c404ca76953bc50996227da0 Mon Sep 17 00:00:00 2001 From: Afonso Jorge Ramos Date: Tue, 11 Aug 2026 00:35:37 +0200 Subject: [PATCH 03/12] docs(v3): reflect first-apply seeding, drop stale version pins and section rules Getting started and the CLI reference now say a fresh apply seeds stdlib and the store, which is what makes the store present without a manual install. The trashbin enable examples used a real version that would age, so they point at the version pkg install prints. Also removes the horizontal rules between sections and one 'should'. --- src/content/docs/cli/commands.md | 14 ++---------- src/content/docs/cli/index.md | 8 ------- .../docs/development/building-a-module.md | 18 --------------- src/content/docs/development/compiling.md | 2 +- src/content/docs/development/publishing.md | 16 -------------- src/content/docs/getting-started.md | 22 +++++-------------- src/content/docs/modules/config-file.md | 8 ------- src/content/docs/modules/index.md | 18 +++------------ src/content/docs/uninstallation.md | 8 ------- src/content/docs/whats-new.md | 14 ------------ 10 files changed, 11 insertions(+), 117 deletions(-) diff --git a/src/content/docs/cli/commands.md b/src/content/docs/cli/commands.md index 9c1f6c28..3cfa5a29 100644 --- a/src/content/docs/cli/commands.md +++ b/src/content/docs/cli/commands.md @@ -6,8 +6,6 @@ sidebar_position: 2 Every command accepts the [global options](/docs/cli#global-options), so they are not repeated below. `spicetify --help` is the authority if this page ever falls behind the binary. ---- - ## Core ### `apply` @@ -20,6 +18,8 @@ Patches Spotify. This is the whole setup on a fresh install, and the fix for alm It stops Spotify, unpacks the client, renames Spotify's own archive to `xpui.spa.backup` (that rename is the backup), injects Spicetify's payload, fetches the mapping for your exact Spotify version, stages every enabled module, installs and starts the daemon, registers the `spicetify://` handler, and starts Spotify again. +On a fresh install, where no modules are present yet, it first seeds the standard library and the store from the registry, so the client can manage itself instead of booting empty. Once they exist, the store updates them, and this step does nothing. + Safe to run repeatedly. If the fetch for a new Spotify version fails, whatever is already cached still applies, so `apply` works offline. ### `restore` @@ -46,8 +46,6 @@ spicetify init [--yes] Writes a fresh `config.toml` from what it detects, and **deletes `hooks/`, `modules/` and `store/`**, so every installed module goes with it. It asks first unless you pass `--yes`. This is a clean slate, not a repair. ---- - ## Modules ### `pkg list` @@ -87,8 +85,6 @@ spicetify pkg delete Removes the module and its store entry. ---- - ## Configuration ### `config` @@ -118,8 +114,6 @@ spicetify support Prints diagnostics to paste into a bug report. Start here before opening an issue. ---- - ## Daemon The daemon is what re-applies Spicetify after Spotify updates itself, and it serves the local proxy the client uses for hosts it cannot fetch directly. `apply` installs and starts it unless `daemon = false` in your config. @@ -134,8 +128,6 @@ spicetify daemon uninstall # remove it `daemon status` reports the version it is running. If you have just updated Spicetify and behaviour has not changed, check that first: an old daemon serving old behaviour looks exactly like a fix that did not work. ---- - ## Spotify updates ```bash @@ -146,8 +138,6 @@ spicetify spotify-updates status Blocking patches Spotify's own binary, so Spotify has to be stopped to do it. Run from the terminal it stops the client and leaves it stopped; run from inside the client (through the store) it starts it again for you. ---- - ## Development ### `dev` diff --git a/src/content/docs/cli/index.md b/src/content/docs/cli/index.md index c3cb3ba0..72245510 100644 --- a/src/content/docs/cli/index.md +++ b/src/content/docs/cli/index.md @@ -14,8 +14,6 @@ spicetify restore # put stock Spotify back Everything else is for a specific situation. [Commands](/docs/cli/commands) documents all of them. ---- - ## First run ```bash @@ -24,8 +22,6 @@ spicetify apply There is no backup step and no config generation step to remember. `apply` stops Spotify, patches it, sets up the daemon, registers the `spicetify://` handler and starts Spotify again. The backup is Spotify's own archive renamed in place, which is why `restore` needs nothing from you. ---- - ## Getting help Every command prints its own usage, and that output is the authority if this page and the binary ever disagree: @@ -36,8 +32,6 @@ spicetify pkg --help spicetify pkg install --help ``` ---- - ## Global options These work on any command: @@ -53,8 +47,6 @@ These work on any command: The path options exist because auto-detection can be wrong on unusual installs. They override [`config.toml`](/docs/modules/config-file) for that one command without changing it. ---- - ## Do not mix v2 and v3 The two CLIs keep their backups differently and must never be pointed at the same client. Both refuse when they detect the other's work, but restore with whichever one you applied with before switching. diff --git a/src/content/docs/development/building-a-module.md b/src/content/docs/development/building-a-module.md index 9ae28059..ef156849 100644 --- a/src/content/docs/development/building-a-module.md +++ b/src/content/docs/development/building-a-module.md @@ -6,8 +6,6 @@ sidebar_position: 2 A module is the one thing you build for Spicetify v3. It can add a button, register a whole page, restyle the client, or all three. This page is the path from nothing to something running in your client; [the module standard](https://github.com/spicetify/modules/blob/main/docs/module-standard.md) is the contract it has to meet. ---- - ## Scaffold and run ```bash @@ -26,8 +24,6 @@ Spicetify.Modules.removeLocal('my-module'); Templates: `--template basic` (a button and a route), `extension` (behaviour only), `app` (a nav entry and a full page), `theme` (CSS only, no TypeScript). ---- - ## What the scaffold gives you | File | Role | @@ -38,8 +34,6 @@ Templates: `--template basic` (a button and a route), `extension` (behaviour onl | `logic.ts` | dependency-free logic, unit testable in Node | | `index.scss` | styles, adopted as a stylesheet and removed on unload | ---- - ## The entry point A module default-exports one function, and the loader awaits it: @@ -67,8 +61,6 @@ Two rules that matter more than they look: **Undo everything on unload.** Modules load and unload at runtime. The registrar removes what you registered and the stylesheet it adopted; the timers, subscriptions and overlays you created yourself are yours to clear through `ctx.defer`. A module that lingers after a reload is a bug. ::: ---- - ## Adding UI **Buttons** go through `placeButton`, which handles ordering and placement for you: @@ -96,16 +88,12 @@ registrar.registerRoute(ROUTE, ); **Settings** rows from every module render together under one Spicetify section in Spotify's own settings page, so a module with a single toggle does not need a page of its own. ---- - ## The Spicetify global `Spicetify` is typed and available without importing anything, and it is the same surface v2 extensions used: `Player`, `Platform`, `URI`, `React`, `SVGIcons`, `CosmosAsync`, `GraphQL`, `Menu`, `PopupModal`, `LocalStorage` and the rest. See the [API reference](/docs/development/api-wrapper). Reach for a native `Platform.*API` before making an HTTP call of your own. ---- - ## Class names Spotify's own class names are hashed and change with every client build, so never hardcode one. Reference them through `MAP`: @@ -116,8 +104,6 @@ const cls = MAP.main.topbar.right.button_t.wrapper; Modules ship with those references intact and the CLI resolves them at apply time against the exact Spotify version installed, which is why one build of your module works on every supported client. `classmap.d.ts` is generated for you, so the paths autocomplete. ---- - ## Testing Put anything worth testing in `logic.ts`, free of client imports, and inject the client objects from `mod.tsx`. Then: @@ -129,8 +115,6 @@ npm run test # unit tests UI is verified live through the dev loop, because JSX and the client's runtime URLs do not resolve in Node. ---- - ## Build and pack ```bash @@ -146,8 +130,6 @@ To sideload a packed build into a running client without publishing: spicetify-kit install my-module@1.0.0.zip ``` ---- - ## Then publish [Publishing](/docs/development/publishing) covers getting it into the store. diff --git a/src/content/docs/development/compiling.md b/src/content/docs/development/compiling.md index 012c3af1..0980cffb 100644 --- a/src/content/docs/development/compiling.md +++ b/src/content/docs/development/compiling.md @@ -3,7 +3,7 @@ title: Compiling description: Build the Spicetify CLI from source. --- -v3 is written in Rust. Most people should [install a release](/docs/getting-started); build from source to work on Spicetify itself, or to run it on a platform with no published build. +v3 is written in Rust. Most people are better off with a [prebuilt release](/docs/getting-started); build from source to work on Spicetify itself, or to run it on a platform with no published build. ## Requirements diff --git a/src/content/docs/development/publishing.md b/src/content/docs/development/publishing.md index a3a79edf..f985e80e 100644 --- a/src/content/docs/development/publishing.md +++ b/src/content/docs/development/publishing.md @@ -6,8 +6,6 @@ sidebar_position: 3 The store reads one registry: `vault.json` in [spicetify/modules](https://github.com/spicetify/modules). Your code stays in your own repository under your own license; what you submit is the entry that points at it. That is what makes every module in the store reviewable, checksummed and revocable. ---- - ## Submit Build, pack, upload the zip to your own release, then record the entry: @@ -30,8 +28,6 @@ Your `metadata.json` needs four things the registry will not accept an entry wit | `license` | an SPDX identifier, shown next to the install button | | `name`, `version` | must match the entry, and the id is permanent | ---- - ## Automate it Call the publish action from your own release workflow instead: @@ -46,8 +42,6 @@ Call the publish action from your own release workflow instead: The token is one with `public_repo` scope on your own account: the action pushes a branch to your fork and opens the pull request with it. Leave it out and the action prints the exact entry for you to submit by hand, which is a fine way to start. ---- - ## What gets checked Nothing in the pull request is taken on trust. CI downloads the artifact and checks the entry against what is actually inside it: @@ -63,33 +57,23 @@ Nothing in the pull request is taken on trust. CI downloads the artifact and che A red check is something to fix, not a conversation to have. First submission gets a human review as well; after that a green check is the gate. ---- - ## Updating Same flow with a new version key. The old versions stay exactly as they are, which is what makes rolling back possible for the people who installed them. ---- - ## After the merge 1. CI rebuilds the aggregate registry, and your module appears in the store. 2. Your artifact is copied to a mirror release in the registry repository and the mirror URL is appended to your entry. Installers try your host first and fall back to the mirror, so a release asset that disappears later does not break every install of that version. ---- - ## Choosing an id Ids are global and permanent. The first submission binds the id to your account and nothing else can publish it afterwards, so pick something you would be happy to keep. Make it describe what the module does; suffix themes with `-theme` and snippet collections with `-snippets`. ---- - ## Snippets A CSS-only module small enough to have no artifact can ship inline in the registry entry. Inline entries install with no download, so they are restricted to `.css` files: anything executable arrives as a checksummed zip. ---- - ## Distributing outside the store You do not have to use the store. Anyone can install a packed module directly: diff --git a/src/content/docs/getting-started.md b/src/content/docs/getting-started.md index 14e37bc4..3ca14d40 100644 --- a/src/content/docs/getting-started.md +++ b/src/content/docs/getting-started.md @@ -8,15 +8,11 @@ Spicetify customizes the official Spotify desktop client. v3 is a rewrite: every If you are coming from v2, read [what changes in v3](/docs/whats-new) first: it is a reinstall rather than an upgrade, and the two must not share a client. ---- - ## Requirements - The official Spotify desktop client, from Spotify's own installer. Sandboxed builds (Microsoft Store, Snap, Flatpak) hide the files Spicetify has to patch. - If Spotify is a fresh install, open it and log in for a minute before running Spicetify, so it writes the files that get patched. ---- - ## Install ### macOS and Linux @@ -66,8 +62,6 @@ spicetify self-update Downloads are checksum-verified. If you installed through a package manager, update through that instead. ---- - ## Apply ```bash @@ -76,14 +70,14 @@ spicetify apply That is the whole setup. `apply` stops Spotify, patches the client, installs and starts the background daemon, registers the `spicetify://` handler, and starts Spotify again. +On a fresh install it also downloads the store and the standard library from the registry, so the store is waiting in your sidebar the first time Spotify reopens. Nothing to install by hand. + There is no separate backup step. v3 renames Spotify's own `xpui.spa` to `xpui.spa.backup` in place, and that rename **is** the backup, which is why `spicetify restore` needs nothing from you. :::warning Never point the v2 (Go) and v3 (Rust) binaries at the same client. They keep their backups differently, and running one over the other's state corrupts the install. Both detect a foreign apply and refuse, but restore with the same CLI that applied before switching. ::: ---- - ## Add your first module Open Spotify and click **Module Store** in the sidebar. Browse, click install, and most modules take effect immediately. The few that need a restart say so. @@ -91,17 +85,15 @@ Open Spotify and click **Module Store** in the sidebar. Browse, click install, a From the terminal instead: ```bash -spicetify pkg install trashbin -spicetify pkg enable trashbin@0.2.0 +spicetify pkg install trashbin # prints the version it unpacked +spicetify pkg enable trashbin@ spicetify apply ``` :::warning -Installing does not enable. `pkg install` unpacks the module, `pkg enable` is what points the client at it, and `apply` stages it. An install with no enable sits on disk doing nothing. +Installing does not enable. `pkg install` unpacks the module and prints its version, `pkg enable` points the client at that version, and `apply` stages it. An install with no enable sits on disk doing nothing. ::: ---- - ## Keeping it working A Spotify update no longer breaks your client. The daemon notices Spotify updating itself and re-applies afterwards, so in the normal case there is nothing to do. @@ -120,8 +112,6 @@ To keep Spotify on the build you have: spicetify spotify-updates block ``` ---- - ## Where things live ```bash @@ -130,8 +120,6 @@ spicetify path Configuration lives in `config.toml` under Spicetify's config folder, with `modules/` and `store/` beside it. `spicetify config open` opens that folder. ---- - ## Next - [What changes in v3](/docs/whats-new) if you are coming from v2 diff --git a/src/content/docs/modules/config-file.md b/src/content/docs/modules/config-file.md index f07f6bd2..ff5ee8b9 100644 --- a/src/content/docs/modules/config-file.md +++ b/src/content/docs/modules/config-file.md @@ -13,8 +13,6 @@ spicetify path # print the paths spicetify is using The file is written for you by `spicetify init`, and every key has a working default, so an empty file is a valid file. ---- - ## Keys ```toml @@ -45,8 +43,6 @@ Mirror mode, off by default. Also settable per invocation with `--mirror`. Where Spotify's data folder, executable and offline cache are. Spicetify finds all three on its own; set them only when it cannot, which usually means a non-standard install location. `spicetify path` prints what it resolved. ---- - ## Overriding for one command Every path key has a matching flag, which takes precedence over the file for that invocation and does not change it: @@ -58,8 +54,6 @@ spicetify --offline-bnk-dir ~/custom/bnk apply spicetify --mirror true apply ``` ---- - ## Resetting ```bash @@ -68,8 +62,6 @@ spicetify init `init` writes a fresh `config.toml` from what it detects and **deletes `hooks/`, `modules/` and `store/`**, which means every installed module goes with it. It asks first; `--yes` skips the prompt. Use it to start clean, not to fix a config typo. ---- - ## What happened to `config-xpui.ini` That is v2's configuration, and v3 does not read it. It stays on disk untouched, so switching back to v2 finds it exactly as it was. The v2 keys that listed your themes and extensions have no v3 equivalent by design: what is installed is now recorded in the store, not in a config file you hand-edit. diff --git a/src/content/docs/modules/index.md b/src/content/docs/modules/index.md index 5350a555..fd92c2f4 100644 --- a/src/content/docs/modules/index.md +++ b/src/content/docs/modules/index.md @@ -7,8 +7,6 @@ category_index: true Everything you add to Spotify in v3 is a **module**. A theme, a small tweak like skipping explicit tracks, a whole extra page in the sidebar: same format, same install path, same lifecycle. Modules are tagged so you can still tell them apart in the store, but there is nothing different about installing one over another. ---- - ## The store The store ships with Spicetify. Open Spotify and click **Module Store** in the sidebar. @@ -28,16 +26,14 @@ The store shows what has a newer version, and **Update all** installs them in de Export writes a small file listing your preferences and which modules you have installed. Import restores the preferences and reinstalls those modules from the registry, verified the same way as any other install. The file never contains module code, so importing one cannot install something the registry does not carry. ---- - ## From the terminal The same catalog, without leaving the shell: ```bash -spicetify pkg install trashbin # unpack it -spicetify pkg enable trashbin@0.2.0 # point the client at that version -spicetify apply # stage it into the client +spicetify pkg install trashbin # unpack it, printing its version +spicetify pkg enable trashbin@ # point the client at that version +spicetify apply # stage it into the client ``` `pkg install` on its own is inert. It unpacks the module and marks it installed, but nothing points at it until `pkg enable`, and nothing reaches the client until `apply`. @@ -55,8 +51,6 @@ spicetify pkg install my-module https://example.com/my-module@1.0.0.zip That bypasses the registry, so there is no checksum to hold it to and no review behind it. The CLI says so, and prints the digest it got. ---- - ## Themes Themes are modules tagged `theme`, with one rule of their own: **exactly one theme is active at a time**. Enabling a theme unloads the previous one, so there is never an overlap and never a half-applied look. @@ -65,8 +59,6 @@ Many themes ship several colour **schemes**. Switch scheme from the theme's entr If a theme leaves the client looking wrong, disable it from the Installed tab and the client returns to its stock appearance straight away. ---- - ## Rolling back Installed versions are kept side by side, so going back to a version that worked is one command: @@ -76,14 +68,10 @@ spicetify pkg enable my-module@1.2.0 spicetify apply ``` ---- - ## When a module is withdrawn The registry can revoke a module (a security problem, a takedown). A revoked module stops being offered, and the store disables it in your client and tells you why rather than leaving it running quietly. ---- - ## Where modules live on disk ```bash diff --git a/src/content/docs/uninstallation.md b/src/content/docs/uninstallation.md index ea5e9cf2..38097920 100644 --- a/src/content/docs/uninstallation.md +++ b/src/content/docs/uninstallation.md @@ -14,8 +14,6 @@ That undoes the patch and returns the stock client. Restore with the same CLI th If you only wanted to stop a module misbehaving, you do not need any of this: disable it from the store's Installed tab, or `spicetify pkg delete `. ---- - ## Stop the daemon ```bash @@ -25,8 +23,6 @@ spicetify daemon uninstall `stop` also unloads the service, so it does not come back on its own. `uninstall` removes it entirely. ---- - ## Remove the files `spicetify path` prints where everything is. Removing the config folder removes your configuration, every installed module and the store's record of them. @@ -41,8 +37,6 @@ On Windows, delete the folder `spicetify path` names. If you plan to reinstall, keep the folder. Reinstalling with it intact brings back every module you had, at the versions you had. ::: ---- - ## Remove the binary If you installed with the script, everything lives in one directory: @@ -55,8 +49,6 @@ Then remove the `PATH` entry the installer added to your shell profile. If you i On macOS, `apply` also created `~/Applications/Spicetify.app`, the small bundle that receives `spicetify://` links. Delete it too. ---- - ## Going back to v2 Restore with v3 first, then install v2 and apply with it. Your v2 configuration (`config-xpui.ini`) was never touched, so it is exactly as you left it. See the [v2 guide](/docs/legacy/getting-started). diff --git a/src/content/docs/whats-new.md b/src/content/docs/whats-new.md index 073d6827..ab9a09ea 100644 --- a/src/content/docs/whats-new.md +++ b/src/content/docs/whats-new.md @@ -6,8 +6,6 @@ sidebar_position: 2 v3 is a different model, not a faster v2. The short version: one kind of add-on instead of four, a store inside Spotify instead of a CLI-only flow, and a client that repairs itself after Spotify updates. ---- - ## Modules replace themes, extensions, custom apps and snippets In v2 you managed four things in four ways: a theme folder, an extension `.js`, a custom app directory, and CSS snippets, each wired up through `config-xpui.ini` and re-applied by hand. @@ -16,24 +14,18 @@ In v3 all four are the same thing, a **module**: a directory with a `metadata.js What that buys you: modules load and unload at runtime, so most installs take effect without restarting Spotify, and a module that fails is contained instead of taking the client with it. ---- - ## A store inside Spotify The [Marketplace](/docs/legacy/customization/marketplace) was a custom app you installed separately. In v3 the store ships with Spicetify and is the normal way to find and install things, with the CLI (`spicetify pkg`) as the equivalent path for people who prefer a terminal. Every module in the store comes from one registry, and every entry in it was checked before it merged: the artifact is downloaded and re-hashed, published versions can never be rewritten, and a module id stays with the account that first published it. Installs verify the checksum before unpacking. ---- - ## Spotify updates stop breaking things In v2, Spotify updating itself left you with a stock client until you re-ran `spicetify backup apply`, and often waiting for a new Spicetify release that understood the new build. v3 installs a small daemon that notices the update and re-applies afterwards. Support for a new Spotify build no longer needs a new Spicetify release either: the mapping between Spicetify and Spotify's internals is fetched per apply, so a new client version usually just works. When something genuinely is not supported yet, the client says which part is degraded rather than looking silently wrong. ---- - ## Going back is cheap Installed versions are kept side by side. If an update misbehaves: @@ -45,8 +37,6 @@ spicetify apply No hunting for an old download. ---- - ## Command changes | v2 | v3 | @@ -64,8 +54,6 @@ No hunting for an old download. `spicetify restore` still restores stock Spotify, and `spicetify path` still prints where things live. ---- - ## Upgrading from v2 v3 is a reinstall, not an in-place upgrade, and the two must never share a client. @@ -77,8 +65,6 @@ v3 is a reinstall, not an in-place upgrade, and the two must never share a clien Your v2 config (`config-xpui.ini`) is left alone. v3 reads `config.toml` and ignores it, so nothing is lost if you go back. ---- - ## What is not in v3 yet - **Source transforms.** v2 extensions that rewrote the client bundle are off by default; features built on that degrade. From 1e699d23da246ffe91d9000fadef8cd7c19a7607 Mon Sep 17 00:00:00 2001 From: Afonso Jorge Ramos Date: Tue, 11 Aug 2026 00:40:16 +0200 Subject: [PATCH 04/12] docs(v3): point the publish action at spicetify/actions/publish@v1 The action moved out of spicetify/modules into its own repo so authors can pin a stable v1 instead of an unversioned @main. --- src/content/docs/development/publishing.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/content/docs/development/publishing.md b/src/content/docs/development/publishing.md index f985e80e..8a2f92d9 100644 --- a/src/content/docs/development/publishing.md +++ b/src/content/docs/development/publishing.md @@ -33,7 +33,7 @@ Your `metadata.json` needs four things the registry will not accept an entry wit Call the publish action from your own release workflow instead: ```yaml -- uses: spicetify/modules/.github/actions/submit@main +- uses: spicetify/actions/publish@v1 with: dist: dist/my-module@1.0.0 release-tag: ${{ github.ref_name }} From 27681af6d5ef26b825d0255472d8b59793fb33ed Mon Sep 17 00:00:00 2001 From: Afonso Jorge Ramos Date: Wed, 12 Aug 2026 15:09:03 +0200 Subject: [PATCH 05/12] build: add pnpm lockfile --- package.json | 7 +- pnpm-lock.yaml | 5503 +++++++++++++++++++++++++++++++++++++++++++ pnpm-workspace.yaml | 3 + 3 files changed, 5511 insertions(+), 2 deletions(-) create mode 100644 pnpm-lock.yaml create mode 100644 pnpm-workspace.yaml diff --git a/package.json b/package.json index a2ae41ee..7b338b6b 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,9 @@ "react-dom": "19.2.5", "reading-time": "^1.5.0", "remark-directive": "^4.0.0", - "swiper": "^12.1.3" + "sharp": "^0.34.0", + "swiper": "^12.1.3", + "unist-util-visit": "^5.1.0" }, "devDependencies": { "@types/node": "^25.6.0", @@ -58,5 +60,6 @@ "last 1 firefox version", "last 1 safari version" ] - } + }, + "packageManager": "pnpm@11.9.0" } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml new file mode 100644 index 00000000..1cae4a82 --- /dev/null +++ b/pnpm-lock.yaml @@ -0,0 +1,5503 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + '@astrojs/mdx': + specifier: ^5.0.3 + version: 5.0.6(astro@6.4.8(@types/node@25.9.5)(rollup@4.62.4)(yaml@2.9.0)) + '@astrojs/react': + specifier: ^5.0.3 + version: 5.0.7(@types/node@25.9.5)(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(yaml@2.9.0) + '@astrojs/rss': + specifier: ^4.0.18 + version: 4.0.19 + '@astrojs/sitemap': + specifier: ^3.7.2 + version: 3.7.3 + astro: + specifier: ^6.1.7 + version: 6.4.8(@types/node@25.9.5)(rollup@4.62.4)(yaml@2.9.0) + astro-expressive-code: + specifier: ^0.41.7 + version: 0.41.7(astro@6.4.8(@types/node@25.9.5)(rollup@4.62.4)(yaml@2.9.0)) + astro-pagefind: + specifier: ^1.8.6 + version: 1.8.6(astro@6.4.8(@types/node@25.9.5)(rollup@4.62.4)(yaml@2.9.0)) + clsx: + specifier: ^2.1.1 + version: 2.1.1 + mdast-util-to-string: + specifier: ^4.0.0 + version: 4.0.0 + react: + specifier: 19.2.5 + version: 19.2.5 + react-dom: + specifier: 19.2.5 + version: 19.2.5(react@19.2.5) + reading-time: + specifier: ^1.5.0 + version: 1.5.0 + remark-directive: + specifier: ^4.0.0 + version: 4.0.0 + sharp: + specifier: ^0.34.0 + version: 0.34.5 + swiper: + specifier: ^12.1.3 + version: 12.2.0 + unist-util-visit: + specifier: ^5.1.0 + version: 5.1.0 + devDependencies: + '@types/node': + specifier: ^25.6.0 + version: 25.9.5 + '@types/react': + specifier: ^19.2.14 + version: 19.2.18 + '@types/react-dom': + specifier: ^19.2.3 + version: 19.2.4(@types/react@19.2.18) + husky: + specifier: ^9.1.7 + version: 9.1.7 + lint-staged: + specifier: ^16.4.0 + version: 16.4.0 + oxfmt: + specifier: ^0.45.0 + version: 0.45.0 + oxlint: + specifier: ^1.60.0 + version: 1.78.0 + typescript: + specifier: ^6.0.3 + version: 6.0.3 + +packages: + + '@astrojs/compiler@4.0.0': + resolution: {integrity: sha512-eouss7G8ygdZqHuke033VMcVw5HTZUu+PXd/h06DGDUg/jt5btPYPqh66ENWw/mU78rBrf/oeC4oqoBwMtDMNA==} + + '@astrojs/internal-helpers@0.10.0': + resolution: {integrity: sha512-Ry2R3VPeIN4uPCSA4xQc+e+vsJXkalKpEbDc07hV+a/o5Bs2N/s/uDcPJH/05L19DKh9tAy7e6JM3YZ6Cxfezw==} + + '@astrojs/internal-helpers@0.9.1': + resolution: {integrity: sha512-1pWuARqYom/TzuU3+0ZugsTrKlUydWKuULmDqSMTuonY+9IRDUEGKX/8PXQ1nBxRq3w85uGtd9q9SXfqEldMIQ==} + + '@astrojs/markdown-remark@7.1.2': + resolution: {integrity: sha512-caXZ4Dc2St2dW8luEg22GlP0gupLdztCTQE4EzZOxW1pqWXz9mbeJEuHUkgDYcKWW8tjIHkydYDhWLVoxJ327Q==} + + '@astrojs/markdown-remark@7.2.0': + resolution: {integrity: sha512-+YxmVQu1Bd+MFfSzjq1rOJvD9+nIOJzz5YIIhdIH01RrxRkKbyKoEgyIqP3yv51MhzMDgd79QaPv+kCVPT8vHw==} + + '@astrojs/mdx@5.0.6': + resolution: {integrity: sha512-4dKe0ZMmqujofPNDHahzClkwinn9f8jHPcaXcgdGvPAlboD2mjzkUCofli2cBnxYAkdfhC6d50gBJ8i/cH8gHw==} + engines: {node: '>=22.12.0'} + peerDependencies: + astro: ^6.0.0 + + '@astrojs/prism@4.0.2': + resolution: {integrity: sha512-KTivpmnz6lDsC6o9H4+DNm2SrE/GHzw8cNAvEJwAvUT+eoaEnn/4NtbDNfRRaxaJHdp15gf+tfHAWiXR4wB3BA==} + engines: {node: '>=22.12.0'} + + '@astrojs/react@5.0.7': + resolution: {integrity: sha512-N9cCoxvnLWaP+AK1Fv4e5Mc7ktnVTpSo2nWLwvD9Ohr1dJKygwrTSm9yatqoahgb1A5Kwjg/rT2shRiIVdn3aw==} + engines: {node: '>=22.12.0'} + peerDependencies: + '@types/react': ^17.0.50 || ^18.0.21 || ^19.0.0 + '@types/react-dom': ^17.0.17 || ^18.0.6 || ^19.0.0 + react: ^17.0.2 || ^18.0.0 || ^19.0.0 + react-dom: ^17.0.2 || ^18.0.0 || ^19.0.0 + + '@astrojs/rss@4.0.19': + resolution: {integrity: sha512-e+z5wYeYtffQdHQO8c2tkSd2JEBdAuRXJV4ZEU5IxkYeE6e39woDd7nw1PH1Kk2tEYNCYuKdylnnbhGmt61awA==} + + '@astrojs/sitemap@3.7.3': + resolution: {integrity: sha512-f8euLVsyeAmAkSm/1M2Kb8sL8byQmfgbvBNaHFItCheTj/IpiJYSEWVcqDHZ/yEHxiS7+w87mQkzwZaPHmk5GA==} + + '@astrojs/telemetry@3.3.2': + resolution: {integrity: sha512-j8DNruA8ors99Al39RYZPJK4DC1bKkoNm93mAMuBhY9TCNC4R8n1q7ovFnJ5qhGh5Lsh7pa1gpQVpYpsJPeTHQ==} + engines: {node: 18.20.8 || ^20.3.0 || >=22.0.0} + + '@babel/code-frame@7.29.7': + resolution: {integrity: sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==} + engines: {node: '>=6.9.0'} + + '@babel/compat-data@7.29.7': + resolution: {integrity: sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==} + engines: {node: '>=6.9.0'} + + '@babel/core@7.29.7': + resolution: {integrity: sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==} + engines: {node: '>=6.9.0'} + + '@babel/generator@7.29.8': + resolution: {integrity: sha512-gZbepsdh3WDtgZKWL+vTPh71LSBrm/Y4/QDZBVCcYfmeTEEuoOYwlSy+G1StfJg+/Zy550u/3TATbm7qDbbMtg==} + engines: {node: '>=6.9.0'} + + '@babel/helper-compilation-targets@7.29.7': + resolution: {integrity: sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==} + engines: {node: '>=6.9.0'} + + '@babel/helper-globals@7.29.7': + resolution: {integrity: sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-imports@7.29.7': + resolution: {integrity: sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-transforms@7.29.7': + resolution: {integrity: sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-plugin-utils@7.29.7': + resolution: {integrity: sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-string-parser@7.29.7': + resolution: {integrity: sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-identifier@7.29.7': + resolution: {integrity: sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-option@7.29.7': + resolution: {integrity: sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==} + engines: {node: '>=6.9.0'} + + '@babel/helpers@7.29.7': + resolution: {integrity: sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==} + engines: {node: '>=6.9.0'} + + '@babel/parser@7.29.8': + resolution: {integrity: sha512-E8lTAYNB1KW+FH+VGJuZM1ioAx2E6oVlvQFRrf5P8ZZmsiJXYAD9vTFV7yyEURNzgh1dFqMZuO6tUwcARbqFCA==} + engines: {node: '>=6.0.0'} + hasBin: true + + '@babel/plugin-transform-react-jsx-self@7.29.7': + resolution: {integrity: sha512-TL0hMc9xzy86VD31nUiwzd5otRAcyEPcsegCxolO0PvcXuH1v0kECe/UIznYFihpkvU5wg/jk4v0TTEFfm53fw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-react-jsx-source@7.29.7': + resolution: {integrity: sha512-06IyK09H3wi4cGbhDBwp5gUGo0IKtnYa8tyTiephirPCK6fbobVGiXMMI5zLQ4aKEYP3wZ3ArU44o+8KMrSG/Q==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/template@7.29.7': + resolution: {integrity: sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==} + engines: {node: '>=6.9.0'} + + '@babel/traverse@7.29.8': + resolution: {integrity: sha512-I5z7H3bf/41ktsNVLtpN0wAa336HkqIHQ5BuPLEhTkt1jVSyZpeNKIzTgEWmlxjdg81R0IgUCcaE+Ok3NvrfZg==} + engines: {node: '>=6.9.0'} + + '@babel/types@7.29.8': + resolution: {integrity: sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==} + engines: {node: '>=6.9.0'} + + '@capsizecss/unpack@4.0.1': + resolution: {integrity: sha512-CuNiSqg7+e1cO/GjffyMOm5Tt2jUF9CWHHnvQ/UkqvtkGfHdgwEC0wpmq7fkN3gxwpRnrAN0WzO3vREKmNolMQ==} + engines: {node: '>=18'} + + '@clack/core@1.4.3': + resolution: {integrity: sha512-/kr3UWNtdJfxZtPgDqUOmG2pvwlmcLGheex5yiZKdwbzZJxhV+HMNR9QNmyY5cGwTNV6LrR7Jtp+KjhUAP1qBQ==} + engines: {node: '>= 20.12.0'} + + '@clack/prompts@1.7.0': + resolution: {integrity: sha512-y7/yvZ2TPAnR9+jnc00klvNNLkJiXFFrQA/hlLCcxA9a2A4zQIOimyFQ9XfwYKiGD1fb5GY8vbKIIgO8d5Tb2A==} + engines: {node: '>= 20.12.0'} + + '@ctrl/tinycolor@4.2.0': + resolution: {integrity: sha512-kzyuwOAQnXJNLS9PSyrk0CWk35nWJW/zl/6KvnTBMFK65gm7U1/Z5BqjxeapjZCIhQcM/DsrEmcbRwDyXyXK4A==} + engines: {node: '>=14'} + + '@emnapi/runtime@1.11.3': + resolution: {integrity: sha512-Xz4Tpyki7XyrpbUK1jR1AhdAdaXyhhY4lZ3neLodmhpuWfy2PAQN5B46sAiU4liOXGLkHypn/qU+jvfWSCYYLA==} + + '@esbuild/aix-ppc64@0.27.7': + resolution: {integrity: sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/aix-ppc64@0.28.2': + resolution: {integrity: sha512-XExcO+dvLKvVtNTibSTBej1NCAbaGhWn9Ww1ZPx80qsahhPFe/8jgWP0IchNe0F3HwkU7n8ejhH8bjonqht8mQ==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.27.7': + resolution: {integrity: sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm64@0.28.2': + resolution: {integrity: sha512-5YfKeeI8qWfBZIX+u2xZC3Zlb3Os/gLS2sbEKM+I4ZOcsWmHS2WLysCcQZDAFRslDUU5Oiq44gf6PYN1vGwG5A==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.27.7': + resolution: {integrity: sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + + '@esbuild/android-arm@0.28.2': + resolution: {integrity: sha512-kXXoiPVVGQcnIYGOeaovwOURpniDBpSq4A03qkQ+BMQqtGG6HYap3xne9C1O1yo4TR3qxlCX5IqqmX6fFo2Lqg==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.27.7': + resolution: {integrity: sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/android-x64@0.28.2': + resolution: {integrity: sha512-O387ite7SzUyCcy3JQX4P4bLtEA7bLLkx+esve5JHnyYfNTxcVpXZo9jhdB0lTKN44gztELTdU7nS8Nr16Fs1Q==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.27.7': + resolution: {integrity: sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-arm64@0.28.2': + resolution: {integrity: sha512-n4KqkOQrraxHJcgjM1RvwbigfQKIKJVpM7xp+KsxiyUSrRdIXnt73VhrPAx0fV44hgfmIVKjxMN9J1t5jySVkw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.27.7': + resolution: {integrity: sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/darwin-x64@0.28.2': + resolution: {integrity: sha512-uq6suIWYP37qzGddBKPw5QEQPi6HiLGsO7UmkpfyaYNQ3D+rN6w6WfwH+nuqcGXWvawGwxOEroO4YGnFh95azw==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.27.7': + resolution: {integrity: sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-arm64@0.28.2': + resolution: {integrity: sha512-n+I0BTSRIoy+d6RPKnEVwql5UwBJolytvY4mAOIEJorKlqgPII8ix6slVVrfZ5Tnj7glIZvloylbB/EJPMWEXw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.27.7': + resolution: {integrity: sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.28.2': + resolution: {integrity: sha512-78XJTJkvPs0kz2w61301PJjXl4g7q3JqiYMZ/M/yVI73EHBrCRTgkhu9oqG7vPqq+a/yadEW8aD+agKlk5xrmg==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.27.7': + resolution: {integrity: sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm64@0.28.2': + resolution: {integrity: sha512-pW4AC0P3it8c7do9MVM4p51FzHzdM/TZrerurgRcHJ2WTa1VQ1CIq18xncfpBJw4ojkiZZrKW2yIBWBP92j6Ug==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.27.7': + resolution: {integrity: sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-arm@0.28.2': + resolution: {integrity: sha512-XlDnu2q5yoqems+xay6wSAcg9DDD7K9RLKZEBOMZm3ckNpJBvOX20tSfby8KfrrhINDyv9V2YVZKY/SpoGJI8w==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.27.7': + resolution: {integrity: sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-ia32@0.28.2': + resolution: {integrity: sha512-CYbnj78HsIeA+DhgUKgFCfvNsTHFhMMrinUrMZpDXJXKN8T3XViTZ/+wtHeVxEWY8ewSzTFN+nRmSwO2tZaLUQ==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.27.7': + resolution: {integrity: sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-loong64@0.28.2': + resolution: {integrity: sha512-buwkd8nsph4R+ajRvw0qM5Hja/TXQow3ptzWO2EbG/cqcIkHloRrdlBtQlshyYGTNFvfkfJ5tpPLVkY4DtsPfQ==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.27.7': + resolution: {integrity: sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-mips64el@0.28.2': + resolution: {integrity: sha512-ZVykbDyk7519VwiNb9Lcj9m8XM6v5V9uKPvrEMkkEedVewf+0itkhahp4HDpgERXhwLRpWFypsGbG/J8s0QjJA==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.27.7': + resolution: {integrity: sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-ppc64@0.28.2': + resolution: {integrity: sha512-CAXl+Dtd9UUuJd8pKKdwh6MLm3MUMiqMPmhZ3tTSXPqfyQ3vDl6R5hZdZ/kYojK4ofXtdfSv1tFq8XzWx3heNQ==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.27.7': + resolution: {integrity: sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-riscv64@0.28.2': + resolution: {integrity: sha512-GeXCej4IQtU1B+QlDV8W/RRvbzI3O/Stss+/bCXv4lZls5WGRtu2a+3JkA3i4qIUlMXpcHebWpF8AkJhATowuA==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.27.7': + resolution: {integrity: sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-s390x@0.28.2': + resolution: {integrity: sha512-3H1weTYZPxt/WOhByszQZybS9w5lKzUn1FDMsgEChbHWQwHYQQRfBxgCcZvPhjHfKyJjIievvMmEUawJrdY9Dg==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.27.7': + resolution: {integrity: sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/linux-x64@0.28.2': + resolution: {integrity: sha512-4xTZr1FUmSoQW4XIWmit3tzQrUTZM+N3P0XV8xROKYF50XfI7xeO90+1bZvNwxIufQ9hDQVRJH5YhgPVF8A/HQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-arm64@0.27.7': + resolution: {integrity: sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + + '@esbuild/netbsd-arm64@0.28.2': + resolution: {integrity: sha512-sSATRjPeDBg3pdgHoQfoYBob11Kk1FGa9lui5RIHZCoCkJa9QKlvl3/vKz2usCmYYjs7ymJR/2Nnsqe+Hjt5nw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.27.7': + resolution: {integrity: sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.28.2': + resolution: {integrity: sha512-lqnzCV+mM0gIADaKihiCg6ifgfU2L3h5E33rNQBN1Y4MaVGnzryzmvvf7UHxprpQdE8hpqLolJ9Rl+SkIRDpyw==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-arm64@0.27.7': + resolution: {integrity: sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-arm64@0.28.2': + resolution: {integrity: sha512-AL2qJILH7lNjrDmCQDvdxMfAUIv8KMNZOvrwAQ8i8//ntL9FflhOyMJ8OZSMBb8/AWXe3/5v5S20y3zCoZWKoQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.27.7': + resolution: {integrity: sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.28.2': + resolution: {integrity: sha512-QtiuPytchRyC4rwUKhexJdQKvDuZ6hWloi3igqPQNUJCS1/v9EiO3UTOXR6A3FoMo4fnAKbWJdqaIwhOzh8qEw==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/openharmony-arm64@0.27.7': + resolution: {integrity: sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + + '@esbuild/openharmony-arm64@0.28.2': + resolution: {integrity: sha512-WkhYDmpTjLvGlScA1rwjRUmhl4k8oXR3cIbtqWmELgU/dFeHHlEllxDvdWcNJV9rbzCexB5vz8gtNewWLgCT7Q==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + + '@esbuild/sunos-x64@0.27.7': + resolution: {integrity: sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/sunos-x64@0.28.2': + resolution: {integrity: sha512-GPMSkTOtMnv2U2F8gxe4Io6qmVs+YKyp832Etqqxr0hFngmXQ3rzwytelm3GIn7T4VviRUlf3sOgBOiTdvaf7g==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.27.7': + resolution: {integrity: sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-arm64@0.28.2': + resolution: {integrity: sha512-PIhhEkE9uPBleRBrQEJpUn7MBnibZzbGzYWPmY3x+YoVg/95zbjB4CxPPOQ8l5tYYM4mMaCthF8/1DIfBQQyWQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.27.7': + resolution: {integrity: sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-ia32@0.28.2': + resolution: {integrity: sha512-YmJbfTlvU7Sdn9BB+4PRES4oB6pxgS37MAONj+hBr/cpXS1aBPKXxNnDbu+QCWPj0o9dgyxeq79g6c5P8KeuYA==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.27.7': + resolution: {integrity: sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + + '@esbuild/win32-x64@0.28.2': + resolution: {integrity: sha512-5ebpxr3nWMzrL/rnUI755Jkuee0bHL/Gq0WTF9lvcpv73wAp5eu8MfBUgWK9bhWvZjj7yX8etf/8tI8Ney695g==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + + '@expressive-code/core@0.41.7': + resolution: {integrity: sha512-ck92uZYZ9Wba2zxkiZLsZGi9N54pMSAVdrI9uW3Oo9AtLglD5RmrdTwbYPCT2S/jC36JGB2i+pnQtBm/Ib2+dg==} + + '@expressive-code/plugin-frames@0.41.7': + resolution: {integrity: sha512-diKtxjQw/979cTglRFaMCY/sR6hWF0kSMg8jsKLXaZBSfGS0I/Hoe7Qds3vVEgeoW+GHHQzMcwvgx/MOIXhrTA==} + + '@expressive-code/plugin-shiki@0.41.7': + resolution: {integrity: sha512-DL605bLrUOgqTdZ0Ot5MlTaWzppRkzzqzeGEu7ODnHF39IkEBbFdsC7pbl3LbUQ1DFtnfx6rD54k/cdofbW6KQ==} + + '@expressive-code/plugin-text-markers@0.41.7': + resolution: {integrity: sha512-Ewpwuc5t6eFdZmWlFyeuy3e1PTQC0jFvw2Q+2bpcWXbOZhPLsT7+h8lsSIJxb5mS7wZko7cKyQ2RLYDyK6Fpmw==} + + '@img/colour@1.1.0': + resolution: {integrity: sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==} + engines: {node: '>=18'} + + '@img/sharp-darwin-arm64@0.34.5': + resolution: {integrity: sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [darwin] + + '@img/sharp-darwin-x64@0.34.5': + resolution: {integrity: sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [darwin] + + '@img/sharp-libvips-darwin-arm64@1.2.4': + resolution: {integrity: sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==} + cpu: [arm64] + os: [darwin] + + '@img/sharp-libvips-darwin-x64@1.2.4': + resolution: {integrity: sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==} + cpu: [x64] + os: [darwin] + + '@img/sharp-libvips-linux-arm64@1.2.4': + resolution: {integrity: sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@img/sharp-libvips-linux-arm@1.2.4': + resolution: {integrity: sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==} + cpu: [arm] + os: [linux] + libc: [glibc] + + '@img/sharp-libvips-linux-ppc64@1.2.4': + resolution: {integrity: sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@img/sharp-libvips-linux-riscv64@1.2.4': + resolution: {integrity: sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@img/sharp-libvips-linux-s390x@1.2.4': + resolution: {integrity: sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@img/sharp-libvips-linux-x64@1.2.4': + resolution: {integrity: sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@img/sharp-libvips-linuxmusl-arm64@1.2.4': + resolution: {integrity: sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@img/sharp-libvips-linuxmusl-x64@1.2.4': + resolution: {integrity: sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==} + cpu: [x64] + os: [linux] + libc: [musl] + + '@img/sharp-linux-arm64@0.34.5': + resolution: {integrity: sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@img/sharp-linux-arm@0.34.5': + resolution: {integrity: sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm] + os: [linux] + libc: [glibc] + + '@img/sharp-linux-ppc64@0.34.5': + resolution: {integrity: sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@img/sharp-linux-riscv64@0.34.5': + resolution: {integrity: sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@img/sharp-linux-s390x@0.34.5': + resolution: {integrity: sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@img/sharp-linux-x64@0.34.5': + resolution: {integrity: sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@img/sharp-linuxmusl-arm64@0.34.5': + resolution: {integrity: sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@img/sharp-linuxmusl-x64@0.34.5': + resolution: {integrity: sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [linux] + libc: [musl] + + '@img/sharp-wasm32@0.34.5': + resolution: {integrity: sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [wasm32] + + '@img/sharp-win32-arm64@0.34.5': + resolution: {integrity: sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [win32] + + '@img/sharp-win32-ia32@0.34.5': + resolution: {integrity: sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [ia32] + os: [win32] + + '@img/sharp-win32-x64@0.34.5': + resolution: {integrity: sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [win32] + + '@jridgewell/gen-mapping@0.3.13': + resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} + + '@jridgewell/remapping@2.3.5': + resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==} + + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + + '@jridgewell/sourcemap-codec@1.5.5': + resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} + + '@jridgewell/trace-mapping@0.3.31': + resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + + '@mdx-js/mdx@3.1.1': + resolution: {integrity: sha512-f6ZO2ifpwAQIpzGWaBQT2TXxPv6z3RBzQKpVftEWN78Vl/YweF1uwussDx8ECAXVtr3Rs89fKyG9YlzUs9DyGQ==} + + '@napi-rs/lzma-linux-x64-gnu@1.5.1': + resolution: {integrity: sha512-oTXEIha4SsuXdTA4Iyskj0kpdx2yVXdhd75c2v3xGrHFfVMsbhTPZU/nMPL4sWKo4pBHm3aucLaqGlF696dTyQ==} + engines: {node: ^22.20 || ^24.12 || >=25} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@nodable/entities@3.0.0': + resolution: {integrity: sha512-8L9xFeTYKhm49xfIypoe2W5wV1m/3Z58kT+7kR9A8OyFxcPduI4VmxaUMQyKYrRjUoLLSXv6EKKID5Tvj9cUVw==} + + '@oslojs/encoding@1.1.0': + resolution: {integrity: sha512-70wQhgYmndg4GCPxPPxPGevRKqTIJ2Nh4OkiMWmDAVYsTQ+Ta7Sq+rPevXyXGdzr30/qZBnyOalCszoMxlyldQ==} + + '@oxfmt/binding-android-arm-eabi@0.45.0': + resolution: {integrity: sha512-A/UMxFob1fefCuMeGxQBulGfFE38g2Gm23ynr3u6b+b7fY7/ajGbNsa3ikMIkGMLJW/TRoQaMoP1kME7S+815w==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [android] + + '@oxfmt/binding-android-arm64@0.45.0': + resolution: {integrity: sha512-L63z4uZmHjgvvqvMJD7mwff8aSBkM0+X4uFr6l6U5t6+Qc9DCLVZWIunJ7Gm4fn4zHPdSq6FFQnhu9yqqobxIg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [android] + + '@oxfmt/binding-darwin-arm64@0.45.0': + resolution: {integrity: sha512-UV34dd623FzqT+outIGndsCA/RBB+qgB3XVQhgmmJ9PJwa37NzPC9qzgKeOhPKxVk2HW+JKldQrVL54zs4Noww==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [darwin] + + '@oxfmt/binding-darwin-x64@0.45.0': + resolution: {integrity: sha512-pMNJv0CMa1pDefVPeNbuQxibh8ITpWDFEhMC/IBB9Zlu76EbgzYwrzI4Cb11mqX2+rIYN70UTrh3z06TM59ptQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [darwin] + + '@oxfmt/binding-freebsd-x64@0.45.0': + resolution: {integrity: sha512-xTcRoxbbo61sW2+ZRPeH+vp/o9G8gkdhiVumFU+TpneiPm14c79l6GFlxPXlCE9bNWikigbsrvJw46zCVAQFfg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [freebsd] + + '@oxfmt/binding-linux-arm-gnueabihf@0.45.0': + resolution: {integrity: sha512-hWL8Hdni+3U1mPFx1UtWeGp3tNb6EhBAUHRMbKUxVkOp3WwoJbpVO2bfUVbS4PfpledviXXNHSTl1veTa6FhkQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@oxfmt/binding-linux-arm-musleabihf@0.45.0': + resolution: {integrity: sha512-6Blt/0OBT7vvfQpqYuYbpbFLPqSiaYpEJzUUWhinPEuADypDbtV1+LdjM0vYBNGPvnj85ex7lTerEX6JGcPt9w==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@oxfmt/binding-linux-arm64-gnu@0.45.0': + resolution: {integrity: sha512-jLjoLfe+hGfjhA8hNBSdw85yCA8ePKq7ME4T+g6P9caQXvmt6IhE2X7iVjnVdkmYUWEzZrxlh4p6RkDmAMJY/A==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@oxfmt/binding-linux-arm64-musl@0.45.0': + resolution: {integrity: sha512-XQKXZIKYJC3GQJ8FnD3iMntpw69Wd9kDDK/Xt79p6xnFYlGGxSNv2vIBvRTDg5CKByWFWWZLCRDOXoP/m6YN4g==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@oxfmt/binding-linux-ppc64-gnu@0.45.0': + resolution: {integrity: sha512-+g5RiG+xOkdrCWkKodv407nTvMq4vYM18Uox2MhZBm/YoqFxxJpWKsloskFFG5NU13HGPw1wzYjjOVcyd9moCA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@oxfmt/binding-linux-riscv64-gnu@0.45.0': + resolution: {integrity: sha512-V7dXKoSyEbWAkkSF4JJNtF+NJZDmJoSarSoP30WCsB3X636Rehd3CvxBj49FIJxEBFWhvcUjGSHVeU8Erck1bQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@oxfmt/binding-linux-riscv64-musl@0.45.0': + resolution: {integrity: sha512-Vdelft1sAEYojVGgcODEFXSWYQYlIvoyIGWebKCuUibd1tvS1TjTx413xG2ZLuHpYj45CkN/ztMLMX6jrgqpgg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [riscv64] + os: [linux] + libc: [musl] + + '@oxfmt/binding-linux-s390x-gnu@0.45.0': + resolution: {integrity: sha512-RR7xKgNpqwENnK0aYCGYg0JycY2n93J0reNjHyes+I9Gq52dH95x+CBlnlAQHCPfz6FGnKA9HirgUl14WO6o7w==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@oxfmt/binding-linux-x64-gnu@0.45.0': + resolution: {integrity: sha512-U/QQ0+BQNSHxjuXR/utvXnQ50Vu5kUuqEomZvQ1/3mhgbBiMc2WU9q5kZ5WwLp3gnFIx9ibkveoRSe2EZubkqg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@oxfmt/binding-linux-x64-musl@0.45.0': + resolution: {integrity: sha512-o5TLOUCF0RWQjsIS06yVC+kFgp092/yLe6qBGSUvtnmTVw9gxjpdQSXc3VN5Cnive4K11HNstEZF8ROKHfDFSw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [musl] + + '@oxfmt/binding-openharmony-arm64@0.45.0': + resolution: {integrity: sha512-RnGcV3HgPuOjsGx/k9oyRNKmOp+NBLGzZTdPDYbc19r7NGeYPplnUU/BfU35bX2Y/O4ejvHxcfkvW2WoYL/gsg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [openharmony] + + '@oxfmt/binding-win32-arm64-msvc@0.45.0': + resolution: {integrity: sha512-v3Vj7iKKsUFwt9w5hsqIIoErKVoENC6LoqfDlteOQ5QMDCXihlqLoxpmviUhXnNncg4zV6U9BPwlBbwa+qm4wg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [win32] + + '@oxfmt/binding-win32-ia32-msvc@0.45.0': + resolution: {integrity: sha512-N8yotPBX6ph0H3toF4AEpdCeVPrdcSetj+8eGiZGsrLsng3bs/Q5HPu4bbSxip5GBPx5hGbGHrZwH4+rcrjhHA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ia32] + os: [win32] + + '@oxfmt/binding-win32-x64-msvc@0.45.0': + resolution: {integrity: sha512-w5MMTRCK1dpQeRA+HHqXQXyN33DlG/N2LOYxJmaT4fJjcmZrbNnqw7SmIk7I2/a2493PPLZ+2E/Ar6t2iKVMug==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [win32] + + '@oxlint/binding-android-arm-eabi@1.78.0': + resolution: {integrity: sha512-Bu819lmAfZMUHErrpe0cEWj3iaefuUODHSU8+UbXy67V/r7/7f4K3FL0NmbD85E+wiFLDYuhP8Zlv0XnVeXshw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [android] + + '@oxlint/binding-android-arm64@1.78.0': + resolution: {integrity: sha512-CDfxZgB61B7buRdY2FJoAYYPPXCZ1EoC1LKscnC5dg3kjobdxiconvAvvN1BmHyW4PyFT3jRLDag/BY/roSNBQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [android] + + '@oxlint/binding-darwin-arm64@1.78.0': + resolution: {integrity: sha512-2Y2U9Ahrz+OO0Ej88f9SJYq51/jUBp1Mc7iZu0ukrbeeZ3gpRGfzIFnoqfHDY96xr0GEfNrPUBFEy0nN5aD7HA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [darwin] + + '@oxlint/binding-darwin-x64@1.78.0': + resolution: {integrity: sha512-rpych6eJq6m9jDRypTEaPD1xysaEW5h9+xuxhGK/QhOg+/xaqPZrCrTNoIl/f3nEjuJeCEmstNDlrE9rJi/3/g==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [darwin] + + '@oxlint/binding-freebsd-x64@1.78.0': + resolution: {integrity: sha512-IcMGrQT3QizkOESUJd5et+rOhVqSkNDfNik1cvrKDqIbzqx9KMtRswpFgkCuNTSwylCFLKhGUu8KmqY1ZnC0Dg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [freebsd] + + '@oxlint/binding-linux-arm-gnueabihf@1.78.0': + resolution: {integrity: sha512-/uLdoJ0IXE6vo/0f0LKjinQAp+re+VMaCWaNT8ENIv2EOCkSsc8SGaflXAuW0Jua2dq5+GLVWm1NQK7P3UFSNQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@oxlint/binding-linux-arm-musleabihf@1.78.0': + resolution: {integrity: sha512-7xi4Wb/O8NRJhLoUXmDJMUVpNYvB5kefdhFU1Jb8rtae4QoXlTiLwI14X4YvAXVZLNZChP8m5qO9SQAlWQTbkQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@oxlint/binding-linux-arm64-gnu@1.78.0': + resolution: {integrity: sha512-4hFW0+fVXa3OIh1Y4A5SPkmvI4wuuBSrCVKzOyE7PTjhc7yEqZ1pmvEEeS5Lj/MaqvegFxXyF33N+6jkehxdyg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@oxlint/binding-linux-arm64-musl@1.78.0': + resolution: {integrity: sha512-oC0mvsgBJjlMijSDEhx9KuvR9zYeHXceA9MjbuXB1F8NSR78Yj2unOBrstEvTVaq+pko+kuue6DajC00eqvTdg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@oxlint/binding-linux-ppc64-gnu@1.78.0': + resolution: {integrity: sha512-XAllT5SUZS+ohjuZ3/5S0cwe0r7eboiuigeStCZ5DXRYx/2KVM2UvQXvAfyzXEimtQjAB7cDQ2YxDe2Zl2WNQQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@oxlint/binding-linux-riscv64-gnu@1.78.0': + resolution: {integrity: sha512-trucMER/0QtecoXvc1y/UVqE3kwJipDwrx4oHfj+nNm3dq2zjP44WT0CfHNDPM3G1DXIkx/gY6lAD21NSCZVhA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@oxlint/binding-linux-riscv64-musl@1.78.0': + resolution: {integrity: sha512-cm3O4F/HQbdzOUX5mKHqG5KDL6E5w0pnlZ+fbBy2rmLryPOowkuLagFHTopQsEIpjcaZoPOrL+BmmAytAG9HFg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [riscv64] + os: [linux] + libc: [musl] + + '@oxlint/binding-linux-s390x-gnu@1.78.0': + resolution: {integrity: sha512-33wRf6HqGNsybJ3qX4cGaQN2ODPxNmc1rMa0mrTmx3eFq1VzOnvQooi9bIGVYakW8a/wmqVx1mgsUm8R2xfTiw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@oxlint/binding-linux-x64-gnu@1.78.0': + resolution: {integrity: sha512-rRdISSYegj6VganMZ9tjRjijowfHJ09IZU01i0toBAqr6n5LEtwHq2IeS4FjW2RoskOHlb6efB26H5izYb3GEQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@oxlint/binding-linux-x64-musl@1.78.0': + resolution: {integrity: sha512-GmsP4rW0xTL6u5CVdcDsaN5Fbc7hBc382Wmar1kttbnwSEviM+rSINKOMQ+UQ6iH+AGwC+8gaAiwu134Tgh6Lg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [musl] + + '@oxlint/binding-openharmony-arm64@1.78.0': + resolution: {integrity: sha512-sy9yeYuADc8a+n4TLBayzMCZiHPW78DcIFVpOXTmdKHWQeM9xe5uzkqIIZmi326D5hY9XVwacipEB1p7tQjPAg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [openharmony] + + '@oxlint/binding-win32-arm64-msvc@1.78.0': + resolution: {integrity: sha512-rjc2hF1KfMi8fZj1X/m3AmnHbdsF3rL0v6KQg0Uc880Yb2khjz+3U14sfdZ7jWTpRnN1m1NQa/TT7uU9lJWPrA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [win32] + + '@oxlint/binding-win32-ia32-msvc@1.78.0': + resolution: {integrity: sha512-zcuXFVrEFHIafRfkCQT8w/Xe41o07ozl/vwHq7p94vB29xVzsB0sZGYORU1jhcYKv3Lr0J3HbJ2T4fHH5rWmvA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ia32] + os: [win32] + + '@oxlint/binding-win32-x64-msvc@1.78.0': + resolution: {integrity: sha512-Sb5ocmLSuYeOuXd+CFOToGKp/gjXUEWDnvIGwhnh8aq8wY4TMmEnKnvbogSW7RdMZv77JSARduS7/gv+khYEjA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [win32] + + '@pagefind/darwin-arm64@1.5.2': + resolution: {integrity: sha512-MXpI+7HsAdPkvJ0gk9xj9g541BCqBZOBbdwj9g6lB5LCj6kSV6nqDSjzcAJwvOsfu0fjwvC8hQU+ecfhp+MpiQ==} + cpu: [arm64] + os: [darwin] + + '@pagefind/darwin-x64@1.5.2': + resolution: {integrity: sha512-IojxFWMEJe0RQ7PQ3KXQsPIImNsbpPYpoZ+QUDrL8fAl/O27IX+LVLs74/UzEZy5uA2LD8Nz1AiwKr72vrkZQw==} + cpu: [x64] + os: [darwin] + + '@pagefind/default-ui@1.5.2': + resolution: {integrity: sha512-pm1LMnQg8N2B3n2TnjKlhaFihpz6zTiA4HiGQ6/slKO/+8K9CAU5kcjdSSPgpuk1PMuuN4hxLipUIifnrkl3Sg==} + + '@pagefind/freebsd-x64@1.5.2': + resolution: {integrity: sha512-7EVzo9+0w+2cbe671BtMj10UlNo83I+HrLVLfRxO731svHRJKUfJ/mo05gU14pe9PCfpKNQT8FS3Xc/oDN6pOA==} + cpu: [x64] + os: [freebsd] + + '@pagefind/linux-arm64@1.5.2': + resolution: {integrity: sha512-Ovt9+K35sqzn8H3ZMXGwls4TD/wMJuvRtShHIsmUQREmaxjrDEX7gHckRCrwYJ4XE1H1p6HkLz3wukrAnsfXQw==} + cpu: [arm64] + os: [linux] + + '@pagefind/linux-x64@1.5.2': + resolution: {integrity: sha512-V+tFqHKXhQKq/WqPBD67AFy7scn1/aZID00ws4fSDd+1daSi5UHR9VVlRrOUYKxn3VuFQYRD7lYXdZK1WED1YA==} + cpu: [x64] + os: [linux] + + '@pagefind/windows-arm64@1.5.2': + resolution: {integrity: sha512-hN9Nh90fNW61nNRCW9ZyQrAj/mD0eRvmJ8NlTUzkbuW8kIzGJUi3cxjFkEcMZ5h/8FsKWD/VcouZl4yo1F7B6g==} + cpu: [arm64] + os: [win32] + + '@pagefind/windows-x64@1.5.2': + resolution: {integrity: sha512-Fa2Iyw7kaDRzGMfNYNUXNW2zbL5FQVDgSOcbDHdzBrDEdpqOqg8TcZ68F22ol6NJ9IGzvUdmeyZypLW5dyhqsg==} + cpu: [x64] + os: [win32] + + '@polka/url@1.0.0-next.29': + resolution: {integrity: sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==} + + '@rolldown/pluginutils@1.0.0-rc.3': + resolution: {integrity: sha512-eybk3TjzzzV97Dlj5c+XrBFW57eTNhzod66y9HrBlzJ6NsCrWCp/2kaPS3K9wJmurBC0Tdw4yPjXKZqlznim3Q==} + + '@rollup/pluginutils@5.4.0': + resolution: {integrity: sha512-MfPp06CjRLfXQ3wY0R8vJDYBy/MvVcc9OulEfR0B8Iv9ko+GCNaRZ+EpJYFl27LhKsZK0o420sYCRHCjfCgeUg==} + engines: {node: '>=14.0.0'} + peerDependencies: + rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0 + peerDependenciesMeta: + rollup: + optional: true + + '@rollup/rollup-android-arm-eabi@4.62.4': + resolution: {integrity: sha512-RrPokAb7dmbxFoeO3TloqHyOjgye8RkBhSqmp4aJMIex4c9r46ZstPnleDQOq1t46VOVjwIuwNogIqbodV1Vvg==} + cpu: [arm] + os: [android] + + '@rollup/rollup-android-arm64@4.62.4': + resolution: {integrity: sha512-JKuJc+pnpks2pjy7L/N3v/cAkZxYlnmuZoD840ldbMI5KDbC4iO9NKwPKYdjYFCMAIIlBzYSFHxIJVYzRo2/8A==} + cpu: [arm64] + os: [android] + + '@rollup/rollup-darwin-arm64@4.62.4': + resolution: {integrity: sha512-krw5uS2STmvJ02x0uTXHbqQNuz+9eZ1iw+qXk9dmW2gvV4jV7O2hEoOnuhFrpOPiel1mBFtqbxYZZtC46hXLOw==} + cpu: [arm64] + os: [darwin] + + '@rollup/rollup-darwin-x64@4.62.4': + resolution: {integrity: sha512-wsTxtgApb4PrOsNJIm0FZ1h3WvCC+k9uxLJ4ad75hgoS4NiRes2SoJFlDAyMwiUY8IssDqGcHbXuN0sx1tfF1A==} + cpu: [x64] + os: [darwin] + + '@rollup/rollup-freebsd-arm64@4.62.4': + resolution: {integrity: sha512-GUOnQlyZe3yAXhWOtOMsn5Qkrv5E5mZXa0thbARWi5Ei2szlVXJFQhddZ4HbAzh8q92w5twp+CQvs/eFanz9YQ==} + cpu: [arm64] + os: [freebsd] + + '@rollup/rollup-freebsd-x64@4.62.4': + resolution: {integrity: sha512-/Y7f3QuxjzPKsjA/rfEDa3+0vXqyjmJ50Ln8dPpCmWkKTrUoWHG1cWhTqaAMLob2m2nESWuC7yGrREz019Ztqg==} + cpu: [x64] + os: [freebsd] + + '@rollup/rollup-linux-arm-gnueabihf@4.62.4': + resolution: {integrity: sha512-81wiiX3v7aqy+T+bT61TJ78yJjRquqFFTTbAPt08imfQQzkPIW8t6aJbkTagtCCrXMNc9D66+geqlK7ydLPNqA==} + cpu: [arm] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-arm-musleabihf@4.62.4': + resolution: {integrity: sha512-9kmDIvNZqdoHOBZgNtpTBeLWYO/LVipM3H/j62P8848/l/VPEQL6N3uxU9pvP1oZAsXyC2MEnFP3ovRjo7WYNQ==} + cpu: [arm] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-arm64-gnu@4.62.4': + resolution: {integrity: sha512-CcnXHWnXg69g+DX5VWL3FHts3qMRN2uVEHX+BZvGLdd07/gXkn3ePjYtO1LDJvxkGKVHMclKBRa1QUTH+6toYQ==} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-arm64-musl@4.62.4': + resolution: {integrity: sha512-iFOibiHnTRuhrWLlRsOQFdZJJIa7S8OwkneJr4ocALP16u5yk6lWLINFwhHaEqBFMsKDUZofLkGos7+CPzGB3g==} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-loong64-gnu@4.62.4': + resolution: {integrity: sha512-XnWYMI7euHlb5a871xPja+Gm7DRCFU+FGRrtS2sMq9N8FvqtpagUy6gD4YOemC5MRk9xbh8+jYMEJbigFQwsgA==} + cpu: [loong64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-loong64-musl@4.62.4': + resolution: {integrity: sha512-qGDAlO0U8xedCcsdRm9oaoQY8DAx/QT7uIxJWhCdx0ceIWX783UC9QSYkdpzAe29wNiVfp24+bZdQmn49o45SQ==} + cpu: [loong64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-ppc64-gnu@4.62.4': + resolution: {integrity: sha512-ru4H6ezD7ysA5EiEK6qkkaEb4modH8CTej6kUy/gQi20u3kB3G7Zn8snXXkeJSCOFKG/rbPPtM/+9Wgas1961w==} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-ppc64-musl@4.62.4': + resolution: {integrity: sha512-2W4MO5WQVJnbJaZdvDb9rhBDuFU1nKIepPFpJUBsTh2k1YY2g+ODViaWuyOAjQ5cOP7NvrvLzt3wvHOoiAvc7w==} + cpu: [ppc64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-riscv64-gnu@4.62.4': + resolution: {integrity: sha512-+fxjfuoAmVMCYV5QyjoIpu0cp5DOiOTeqYFk1AVaxGr+/ravWLX89XfQmptsoWcaVy/TGf2hexzbUOrCQIL1CQ==} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-riscv64-musl@4.62.4': + resolution: {integrity: sha512-jTn8JfHGL4djjFxPuM06LmNUJDsst2jeVlsd9OmIH6zc5sC9K6rIuO4YajXatLUpBmBKl6b35ro1QZocLi+tcA==} + cpu: [riscv64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-s390x-gnu@4.62.4': + resolution: {integrity: sha512-oCJCJL4pXsoDcP2QZ+JVlPTIRc6266zsIaeJJsWImmF7HO0W8nb6HuSgZlMWxJwaPf8ehbSw8yo0EUw925hKsA==} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-x64-gnu@4.62.4': + resolution: {integrity: sha512-W69hukhZ3KKNRCaMIEzKvcFye42hh0FE1+YoYaf5+Ikacuftoco6yO/xouz0hc5d5W/s3yBro5jRiuEE/Q5vUw==} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-x64-musl@4.62.4': + resolution: {integrity: sha512-qiXbGG2jkjXhzXpsFZSR2Xpb8DN/UaxYsbb/STbuR/6fpaDgRmmaq1B/LmtF2wQFOFOSsK2jdE0RZ3a0zHn4QA==} + cpu: [x64] + os: [linux] + libc: [musl] + + '@rollup/rollup-openbsd-x64@4.62.4': + resolution: {integrity: sha512-nWeM//hxv8mIo6jD7Hu4o48DVmV9pbV6gsKaWU+4NFyqHoPKwrkRiZGLKUhOBk8qNmDmpwFtPKg80Bo/Tn4xiQ==} + cpu: [x64] + os: [openbsd] + + '@rollup/rollup-openharmony-arm64@4.62.4': + resolution: {integrity: sha512-s62SQ/vgsRSvMwDkOEfTqfgASF0f26ZNaQuTA6Aok5lrikf89yI2W0gFHvZb2Jpgc6N8JnOKZgCK2iciO3CsxQ==} + cpu: [arm64] + os: [openharmony] + + '@rollup/rollup-win32-arm64-msvc@4.62.4': + resolution: {integrity: sha512-J6wGf8TVGbXJq+HH+ttTvrcfNKPbuZecV6KT1B8I18BC5IURUh5kl4Yl5OEP5eFIUoI5BWxCsyYMhFsDx8kekw==} + cpu: [arm64] + os: [win32] + + '@rollup/rollup-win32-ia32-msvc@4.62.4': + resolution: {integrity: sha512-zmfrQd/0wu6oJs8Vq8KwY/YtsKSsLtKe/HwAP4Wqy8LhWjeT55fHRAkOhYQ12wI3ayS4Tt12d5CDRD7N96SAYQ==} + cpu: [ia32] + os: [win32] + + '@rollup/rollup-win32-x64-gnu@4.62.4': + resolution: {integrity: sha512-qPzHqdj9rfUD+w79dtE07zi/kFwKyCJqplp5K5ygeLTp7jLpAoc16OAH39HSmRC9UpozaecsleI8uAdEj6v2yw==} + cpu: [x64] + os: [win32] + + '@rollup/rollup-win32-x64-msvc@4.62.4': + resolution: {integrity: sha512-zD6NdeWEByGE9QF9vCrlJ5YQB4oq9q91kPZS37Jwj5hOkvR1lTBSpsKhKDw4IJtbQ35LsTS1HD9DZYGKIshU1Q==} + cpu: [x64] + os: [win32] + + '@shikijs/core@3.23.0': + resolution: {integrity: sha512-NSWQz0riNb67xthdm5br6lAkvpDJRTgB36fxlo37ZzM2yq0PQFFzbd8psqC2XMPgCzo1fW6cVi18+ArJ44wqgA==} + + '@shikijs/core@4.4.3': + resolution: {integrity: sha512-QCR4q2ZO/ILJEuwiBMel4wdcTDb1JGwfjKTxPDF6x8ixOaluPrVqIn06C99AcRPhmYlBR56d/Fb+GN58GzExpg==} + engines: {node: '>=20'} + + '@shikijs/engine-javascript@3.23.0': + resolution: {integrity: sha512-aHt9eiGFobmWR5uqJUViySI1bHMqrAgamWE1TYSUoftkAeCCAiGawPMwM+VCadylQtF4V3VNOZ5LmfItH5f3yA==} + + '@shikijs/engine-javascript@4.4.3': + resolution: {integrity: sha512-FbOjFJp9VLdo1Wevs10BBtVxiTWwNLqZh5Gkhjgda/ioL15YOgeSl9n+6XMa3qRlPQzfhFNe641SrynFHYG0nQ==} + engines: {node: '>=20'} + + '@shikijs/engine-oniguruma@3.23.0': + resolution: {integrity: sha512-1nWINwKXxKKLqPibT5f4pAFLej9oZzQTsby8942OTlsJzOBZ0MWKiwzMsd+jhzu8YPCHAswGnnN1YtQfirL35g==} + + '@shikijs/engine-oniguruma@4.4.3': + resolution: {integrity: sha512-EcOQkxdxGQrc1Row/cC2c96/v1dbZqGnEVu1qTuT/MJmp6+cXCvQussowVmCv5Tqr3KuY3c7IbM6HTW3LJ1k9w==} + engines: {node: '>=20'} + + '@shikijs/langs@3.23.0': + resolution: {integrity: sha512-2Ep4W3Re5aB1/62RSYQInK9mM3HsLeB91cHqznAJMuylqjzNVAVCMnNWRHFtcNHXsoNRayP9z1qj4Sq3nMqYXg==} + + '@shikijs/langs@4.4.3': + resolution: {integrity: sha512-ePic0yfAJGOF83D5wBHK/00EjK65oahBYxFk5epgq33WRv7X9UuxLEV8PtR0szC0z8dl7INIpIodB99JRFlR+A==} + engines: {node: '>=20'} + + '@shikijs/primitive@4.4.3': + resolution: {integrity: sha512-m0wBeLDQDeIxRdUmrCPdQqfuUamDwRL5isCfYbguKD6NiaKpVbsv+3J81DyIKgNW5h4WAIIr8T4EkgQrBBxvaQ==} + engines: {node: '>=20'} + + '@shikijs/themes@3.23.0': + resolution: {integrity: sha512-5qySYa1ZgAT18HR/ypENL9cUSGOeI2x+4IvYJu4JgVJdizn6kG4ia5Q1jDEOi7gTbN4RbuYtmHh0W3eccOrjMA==} + + '@shikijs/themes@4.4.3': + resolution: {integrity: sha512-w8UHjeUnIR965KMWJHUPXOc2mNJUnK3vpVLYLvw5IYU2mnTTJ89E24OrJDBNiJDQ0qzb0tc4l7mrIXx5cFeIyw==} + engines: {node: '>=20'} + + '@shikijs/types@3.23.0': + resolution: {integrity: sha512-3JZ5HXOZfYjsYSk0yPwBrkupyYSLpAE26Qc0HLghhZNGTZg/SKxXIIgoxOpmmeQP0RRSDJTk1/vPfw9tbw+jSQ==} + + '@shikijs/types@4.4.3': + resolution: {integrity: sha512-UEJxmRR++MAGR6hugn0vgVS2W/6lWAts84FFSrnlH9sP0LNol7E5+NQ792pH8liWUhyMyjhTgSUH3k7iD7tc5g==} + engines: {node: '>=20'} + + '@shikijs/vscode-textmate@10.0.2': + resolution: {integrity: sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==} + + '@types/babel__core@7.20.5': + resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} + + '@types/babel__generator@7.27.0': + resolution: {integrity: sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==} + + '@types/babel__template@7.4.4': + resolution: {integrity: sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==} + + '@types/babel__traverse@7.28.0': + resolution: {integrity: sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==} + + '@types/debug@4.1.13': + resolution: {integrity: sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw==} + + '@types/estree-jsx@1.0.5': + resolution: {integrity: sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==} + + '@types/estree@1.0.9': + resolution: {integrity: sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==} + + '@types/hast@3.0.5': + resolution: {integrity: sha512-rp/ezSWaD1m44dPKICGhiskI13nVr7qTloFwDa/IYkhhf5nzwP+zIQcIJh3WIFSBOy/H1PzB40jPjMDksN4F+g==} + + '@types/mdast@4.0.4': + resolution: {integrity: sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==} + + '@types/mdx@2.0.14': + resolution: {integrity: sha512-T48PeuJtvLosNTPVhfnIp3i/n3a4g4Bad7YCq5k64D4u7NwDrAotikQ+5+sjtUvBmxCMlbo3dVL+C2dP0rWHzg==} + + '@types/ms@2.1.0': + resolution: {integrity: sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==} + + '@types/nlcst@2.0.3': + resolution: {integrity: sha512-vSYNSDe6Ix3q+6Z7ri9lyWqgGhJTmzRjZRqyq15N0Z/1/UnVsno9G/N40NBijoYx2seFDIl0+B2mgAb9mezUCA==} + + '@types/node@24.13.3': + resolution: {integrity: sha512-Dh8vAsV36ig5wa9OX4pXvMc9D3Veibfw2wix0CUwYODLD8nkj9UsLjASr49nPg+2eKzxhBV+v7L8pXvT4e639Q==} + + '@types/node@25.9.5': + resolution: {integrity: sha512-OScDchr2fwuUmWdf4kZ9h7PcJiYDVInhJizG/biAq3cAvqwYktuy/TYGGdZNMtNTFUP7rnb0NU4TUdm82kt4Rg==} + + '@types/react-dom@19.2.4': + resolution: {integrity: sha512-Bsc+QHgp+P/F02XDzNCY9jnZNCUuLki36KT7VKrTXXLdHf+vHMNZnW1rVu5DNW/rCK+fya3DATySbLM4yhtKUw==} + peerDependencies: + '@types/react': ^19.2.0 + + '@types/react@19.2.18': + resolution: {integrity: sha512-AnzbBERsrLKtk2XSfTbYRLjQPdy116Sty4q+T+Bp3IC4l6jNBvreVPAHmpq9qhXQM7CXZPjLVmGMw9sy+hxQ3w==} + + '@types/sax@1.2.7': + resolution: {integrity: sha512-rO73L89PJxeYM3s3pPPjiPgVVcymqU490g0YO5n5By0k2Erzj6tay/4lr1CHAAU4JyOWd1rpQ8bCf6cZfHU96A==} + + '@types/unist@2.0.11': + resolution: {integrity: sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==} + + '@types/unist@3.0.3': + resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==} + + '@ungap/structured-clone@1.3.3': + resolution: {integrity: sha512-60YRaenCQcVjYEKOcG824+DRGGIQ3VKErcBoAEDJZz5bKIs2ZG+X/H9Nk+Q6EVkwJk5QNApxbrc5QtBSwtrXAg==} + + '@vitejs/plugin-react@5.2.0': + resolution: {integrity: sha512-YmKkfhOAi3wsB1PhJq5Scj3GXMn3WvtQ/JC0xoopuHoXSdmtdStOpFrYaT1kie2YgFBcIe64ROzMYRjCrYOdYw==} + engines: {node: ^20.19.0 || >=22.12.0} + peerDependencies: + vite: ^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 + + acorn-jsx@5.3.2: + resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + + acorn@8.18.0: + resolution: {integrity: sha512-lGq+9yr1/GuAWaVYIHRjvvySG5/4VfKIvC8EWxStPdcDh/Ka7FG3twP6v4d5BkravUilhIAsG4Qj83t02LWUPQ==} + engines: {node: '>=0.4.0'} + hasBin: true + + ansi-escapes@7.3.0: + resolution: {integrity: sha512-BvU8nYgGQBxcmMuEeUEmNTvrMVjJNSH7RgW24vXexN4Ven6qCvy4TntnvlnwnMLTVlcRQQdbRY8NKnaIoeWDNg==} + engines: {node: '>=18'} + + ansi-regex@6.2.2: + resolution: {integrity: sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==} + engines: {node: '>=12'} + + ansi-styles@6.2.3: + resolution: {integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==} + engines: {node: '>=12'} + + anymatch@3.1.3: + resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} + engines: {node: '>= 8'} + + anynum@1.0.1: + resolution: {integrity: sha512-N6//FLET/tXYNM/F6ABca1oH6fWB+KlTt909Le28WMDBk8oaT4vY17DCrwg2MvmuqUKt3Ni4N5dGJ/EoBgcO6A==} + + arg@5.0.2: + resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==} + + argparse@2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + + aria-query@5.3.2: + resolution: {integrity: sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==} + engines: {node: '>= 0.4'} + + array-iterate@2.0.1: + resolution: {integrity: sha512-I1jXZMjAgCMmxT4qxXfPXa6SthSoE8h6gkSI9BGGNv8mP8G/v0blc+qFnZu6K42vTOiuME596QaLO0TP3Lk0xg==} + + astring@1.9.0: + resolution: {integrity: sha512-LElXdjswlqjWrPpJFg1Fx4wpkOCxj1TDHlSV4PlaRxHGWko024xICaa97ZkMfs6DRKlCguiAI+rbXv5GWwXIkg==} + hasBin: true + + astro-expressive-code@0.41.7: + resolution: {integrity: sha512-hUpogGc6DdAd+I7pPXsctyYPRBJDK7Q7d06s4cyP0Vz3OcbziP3FNzN0jZci1BpCvLn9675DvS7B9ctKKX64JQ==} + peerDependencies: + astro: ^4.0.0-beta || ^5.0.0-beta || ^3.3.0 || ^6.0.0-beta + + astro-pagefind@1.8.6: + resolution: {integrity: sha512-pofDcWMgA3qLQX9gSJ1mc3NSJDv6o1PDs68MrtdupxMFIlAFT2yckFSiOoaab2stSDmKyX/wVaTObyj5FuBulA==} + peerDependencies: + astro: ^2.0.4 || ^3 || ^4 || ^5 || ^6 + + astro@6.4.8: + resolution: {integrity: sha512-KK5lX90uU9EeVaTjINyj3sy9/NFXVa59aowaqbWBDDKLXZh4rr7GwIaCFYVetE22MJtsCNFerQXn0vlCLmpP/Q==} + engines: {node: '>=22.12.0', npm: '>=9.6.5', pnpm: '>=7.1.0'} + hasBin: true + + axobject-query@4.1.0: + resolution: {integrity: sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==} + engines: {node: '>= 0.4'} + + bail@2.0.2: + resolution: {integrity: sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==} + + baseline-browser-mapping@2.11.13: + resolution: {integrity: sha512-k9HNuUVMlqVjQ9UHzfPjIqiDbWw7WqT1AoT7GL8VwvF3r0ZfArtgiSPAlmupyNquNgOJHTuH4CKYf8ttMTWBTQ==} + engines: {node: '>=6.0.0'} + hasBin: true + + bcp-47-match@2.0.3: + resolution: {integrity: sha512-JtTezzbAibu8G0R9op9zb3vcWZd9JF6M0xOYGPn0fNCd7wOpRB1mU2mH9T8gaBGbAAyIIVgB2G7xG0GP98zMAQ==} + + boolbase@1.0.0: + resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==} + + browserslist@4.28.8: + resolution: {integrity: sha512-V2NpofLblG64mfOtSgDhOJESZEGogzDMBv/q+W6oc4LXWP/q75eOXoOaaOu1EOadB9U4Bwx/e0yzbvwKH8zalA==} + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} + hasBin: true + + caniuse-lite@1.0.30001809: + resolution: {integrity: sha512-xxWVywk6a6Arlk+hymeycyn/VgqEfLDxupvhH/xiY5SJ/18kmi9o6MiO320DCUzypORHLtvh0I4i04tUhCNHNQ==} + + ccount@2.0.1: + resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==} + + character-entities-html4@2.1.0: + resolution: {integrity: sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==} + + character-entities-legacy@3.0.0: + resolution: {integrity: sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==} + + character-entities@2.0.2: + resolution: {integrity: sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==} + + character-reference-invalid@2.0.1: + resolution: {integrity: sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==} + + chokidar@5.0.0: + resolution: {integrity: sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==} + engines: {node: '>= 20.19.0'} + + ci-info@4.4.0: + resolution: {integrity: sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg==} + engines: {node: '>=8'} + + cli-cursor@5.0.0: + resolution: {integrity: sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==} + engines: {node: '>=18'} + + cli-truncate@5.2.0: + resolution: {integrity: sha512-xRwvIOMGrfOAnM1JYtqQImuaNtDEv9v6oIYAs4LIHwTiKee8uwvIi363igssOC0O5U04i4AlENs79LQLu9tEMw==} + engines: {node: '>=20'} + + clsx@2.1.1: + resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} + engines: {node: '>=6'} + + collapse-white-space@2.1.0: + resolution: {integrity: sha512-loKTxY1zCOuG4j9f6EPnuyyYkf58RnhhWTvRoZEokgB+WbdXehfjFviyOVYkqzEWz1Q5kRiZdBYS5SwxbQYwzw==} + + colorette@2.0.20: + resolution: {integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==} + + comma-separated-tokens@2.0.3: + resolution: {integrity: sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==} + + commander@11.1.0: + resolution: {integrity: sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==} + engines: {node: '>=16'} + + commander@14.0.3: + resolution: {integrity: sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==} + engines: {node: '>=20'} + + common-ancestor-path@2.0.0: + resolution: {integrity: sha512-dnN3ibLeoRf2HNC+OlCiNc5d2zxbLJXOtiZUudNFSXZrNSydxcCsSpRzXwfu7BBWCIfHPw+xTayeBvJCP/D8Ng==} + engines: {node: '>= 18'} + + convert-source-map@2.0.0: + resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + + cookie-es@1.2.3: + resolution: {integrity: sha512-lXVyvUvrNXblMqzIRrxHb57UUVmqsSWlxqt3XIjCkUP0wDAf6uicO6KMbEgYrMNtEvWgWHwe42CKxPu9MYAnWw==} + + cookie@1.1.1: + resolution: {integrity: sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==} + engines: {node: '>=18'} + + crossws@0.3.5: + resolution: {integrity: sha512-ojKiDvcmByhwa8YYqbQI/hg7MEU0NC03+pSdEq4ZUnZR9xXpwk7E43SMNGkn+JxJGPFtNvQ48+vV2p+P1ml5PA==} + + css-select@5.2.2: + resolution: {integrity: sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==} + + css-selector-parser@3.3.0: + resolution: {integrity: sha512-Y2asgMGFqJKF4fq4xHDSlFYIkeVfRsm69lQC1q9kbEsH5XtnINTMrweLkjYMeaUgiXBy/uvKeO/a1JHTNnmB2g==} + + css-tree@2.2.1: + resolution: {integrity: sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA==} + engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0, npm: '>=7.0.0'} + + css-tree@3.2.1: + resolution: {integrity: sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA==} + engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0} + + css-what@6.2.2: + resolution: {integrity: sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==} + engines: {node: '>= 6'} + + cssesc@3.0.0: + resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==} + engines: {node: '>=4'} + hasBin: true + + csso@5.0.5: + resolution: {integrity: sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ==} + engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0, npm: '>=7.0.0'} + + csstype@3.2.3: + resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} + + debug@4.4.3: + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + decode-named-character-reference@1.3.0: + resolution: {integrity: sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q==} + + defu@6.1.7: + resolution: {integrity: sha512-7z22QmUWiQ/2d0KkdYmANbRUVABpZ9SNYyH5vx6PZ+nE5bcC0l7uFvEfHlyld/HcGBFTL536ClDt3DEcSlEJAQ==} + + dequal@2.0.3: + resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} + engines: {node: '>=6'} + + destr@2.0.5: + resolution: {integrity: sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==} + + detect-libc@2.1.2: + resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} + engines: {node: '>=8'} + + devalue@5.9.0: + resolution: {integrity: sha512-RWrqdArjvPbsATEhOPUo6Wndc/iWnkWKlhIrdlF3zMMYo/c3CVtoaVAyLtWxz5h8nSlkHzxnzV2uLydPXmtF+A==} + + devlop@1.1.0: + resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==} + + diff@8.0.4: + resolution: {integrity: sha512-DPi0FmjiSU5EvQV0++GFDOJ9ASQUVFh5kD+OzOnYdi7n3Wpm9hWWGfB/O2blfHcMVTL5WkQXSnRiK9makhrcnw==} + engines: {node: '>=0.3.1'} + + direction@2.0.1: + resolution: {integrity: sha512-9S6m9Sukh1cZNknO1CWAr2QAWsbKLafQiyM5gZ7VgXHeuaoUwffKN4q6NC4A/Mf9iiPlOXQEKW/Mv/mh9/3YFA==} + hasBin: true + + dom-serializer@2.0.0: + resolution: {integrity: sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==} + + domelementtype@2.3.0: + resolution: {integrity: sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==} + + domhandler@5.0.3: + resolution: {integrity: sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==} + engines: {node: '>= 4'} + + domutils@3.2.2: + resolution: {integrity: sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==} + + dset@3.1.4: + resolution: {integrity: sha512-2QF/g9/zTaPDc3BjNcVTGoBbXBgYfMTTceLaYcFJ/W9kggFUkhxD/hMEeuLKbugyef9SqAx8cpgwlIP/jinUTA==} + engines: {node: '>=4'} + + electron-to-chromium@1.5.404: + resolution: {integrity: sha512-3WJtd7/lVq2Jnuz6wed1l9+1ZD2u2Tet1/1NBc4Iedkmgbu+I7YuAqdAQ8T+VZtnwysMsAf3IqSq9D1gyZjA2g==} + + emoji-regex@10.6.0: + resolution: {integrity: sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==} + + entities@4.5.0: + resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} + engines: {node: '>=0.12'} + + entities@6.0.1: + resolution: {integrity: sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==} + engines: {node: '>=0.12'} + + environment@1.1.0: + resolution: {integrity: sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==} + engines: {node: '>=18'} + + es-module-lexer@2.3.1: + resolution: {integrity: sha512-shc1dbU90Yl/xq1QrC7QRtfcwURZuVRfPhZbDoldJ1cn1gzDvBaBWlv0eFolj5+0znnPJz5TXLxsN77X/12KTA==} + + esast-util-from-estree@2.0.0: + resolution: {integrity: sha512-4CyanoAudUSBAn5K13H4JhsMH6L9ZP7XbLVe/dKybkxMO7eDyLsT8UHl9TRNrU2Gr9nz+FovfSIjuXWJ81uVwQ==} + + esast-util-from-js@2.0.1: + resolution: {integrity: sha512-8Ja+rNJ0Lt56Pcf3TAmpBZjmx8ZcK5Ts4cAzIOjsjevg9oSXJnl6SUQ2EevU8tv3h6ZLWmoKL5H4fgWvdvfETw==} + + esbuild@0.27.7: + resolution: {integrity: sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==} + engines: {node: '>=18'} + hasBin: true + + esbuild@0.28.2: + resolution: {integrity: sha512-HKVLS8dvII+xoKW9kmqxbRKrnWEXfJJr/FZhhJmiqIB0e053QNYFqOBouTMO/k5sID4MvCiUCvv8b9M4h32wIA==} + engines: {node: '>=18'} + hasBin: true + + escalade@3.2.0: + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} + engines: {node: '>=6'} + + escape-string-regexp@5.0.0: + resolution: {integrity: sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==} + engines: {node: '>=12'} + + estree-util-attach-comments@3.0.0: + resolution: {integrity: sha512-cKUwm/HUcTDsYh/9FgnuFqpfquUbwIqwKM26BVCGDPVgvaCl/nDCCjUfiLlx6lsEZ3Z4RFxNbOQ60pkaEwFxGw==} + + estree-util-build-jsx@3.0.1: + resolution: {integrity: sha512-8U5eiL6BTrPxp/CHbs2yMgP8ftMhR5ww1eIKoWRMlqvltHF8fZn5LRDvTKuxD3DUn+shRbLGqXemcP51oFCsGQ==} + + estree-util-is-identifier-name@3.0.0: + resolution: {integrity: sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg==} + + estree-util-scope@1.0.0: + resolution: {integrity: sha512-2CAASclonf+JFWBNJPndcOpA8EMJwa0Q8LUFJEKqXLW6+qBvbFZuF5gItbQOs/umBUkjviCSDCbBwU2cXbmrhQ==} + + estree-util-to-js@2.0.0: + resolution: {integrity: sha512-WDF+xj5rRWmD5tj6bIqRi6CkLIXbbNQUcxQHzGysQzvHmdYG2G7p/Tf0J0gpxGgkeMZNTIjT/AoSvC9Xehcgdg==} + + estree-util-visit@2.0.0: + resolution: {integrity: sha512-m5KgiH85xAhhW8Wta0vShLcUvOsh3LLPI2YVwcbio1l7E09NTLL1EyMZFM1OyWowoH0skScNbhOPl4kcBgzTww==} + + estree-walker@2.0.2: + resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} + + estree-walker@3.0.3: + resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} + + eventemitter3@5.0.4: + resolution: {integrity: sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==} + + expressive-code@0.41.7: + resolution: {integrity: sha512-2wZjC8OQ3TaVEMcBtYY4Va3lo6J+Ai9jf3d4dbhURMJcU4Pbqe6EcHe424MIZI0VHUA1bR6xdpoHYi3yxokWqA==} + + extend@3.0.2: + resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} + + fast-string-truncated-width@3.0.3: + resolution: {integrity: sha512-0jjjIEL6+0jag3l2XWWizO64/aZVtpiGE3t0Zgqxv0DPuxiMjvB3M24fCyhZUO4KomJQPj3LTSUnDP3GpdwC0g==} + + fast-string-width@3.0.2: + resolution: {integrity: sha512-gX8LrtNEI5hq8DVUfRQMbr5lpaS4nMIWV+7XEbXk2b8kiQIizgnlr12B4dA3ZEx3308ze0O4Q1R+cHts8kyUJg==} + + fast-wrap-ansi@0.2.2: + resolution: {integrity: sha512-7F2Fl+TjRSenLqlU3UjSH0iyqopqoZIu7eZVpEirP2g1GtWa2G/ecEmBdgz31+Mxr+ELclgg6sokpSFIQiZ02Q==} + + fast-xml-builder@1.3.0: + resolution: {integrity: sha512-F74cZEdCvuw9P41GAC3rod4X04jjWGM1JPEv/GWSqFTWLsdyMSBMBMlm9Hk3GLBgLBbdBNY8yee0pQh2RBVESQ==} + + fast-xml-parser@5.10.1: + resolution: {integrity: sha512-IEMIf7298kXuZSRFoGfMYrl7is8LpavODgbNz1cwIudv7KwVFnuU+UsMporfq6PD6aXSlawZlARiA3UywCTfMw==} + hasBin: true + + fdir@6.5.0: + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} + engines: {node: '>=12.0.0'} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + + flattie@1.1.1: + resolution: {integrity: sha512-9UbaD6XdAL97+k/n+N7JwX46K/M6Zc6KcFYskrYL8wbBV/Uyk0CTAMY0VT+qiK5PM7AIc9aTWYtq65U7T+aCNQ==} + engines: {node: '>=8'} + + fontace@0.4.1: + resolution: {integrity: sha512-lDMvbAzSnHmbYMTEld5qdtvNH2/pWpICOqpean9IgC7vUbUJc3k+k5Dokp85CegamqQpFbXf0rAVkbzpyTA8aw==} + + fontkitten@1.0.3: + resolution: {integrity: sha512-Wp1zXWPVUPBmfoa3Cqc9ctaKuzKAV6uLstRqlR56kSjplf5uAce+qeyYym7F+PHbGTk+tCEdkCW6RD7DX/gBZw==} + engines: {node: '>=20'} + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + gensync@1.0.0-beta.2: + resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} + engines: {node: '>=6.9.0'} + + get-east-asian-width@1.6.0: + resolution: {integrity: sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA==} + engines: {node: '>=18'} + + get-tsconfig@5.0.0-beta.4: + resolution: {integrity: sha512-7nF7C9fIPFEMHgEMEfgIlO9wDdZ8CyHw27rWciFZfHvHDReIiPhsYuzPRXsfvBCqFy1l8RRyyWV7QLM+ZhUJsQ==} + engines: {node: '>=20.20.0'} + + github-slugger@2.0.0: + resolution: {integrity: sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw==} + + h3@1.15.11: + resolution: {integrity: sha512-L3THSe2MPeBwgIZVSH5zLdBBU90TOxarvhK9d04IDY2AmVS8j2Jz2LIWtwsGOU3lu2I5jCN7FNvVfY2+XyF+mg==} + + hast-util-from-html@2.0.3: + resolution: {integrity: sha512-CUSRHXyKjzHov8yKsQjGOElXy/3EKpyX56ELnkHH34vDVw1N1XSQ1ZcAvTyAPtGqLTuKP/uxM+aLkSPqF/EtMw==} + + hast-util-from-parse5@8.0.3: + resolution: {integrity: sha512-3kxEVkEKt0zvcZ3hCRYI8rqrgwtlIOFMWkbclACvjlDw8Li9S2hk/d51OI0nr/gIpdMHNepwgOKqZ/sy0Clpyg==} + + hast-util-has-property@3.0.0: + resolution: {integrity: sha512-MNilsvEKLFpV604hwfhVStK0usFY/QmM5zX16bo7EjnAEGofr5YyI37kzopBlZJkHD4t887i+q/C8/tr5Q94cA==} + + hast-util-is-element@3.0.0: + resolution: {integrity: sha512-Val9mnv2IWpLbNPqc/pUem+a7Ipj2aHacCwgNfTiK0vJKl0LF+4Ba4+v1oPHFpf3bLYmreq0/l3Gud9S5OH42g==} + + hast-util-parse-selector@4.0.0: + resolution: {integrity: sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A==} + + hast-util-raw@9.1.0: + resolution: {integrity: sha512-Y8/SBAHkZGoNkpzqqfCldijcuUKh7/su31kEBp67cFY09Wy0mTRgtsLYsiIxMJxlu0f6AA5SUTbDR8K0rxnbUw==} + + hast-util-select@6.0.4: + resolution: {integrity: sha512-RqGS1ZgI0MwxLaKLDxjprynNzINEkRHY2i8ln4DDjgv9ZhcYVIHN9rlpiYsqtFwrgpYU361SyWDQcGNIBVu3lw==} + + hast-util-to-estree@3.1.3: + resolution: {integrity: sha512-48+B/rJWAp0jamNbAAf9M7Uf//UVqAoMmgXhBdxTDJLGKY+LRnZ99qcG+Qjl5HfMpYNzS5v4EAwVEF34LeAj7w==} + + hast-util-to-html@9.0.5: + resolution: {integrity: sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw==} + + hast-util-to-jsx-runtime@2.3.6: + resolution: {integrity: sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg==} + + hast-util-to-parse5@8.0.1: + resolution: {integrity: sha512-MlWT6Pjt4CG9lFCjiz4BH7l9wmrMkfkJYCxFwKQic8+RTZgWPuWxwAfjJElsXkex7DJjfSJsQIt931ilUgmwdA==} + + hast-util-to-string@3.0.1: + resolution: {integrity: sha512-XelQVTDWvqcl3axRfI0xSeoVKzyIFPwsAGSLIsKdJKQMXDYJS4WYrBNF/8J7RdhIcFI2BOHgAifggsvsxp/3+A==} + + hast-util-to-text@4.0.2: + resolution: {integrity: sha512-KK6y/BN8lbaq654j7JgBydev7wuNMcID54lkRav1P0CaE1e47P72AWWPiGKXTJU271ooYzcvTAn/Zt0REnvc7A==} + + hast-util-whitespace@3.0.0: + resolution: {integrity: sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==} + + hastscript@9.0.1: + resolution: {integrity: sha512-g7df9rMFX/SPi34tyGCyUBREQoKkapwdY/T04Qn9TDWfHhAYt4/I0gMVirzK5wEzeUqIjEB+LXC/ypb7Aqno5w==} + + html-escaper@3.0.3: + resolution: {integrity: sha512-RuMffC89BOWQoY0WKGpIhn5gX3iI54O6nRA0yC124NYVtzjmFWBIiFd8M0x+ZdX0P9R4lADg1mgP8C7PxGOWuQ==} + + html-void-elements@3.0.0: + resolution: {integrity: sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==} + + http-cache-semantics@4.2.0: + resolution: {integrity: sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==} + + husky@9.1.7: + resolution: {integrity: sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA==} + engines: {node: '>=18'} + hasBin: true + + inline-style-parser@0.2.7: + resolution: {integrity: sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA==} + + iron-webcrypto@1.2.1: + resolution: {integrity: sha512-feOM6FaSr6rEABp/eDfVseKyTMDt+KGpeB35SkVn9Tyn0CqvVsY3EwI0v5i8nMHyJnzCIQf7nsy3p41TPkJZhg==} + + is-alphabetical@2.0.1: + resolution: {integrity: sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==} + + is-alphanumerical@2.0.1: + resolution: {integrity: sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==} + + is-decimal@2.0.1: + resolution: {integrity: sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==} + + is-docker@3.0.0: + resolution: {integrity: sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + hasBin: true + + is-docker@4.0.0: + resolution: {integrity: sha512-LHE+wROyG/Y/0ZnbktRCoTix2c1RhgWaZraMZ8o1Q7zCh0VSrICJQO5oqIIISrcSBtrXv0o233w1IYwsWCjTzA==} + engines: {node: '>=20'} + hasBin: true + + is-fullwidth-code-point@5.1.0: + resolution: {integrity: sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ==} + engines: {node: '>=18'} + + is-hexadecimal@2.0.1: + resolution: {integrity: sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==} + + is-inside-container@1.0.0: + resolution: {integrity: sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==} + engines: {node: '>=14.16'} + hasBin: true + + is-plain-obj@4.1.0: + resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==} + engines: {node: '>=12'} + + is-unsafe@2.0.0: + resolution: {integrity: sha512-2LdV822R+wmI86unXA93WCFpL6g+av8ynWk0nrHyJqGop5VoocYsSLFgN8jrfalT6iGeLNM4KXuVSsULP53kEA==} + + is-wsl@3.1.1: + resolution: {integrity: sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw==} + engines: {node: '>=16'} + + js-tokens@4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + + js-yaml@4.3.1: + resolution: {integrity: sha512-CY6crGq313MX8GkwvB7tzgp99vjQxY1++5y10/BKN/GUfHqWaOGQMNZkBvqSzsZKWk/ijwHlWzzkLulsGHhjWQ==} + hasBin: true + + jsesc@3.1.0: + resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} + engines: {node: '>=6'} + hasBin: true + + json5@2.2.3: + resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} + engines: {node: '>=6'} + hasBin: true + + jsonc-parser@3.3.1: + resolution: {integrity: sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==} + + lint-staged@16.4.0: + resolution: {integrity: sha512-lBWt8hujh/Cjysw5GYVmZpFHXDCgZzhrOm8vbcUdobADZNOK/bRshr2kM3DfgrrtR1DQhfupW9gnIXOfiFi+bw==} + engines: {node: '>=20.17'} + hasBin: true + + listr2@9.0.5: + resolution: {integrity: sha512-ME4Fb83LgEgwNw96RKNvKV4VTLuXfoKudAmm2lP8Kk87KaMK0/Xrx/aAkMWmT8mDb+3MlFDspfbCs7adjRxA2g==} + engines: {node: '>=20.0.0'} + + log-update@6.1.0: + resolution: {integrity: sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==} + engines: {node: '>=18'} + + longest-streak@3.1.0: + resolution: {integrity: sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==} + + lru-cache@11.5.2: + resolution: {integrity: sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g==} + engines: {node: 20 || >=22} + + lru-cache@5.1.1: + resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} + + magic-string@0.30.21: + resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + + magicast@0.5.4: + resolution: {integrity: sha512-llBEhWm1SacoRwgHUoQJYtwp4PBLF4faQi5TCpIGyGs9n4y5+juI0tDgyKIfpqxckRHaHzouUEph3THklWh03w==} + + markdown-extensions@2.0.0: + resolution: {integrity: sha512-o5vL7aDWatOTX8LzaS1WMoaoxIiLRQJuIKKe2wAw6IeULDHaqbiqiggmx+pKvZDb1Sj+pE46Sn1T7lCqfFtg1Q==} + engines: {node: '>=16'} + + markdown-table@3.0.4: + resolution: {integrity: sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==} + + mdast-util-definitions@6.0.0: + resolution: {integrity: sha512-scTllyX6pnYNZH/AIp/0ePz6s4cZtARxImwoPJ7kS42n+MnVsI4XbnG6d4ibehRIldYMWM2LD7ImQblVhUejVQ==} + + mdast-util-directive@3.1.0: + resolution: {integrity: sha512-I3fNFt+DHmpWCYAT7quoM6lHf9wuqtI+oCOfvILnoicNIqjh5E3dEJWiXuYME2gNe8vl1iMQwyUHa7bgFmak6Q==} + + mdast-util-find-and-replace@3.0.2: + resolution: {integrity: sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg==} + + mdast-util-from-markdown@2.0.3: + resolution: {integrity: sha512-W4mAWTvSlKvf8L6J+VN9yLSqQ9AOAAvHuoDAmPkz4dHf553m5gVj2ejadHJhoJmcmxEnOv6Pa8XJhpxE93kb8Q==} + + mdast-util-gfm-autolink-literal@2.0.1: + resolution: {integrity: sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ==} + + mdast-util-gfm-footnote@2.1.0: + resolution: {integrity: sha512-sqpDWlsHn7Ac9GNZQMeUzPQSMzR6Wv0WKRNvQRg0KqHh02fpTz69Qc1QSseNX29bhz1ROIyNyxExfawVKTm1GQ==} + + mdast-util-gfm-strikethrough@2.0.0: + resolution: {integrity: sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg==} + + mdast-util-gfm-table@2.0.0: + resolution: {integrity: sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg==} + + mdast-util-gfm-task-list-item@2.0.0: + resolution: {integrity: sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ==} + + mdast-util-gfm@3.1.0: + resolution: {integrity: sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ==} + + mdast-util-mdx-expression@2.0.1: + resolution: {integrity: sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ==} + + mdast-util-mdx-jsx@3.2.0: + resolution: {integrity: sha512-lj/z8v0r6ZtsN/cGNNtemmmfoLAFZnjMbNyLzBafjzikOM+glrjNHPlf6lQDOTccj9n5b0PPihEBbhneMyGs1Q==} + + mdast-util-mdx@3.0.0: + resolution: {integrity: sha512-JfbYLAW7XnYTTbUsmpu0kdBUVe+yKVJZBItEjwyYJiDJuZ9w4eeaqks4HQO+R7objWgS2ymV60GYpI14Ug554w==} + + mdast-util-mdxjs-esm@2.0.1: + resolution: {integrity: sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg==} + + mdast-util-phrasing@4.1.0: + resolution: {integrity: sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==} + + mdast-util-to-hast@13.2.1: + resolution: {integrity: sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA==} + + mdast-util-to-markdown@2.1.2: + resolution: {integrity: sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==} + + mdast-util-to-string@4.0.0: + resolution: {integrity: sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==} + + mdn-data@2.0.28: + resolution: {integrity: sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g==} + + mdn-data@2.27.1: + resolution: {integrity: sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==} + + micromark-core-commonmark@2.0.3: + resolution: {integrity: sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==} + + micromark-extension-directive@4.0.0: + resolution: {integrity: sha512-/C2nqVmXXmiseSSuCdItCMho7ybwwop6RrrRPk0KbOHW21JKoCldC+8rFOaundDoRBUWBnJJcxeA/Kvi34WQXg==} + + micromark-extension-gfm-autolink-literal@2.1.0: + resolution: {integrity: sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==} + + micromark-extension-gfm-footnote@2.1.0: + resolution: {integrity: sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw==} + + micromark-extension-gfm-strikethrough@2.1.0: + resolution: {integrity: sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw==} + + micromark-extension-gfm-table@2.1.1: + resolution: {integrity: sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg==} + + micromark-extension-gfm-tagfilter@2.0.0: + resolution: {integrity: sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg==} + + micromark-extension-gfm-task-list-item@2.1.0: + resolution: {integrity: sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw==} + + micromark-extension-gfm@3.0.0: + resolution: {integrity: sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w==} + + micromark-extension-mdx-expression@3.0.1: + resolution: {integrity: sha512-dD/ADLJ1AeMvSAKBwO22zG22N4ybhe7kFIZ3LsDI0GlsNr2A3KYxb0LdC1u5rj4Nw+CHKY0RVdnHX8vj8ejm4Q==} + + micromark-extension-mdx-jsx@3.0.2: + resolution: {integrity: sha512-e5+q1DjMh62LZAJOnDraSSbDMvGJ8x3cbjygy2qFEi7HCeUT4BDKCvMozPozcD6WmOt6sVvYDNBKhFSz3kjOVQ==} + + micromark-extension-mdx-md@2.0.0: + resolution: {integrity: sha512-EpAiszsB3blw4Rpba7xTOUptcFeBFi+6PY8VnJ2hhimH+vCQDirWgsMpz7w1XcZE7LVrSAUGb9VJpG9ghlYvYQ==} + + micromark-extension-mdxjs-esm@3.0.0: + resolution: {integrity: sha512-DJFl4ZqkErRpq/dAPyeWp15tGrcrrJho1hKK5uBS70BCtfrIFg81sqcTVu3Ta+KD1Tk5vAtBNElWxtAa+m8K9A==} + + micromark-extension-mdxjs@3.0.0: + resolution: {integrity: sha512-A873fJfhnJ2siZyUrJ31l34Uqwy4xIFmvPY1oj+Ean5PHcPBYzEsvqvWGaWcfEIr11O5Dlw3p2y0tZWpKHDejQ==} + + micromark-factory-destination@2.0.1: + resolution: {integrity: sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==} + + micromark-factory-label@2.0.1: + resolution: {integrity: sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==} + + micromark-factory-mdx-expression@2.0.3: + resolution: {integrity: sha512-kQnEtA3vzucU2BkrIa8/VaSAsP+EJ3CKOvhMuJgOEGg9KDC6OAY6nSnNDVRiVNRqj7Y4SlSzcStaH/5jge8JdQ==} + + micromark-factory-space@2.0.1: + resolution: {integrity: sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==} + + micromark-factory-title@2.0.1: + resolution: {integrity: sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==} + + micromark-factory-whitespace@2.0.1: + resolution: {integrity: sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==} + + micromark-util-character@2.1.1: + resolution: {integrity: sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==} + + micromark-util-chunked@2.0.1: + resolution: {integrity: sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==} + + micromark-util-classify-character@2.0.1: + resolution: {integrity: sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==} + + micromark-util-combine-extensions@2.0.1: + resolution: {integrity: sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==} + + micromark-util-decode-numeric-character-reference@2.0.2: + resolution: {integrity: sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==} + + micromark-util-decode-string@2.0.1: + resolution: {integrity: sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==} + + micromark-util-encode@2.0.1: + resolution: {integrity: sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==} + + micromark-util-events-to-acorn@2.0.3: + resolution: {integrity: sha512-jmsiEIiZ1n7X1Rr5k8wVExBQCg5jy4UXVADItHmNk1zkwEVhBuIUKRu3fqv+hs4nxLISi2DQGlqIOGiFxgbfHg==} + + micromark-util-html-tag-name@2.0.1: + resolution: {integrity: sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==} + + micromark-util-normalize-identifier@2.0.1: + resolution: {integrity: sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==} + + micromark-util-resolve-all@2.0.1: + resolution: {integrity: sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==} + + micromark-util-sanitize-uri@2.0.1: + resolution: {integrity: sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==} + + micromark-util-subtokenize@2.1.0: + resolution: {integrity: sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==} + + micromark-util-symbol@2.0.1: + resolution: {integrity: sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==} + + micromark-util-types@2.0.2: + resolution: {integrity: sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==} + + micromark@4.0.2: + resolution: {integrity: sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==} + + mimic-function@5.0.1: + resolution: {integrity: sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==} + engines: {node: '>=18'} + + mrmime@2.0.1: + resolution: {integrity: sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==} + engines: {node: '>=10'} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + nanoid@3.3.18: + resolution: {integrity: sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + neotraverse@0.6.18: + resolution: {integrity: sha512-Z4SmBUweYa09+o6pG+eASabEpP6QkQ70yHj351pQoEXIs8uHbaU2DWVmzBANKgflPa47A50PtB2+NgRpQvr7vA==} + engines: {node: '>= 10'} + + nlcst-to-string@4.0.0: + resolution: {integrity: sha512-YKLBCcUYKAg0FNlOBT6aI91qFmSiFKiluk655WzPF+DDMA02qIyy8uiRqI8QXtcFpEvll12LpL5MXqEmAZ+dcA==} + + node-fetch-native@1.6.7: + resolution: {integrity: sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q==} + + node-mock-http@1.0.5: + resolution: {integrity: sha512-KQyt/wLjG3TAc7DOUhpqWzgd4ERxR80JOlTK5VE5R1S12IaPVN5qkj4klBce9HPG1Njuup4Sb5bljaT34lIyjw==} + + node-releases@2.0.53: + resolution: {integrity: sha512-D9UOmYG3UH1V+ENW56t5QXBwJw1YEY18ruVeus89Rw+SyIgjPkCO84bRzO3uNIYosJbNwiabWVn48o3uJLjxFQ==} + engines: {node: '>=18'} + + normalize-path@3.0.0: + resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} + engines: {node: '>=0.10.0'} + + nth-check@2.1.1: + resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==} + + obug@2.1.4: + resolution: {integrity: sha512-4a+OsYv9UktOJKE+l1A4OufDgdRF9PifWj+tJnHURo/P+WOxpG4GzUFL9qCalmWauao6ogiG+QvnCovwPoyAWA==} + engines: {node: '>=12.20.0'} + + ofetch@1.5.1: + resolution: {integrity: sha512-2W4oUZlVaqAPAil6FUg/difl6YhqhUR7x2eZY4bQCko22UXg3hptq9KLQdqFClV+Wu85UX7hNtdGTngi/1BxcA==} + + ohash@2.0.11: + resolution: {integrity: sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==} + + onetime@7.0.0: + resolution: {integrity: sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==} + engines: {node: '>=18'} + + oniguruma-parser@0.12.2: + resolution: {integrity: sha512-6HVa5oIrgMC6aA6WF6XyyqbhRPJrKR02L20+2+zpDtO5QAzGHAUGw5TKQvwi5vctNnRHkJYmjAhRVQF2EKdTQw==} + + oniguruma-to-es@4.3.6: + resolution: {integrity: sha512-csuQ9x3Yr0cEIs/Zgx/OEt9iBw9vqIunAPQkx19R/fiMq2oGVTgcMqO/V3Ybqefr1TBvosI6jU539ksaBULJyA==} + + oxfmt@0.45.0: + resolution: {integrity: sha512-0o/COoN9fY50bjVeM7PQsNgbhndKurBIeTIcspW033OumksjJJmIVDKjAk5HMwU/GHTxSOdGDdhJ6BRzGPmsHg==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + + oxlint@1.78.0: + resolution: {integrity: sha512-QgQePuxIqKOzo1KSjG2EnITEeWvWnKAm77eq8nrMtf6AGoA+zyGc4PFYtDNJSD25g/ibOwfQ851hZ4/SPkMVoA==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + peerDependencies: + oxlint-tsgolint: '>=7.0.2001' + vite-plus: '*' + peerDependenciesMeta: + oxlint-tsgolint: + optional: true + vite-plus: + optional: true + + p-limit@7.3.1: + resolution: {integrity: sha512-0trZaiG7Y7kN/Egy9a8j47t9osC0Tch4PaIWd9yGF6bvmlk7muExRvGNYb8sXBwEKMoNKsbNN9P8EefuQekE4Q==} + engines: {node: '>=20'} + + p-queue@9.3.3: + resolution: {integrity: sha512-NXAOdnEe5FsZJfT4oK84lE1Y5cFFdWlRuOo5tww8DyNMxyRXwn39fIkUtNLKppcPC+UYU/bXujNCUGDv01y7CA==} + engines: {node: '>=20'} + + p-timeout@7.0.1: + resolution: {integrity: sha512-AxTM2wDGORHGEkPCt8yqxOTMgpfbEHqF51f/5fJCmwFC3C/zNcGT63SymH2ttOAaiIws2zVg4+izQCjrakcwHg==} + engines: {node: '>=20'} + + package-manager-detector@1.8.0: + resolution: {integrity: sha512-yQA4H19AmPEoMUeavPMDIe1higySl/gH/yaQrkT/s07Qp+7pp2hYz30N3z2l5BkjVkF9Ow6o0wjJamm2y7Sn0A==} + + pagefind@1.5.2: + resolution: {integrity: sha512-XTUaK0hXMCu2jszWE584JGQT7y284TmMV9l/HX3rnG5uo3rHI/uHU56XTyyyPFjeWEBxECbAi0CaFDJOONtG0Q==} + hasBin: true + + parse-entities@4.0.2: + resolution: {integrity: sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==} + + parse-latin@7.0.0: + resolution: {integrity: sha512-mhHgobPPua5kZ98EF4HWiH167JWBfl4pvAIXXdbaVohtK7a6YBOy56kvhCqduqyo/f3yrHFWmqmiMg/BkBkYYQ==} + + parse5@7.3.0: + resolution: {integrity: sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==} + + path-expression-matcher@1.6.2: + resolution: {integrity: sha512-enSlaiat05iasnzmgNxRj8reFdj3puY2QpNgP1aPIaVfT6nn9ICuPoFlKHk8EN22HcwewshO+mN2DGbkCEOtqQ==} + engines: {node: '>=14.0.0'} + + piccolore@0.1.3: + resolution: {integrity: sha512-o8bTeDWjE086iwKrROaDf31K0qC/BENdm15/uH9usSC/uZjJOKb2YGiVHfLY4GhwsERiPI1jmwI2XrA7ACOxVw==} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + picomatch@2.3.2: + resolution: {integrity: sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==} + engines: {node: '>=8.6'} + + picomatch@4.0.5: + resolution: {integrity: sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==} + engines: {node: '>=12'} + + postcss-nested@6.2.0: + resolution: {integrity: sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==} + engines: {node: '>=12.0'} + peerDependencies: + postcss: ^8.2.14 + + postcss-selector-parser@6.1.4: + resolution: {integrity: sha512-bIoJLOmjCO1S9XdY/DcnR5hJxvrDir1PbGChrzXG3vw0/FOliy/fA3dmdhQ441kah4gKv+TwckGzex6wNS5cnQ==} + engines: {node: '>=4'} + + postcss@8.5.26: + resolution: {integrity: sha512-u82N74LFzG8ca+dD8puPnplTXoGH4fTPpVGuIbt36G3qvNlkvfD0lEAZSxaly3KX8TS/L1A1gsCEmvKmBcVbkQ==} + engines: {node: ^10 || ^12 || >=14} + + prismjs@1.30.0: + resolution: {integrity: sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw==} + engines: {node: '>=6'} + + property-information@7.2.0: + resolution: {integrity: sha512-IAtzIB6sUiWaJYrX9smp3V46pBGbBeLFRGdh25kg1334VcBlD8HzhPeNIWQH9zhGmo2itIe25EHt9dQP7G5hmg==} + + radix3@1.1.2: + resolution: {integrity: sha512-b484I/7b8rDEdSDKckSSBA8knMpcdsXudlE/LNL639wFoHKwLbEkQFZHWEYwDC0wa0FKUcCY+GAF73Z7wxNVFA==} + + react-dom@19.2.5: + resolution: {integrity: sha512-J5bAZz+DXMMwW/wV3xzKke59Af6CHY7G4uYLN1OvBcKEsWOs4pQExj86BBKamxl/Ik5bx9whOrvBlSDfWzgSag==} + peerDependencies: + react: ^19.2.5 + + react-refresh@0.18.0: + resolution: {integrity: sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw==} + engines: {node: '>=0.10.0'} + + react@19.2.5: + resolution: {integrity: sha512-llUJLzz1zTUBrskt2pwZgLq59AemifIftw4aB7JxOqf1HY2FDaGDxgwpAPVzHU1kdWabH7FauP4i1oEeer2WCA==} + engines: {node: '>=0.10.0'} + + readdirp@5.1.1: + resolution: {integrity: sha512-Kko+Y5XQ6fM+Ce3dq3m9YGxnacYZYl9cA1wZjaF3Vbry2L3i1qVg8+CAgNPsXRArPMUMCaOR7oa9Nqntc43JKA==} + engines: {node: '>= 20.19.0'} + + reading-time@1.5.0: + resolution: {integrity: sha512-onYyVhBNr4CmAxFsKS7bz+uTLRakypIe4R+5A824vBSkQy/hB3fZepoVEf8OVAxzLvK+H/jm9TzpI3ETSm64Kg==} + + recma-build-jsx@1.0.0: + resolution: {integrity: sha512-8GtdyqaBcDfva+GUKDr3nev3VpKAhup1+RvkMvUxURHpW7QyIvk9F5wz7Vzo06CEMSilw6uArgRqhpiUcWp8ew==} + + recma-jsx@1.0.1: + resolution: {integrity: sha512-huSIy7VU2Z5OLv6oFLosQGGDqPqdO1iq6bWNAdhzMxSJP7RAso4fCZ1cKu8j9YHCZf3TPrq4dw3okhrylgcd7w==} + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + + recma-parse@1.0.0: + resolution: {integrity: sha512-OYLsIGBB5Y5wjnSnQW6t3Xg7q3fQ7FWbw/vcXtORTnyaSFscOtABg+7Pnz6YZ6c27fG1/aN8CjfwoUEUIdwqWQ==} + + recma-stringify@1.0.0: + resolution: {integrity: sha512-cjwII1MdIIVloKvC9ErQ+OgAtwHBmcZ0Bg4ciz78FtbT8In39aAYbaA7zvxQ61xVMSPE8WxhLwLbhif4Js2C+g==} + + regex-recursion@6.0.2: + resolution: {integrity: sha512-0YCaSCq2VRIebiaUviZNs0cBz1kg5kVS2UKUfNIx8YVs1cN3AV7NTctO5FOKBA+UT2BPJIWZauYHPqJODG50cg==} + + regex-utilities@2.3.0: + resolution: {integrity: sha512-8VhliFJAWRaUiVvREIiW2NXXTmHs4vMNnSzuJVhscgmGav3g9VDxLrQndI3dZZVVdp0ZO/5v0xmX516/7M9cng==} + + regex@6.1.0: + resolution: {integrity: sha512-6VwtthbV4o/7+OaAF9I5L5V3llLEsoPyq9P1JVXkedTP33c7MfCG0/5NOPcSJn0TzXcG9YUrR0gQSWioew3LDg==} + + rehype-expressive-code@0.41.7: + resolution: {integrity: sha512-25f8ZMSF1d9CMscX7Cft0TSQIqdwjce2gDOvQ+d/w0FovsMwrSt3ODP4P3Z7wO1jsIJ4eYyaDRnIR/27bd/EMQ==} + + rehype-parse@9.0.1: + resolution: {integrity: sha512-ksCzCD0Fgfh7trPDxr2rSylbwq9iYDkSn8TCDmEJ49ljEUBxDVCzCHv7QNzZOfODanX4+bWQ4WZqLCRWYLfhag==} + + rehype-raw@7.0.0: + resolution: {integrity: sha512-/aE8hCfKlQeA8LmyeyQvQF3eBiLRGNlfBJEvWH7ivp9sBqs7TNqBL5X3v157rM4IFETqDnIOO+z5M/biZbo9Ww==} + + rehype-recma@1.0.0: + resolution: {integrity: sha512-lqA4rGUf1JmacCNWWZx0Wv1dHqMwxzsDWYMTowuplHF3xH0N/MmrZ/G3BDZnzAkRmxDadujCjaKM2hqYdCBOGw==} + + rehype-stringify@10.0.1: + resolution: {integrity: sha512-k9ecfXHmIPuFVI61B9DeLPN0qFHfawM6RsuX48hoqlaKSF61RskNjSm1lI8PhBEM0MRdLxVVm4WmTqJQccH9mA==} + + rehype@13.0.2: + resolution: {integrity: sha512-j31mdaRFrwFRUIlxGeuPXXKWQxet52RBQRvCmzl5eCefn/KGbomK5GMHNMsOJf55fgo3qw5tST5neDuarDYR2A==} + + remark-directive@4.0.0: + resolution: {integrity: sha512-7sxn4RfF1o3izevPV1DheyGDD6X4c9hrGpfdUpm7uC++dqrnJxIZVkk7CoKqcLm0VUMAuOol7Mno3m6g8cfMuA==} + + remark-gfm@4.0.1: + resolution: {integrity: sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg==} + + remark-mdx@3.1.1: + resolution: {integrity: sha512-Pjj2IYlUY3+D8x00UJsIOg5BEvfMyeI+2uLPn9VO9Wg4MEtN/VTIq2NEJQfde9PnX15KgtHyl9S0BcTnWrIuWg==} + + remark-parse@11.0.0: + resolution: {integrity: sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==} + + remark-rehype@11.1.2: + resolution: {integrity: sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw==} + + remark-smartypants@3.0.3: + resolution: {integrity: sha512-gCaK+ndZ0hYezlqFegHFCVh2CQemsi0Npdh1qVM9bxlUFknjkbP6VmojWhddOCrbK0PbbacmYLWfTULRiT1eWA==} + engines: {node: '>=16.0.0'} + + remark-stringify@11.0.0: + resolution: {integrity: sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==} + + resolve-pkg-maps@1.0.0: + resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} + + restore-cursor@5.1.0: + resolution: {integrity: sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==} + engines: {node: '>=18'} + + retext-latin@4.0.0: + resolution: {integrity: sha512-hv9woG7Fy0M9IlRQloq/N6atV82NxLGveq+3H2WOi79dtIYWN8OaxogDm77f8YnVXJL2VD3bbqowu5E3EMhBYA==} + + retext-smartypants@6.2.0: + resolution: {integrity: sha512-kk0jOU7+zGv//kfjXEBjdIryL1Acl4i9XNkHxtM7Tm5lFiCog576fjNC9hjoR7LTKQ0DsPWy09JummSsH1uqfQ==} + + retext-stringify@4.0.0: + resolution: {integrity: sha512-rtfN/0o8kL1e+78+uxPTqu1Klt0yPzKuQ2BfWwwfgIUSayyzxpM1PJzkKt4V8803uB9qSy32MvI7Xep9khTpiA==} + + retext@9.0.0: + resolution: {integrity: sha512-sbMDcpHCNjvlheSgMfEcVrZko3cDzdbe1x/e7G66dFp0Ff7Mldvi2uv6JkJQzdRcvLYE8CA8Oe8siQx8ZOgTcA==} + + rfdc@1.4.1: + resolution: {integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==} + + rollup@4.62.4: + resolution: {integrity: sha512-RXOqwaPsBGjMNMa4sQjDjHieHEZDFoj/Rdr46l2MU5DfEs16wHJPC2RPTPHWhNl+M3aI472LLqFkFKut4SblOg==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} + hasBin: true + + sax@1.6.1: + resolution: {integrity: sha512-42tBVwLWnaQvW5zc4HbZrTuWccECCZfBi92FDuwtqxasH+JbPB3/FOKb1m222K42R4WxuxzzMsTswfzgtSu64Q==} + engines: {node: '>=11.0.0'} + + scheduler@0.27.0: + resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==} + + semver@6.3.1: + resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} + hasBin: true + + semver@7.8.5: + resolution: {integrity: sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==} + engines: {node: '>=10'} + hasBin: true + + sharp@0.34.5: + resolution: {integrity: sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + + shiki@3.23.0: + resolution: {integrity: sha512-55Dj73uq9ZXL5zyeRPzHQsK7Nbyt6Y10k5s7OjuFZGMhpp4r/rsLBH0o/0fstIzX1Lep9VxefWljK/SKCzygIA==} + + shiki@4.4.3: + resolution: {integrity: sha512-Mb/GvXPHBAXdgGIcnfU5L3ldpn1XcxrGkPHwqgRx17/I2XRfqlFKk2vGkHWINn1kdXvzJZeuO3is6I9KLPFm0g==} + engines: {node: '>=20'} + + signal-exit@4.1.0: + resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} + engines: {node: '>=14'} + + sirv@3.0.2: + resolution: {integrity: sha512-2wcC/oGxHis/BoHkkPwldgiPSYcpZK3JU28WoMVv55yHJgcZ8rlXvuG9iZggz+sU1d4bRgIGASwyWqjxu3FM0g==} + engines: {node: '>=18'} + + sisteransi@1.0.5: + resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==} + + sitemap@9.0.1: + resolution: {integrity: sha512-S6hzjGJSG3d6if0YoF5kTyeRJvia6FSTBroE5fQ0bu1QNxyJqhhinfUsXi9fH3MgtXODWvwo2BDyQSnhPQ88uQ==} + engines: {node: '>=20.19.5', npm: '>=10.8.2'} + hasBin: true + + slice-ansi@7.1.2: + resolution: {integrity: sha512-iOBWFgUX7caIZiuutICxVgX1SdxwAVFFKwt1EvMYYec/NWO5meOJ6K5uQxhrYBdQJne4KxiqZc+KptFOWFSI9w==} + engines: {node: '>=18'} + + slice-ansi@8.0.0: + resolution: {integrity: sha512-stxByr12oeeOyY2BlviTNQlYV5xOj47GirPr4yA1hE9JCtxfQN0+tVbkxwCtYDQWhEKWFHsEK48ORg5jrouCAg==} + engines: {node: '>=20'} + + smol-toml@1.7.1: + resolution: {integrity: sha512-PPlsspAZ4jbMBu5DMFhfUGDQLu/vrL4SyBROVS37x8ynnVmFIs1VPBz1Co8Xks3TvpIaZXmU85y4DrQ+UyVFoQ==} + engines: {node: '>= 18'} + + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} + + source-map@0.7.6: + resolution: {integrity: sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==} + engines: {node: '>= 12'} + + space-separated-tokens@2.0.2: + resolution: {integrity: sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==} + + stream-replace-string@2.0.0: + resolution: {integrity: sha512-TlnjJ1C0QrmxRNrON00JvaFFlNh5TTG00APw23j74ET7gkQpTASi6/L2fuiav8pzK715HXtUeClpBTw2NPSn6w==} + + string-argv@0.3.2: + resolution: {integrity: sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==} + engines: {node: '>=0.6.19'} + + string-width@7.2.0: + resolution: {integrity: sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==} + engines: {node: '>=18'} + + string-width@8.2.2: + resolution: {integrity: sha512-GaPUh5gfdrYzqeVNZvUfT23vYYxXzKYidUcnMtJg/3rxRV63EFZy3k6xfKlmfeJD0176lnUV/Usr3XcwSvFzpg==} + engines: {node: '>=20'} + + stringify-entities@4.0.4: + resolution: {integrity: sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==} + + strip-ansi@7.2.0: + resolution: {integrity: sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==} + engines: {node: '>=12'} + + strnum@2.4.1: + resolution: {integrity: sha512-M9eUSMT2dCB2cTNPG7UYj6KuK7RJR2SN2+yCV/fTW3xzTCS6EaGZ5pSMgDIjB7r8zSfTGk+dvvn9rTjpVS9Mwg==} + + style-to-js@1.1.21: + resolution: {integrity: sha512-RjQetxJrrUJLQPHbLku6U/ocGtzyjbJMP9lCNK7Ag0CNh690nSH8woqWH9u16nMjYBAok+i7JO1NP2pOy8IsPQ==} + + style-to-object@1.0.14: + resolution: {integrity: sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw==} + + svgo@4.0.2: + resolution: {integrity: sha512-ekx94z1rRc5LDi6oSUaeRnYhd0UOJxdtQCL2rF8xpWxD3TPAsISWOrxezqGovqS38GRZOdpDfvQe3ts6F7nsng==} + engines: {node: '>=16'} + hasBin: true + + swiper@12.2.0: + resolution: {integrity: sha512-K8uXsBZU6ME97Ia3xbBge8IRCnR1lOmIILzvY/jGVic7dSTQ530s3uO8RvXbPUtkkXLWIwmZLRPbtDxRWVAFdg==} + engines: {node: '>= 4.7.0'} + + tiny-inflate@1.0.3: + resolution: {integrity: sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==} + + tinyclip@0.1.15: + resolution: {integrity: sha512-uo33abH+Ays0xYaDysoBt494Hb3hsEczMpcC0MwFl773pazORx4fmvKhclhR1wonUbB6vvpRsvVMwnhfqeMc+A==} + engines: {node: ^16.14.0 || >= 17.3.0} + + tinyexec@1.3.0: + resolution: {integrity: sha512-QKAl9m8gWWGHV8jZcPeym6j+XULi6tOf1mT83WYJ4Lk2ytW/uwAWkrP0uFsdoYMdueVJ0qs26wZ+23xeB4ibNQ==} + engines: {node: '>=18'} + + tinyglobby@0.2.17: + resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==} + engines: {node: '>=12.0.0'} + + tinypool@2.1.0: + resolution: {integrity: sha512-Pugqs6M0m7Lv1I7FtxN4aoyToKg1C4tu+/381vH35y8oENM/Ai7f7C4StcoK4/+BSw9ebcS8jRiVrORFKCALLw==} + engines: {node: ^20.0.0 || >=22.0.0} + + totalist@3.0.1: + resolution: {integrity: sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==} + engines: {node: '>=6'} + + trim-lines@3.0.1: + resolution: {integrity: sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==} + + trough@2.2.0: + resolution: {integrity: sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==} + + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + + typescript@6.0.3: + resolution: {integrity: sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==} + engines: {node: '>=14.17'} + hasBin: true + + ufo@1.6.4: + resolution: {integrity: sha512-JFNbkD1Svwe0KvGi8GOeLcP4kAWQ609twvCdcHxq1oSL8svv39ZuSvajcD8B+5D0eL4+s1Is2D/O6KN3qcTeRA==} + + ultrahtml@1.7.0: + resolution: {integrity: sha512-2xRd0VHoAQE4M+vF/DvFFB7pUV0ZxTW1TLi7lHQWnF/Sb5TPeEUV/l+hxcNnGO00ZXGnR0voCMmYRKQf+rvJ2g==} + + uncrypto@0.1.3: + resolution: {integrity: sha512-Ql87qFHB3s/De2ClA9e0gsnS6zXG27SkTiSJwjCc9MebbfapQfuPzumMIUMi38ezPZVNFcHI9sUIepeQfw8J8Q==} + + undici-types@7.18.2: + resolution: {integrity: sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==} + + undici-types@7.24.6: + resolution: {integrity: sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==} + + unified@11.0.5: + resolution: {integrity: sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==} + + unifont@0.7.4: + resolution: {integrity: sha512-oHeis4/xl42HUIeHuNZRGEvxj5AaIKR+bHPNegRq5LV1gdc3jundpONbjglKpihmJf+dswygdMJn3eftGIMemg==} + + unist-util-find-after@5.0.0: + resolution: {integrity: sha512-amQa0Ep2m6hE2g72AugUItjbuM8X8cGQnFoHk0pGfrFeT9GZhzN5SW8nRsiGKK7Aif4CrACPENkA6P/Lw6fHGQ==} + + unist-util-is@6.0.1: + resolution: {integrity: sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==} + + unist-util-modify-children@4.0.0: + resolution: {integrity: sha512-+tdN5fGNddvsQdIzUF3Xx82CU9sMM+fA0dLgR9vOmT0oPT2jH+P1nd5lSqfCfXAw+93NhcXNY2qqvTUtE4cQkw==} + + unist-util-position-from-estree@2.0.0: + resolution: {integrity: sha512-KaFVRjoqLyF6YXCbVLNad/eS4+OfPQQn2yOd7zF/h5T/CSL2v8NpN6a5TPvtbXthAGw5nG+PuTtq+DdIZr+cRQ==} + + unist-util-position@5.0.0: + resolution: {integrity: sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==} + + unist-util-remove-position@5.0.0: + resolution: {integrity: sha512-Hp5Kh3wLxv0PHj9m2yZhhLt58KzPtEYKQQ4yxfYFEO7EvHwzyDYnduhHnY1mDxoqr7VUwVuHXk9RXKIiYS1N8Q==} + + unist-util-stringify-position@4.0.0: + resolution: {integrity: sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==} + + unist-util-visit-children@3.0.0: + resolution: {integrity: sha512-RgmdTfSBOg04sdPcpTSD1jzoNBjt9a80/ZCzp5cI9n1qPzLZWF9YdvWGN2zmTumP1HWhXKdUWexjy/Wy/lJ7tA==} + + unist-util-visit-parents@6.0.2: + resolution: {integrity: sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==} + + unist-util-visit@5.1.0: + resolution: {integrity: sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg==} + + unstorage@1.17.5: + resolution: {integrity: sha512-0i3iqvRfx29hkNntHyQvJTpf5W9dQ9ZadSoRU8+xVlhVtT7jAX57fazYO9EHvcRCfBCyi5YRya7XCDOsbTgkPg==} + peerDependencies: + '@azure/app-configuration': ^1.8.0 + '@azure/cosmos': ^4.2.0 + '@azure/data-tables': ^13.3.0 + '@azure/identity': ^4.6.0 + '@azure/keyvault-secrets': ^4.9.0 + '@azure/storage-blob': ^12.26.0 + '@capacitor/preferences': ^6 || ^7 || ^8 + '@deno/kv': '>=0.9.0' + '@netlify/blobs': ^6.5.0 || ^7.0.0 || ^8.1.0 || ^9.0.0 || ^10.0.0 + '@planetscale/database': ^1.19.0 + '@upstash/redis': ^1.34.3 + '@vercel/blob': '>=0.27.1' + '@vercel/functions': ^2.2.12 || ^3.0.0 + '@vercel/kv': ^1 || ^2 || ^3 + aws4fetch: ^1.0.20 + db0: '>=0.2.1' + idb-keyval: ^6.2.1 + ioredis: ^5.4.2 + uploadthing: ^7.4.4 + peerDependenciesMeta: + '@azure/app-configuration': + optional: true + '@azure/cosmos': + optional: true + '@azure/data-tables': + optional: true + '@azure/identity': + optional: true + '@azure/keyvault-secrets': + optional: true + '@azure/storage-blob': + optional: true + '@capacitor/preferences': + optional: true + '@deno/kv': + optional: true + '@netlify/blobs': + optional: true + '@planetscale/database': + optional: true + '@upstash/redis': + optional: true + '@vercel/blob': + optional: true + '@vercel/functions': + optional: true + '@vercel/kv': + optional: true + aws4fetch: + optional: true + db0: + optional: true + idb-keyval: + optional: true + ioredis: + optional: true + uploadthing: + optional: true + + update-browserslist-db@1.3.1: + resolution: {integrity: sha512-ZZ61DsRsOnakl74HAmp3oSN4aXUmEWXf+i/yv0h7tIBfICc3VdrFErQKUUKPgu3AMsTUMbcongALEN4l6GSUrQ==} + hasBin: true + peerDependencies: + browserslist: '>= 4.21.0' + + util-deprecate@1.0.2: + resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + + vfile-location@5.0.3: + resolution: {integrity: sha512-5yXvWDEgqeiYiBe1lbxYF7UMAIm/IcopxMHrMQDq3nvKcjPKIhZklUKL+AE7J7uApI4kwe2snsK+eI6UTj9EHg==} + + vfile-message@4.0.3: + resolution: {integrity: sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==} + + vfile@6.0.3: + resolution: {integrity: sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==} + + vite@7.3.6: + resolution: {integrity: sha512-4XP60spRGjSZFf1qYH+dJIkK2znL3zQfl9KkOV9MkkRR/3Dls0dxaBsQPTloEc5BLXWPL9vsOxopxyKoMmDueg==} + 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 + + vitefu@1.1.3: + resolution: {integrity: sha512-ub4okH7Z5KLjb6hDyjqrGXqWtWvoYdU3IGm/NorpgHncKoLTCfRIbvlhBm7r0YstIaQRYlp4yEbFqDcKSzXSSg==} + peerDependencies: + vite: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 + peerDependenciesMeta: + vite: + optional: true + + web-namespaces@2.0.1: + resolution: {integrity: sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==} + + which-pm-runs@1.1.0: + resolution: {integrity: sha512-n1brCuqClxfFfq/Rb0ICg9giSZqCS+pLtccdag6C2HyufBrh3fBOiy9nb6ggRMvWOVH5GrdJskj5iGTZNxd7SA==} + engines: {node: '>=4'} + + wrap-ansi@9.0.2: + resolution: {integrity: sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==} + engines: {node: '>=18'} + + xml-naming@0.3.0: + resolution: {integrity: sha512-ghig2TBE/H11aOVgmahA3MhimvkBr6JIYknH/Dhdk10nXwdbIqBJsbfMxpvFPG8bAw77gN29aQWvKpmVoPlvPQ==} + engines: {node: '>=16.0.0'} + + xxhash-wasm@1.1.0: + resolution: {integrity: sha512-147y/6YNh+tlp6nd/2pWq38i9h6mz/EuQ6njIrmW8D1BS5nCqs0P6DG+m6zTGnNz5I+uhZ0SHxBs9BsPrwcKDA==} + + yallist@3.1.1: + resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} + + yaml@2.9.0: + resolution: {integrity: sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==} + engines: {node: '>= 14.6'} + hasBin: true + + yargs-parser@22.0.0: + resolution: {integrity: sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw==} + engines: {node: ^20.19.0 || ^22.12.0 || >=23} + + yocto-queue@1.2.2: + resolution: {integrity: sha512-4LCcse/U2MHZ63HAJVE+v71o7yOdIe4cZ70Wpf8D/IyjDKYQLV5GD46B+hSTjJsvV5PztjvHoU580EftxjDZFQ==} + engines: {node: '>=12.20'} + + zod@4.4.3: + resolution: {integrity: sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==} + + zwitch@2.0.4: + resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==} + +snapshots: + + '@astrojs/compiler@4.0.0': {} + + '@astrojs/internal-helpers@0.10.0': + dependencies: + '@types/hast': 3.0.5 + '@types/mdast': 4.0.4 + js-yaml: 4.3.1 + picomatch: 4.0.5 + retext-smartypants: 6.2.0 + shiki: 4.4.3 + smol-toml: 1.7.1 + unified: 11.0.5 + + '@astrojs/internal-helpers@0.9.1': + dependencies: + picomatch: 4.0.5 + + '@astrojs/markdown-remark@7.1.2': + dependencies: + '@astrojs/internal-helpers': 0.9.1 + '@astrojs/prism': 4.0.2 + github-slugger: 2.0.0 + hast-util-from-html: 2.0.3 + hast-util-to-text: 4.0.2 + js-yaml: 4.3.1 + mdast-util-definitions: 6.0.0 + rehype-raw: 7.0.0 + rehype-stringify: 10.0.1 + remark-gfm: 4.0.1 + remark-parse: 11.0.0 + remark-rehype: 11.1.2 + remark-smartypants: 3.0.3 + retext-smartypants: 6.2.0 + shiki: 4.4.3 + smol-toml: 1.7.1 + unified: 11.0.5 + unist-util-remove-position: 5.0.0 + unist-util-visit: 5.1.0 + unist-util-visit-parents: 6.0.2 + vfile: 6.0.3 + transitivePeerDependencies: + - supports-color + + '@astrojs/markdown-remark@7.2.0': + dependencies: + '@astrojs/internal-helpers': 0.10.0 + '@astrojs/prism': 4.0.2 + github-slugger: 2.0.0 + hast-util-from-html: 2.0.3 + hast-util-to-text: 4.0.2 + mdast-util-definitions: 6.0.0 + rehype-raw: 7.0.0 + rehype-stringify: 10.0.1 + remark-gfm: 4.0.1 + remark-parse: 11.0.0 + remark-rehype: 11.1.2 + remark-smartypants: 3.0.3 + unified: 11.0.5 + unist-util-remove-position: 5.0.0 + unist-util-visit: 5.1.0 + unist-util-visit-parents: 6.0.2 + vfile: 6.0.3 + transitivePeerDependencies: + - supports-color + + '@astrojs/mdx@5.0.6(astro@6.4.8(@types/node@25.9.5)(rollup@4.62.4)(yaml@2.9.0))': + dependencies: + '@astrojs/markdown-remark': 7.1.2 + '@mdx-js/mdx': 3.1.1 + acorn: 8.18.0 + astro: 6.4.8(@types/node@25.9.5)(rollup@4.62.4)(yaml@2.9.0) + es-module-lexer: 2.3.1 + estree-util-visit: 2.0.0 + hast-util-to-html: 9.0.5 + piccolore: 0.1.3 + rehype-raw: 7.0.0 + remark-gfm: 4.0.1 + remark-smartypants: 3.0.3 + source-map: 0.7.6 + unist-util-visit: 5.1.0 + vfile: 6.0.3 + transitivePeerDependencies: + - supports-color + + '@astrojs/prism@4.0.2': + dependencies: + prismjs: 1.30.0 + + '@astrojs/react@5.0.7(@types/node@25.9.5)(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(yaml@2.9.0)': + dependencies: + '@astrojs/internal-helpers': 0.10.0 + '@types/react': 19.2.18 + '@types/react-dom': 19.2.4(@types/react@19.2.18) + '@vitejs/plugin-react': 5.2.0(vite@7.3.6(@types/node@25.9.5)(yaml@2.9.0)) + devalue: 5.9.0 + react: 19.2.5 + react-dom: 19.2.5(react@19.2.5) + ultrahtml: 1.7.0 + vite: 7.3.6(@types/node@25.9.5)(yaml@2.9.0) + transitivePeerDependencies: + - '@types/node' + - jiti + - less + - lightningcss + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + - tsx + - yaml + + '@astrojs/rss@4.0.19': + dependencies: + fast-xml-parser: 5.10.1 + piccolore: 0.1.3 + zod: 4.4.3 + + '@astrojs/sitemap@3.7.3': + dependencies: + sitemap: 9.0.1 + stream-replace-string: 2.0.0 + zod: 4.4.3 + + '@astrojs/telemetry@3.3.2': + dependencies: + ci-info: 4.4.0 + dset: 3.1.4 + is-docker: 4.0.0 + is-wsl: 3.1.1 + which-pm-runs: 1.1.0 + + '@babel/code-frame@7.29.7': + dependencies: + '@babel/helper-validator-identifier': 7.29.7 + js-tokens: 4.0.0 + picocolors: 1.1.1 + + '@babel/compat-data@7.29.7': {} + + '@babel/core@7.29.7': + dependencies: + '@babel/code-frame': 7.29.7 + '@babel/generator': 7.29.8 + '@babel/helper-compilation-targets': 7.29.7 + '@babel/helper-module-transforms': 7.29.7(@babel/core@7.29.7) + '@babel/helpers': 7.29.7 + '@babel/parser': 7.29.8 + '@babel/template': 7.29.7 + '@babel/traverse': 7.29.8 + '@babel/types': 7.29.8 + '@jridgewell/remapping': 2.3.5 + convert-source-map: 2.0.0 + debug: 4.4.3 + gensync: 1.0.0-beta.2 + json5: 2.2.3 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/generator@7.29.8': + dependencies: + '@babel/parser': 7.29.8 + '@babel/types': 7.29.8 + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + jsesc: 3.1.0 + + '@babel/helper-compilation-targets@7.29.7': + dependencies: + '@babel/compat-data': 7.29.7 + '@babel/helper-validator-option': 7.29.7 + browserslist: 4.28.8 + lru-cache: 5.1.1 + semver: 6.3.1 + + '@babel/helper-globals@7.29.7': {} + + '@babel/helper-module-imports@7.29.7': + dependencies: + '@babel/traverse': 7.29.8 + '@babel/types': 7.29.8 + transitivePeerDependencies: + - supports-color + + '@babel/helper-module-transforms@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-module-imports': 7.29.7 + '@babel/helper-validator-identifier': 7.29.7 + '@babel/traverse': 7.29.8 + transitivePeerDependencies: + - supports-color + + '@babel/helper-plugin-utils@7.29.7': {} + + '@babel/helper-string-parser@7.29.7': {} + + '@babel/helper-validator-identifier@7.29.7': {} + + '@babel/helper-validator-option@7.29.7': {} + + '@babel/helpers@7.29.7': + dependencies: + '@babel/template': 7.29.7 + '@babel/types': 7.29.8 + + '@babel/parser@7.29.8': + dependencies: + '@babel/types': 7.29.8 + + '@babel/plugin-transform-react-jsx-self@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-transform-react-jsx-source@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/template@7.29.7': + dependencies: + '@babel/code-frame': 7.29.7 + '@babel/parser': 7.29.8 + '@babel/types': 7.29.8 + + '@babel/traverse@7.29.8': + dependencies: + '@babel/code-frame': 7.29.7 + '@babel/generator': 7.29.8 + '@babel/helper-globals': 7.29.7 + '@babel/parser': 7.29.8 + '@babel/template': 7.29.7 + '@babel/types': 7.29.8 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + '@babel/types@7.29.8': + dependencies: + '@babel/helper-string-parser': 7.29.7 + '@babel/helper-validator-identifier': 7.29.7 + + '@capsizecss/unpack@4.0.1': + dependencies: + fontkitten: 1.0.3 + + '@clack/core@1.4.3': + dependencies: + fast-wrap-ansi: 0.2.2 + sisteransi: 1.0.5 + + '@clack/prompts@1.7.0': + dependencies: + '@clack/core': 1.4.3 + fast-string-width: 3.0.2 + fast-wrap-ansi: 0.2.2 + sisteransi: 1.0.5 + + '@ctrl/tinycolor@4.2.0': {} + + '@emnapi/runtime@1.11.3': + dependencies: + tslib: 2.8.1 + optional: true + + '@esbuild/aix-ppc64@0.27.7': + optional: true + + '@esbuild/aix-ppc64@0.28.2': + optional: true + + '@esbuild/android-arm64@0.27.7': + optional: true + + '@esbuild/android-arm64@0.28.2': + optional: true + + '@esbuild/android-arm@0.27.7': + optional: true + + '@esbuild/android-arm@0.28.2': + optional: true + + '@esbuild/android-x64@0.27.7': + optional: true + + '@esbuild/android-x64@0.28.2': + optional: true + + '@esbuild/darwin-arm64@0.27.7': + optional: true + + '@esbuild/darwin-arm64@0.28.2': + optional: true + + '@esbuild/darwin-x64@0.27.7': + optional: true + + '@esbuild/darwin-x64@0.28.2': + optional: true + + '@esbuild/freebsd-arm64@0.27.7': + optional: true + + '@esbuild/freebsd-arm64@0.28.2': + optional: true + + '@esbuild/freebsd-x64@0.27.7': + optional: true + + '@esbuild/freebsd-x64@0.28.2': + optional: true + + '@esbuild/linux-arm64@0.27.7': + optional: true + + '@esbuild/linux-arm64@0.28.2': + optional: true + + '@esbuild/linux-arm@0.27.7': + optional: true + + '@esbuild/linux-arm@0.28.2': + optional: true + + '@esbuild/linux-ia32@0.27.7': + optional: true + + '@esbuild/linux-ia32@0.28.2': + optional: true + + '@esbuild/linux-loong64@0.27.7': + optional: true + + '@esbuild/linux-loong64@0.28.2': + optional: true + + '@esbuild/linux-mips64el@0.27.7': + optional: true + + '@esbuild/linux-mips64el@0.28.2': + optional: true + + '@esbuild/linux-ppc64@0.27.7': + optional: true + + '@esbuild/linux-ppc64@0.28.2': + optional: true + + '@esbuild/linux-riscv64@0.27.7': + optional: true + + '@esbuild/linux-riscv64@0.28.2': + optional: true + + '@esbuild/linux-s390x@0.27.7': + optional: true + + '@esbuild/linux-s390x@0.28.2': + optional: true + + '@esbuild/linux-x64@0.27.7': + optional: true + + '@esbuild/linux-x64@0.28.2': + optional: true + + '@esbuild/netbsd-arm64@0.27.7': + optional: true + + '@esbuild/netbsd-arm64@0.28.2': + optional: true + + '@esbuild/netbsd-x64@0.27.7': + optional: true + + '@esbuild/netbsd-x64@0.28.2': + optional: true + + '@esbuild/openbsd-arm64@0.27.7': + optional: true + + '@esbuild/openbsd-arm64@0.28.2': + optional: true + + '@esbuild/openbsd-x64@0.27.7': + optional: true + + '@esbuild/openbsd-x64@0.28.2': + optional: true + + '@esbuild/openharmony-arm64@0.27.7': + optional: true + + '@esbuild/openharmony-arm64@0.28.2': + optional: true + + '@esbuild/sunos-x64@0.27.7': + optional: true + + '@esbuild/sunos-x64@0.28.2': + optional: true + + '@esbuild/win32-arm64@0.27.7': + optional: true + + '@esbuild/win32-arm64@0.28.2': + optional: true + + '@esbuild/win32-ia32@0.27.7': + optional: true + + '@esbuild/win32-ia32@0.28.2': + optional: true + + '@esbuild/win32-x64@0.27.7': + optional: true + + '@esbuild/win32-x64@0.28.2': + optional: true + + '@expressive-code/core@0.41.7': + dependencies: + '@ctrl/tinycolor': 4.2.0 + hast-util-select: 6.0.4 + hast-util-to-html: 9.0.5 + hast-util-to-text: 4.0.2 + hastscript: 9.0.1 + postcss: 8.5.26 + postcss-nested: 6.2.0(postcss@8.5.26) + unist-util-visit: 5.1.0 + unist-util-visit-parents: 6.0.2 + + '@expressive-code/plugin-frames@0.41.7': + dependencies: + '@expressive-code/core': 0.41.7 + + '@expressive-code/plugin-shiki@0.41.7': + dependencies: + '@expressive-code/core': 0.41.7 + shiki: 3.23.0 + + '@expressive-code/plugin-text-markers@0.41.7': + dependencies: + '@expressive-code/core': 0.41.7 + + '@img/colour@1.1.0': {} + + '@img/sharp-darwin-arm64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-darwin-arm64': 1.2.4 + optional: true + + '@img/sharp-darwin-x64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-darwin-x64': 1.2.4 + optional: true + + '@img/sharp-libvips-darwin-arm64@1.2.4': + optional: true + + '@img/sharp-libvips-darwin-x64@1.2.4': + optional: true + + '@img/sharp-libvips-linux-arm64@1.2.4': + optional: true + + '@img/sharp-libvips-linux-arm@1.2.4': + optional: true + + '@img/sharp-libvips-linux-ppc64@1.2.4': + optional: true + + '@img/sharp-libvips-linux-riscv64@1.2.4': + optional: true + + '@img/sharp-libvips-linux-s390x@1.2.4': + optional: true + + '@img/sharp-libvips-linux-x64@1.2.4': + optional: true + + '@img/sharp-libvips-linuxmusl-arm64@1.2.4': + optional: true + + '@img/sharp-libvips-linuxmusl-x64@1.2.4': + optional: true + + '@img/sharp-linux-arm64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-arm64': 1.2.4 + optional: true + + '@img/sharp-linux-arm@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-arm': 1.2.4 + optional: true + + '@img/sharp-linux-ppc64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-ppc64': 1.2.4 + optional: true + + '@img/sharp-linux-riscv64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-riscv64': 1.2.4 + optional: true + + '@img/sharp-linux-s390x@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-s390x': 1.2.4 + optional: true + + '@img/sharp-linux-x64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-x64': 1.2.4 + optional: true + + '@img/sharp-linuxmusl-arm64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linuxmusl-arm64': 1.2.4 + optional: true + + '@img/sharp-linuxmusl-x64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linuxmusl-x64': 1.2.4 + optional: true + + '@img/sharp-wasm32@0.34.5': + dependencies: + '@emnapi/runtime': 1.11.3 + optional: true + + '@img/sharp-win32-arm64@0.34.5': + optional: true + + '@img/sharp-win32-ia32@0.34.5': + optional: true + + '@img/sharp-win32-x64@0.34.5': + optional: true + + '@jridgewell/gen-mapping@0.3.13': + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/remapping@2.3.5': + dependencies: + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/resolve-uri@3.1.2': {} + + '@jridgewell/sourcemap-codec@1.5.5': {} + + '@jridgewell/trace-mapping@0.3.31': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.5 + + '@mdx-js/mdx@3.1.1': + dependencies: + '@types/estree': 1.0.9 + '@types/estree-jsx': 1.0.5 + '@types/hast': 3.0.5 + '@types/mdx': 2.0.14 + acorn: 8.18.0 + collapse-white-space: 2.1.0 + devlop: 1.1.0 + estree-util-is-identifier-name: 3.0.0 + estree-util-scope: 1.0.0 + estree-walker: 3.0.3 + hast-util-to-jsx-runtime: 2.3.6 + markdown-extensions: 2.0.0 + recma-build-jsx: 1.0.0 + recma-jsx: 1.0.1(acorn@8.18.0) + recma-stringify: 1.0.0 + rehype-recma: 1.0.0 + remark-mdx: 3.1.1 + remark-parse: 11.0.0 + remark-rehype: 11.1.2 + source-map: 0.7.6 + unified: 11.0.5 + unist-util-position-from-estree: 2.0.0 + unist-util-stringify-position: 4.0.0 + unist-util-visit: 5.1.0 + vfile: 6.0.3 + transitivePeerDependencies: + - supports-color + + '@napi-rs/lzma-linux-x64-gnu@1.5.1': + optional: true + + '@nodable/entities@3.0.0': {} + + '@oslojs/encoding@1.1.0': {} + + '@oxfmt/binding-android-arm-eabi@0.45.0': + optional: true + + '@oxfmt/binding-android-arm64@0.45.0': + optional: true + + '@oxfmt/binding-darwin-arm64@0.45.0': + optional: true + + '@oxfmt/binding-darwin-x64@0.45.0': + optional: true + + '@oxfmt/binding-freebsd-x64@0.45.0': + optional: true + + '@oxfmt/binding-linux-arm-gnueabihf@0.45.0': + optional: true + + '@oxfmt/binding-linux-arm-musleabihf@0.45.0': + optional: true + + '@oxfmt/binding-linux-arm64-gnu@0.45.0': + optional: true + + '@oxfmt/binding-linux-arm64-musl@0.45.0': + optional: true + + '@oxfmt/binding-linux-ppc64-gnu@0.45.0': + optional: true + + '@oxfmt/binding-linux-riscv64-gnu@0.45.0': + optional: true + + '@oxfmt/binding-linux-riscv64-musl@0.45.0': + optional: true + + '@oxfmt/binding-linux-s390x-gnu@0.45.0': + optional: true + + '@oxfmt/binding-linux-x64-gnu@0.45.0': + optional: true + + '@oxfmt/binding-linux-x64-musl@0.45.0': + optional: true + + '@oxfmt/binding-openharmony-arm64@0.45.0': + optional: true + + '@oxfmt/binding-win32-arm64-msvc@0.45.0': + optional: true + + '@oxfmt/binding-win32-ia32-msvc@0.45.0': + optional: true + + '@oxfmt/binding-win32-x64-msvc@0.45.0': + optional: true + + '@oxlint/binding-android-arm-eabi@1.78.0': + optional: true + + '@oxlint/binding-android-arm64@1.78.0': + optional: true + + '@oxlint/binding-darwin-arm64@1.78.0': + optional: true + + '@oxlint/binding-darwin-x64@1.78.0': + optional: true + + '@oxlint/binding-freebsd-x64@1.78.0': + optional: true + + '@oxlint/binding-linux-arm-gnueabihf@1.78.0': + optional: true + + '@oxlint/binding-linux-arm-musleabihf@1.78.0': + optional: true + + '@oxlint/binding-linux-arm64-gnu@1.78.0': + optional: true + + '@oxlint/binding-linux-arm64-musl@1.78.0': + optional: true + + '@oxlint/binding-linux-ppc64-gnu@1.78.0': + optional: true + + '@oxlint/binding-linux-riscv64-gnu@1.78.0': + optional: true + + '@oxlint/binding-linux-riscv64-musl@1.78.0': + optional: true + + '@oxlint/binding-linux-s390x-gnu@1.78.0': + optional: true + + '@oxlint/binding-linux-x64-gnu@1.78.0': + optional: true + + '@oxlint/binding-linux-x64-musl@1.78.0': + optional: true + + '@oxlint/binding-openharmony-arm64@1.78.0': + optional: true + + '@oxlint/binding-win32-arm64-msvc@1.78.0': + optional: true + + '@oxlint/binding-win32-ia32-msvc@1.78.0': + optional: true + + '@oxlint/binding-win32-x64-msvc@1.78.0': + optional: true + + '@pagefind/darwin-arm64@1.5.2': + optional: true + + '@pagefind/darwin-x64@1.5.2': + optional: true + + '@pagefind/default-ui@1.5.2': {} + + '@pagefind/freebsd-x64@1.5.2': + optional: true + + '@pagefind/linux-arm64@1.5.2': + optional: true + + '@pagefind/linux-x64@1.5.2': + optional: true + + '@pagefind/windows-arm64@1.5.2': + optional: true + + '@pagefind/windows-x64@1.5.2': + optional: true + + '@polka/url@1.0.0-next.29': {} + + '@rolldown/pluginutils@1.0.0-rc.3': {} + + '@rollup/pluginutils@5.4.0(rollup@4.62.4)': + dependencies: + '@types/estree': 1.0.9 + estree-walker: 2.0.2 + picomatch: 4.0.5 + optionalDependencies: + rollup: 4.62.4 + + '@rollup/rollup-android-arm-eabi@4.62.4': + optional: true + + '@rollup/rollup-android-arm64@4.62.4': + optional: true + + '@rollup/rollup-darwin-arm64@4.62.4': + optional: true + + '@rollup/rollup-darwin-x64@4.62.4': + optional: true + + '@rollup/rollup-freebsd-arm64@4.62.4': + optional: true + + '@rollup/rollup-freebsd-x64@4.62.4': + optional: true + + '@rollup/rollup-linux-arm-gnueabihf@4.62.4': + optional: true + + '@rollup/rollup-linux-arm-musleabihf@4.62.4': + optional: true + + '@rollup/rollup-linux-arm64-gnu@4.62.4': + optional: true + + '@rollup/rollup-linux-arm64-musl@4.62.4': + optional: true + + '@rollup/rollup-linux-loong64-gnu@4.62.4': + optional: true + + '@rollup/rollup-linux-loong64-musl@4.62.4': + optional: true + + '@rollup/rollup-linux-ppc64-gnu@4.62.4': + optional: true + + '@rollup/rollup-linux-ppc64-musl@4.62.4': + optional: true + + '@rollup/rollup-linux-riscv64-gnu@4.62.4': + optional: true + + '@rollup/rollup-linux-riscv64-musl@4.62.4': + optional: true + + '@rollup/rollup-linux-s390x-gnu@4.62.4': + optional: true + + '@rollup/rollup-linux-x64-gnu@4.62.4': + optional: true + + '@rollup/rollup-linux-x64-musl@4.62.4': + optional: true + + '@rollup/rollup-openbsd-x64@4.62.4': + optional: true + + '@rollup/rollup-openharmony-arm64@4.62.4': + optional: true + + '@rollup/rollup-win32-arm64-msvc@4.62.4': + optional: true + + '@rollup/rollup-win32-ia32-msvc@4.62.4': + optional: true + + '@rollup/rollup-win32-x64-gnu@4.62.4': + optional: true + + '@rollup/rollup-win32-x64-msvc@4.62.4': + optional: true + + '@shikijs/core@3.23.0': + dependencies: + '@shikijs/types': 3.23.0 + '@shikijs/vscode-textmate': 10.0.2 + '@types/hast': 3.0.5 + hast-util-to-html: 9.0.5 + + '@shikijs/core@4.4.3': + dependencies: + '@shikijs/primitive': 4.4.3 + '@shikijs/types': 4.4.3 + '@shikijs/vscode-textmate': 10.0.2 + '@types/hast': 3.0.5 + hast-util-to-html: 9.0.5 + + '@shikijs/engine-javascript@3.23.0': + dependencies: + '@shikijs/types': 3.23.0 + '@shikijs/vscode-textmate': 10.0.2 + oniguruma-to-es: 4.3.6 + + '@shikijs/engine-javascript@4.4.3': + dependencies: + '@shikijs/types': 4.4.3 + '@shikijs/vscode-textmate': 10.0.2 + oniguruma-to-es: 4.3.6 + + '@shikijs/engine-oniguruma@3.23.0': + dependencies: + '@shikijs/types': 3.23.0 + '@shikijs/vscode-textmate': 10.0.2 + + '@shikijs/engine-oniguruma@4.4.3': + dependencies: + '@shikijs/types': 4.4.3 + '@shikijs/vscode-textmate': 10.0.2 + + '@shikijs/langs@3.23.0': + dependencies: + '@shikijs/types': 3.23.0 + + '@shikijs/langs@4.4.3': + dependencies: + '@shikijs/types': 4.4.3 + + '@shikijs/primitive@4.4.3': + dependencies: + '@shikijs/types': 4.4.3 + '@shikijs/vscode-textmate': 10.0.2 + '@types/hast': 3.0.5 + + '@shikijs/themes@3.23.0': + dependencies: + '@shikijs/types': 3.23.0 + + '@shikijs/themes@4.4.3': + dependencies: + '@shikijs/types': 4.4.3 + + '@shikijs/types@3.23.0': + dependencies: + '@shikijs/vscode-textmate': 10.0.2 + '@types/hast': 3.0.5 + + '@shikijs/types@4.4.3': + dependencies: + '@shikijs/vscode-textmate': 10.0.2 + '@types/hast': 3.0.5 + + '@shikijs/vscode-textmate@10.0.2': {} + + '@types/babel__core@7.20.5': + dependencies: + '@babel/parser': 7.29.8 + '@babel/types': 7.29.8 + '@types/babel__generator': 7.27.0 + '@types/babel__template': 7.4.4 + '@types/babel__traverse': 7.28.0 + + '@types/babel__generator@7.27.0': + dependencies: + '@babel/types': 7.29.8 + + '@types/babel__template@7.4.4': + dependencies: + '@babel/parser': 7.29.8 + '@babel/types': 7.29.8 + + '@types/babel__traverse@7.28.0': + dependencies: + '@babel/types': 7.29.8 + + '@types/debug@4.1.13': + dependencies: + '@types/ms': 2.1.0 + + '@types/estree-jsx@1.0.5': + dependencies: + '@types/estree': 1.0.9 + + '@types/estree@1.0.9': {} + + '@types/hast@3.0.5': + dependencies: + '@types/unist': 3.0.3 + + '@types/mdast@4.0.4': + dependencies: + '@types/unist': 3.0.3 + + '@types/mdx@2.0.14': {} + + '@types/ms@2.1.0': {} + + '@types/nlcst@2.0.3': + dependencies: + '@types/unist': 3.0.3 + + '@types/node@24.13.3': + dependencies: + undici-types: 7.18.2 + + '@types/node@25.9.5': + dependencies: + undici-types: 7.24.6 + + '@types/react-dom@19.2.4(@types/react@19.2.18)': + dependencies: + '@types/react': 19.2.18 + + '@types/react@19.2.18': + dependencies: + csstype: 3.2.3 + + '@types/sax@1.2.7': + dependencies: + '@types/node': 25.9.5 + + '@types/unist@2.0.11': {} + + '@types/unist@3.0.3': {} + + '@ungap/structured-clone@1.3.3': {} + + '@vitejs/plugin-react@5.2.0(vite@7.3.6(@types/node@25.9.5)(yaml@2.9.0))': + dependencies: + '@babel/core': 7.29.7 + '@babel/plugin-transform-react-jsx-self': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-react-jsx-source': 7.29.7(@babel/core@7.29.7) + '@rolldown/pluginutils': 1.0.0-rc.3 + '@types/babel__core': 7.20.5 + react-refresh: 0.18.0 + vite: 7.3.6(@types/node@25.9.5)(yaml@2.9.0) + transitivePeerDependencies: + - supports-color + + acorn-jsx@5.3.2(acorn@8.18.0): + dependencies: + acorn: 8.18.0 + + acorn@8.18.0: {} + + ansi-escapes@7.3.0: + dependencies: + environment: 1.1.0 + + ansi-regex@6.2.2: {} + + ansi-styles@6.2.3: {} + + anymatch@3.1.3: + dependencies: + normalize-path: 3.0.0 + picomatch: 2.3.2 + + anynum@1.0.1: {} + + arg@5.0.2: {} + + argparse@2.0.1: {} + + aria-query@5.3.2: {} + + array-iterate@2.0.1: {} + + astring@1.9.0: {} + + astro-expressive-code@0.41.7(astro@6.4.8(@types/node@25.9.5)(rollup@4.62.4)(yaml@2.9.0)): + dependencies: + astro: 6.4.8(@types/node@25.9.5)(rollup@4.62.4)(yaml@2.9.0) + rehype-expressive-code: 0.41.7 + + astro-pagefind@1.8.6(astro@6.4.8(@types/node@25.9.5)(rollup@4.62.4)(yaml@2.9.0)): + dependencies: + '@pagefind/default-ui': 1.5.2 + astro: 6.4.8(@types/node@25.9.5)(rollup@4.62.4)(yaml@2.9.0) + pagefind: 1.5.2 + sirv: 3.0.2 + + astro@6.4.8(@types/node@25.9.5)(rollup@4.62.4)(yaml@2.9.0): + dependencies: + '@astrojs/compiler': 4.0.0 + '@astrojs/internal-helpers': 0.10.0 + '@astrojs/markdown-remark': 7.2.0 + '@astrojs/telemetry': 3.3.2 + '@capsizecss/unpack': 4.0.1 + '@clack/prompts': 1.7.0 + '@oslojs/encoding': 1.1.0 + '@rollup/pluginutils': 5.4.0(rollup@4.62.4) + aria-query: 5.3.2 + axobject-query: 4.1.0 + ci-info: 4.4.0 + clsx: 2.1.1 + common-ancestor-path: 2.0.0 + cookie: 1.1.1 + devalue: 5.9.0 + diff: 8.0.4 + dset: 3.1.4 + es-module-lexer: 2.3.1 + esbuild: 0.27.7 + flattie: 1.1.1 + fontace: 0.4.1 + get-tsconfig: 5.0.0-beta.4 + github-slugger: 2.0.0 + html-escaper: 3.0.3 + http-cache-semantics: 4.2.0 + js-yaml: 4.3.1 + jsonc-parser: 3.3.1 + magic-string: 0.30.21 + magicast: 0.5.4 + mrmime: 2.0.1 + neotraverse: 0.6.18 + obug: 2.1.4 + p-limit: 7.3.1 + p-queue: 9.3.3 + package-manager-detector: 1.8.0 + piccolore: 0.1.3 + picomatch: 4.0.5 + rehype: 13.0.2 + semver: 7.8.5 + shiki: 4.4.3 + smol-toml: 1.7.1 + svgo: 4.0.2 + tinyclip: 0.1.15 + tinyexec: 1.3.0 + tinyglobby: 0.2.17 + ultrahtml: 1.7.0 + unifont: 0.7.4 + unist-util-visit: 5.1.0 + unstorage: 1.17.5 + vfile: 6.0.3 + vite: 7.3.6(@types/node@25.9.5)(yaml@2.9.0) + vitefu: 1.1.3(vite@7.3.6(@types/node@25.9.5)(yaml@2.9.0)) + xxhash-wasm: 1.1.0 + yargs-parser: 22.0.0 + zod: 4.4.3 + optionalDependencies: + sharp: 0.34.5 + transitivePeerDependencies: + - '@azure/app-configuration' + - '@azure/cosmos' + - '@azure/data-tables' + - '@azure/identity' + - '@azure/keyvault-secrets' + - '@azure/storage-blob' + - '@capacitor/preferences' + - '@deno/kv' + - '@netlify/blobs' + - '@planetscale/database' + - '@types/node' + - '@upstash/redis' + - '@vercel/blob' + - '@vercel/functions' + - '@vercel/kv' + - aws4fetch + - db0 + - idb-keyval + - ioredis + - jiti + - less + - lightningcss + - rollup + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + - tsx + - uploadthing + - yaml + + axobject-query@4.1.0: {} + + bail@2.0.2: {} + + baseline-browser-mapping@2.11.13: {} + + bcp-47-match@2.0.3: {} + + boolbase@1.0.0: {} + + browserslist@4.28.8: + dependencies: + baseline-browser-mapping: 2.11.13 + caniuse-lite: 1.0.30001809 + electron-to-chromium: 1.5.404 + node-releases: 2.0.53 + update-browserslist-db: 1.3.1(browserslist@4.28.8) + + caniuse-lite@1.0.30001809: {} + + ccount@2.0.1: {} + + character-entities-html4@2.1.0: {} + + character-entities-legacy@3.0.0: {} + + character-entities@2.0.2: {} + + character-reference-invalid@2.0.1: {} + + chokidar@5.0.0: + dependencies: + readdirp: 5.1.1 + + ci-info@4.4.0: {} + + cli-cursor@5.0.0: + dependencies: + restore-cursor: 5.1.0 + + cli-truncate@5.2.0: + dependencies: + slice-ansi: 8.0.0 + string-width: 8.2.2 + + clsx@2.1.1: {} + + collapse-white-space@2.1.0: {} + + colorette@2.0.20: {} + + comma-separated-tokens@2.0.3: {} + + commander@11.1.0: {} + + commander@14.0.3: {} + + common-ancestor-path@2.0.0: {} + + convert-source-map@2.0.0: {} + + cookie-es@1.2.3: {} + + cookie@1.1.1: {} + + crossws@0.3.5: + dependencies: + uncrypto: 0.1.3 + + css-select@5.2.2: + dependencies: + boolbase: 1.0.0 + css-what: 6.2.2 + domhandler: 5.0.3 + domutils: 3.2.2 + nth-check: 2.1.1 + + css-selector-parser@3.3.0: {} + + css-tree@2.2.1: + dependencies: + mdn-data: 2.0.28 + source-map-js: 1.2.1 + + css-tree@3.2.1: + dependencies: + mdn-data: 2.27.1 + source-map-js: 1.2.1 + + css-what@6.2.2: {} + + cssesc@3.0.0: {} + + csso@5.0.5: + dependencies: + css-tree: 2.2.1 + + csstype@3.2.3: {} + + debug@4.4.3: + dependencies: + ms: 2.1.3 + + decode-named-character-reference@1.3.0: + dependencies: + character-entities: 2.0.2 + + defu@6.1.7: {} + + dequal@2.0.3: {} + + destr@2.0.5: {} + + detect-libc@2.1.2: {} + + devalue@5.9.0: {} + + devlop@1.1.0: + dependencies: + dequal: 2.0.3 + + diff@8.0.4: {} + + direction@2.0.1: {} + + dom-serializer@2.0.0: + dependencies: + domelementtype: 2.3.0 + domhandler: 5.0.3 + entities: 4.5.0 + + domelementtype@2.3.0: {} + + domhandler@5.0.3: + dependencies: + domelementtype: 2.3.0 + + domutils@3.2.2: + dependencies: + dom-serializer: 2.0.0 + domelementtype: 2.3.0 + domhandler: 5.0.3 + + dset@3.1.4: {} + + electron-to-chromium@1.5.404: {} + + emoji-regex@10.6.0: {} + + entities@4.5.0: {} + + entities@6.0.1: {} + + environment@1.1.0: {} + + es-module-lexer@2.3.1: {} + + esast-util-from-estree@2.0.0: + dependencies: + '@types/estree-jsx': 1.0.5 + devlop: 1.1.0 + estree-util-visit: 2.0.0 + unist-util-position-from-estree: 2.0.0 + + esast-util-from-js@2.0.1: + dependencies: + '@types/estree-jsx': 1.0.5 + acorn: 8.18.0 + esast-util-from-estree: 2.0.0 + vfile-message: 4.0.3 + + esbuild@0.27.7: + optionalDependencies: + '@esbuild/aix-ppc64': 0.27.7 + '@esbuild/android-arm': 0.27.7 + '@esbuild/android-arm64': 0.27.7 + '@esbuild/android-x64': 0.27.7 + '@esbuild/darwin-arm64': 0.27.7 + '@esbuild/darwin-x64': 0.27.7 + '@esbuild/freebsd-arm64': 0.27.7 + '@esbuild/freebsd-x64': 0.27.7 + '@esbuild/linux-arm': 0.27.7 + '@esbuild/linux-arm64': 0.27.7 + '@esbuild/linux-ia32': 0.27.7 + '@esbuild/linux-loong64': 0.27.7 + '@esbuild/linux-mips64el': 0.27.7 + '@esbuild/linux-ppc64': 0.27.7 + '@esbuild/linux-riscv64': 0.27.7 + '@esbuild/linux-s390x': 0.27.7 + '@esbuild/linux-x64': 0.27.7 + '@esbuild/netbsd-arm64': 0.27.7 + '@esbuild/netbsd-x64': 0.27.7 + '@esbuild/openbsd-arm64': 0.27.7 + '@esbuild/openbsd-x64': 0.27.7 + '@esbuild/openharmony-arm64': 0.27.7 + '@esbuild/sunos-x64': 0.27.7 + '@esbuild/win32-arm64': 0.27.7 + '@esbuild/win32-ia32': 0.27.7 + '@esbuild/win32-x64': 0.27.7 + + esbuild@0.28.2: + optionalDependencies: + '@esbuild/aix-ppc64': 0.28.2 + '@esbuild/android-arm': 0.28.2 + '@esbuild/android-arm64': 0.28.2 + '@esbuild/android-x64': 0.28.2 + '@esbuild/darwin-arm64': 0.28.2 + '@esbuild/darwin-x64': 0.28.2 + '@esbuild/freebsd-arm64': 0.28.2 + '@esbuild/freebsd-x64': 0.28.2 + '@esbuild/linux-arm': 0.28.2 + '@esbuild/linux-arm64': 0.28.2 + '@esbuild/linux-ia32': 0.28.2 + '@esbuild/linux-loong64': 0.28.2 + '@esbuild/linux-mips64el': 0.28.2 + '@esbuild/linux-ppc64': 0.28.2 + '@esbuild/linux-riscv64': 0.28.2 + '@esbuild/linux-s390x': 0.28.2 + '@esbuild/linux-x64': 0.28.2 + '@esbuild/netbsd-arm64': 0.28.2 + '@esbuild/netbsd-x64': 0.28.2 + '@esbuild/openbsd-arm64': 0.28.2 + '@esbuild/openbsd-x64': 0.28.2 + '@esbuild/openharmony-arm64': 0.28.2 + '@esbuild/sunos-x64': 0.28.2 + '@esbuild/win32-arm64': 0.28.2 + '@esbuild/win32-ia32': 0.28.2 + '@esbuild/win32-x64': 0.28.2 + + escalade@3.2.0: {} + + escape-string-regexp@5.0.0: {} + + estree-util-attach-comments@3.0.0: + dependencies: + '@types/estree': 1.0.9 + + estree-util-build-jsx@3.0.1: + dependencies: + '@types/estree-jsx': 1.0.5 + devlop: 1.1.0 + estree-util-is-identifier-name: 3.0.0 + estree-walker: 3.0.3 + + estree-util-is-identifier-name@3.0.0: {} + + estree-util-scope@1.0.0: + dependencies: + '@types/estree': 1.0.9 + devlop: 1.1.0 + + estree-util-to-js@2.0.0: + dependencies: + '@types/estree-jsx': 1.0.5 + astring: 1.9.0 + source-map: 0.7.6 + + estree-util-visit@2.0.0: + dependencies: + '@types/estree-jsx': 1.0.5 + '@types/unist': 3.0.3 + + estree-walker@2.0.2: {} + + estree-walker@3.0.3: + dependencies: + '@types/estree': 1.0.9 + + eventemitter3@5.0.4: {} + + expressive-code@0.41.7: + dependencies: + '@expressive-code/core': 0.41.7 + '@expressive-code/plugin-frames': 0.41.7 + '@expressive-code/plugin-shiki': 0.41.7 + '@expressive-code/plugin-text-markers': 0.41.7 + + extend@3.0.2: {} + + fast-string-truncated-width@3.0.3: {} + + fast-string-width@3.0.2: + dependencies: + fast-string-truncated-width: 3.0.3 + + fast-wrap-ansi@0.2.2: + dependencies: + fast-string-width: 3.0.2 + + fast-xml-builder@1.3.0: + dependencies: + path-expression-matcher: 1.6.2 + xml-naming: 0.3.0 + + fast-xml-parser@5.10.1: + dependencies: + '@nodable/entities': 3.0.0 + fast-xml-builder: 1.3.0 + is-unsafe: 2.0.0 + path-expression-matcher: 1.6.2 + strnum: 2.4.1 + xml-naming: 0.3.0 + + fdir@6.5.0(picomatch@4.0.5): + optionalDependencies: + picomatch: 4.0.5 + + flattie@1.1.1: {} + + fontace@0.4.1: + dependencies: + fontkitten: 1.0.3 + + fontkitten@1.0.3: + dependencies: + tiny-inflate: 1.0.3 + + fsevents@2.3.3: + optional: true + + gensync@1.0.0-beta.2: {} + + get-east-asian-width@1.6.0: {} + + get-tsconfig@5.0.0-beta.4: + dependencies: + resolve-pkg-maps: 1.0.0 + + github-slugger@2.0.0: {} + + h3@1.15.11: + dependencies: + cookie-es: 1.2.3 + crossws: 0.3.5 + defu: 6.1.7 + destr: 2.0.5 + iron-webcrypto: 1.2.1 + node-mock-http: 1.0.5 + radix3: 1.1.2 + ufo: 1.6.4 + uncrypto: 0.1.3 + + hast-util-from-html@2.0.3: + dependencies: + '@types/hast': 3.0.5 + devlop: 1.1.0 + hast-util-from-parse5: 8.0.3 + parse5: 7.3.0 + vfile: 6.0.3 + vfile-message: 4.0.3 + + hast-util-from-parse5@8.0.3: + dependencies: + '@types/hast': 3.0.5 + '@types/unist': 3.0.3 + devlop: 1.1.0 + hastscript: 9.0.1 + property-information: 7.2.0 + vfile: 6.0.3 + vfile-location: 5.0.3 + web-namespaces: 2.0.1 + + hast-util-has-property@3.0.0: + dependencies: + '@types/hast': 3.0.5 + + hast-util-is-element@3.0.0: + dependencies: + '@types/hast': 3.0.5 + + hast-util-parse-selector@4.0.0: + dependencies: + '@types/hast': 3.0.5 + + hast-util-raw@9.1.0: + dependencies: + '@types/hast': 3.0.5 + '@types/unist': 3.0.3 + '@ungap/structured-clone': 1.3.3 + hast-util-from-parse5: 8.0.3 + hast-util-to-parse5: 8.0.1 + html-void-elements: 3.0.0 + mdast-util-to-hast: 13.2.1 + parse5: 7.3.0 + unist-util-position: 5.0.0 + unist-util-visit: 5.1.0 + vfile: 6.0.3 + web-namespaces: 2.0.1 + zwitch: 2.0.4 + + hast-util-select@6.0.4: + dependencies: + '@types/hast': 3.0.5 + '@types/unist': 3.0.3 + bcp-47-match: 2.0.3 + comma-separated-tokens: 2.0.3 + css-selector-parser: 3.3.0 + devlop: 1.1.0 + direction: 2.0.1 + hast-util-has-property: 3.0.0 + hast-util-to-string: 3.0.1 + hast-util-whitespace: 3.0.0 + nth-check: 2.1.1 + property-information: 7.2.0 + space-separated-tokens: 2.0.2 + unist-util-visit: 5.1.0 + zwitch: 2.0.4 + + hast-util-to-estree@3.1.3: + dependencies: + '@types/estree': 1.0.9 + '@types/estree-jsx': 1.0.5 + '@types/hast': 3.0.5 + comma-separated-tokens: 2.0.3 + devlop: 1.1.0 + estree-util-attach-comments: 3.0.0 + estree-util-is-identifier-name: 3.0.0 + hast-util-whitespace: 3.0.0 + mdast-util-mdx-expression: 2.0.1 + mdast-util-mdx-jsx: 3.2.0 + mdast-util-mdxjs-esm: 2.0.1 + property-information: 7.2.0 + space-separated-tokens: 2.0.2 + style-to-js: 1.1.21 + unist-util-position: 5.0.0 + zwitch: 2.0.4 + transitivePeerDependencies: + - supports-color + + hast-util-to-html@9.0.5: + dependencies: + '@types/hast': 3.0.5 + '@types/unist': 3.0.3 + ccount: 2.0.1 + comma-separated-tokens: 2.0.3 + hast-util-whitespace: 3.0.0 + html-void-elements: 3.0.0 + mdast-util-to-hast: 13.2.1 + property-information: 7.2.0 + space-separated-tokens: 2.0.2 + stringify-entities: 4.0.4 + zwitch: 2.0.4 + + hast-util-to-jsx-runtime@2.3.6: + dependencies: + '@types/estree': 1.0.9 + '@types/hast': 3.0.5 + '@types/unist': 3.0.3 + comma-separated-tokens: 2.0.3 + devlop: 1.1.0 + estree-util-is-identifier-name: 3.0.0 + hast-util-whitespace: 3.0.0 + mdast-util-mdx-expression: 2.0.1 + mdast-util-mdx-jsx: 3.2.0 + mdast-util-mdxjs-esm: 2.0.1 + property-information: 7.2.0 + space-separated-tokens: 2.0.2 + style-to-js: 1.1.21 + unist-util-position: 5.0.0 + vfile-message: 4.0.3 + transitivePeerDependencies: + - supports-color + + hast-util-to-parse5@8.0.1: + dependencies: + '@types/hast': 3.0.5 + comma-separated-tokens: 2.0.3 + devlop: 1.1.0 + property-information: 7.2.0 + space-separated-tokens: 2.0.2 + web-namespaces: 2.0.1 + zwitch: 2.0.4 + + hast-util-to-string@3.0.1: + dependencies: + '@types/hast': 3.0.5 + + hast-util-to-text@4.0.2: + dependencies: + '@types/hast': 3.0.5 + '@types/unist': 3.0.3 + hast-util-is-element: 3.0.0 + unist-util-find-after: 5.0.0 + + hast-util-whitespace@3.0.0: + dependencies: + '@types/hast': 3.0.5 + + hastscript@9.0.1: + dependencies: + '@types/hast': 3.0.5 + comma-separated-tokens: 2.0.3 + hast-util-parse-selector: 4.0.0 + property-information: 7.2.0 + space-separated-tokens: 2.0.2 + + html-escaper@3.0.3: {} + + html-void-elements@3.0.0: {} + + http-cache-semantics@4.2.0: {} + + husky@9.1.7: {} + + inline-style-parser@0.2.7: {} + + iron-webcrypto@1.2.1: {} + + is-alphabetical@2.0.1: {} + + is-alphanumerical@2.0.1: + dependencies: + is-alphabetical: 2.0.1 + is-decimal: 2.0.1 + + is-decimal@2.0.1: {} + + is-docker@3.0.0: {} + + is-docker@4.0.0: {} + + is-fullwidth-code-point@5.1.0: + dependencies: + get-east-asian-width: 1.6.0 + + is-hexadecimal@2.0.1: {} + + is-inside-container@1.0.0: + dependencies: + is-docker: 3.0.0 + + is-plain-obj@4.1.0: {} + + is-unsafe@2.0.0: {} + + is-wsl@3.1.1: + dependencies: + is-inside-container: 1.0.0 + + js-tokens@4.0.0: {} + + js-yaml@4.3.1: + dependencies: + argparse: 2.0.1 + + jsesc@3.1.0: {} + + json5@2.2.3: {} + + jsonc-parser@3.3.1: {} + + lint-staged@16.4.0: + dependencies: + commander: 14.0.3 + listr2: 9.0.5 + picomatch: 4.0.5 + string-argv: 0.3.2 + tinyexec: 1.3.0 + yaml: 2.9.0 + + listr2@9.0.5: + dependencies: + cli-truncate: 5.2.0 + colorette: 2.0.20 + eventemitter3: 5.0.4 + log-update: 6.1.0 + rfdc: 1.4.1 + wrap-ansi: 9.0.2 + + log-update@6.1.0: + dependencies: + ansi-escapes: 7.3.0 + cli-cursor: 5.0.0 + slice-ansi: 7.1.2 + strip-ansi: 7.2.0 + wrap-ansi: 9.0.2 + + longest-streak@3.1.0: {} + + lru-cache@11.5.2: {} + + lru-cache@5.1.1: + dependencies: + yallist: 3.1.1 + + magic-string@0.30.21: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + + magicast@0.5.4: + dependencies: + '@babel/parser': 7.29.8 + '@babel/types': 7.29.8 + source-map-js: 1.2.1 + + markdown-extensions@2.0.0: {} + + markdown-table@3.0.4: {} + + mdast-util-definitions@6.0.0: + dependencies: + '@types/mdast': 4.0.4 + '@types/unist': 3.0.3 + unist-util-visit: 5.1.0 + + mdast-util-directive@3.1.0: + dependencies: + '@types/mdast': 4.0.4 + '@types/unist': 3.0.3 + ccount: 2.0.1 + devlop: 1.1.0 + mdast-util-from-markdown: 2.0.3 + mdast-util-to-markdown: 2.1.2 + parse-entities: 4.0.2 + stringify-entities: 4.0.4 + unist-util-visit-parents: 6.0.2 + transitivePeerDependencies: + - supports-color + + mdast-util-find-and-replace@3.0.2: + dependencies: + '@types/mdast': 4.0.4 + escape-string-regexp: 5.0.0 + unist-util-is: 6.0.1 + unist-util-visit-parents: 6.0.2 + + mdast-util-from-markdown@2.0.3: + dependencies: + '@types/mdast': 4.0.4 + '@types/unist': 3.0.3 + decode-named-character-reference: 1.3.0 + devlop: 1.1.0 + mdast-util-to-string: 4.0.0 + micromark: 4.0.2 + micromark-util-decode-numeric-character-reference: 2.0.2 + micromark-util-decode-string: 2.0.1 + micromark-util-normalize-identifier: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + unist-util-stringify-position: 4.0.0 + transitivePeerDependencies: + - supports-color + + mdast-util-gfm-autolink-literal@2.0.1: + dependencies: + '@types/mdast': 4.0.4 + ccount: 2.0.1 + devlop: 1.1.0 + mdast-util-find-and-replace: 3.0.2 + micromark-util-character: 2.1.1 + + mdast-util-gfm-footnote@2.1.0: + dependencies: + '@types/mdast': 4.0.4 + devlop: 1.1.0 + mdast-util-from-markdown: 2.0.3 + mdast-util-to-markdown: 2.1.2 + micromark-util-normalize-identifier: 2.0.1 + transitivePeerDependencies: + - supports-color + + mdast-util-gfm-strikethrough@2.0.0: + dependencies: + '@types/mdast': 4.0.4 + mdast-util-from-markdown: 2.0.3 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-gfm-table@2.0.0: + dependencies: + '@types/mdast': 4.0.4 + devlop: 1.1.0 + markdown-table: 3.0.4 + mdast-util-from-markdown: 2.0.3 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-gfm-task-list-item@2.0.0: + dependencies: + '@types/mdast': 4.0.4 + devlop: 1.1.0 + mdast-util-from-markdown: 2.0.3 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-gfm@3.1.0: + dependencies: + mdast-util-from-markdown: 2.0.3 + mdast-util-gfm-autolink-literal: 2.0.1 + mdast-util-gfm-footnote: 2.1.0 + mdast-util-gfm-strikethrough: 2.0.0 + mdast-util-gfm-table: 2.0.0 + mdast-util-gfm-task-list-item: 2.0.0 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-mdx-expression@2.0.1: + dependencies: + '@types/estree-jsx': 1.0.5 + '@types/hast': 3.0.5 + '@types/mdast': 4.0.4 + devlop: 1.1.0 + mdast-util-from-markdown: 2.0.3 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-mdx-jsx@3.2.0: + dependencies: + '@types/estree-jsx': 1.0.5 + '@types/hast': 3.0.5 + '@types/mdast': 4.0.4 + '@types/unist': 3.0.3 + ccount: 2.0.1 + devlop: 1.1.0 + mdast-util-from-markdown: 2.0.3 + mdast-util-to-markdown: 2.1.2 + parse-entities: 4.0.2 + stringify-entities: 4.0.4 + unist-util-stringify-position: 4.0.0 + vfile-message: 4.0.3 + transitivePeerDependencies: + - supports-color + + mdast-util-mdx@3.0.0: + dependencies: + mdast-util-from-markdown: 2.0.3 + mdast-util-mdx-expression: 2.0.1 + mdast-util-mdx-jsx: 3.2.0 + mdast-util-mdxjs-esm: 2.0.1 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-mdxjs-esm@2.0.1: + dependencies: + '@types/estree-jsx': 1.0.5 + '@types/hast': 3.0.5 + '@types/mdast': 4.0.4 + devlop: 1.1.0 + mdast-util-from-markdown: 2.0.3 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-phrasing@4.1.0: + dependencies: + '@types/mdast': 4.0.4 + unist-util-is: 6.0.1 + + mdast-util-to-hast@13.2.1: + dependencies: + '@types/hast': 3.0.5 + '@types/mdast': 4.0.4 + '@ungap/structured-clone': 1.3.3 + devlop: 1.1.0 + micromark-util-sanitize-uri: 2.0.1 + trim-lines: 3.0.1 + unist-util-position: 5.0.0 + unist-util-visit: 5.1.0 + vfile: 6.0.3 + + mdast-util-to-markdown@2.1.2: + dependencies: + '@types/mdast': 4.0.4 + '@types/unist': 3.0.3 + longest-streak: 3.1.0 + mdast-util-phrasing: 4.1.0 + mdast-util-to-string: 4.0.0 + micromark-util-classify-character: 2.0.1 + micromark-util-decode-string: 2.0.1 + unist-util-visit: 5.1.0 + zwitch: 2.0.4 + + mdast-util-to-string@4.0.0: + dependencies: + '@types/mdast': 4.0.4 + + mdn-data@2.0.28: {} + + mdn-data@2.27.1: {} + + micromark-core-commonmark@2.0.3: + dependencies: + decode-named-character-reference: 1.3.0 + devlop: 1.1.0 + micromark-factory-destination: 2.0.1 + micromark-factory-label: 2.0.1 + micromark-factory-space: 2.0.1 + micromark-factory-title: 2.0.1 + micromark-factory-whitespace: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-chunked: 2.0.1 + micromark-util-classify-character: 2.0.1 + micromark-util-html-tag-name: 2.0.1 + micromark-util-normalize-identifier: 2.0.1 + micromark-util-resolve-all: 2.0.1 + micromark-util-subtokenize: 2.1.0 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-directive@4.0.0: + dependencies: + devlop: 1.1.0 + micromark-factory-space: 2.0.1 + micromark-factory-whitespace: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + parse-entities: 4.0.2 + + micromark-extension-gfm-autolink-literal@2.1.0: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-sanitize-uri: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-gfm-footnote@2.1.0: + dependencies: + devlop: 1.1.0 + micromark-core-commonmark: 2.0.3 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-normalize-identifier: 2.0.1 + micromark-util-sanitize-uri: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-gfm-strikethrough@2.1.0: + dependencies: + devlop: 1.1.0 + micromark-util-chunked: 2.0.1 + micromark-util-classify-character: 2.0.1 + micromark-util-resolve-all: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-gfm-table@2.1.1: + dependencies: + devlop: 1.1.0 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-gfm-tagfilter@2.0.0: + dependencies: + micromark-util-types: 2.0.2 + + micromark-extension-gfm-task-list-item@2.1.0: + dependencies: + devlop: 1.1.0 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-gfm@3.0.0: + dependencies: + micromark-extension-gfm-autolink-literal: 2.1.0 + micromark-extension-gfm-footnote: 2.1.0 + micromark-extension-gfm-strikethrough: 2.1.0 + micromark-extension-gfm-table: 2.1.1 + micromark-extension-gfm-tagfilter: 2.0.0 + micromark-extension-gfm-task-list-item: 2.1.0 + micromark-util-combine-extensions: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-mdx-expression@3.0.1: + dependencies: + '@types/estree': 1.0.9 + devlop: 1.1.0 + micromark-factory-mdx-expression: 2.0.3 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-events-to-acorn: 2.0.3 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-mdx-jsx@3.0.2: + dependencies: + '@types/estree': 1.0.9 + devlop: 1.1.0 + estree-util-is-identifier-name: 3.0.0 + micromark-factory-mdx-expression: 2.0.3 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-events-to-acorn: 2.0.3 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + vfile-message: 4.0.3 + + micromark-extension-mdx-md@2.0.0: + dependencies: + micromark-util-types: 2.0.2 + + micromark-extension-mdxjs-esm@3.0.0: + dependencies: + '@types/estree': 1.0.9 + devlop: 1.1.0 + micromark-core-commonmark: 2.0.3 + micromark-util-character: 2.1.1 + micromark-util-events-to-acorn: 2.0.3 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + unist-util-position-from-estree: 2.0.0 + vfile-message: 4.0.3 + + micromark-extension-mdxjs@3.0.0: + dependencies: + acorn: 8.18.0 + acorn-jsx: 5.3.2(acorn@8.18.0) + micromark-extension-mdx-expression: 3.0.1 + micromark-extension-mdx-jsx: 3.0.2 + micromark-extension-mdx-md: 2.0.0 + micromark-extension-mdxjs-esm: 3.0.0 + micromark-util-combine-extensions: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-factory-destination@2.0.1: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-factory-label@2.0.1: + dependencies: + devlop: 1.1.0 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-factory-mdx-expression@2.0.3: + dependencies: + '@types/estree': 1.0.9 + devlop: 1.1.0 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-events-to-acorn: 2.0.3 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + unist-util-position-from-estree: 2.0.0 + vfile-message: 4.0.3 + + micromark-factory-space@2.0.1: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-types: 2.0.2 + + micromark-factory-title@2.0.1: + dependencies: + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-factory-whitespace@2.0.1: + dependencies: + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-util-character@2.1.1: + dependencies: + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-util-chunked@2.0.1: + dependencies: + micromark-util-symbol: 2.0.1 + + micromark-util-classify-character@2.0.1: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-util-combine-extensions@2.0.1: + dependencies: + micromark-util-chunked: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-util-decode-numeric-character-reference@2.0.2: + dependencies: + micromark-util-symbol: 2.0.1 + + micromark-util-decode-string@2.0.1: + dependencies: + decode-named-character-reference: 1.3.0 + micromark-util-character: 2.1.1 + micromark-util-decode-numeric-character-reference: 2.0.2 + micromark-util-symbol: 2.0.1 + + micromark-util-encode@2.0.1: {} + + micromark-util-events-to-acorn@2.0.3: + dependencies: + '@types/estree': 1.0.9 + '@types/unist': 3.0.3 + devlop: 1.1.0 + estree-util-visit: 2.0.0 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + vfile-message: 4.0.3 + + micromark-util-html-tag-name@2.0.1: {} + + micromark-util-normalize-identifier@2.0.1: + dependencies: + micromark-util-symbol: 2.0.1 + + micromark-util-resolve-all@2.0.1: + dependencies: + micromark-util-types: 2.0.2 + + micromark-util-sanitize-uri@2.0.1: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-encode: 2.0.1 + micromark-util-symbol: 2.0.1 + + micromark-util-subtokenize@2.1.0: + dependencies: + devlop: 1.1.0 + micromark-util-chunked: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-util-symbol@2.0.1: {} + + micromark-util-types@2.0.2: {} + + micromark@4.0.2: + dependencies: + '@types/debug': 4.1.13 + debug: 4.4.3 + decode-named-character-reference: 1.3.0 + devlop: 1.1.0 + micromark-core-commonmark: 2.0.3 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-chunked: 2.0.1 + micromark-util-combine-extensions: 2.0.1 + micromark-util-decode-numeric-character-reference: 2.0.2 + micromark-util-encode: 2.0.1 + micromark-util-normalize-identifier: 2.0.1 + micromark-util-resolve-all: 2.0.1 + micromark-util-sanitize-uri: 2.0.1 + micromark-util-subtokenize: 2.1.0 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + transitivePeerDependencies: + - supports-color + + mimic-function@5.0.1: {} + + mrmime@2.0.1: {} + + ms@2.1.3: {} + + nanoid@3.3.18: {} + + neotraverse@0.6.18: {} + + nlcst-to-string@4.0.0: + dependencies: + '@types/nlcst': 2.0.3 + + node-fetch-native@1.6.7: {} + + node-mock-http@1.0.5: {} + + node-releases@2.0.53: {} + + normalize-path@3.0.0: {} + + nth-check@2.1.1: + dependencies: + boolbase: 1.0.0 + + obug@2.1.4: {} + + ofetch@1.5.1: + dependencies: + destr: 2.0.5 + node-fetch-native: 1.6.7 + ufo: 1.6.4 + + ohash@2.0.11: {} + + onetime@7.0.0: + dependencies: + mimic-function: 5.0.1 + + oniguruma-parser@0.12.2: {} + + oniguruma-to-es@4.3.6: + dependencies: + oniguruma-parser: 0.12.2 + regex: 6.1.0 + regex-recursion: 6.0.2 + + oxfmt@0.45.0: + dependencies: + tinypool: 2.1.0 + optionalDependencies: + '@oxfmt/binding-android-arm-eabi': 0.45.0 + '@oxfmt/binding-android-arm64': 0.45.0 + '@oxfmt/binding-darwin-arm64': 0.45.0 + '@oxfmt/binding-darwin-x64': 0.45.0 + '@oxfmt/binding-freebsd-x64': 0.45.0 + '@oxfmt/binding-linux-arm-gnueabihf': 0.45.0 + '@oxfmt/binding-linux-arm-musleabihf': 0.45.0 + '@oxfmt/binding-linux-arm64-gnu': 0.45.0 + '@oxfmt/binding-linux-arm64-musl': 0.45.0 + '@oxfmt/binding-linux-ppc64-gnu': 0.45.0 + '@oxfmt/binding-linux-riscv64-gnu': 0.45.0 + '@oxfmt/binding-linux-riscv64-musl': 0.45.0 + '@oxfmt/binding-linux-s390x-gnu': 0.45.0 + '@oxfmt/binding-linux-x64-gnu': 0.45.0 + '@oxfmt/binding-linux-x64-musl': 0.45.0 + '@oxfmt/binding-openharmony-arm64': 0.45.0 + '@oxfmt/binding-win32-arm64-msvc': 0.45.0 + '@oxfmt/binding-win32-ia32-msvc': 0.45.0 + '@oxfmt/binding-win32-x64-msvc': 0.45.0 + + oxlint@1.78.0: + optionalDependencies: + '@oxlint/binding-android-arm-eabi': 1.78.0 + '@oxlint/binding-android-arm64': 1.78.0 + '@oxlint/binding-darwin-arm64': 1.78.0 + '@oxlint/binding-darwin-x64': 1.78.0 + '@oxlint/binding-freebsd-x64': 1.78.0 + '@oxlint/binding-linux-arm-gnueabihf': 1.78.0 + '@oxlint/binding-linux-arm-musleabihf': 1.78.0 + '@oxlint/binding-linux-arm64-gnu': 1.78.0 + '@oxlint/binding-linux-arm64-musl': 1.78.0 + '@oxlint/binding-linux-ppc64-gnu': 1.78.0 + '@oxlint/binding-linux-riscv64-gnu': 1.78.0 + '@oxlint/binding-linux-riscv64-musl': 1.78.0 + '@oxlint/binding-linux-s390x-gnu': 1.78.0 + '@oxlint/binding-linux-x64-gnu': 1.78.0 + '@oxlint/binding-linux-x64-musl': 1.78.0 + '@oxlint/binding-openharmony-arm64': 1.78.0 + '@oxlint/binding-win32-arm64-msvc': 1.78.0 + '@oxlint/binding-win32-ia32-msvc': 1.78.0 + '@oxlint/binding-win32-x64-msvc': 1.78.0 + + p-limit@7.3.1: + dependencies: + yocto-queue: 1.2.2 + + p-queue@9.3.3: + dependencies: + eventemitter3: 5.0.4 + p-timeout: 7.0.1 + + p-timeout@7.0.1: {} + + package-manager-detector@1.8.0: {} + + pagefind@1.5.2: + optionalDependencies: + '@pagefind/darwin-arm64': 1.5.2 + '@pagefind/darwin-x64': 1.5.2 + '@pagefind/freebsd-x64': 1.5.2 + '@pagefind/linux-arm64': 1.5.2 + '@pagefind/linux-x64': 1.5.2 + '@pagefind/windows-arm64': 1.5.2 + '@pagefind/windows-x64': 1.5.2 + + parse-entities@4.0.2: + dependencies: + '@types/unist': 2.0.11 + character-entities-legacy: 3.0.0 + character-reference-invalid: 2.0.1 + decode-named-character-reference: 1.3.0 + is-alphanumerical: 2.0.1 + is-decimal: 2.0.1 + is-hexadecimal: 2.0.1 + + parse-latin@7.0.0: + dependencies: + '@types/nlcst': 2.0.3 + '@types/unist': 3.0.3 + nlcst-to-string: 4.0.0 + unist-util-modify-children: 4.0.0 + unist-util-visit-children: 3.0.0 + vfile: 6.0.3 + + parse5@7.3.0: + dependencies: + entities: 6.0.1 + + path-expression-matcher@1.6.2: {} + + piccolore@0.1.3: {} + + picocolors@1.1.1: {} + + picomatch@2.3.2: {} + + picomatch@4.0.5: {} + + postcss-nested@6.2.0(postcss@8.5.26): + dependencies: + postcss: 8.5.26 + postcss-selector-parser: 6.1.4 + + postcss-selector-parser@6.1.4: + dependencies: + cssesc: 3.0.0 + util-deprecate: 1.0.2 + + postcss@8.5.26: + dependencies: + nanoid: 3.3.18 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + prismjs@1.30.0: {} + + property-information@7.2.0: {} + + radix3@1.1.2: {} + + react-dom@19.2.5(react@19.2.5): + dependencies: + react: 19.2.5 + scheduler: 0.27.0 + + react-refresh@0.18.0: {} + + react@19.2.5: {} + + readdirp@5.1.1: {} + + reading-time@1.5.0: {} + + recma-build-jsx@1.0.0: + dependencies: + '@types/estree': 1.0.9 + estree-util-build-jsx: 3.0.1 + vfile: 6.0.3 + + recma-jsx@1.0.1(acorn@8.18.0): + dependencies: + acorn: 8.18.0 + acorn-jsx: 5.3.2(acorn@8.18.0) + estree-util-to-js: 2.0.0 + recma-parse: 1.0.0 + recma-stringify: 1.0.0 + unified: 11.0.5 + + recma-parse@1.0.0: + dependencies: + '@types/estree': 1.0.9 + esast-util-from-js: 2.0.1 + unified: 11.0.5 + vfile: 6.0.3 + + recma-stringify@1.0.0: + dependencies: + '@types/estree': 1.0.9 + estree-util-to-js: 2.0.0 + unified: 11.0.5 + vfile: 6.0.3 + + regex-recursion@6.0.2: + dependencies: + regex-utilities: 2.3.0 + + regex-utilities@2.3.0: {} + + regex@6.1.0: + dependencies: + regex-utilities: 2.3.0 + + rehype-expressive-code@0.41.7: + dependencies: + expressive-code: 0.41.7 + + rehype-parse@9.0.1: + dependencies: + '@types/hast': 3.0.5 + hast-util-from-html: 2.0.3 + unified: 11.0.5 + + rehype-raw@7.0.0: + dependencies: + '@types/hast': 3.0.5 + hast-util-raw: 9.1.0 + vfile: 6.0.3 + + rehype-recma@1.0.0: + dependencies: + '@types/estree': 1.0.9 + '@types/hast': 3.0.5 + hast-util-to-estree: 3.1.3 + transitivePeerDependencies: + - supports-color + + rehype-stringify@10.0.1: + dependencies: + '@types/hast': 3.0.5 + hast-util-to-html: 9.0.5 + unified: 11.0.5 + + rehype@13.0.2: + dependencies: + '@types/hast': 3.0.5 + rehype-parse: 9.0.1 + rehype-stringify: 10.0.1 + unified: 11.0.5 + + remark-directive@4.0.0: + dependencies: + '@types/mdast': 4.0.4 + mdast-util-directive: 3.1.0 + micromark-extension-directive: 4.0.0 + unified: 11.0.5 + transitivePeerDependencies: + - supports-color + + remark-gfm@4.0.1: + dependencies: + '@types/mdast': 4.0.4 + mdast-util-gfm: 3.1.0 + micromark-extension-gfm: 3.0.0 + remark-parse: 11.0.0 + remark-stringify: 11.0.0 + unified: 11.0.5 + transitivePeerDependencies: + - supports-color + + remark-mdx@3.1.1: + dependencies: + mdast-util-mdx: 3.0.0 + micromark-extension-mdxjs: 3.0.0 + transitivePeerDependencies: + - supports-color + + remark-parse@11.0.0: + dependencies: + '@types/mdast': 4.0.4 + mdast-util-from-markdown: 2.0.3 + micromark-util-types: 2.0.2 + unified: 11.0.5 + transitivePeerDependencies: + - supports-color + + remark-rehype@11.1.2: + dependencies: + '@types/hast': 3.0.5 + '@types/mdast': 4.0.4 + mdast-util-to-hast: 13.2.1 + unified: 11.0.5 + vfile: 6.0.3 + + remark-smartypants@3.0.3: + dependencies: + retext: 9.0.0 + retext-smartypants: 6.2.0 + unified: 11.0.5 + unist-util-visit: 5.1.0 + + remark-stringify@11.0.0: + dependencies: + '@types/mdast': 4.0.4 + mdast-util-to-markdown: 2.1.2 + unified: 11.0.5 + + resolve-pkg-maps@1.0.0: {} + + restore-cursor@5.1.0: + dependencies: + onetime: 7.0.0 + signal-exit: 4.1.0 + + retext-latin@4.0.0: + dependencies: + '@types/nlcst': 2.0.3 + parse-latin: 7.0.0 + unified: 11.0.5 + + retext-smartypants@6.2.0: + dependencies: + '@types/nlcst': 2.0.3 + nlcst-to-string: 4.0.0 + unist-util-visit: 5.1.0 + + retext-stringify@4.0.0: + dependencies: + '@types/nlcst': 2.0.3 + nlcst-to-string: 4.0.0 + unified: 11.0.5 + + retext@9.0.0: + dependencies: + '@types/nlcst': 2.0.3 + retext-latin: 4.0.0 + retext-stringify: 4.0.0 + unified: 11.0.5 + + rfdc@1.4.1: {} + + rollup@4.62.4: + dependencies: + '@types/estree': 1.0.9 + optionalDependencies: + '@napi-rs/lzma-linux-x64-gnu': 1.5.1 + '@rollup/rollup-android-arm-eabi': 4.62.4 + '@rollup/rollup-android-arm64': 4.62.4 + '@rollup/rollup-darwin-arm64': 4.62.4 + '@rollup/rollup-darwin-x64': 4.62.4 + '@rollup/rollup-freebsd-arm64': 4.62.4 + '@rollup/rollup-freebsd-x64': 4.62.4 + '@rollup/rollup-linux-arm-gnueabihf': 4.62.4 + '@rollup/rollup-linux-arm-musleabihf': 4.62.4 + '@rollup/rollup-linux-arm64-gnu': 4.62.4 + '@rollup/rollup-linux-arm64-musl': 4.62.4 + '@rollup/rollup-linux-loong64-gnu': 4.62.4 + '@rollup/rollup-linux-loong64-musl': 4.62.4 + '@rollup/rollup-linux-ppc64-gnu': 4.62.4 + '@rollup/rollup-linux-ppc64-musl': 4.62.4 + '@rollup/rollup-linux-riscv64-gnu': 4.62.4 + '@rollup/rollup-linux-riscv64-musl': 4.62.4 + '@rollup/rollup-linux-s390x-gnu': 4.62.4 + '@rollup/rollup-linux-x64-gnu': 4.62.4 + '@rollup/rollup-linux-x64-musl': 4.62.4 + '@rollup/rollup-openbsd-x64': 4.62.4 + '@rollup/rollup-openharmony-arm64': 4.62.4 + '@rollup/rollup-win32-arm64-msvc': 4.62.4 + '@rollup/rollup-win32-ia32-msvc': 4.62.4 + '@rollup/rollup-win32-x64-gnu': 4.62.4 + '@rollup/rollup-win32-x64-msvc': 4.62.4 + fsevents: 2.3.3 + + sax@1.6.1: {} + + scheduler@0.27.0: {} + + semver@6.3.1: {} + + semver@7.8.5: {} + + sharp@0.34.5: + dependencies: + '@img/colour': 1.1.0 + detect-libc: 2.1.2 + semver: 7.8.5 + optionalDependencies: + '@img/sharp-darwin-arm64': 0.34.5 + '@img/sharp-darwin-x64': 0.34.5 + '@img/sharp-libvips-darwin-arm64': 1.2.4 + '@img/sharp-libvips-darwin-x64': 1.2.4 + '@img/sharp-libvips-linux-arm': 1.2.4 + '@img/sharp-libvips-linux-arm64': 1.2.4 + '@img/sharp-libvips-linux-ppc64': 1.2.4 + '@img/sharp-libvips-linux-riscv64': 1.2.4 + '@img/sharp-libvips-linux-s390x': 1.2.4 + '@img/sharp-libvips-linux-x64': 1.2.4 + '@img/sharp-libvips-linuxmusl-arm64': 1.2.4 + '@img/sharp-libvips-linuxmusl-x64': 1.2.4 + '@img/sharp-linux-arm': 0.34.5 + '@img/sharp-linux-arm64': 0.34.5 + '@img/sharp-linux-ppc64': 0.34.5 + '@img/sharp-linux-riscv64': 0.34.5 + '@img/sharp-linux-s390x': 0.34.5 + '@img/sharp-linux-x64': 0.34.5 + '@img/sharp-linuxmusl-arm64': 0.34.5 + '@img/sharp-linuxmusl-x64': 0.34.5 + '@img/sharp-wasm32': 0.34.5 + '@img/sharp-win32-arm64': 0.34.5 + '@img/sharp-win32-ia32': 0.34.5 + '@img/sharp-win32-x64': 0.34.5 + + shiki@3.23.0: + dependencies: + '@shikijs/core': 3.23.0 + '@shikijs/engine-javascript': 3.23.0 + '@shikijs/engine-oniguruma': 3.23.0 + '@shikijs/langs': 3.23.0 + '@shikijs/themes': 3.23.0 + '@shikijs/types': 3.23.0 + '@shikijs/vscode-textmate': 10.0.2 + '@types/hast': 3.0.5 + + shiki@4.4.3: + dependencies: + '@shikijs/core': 4.4.3 + '@shikijs/engine-javascript': 4.4.3 + '@shikijs/engine-oniguruma': 4.4.3 + '@shikijs/langs': 4.4.3 + '@shikijs/themes': 4.4.3 + '@shikijs/types': 4.4.3 + '@shikijs/vscode-textmate': 10.0.2 + '@types/hast': 3.0.5 + + signal-exit@4.1.0: {} + + sirv@3.0.2: + dependencies: + '@polka/url': 1.0.0-next.29 + mrmime: 2.0.1 + totalist: 3.0.1 + + sisteransi@1.0.5: {} + + sitemap@9.0.1: + dependencies: + '@types/node': 24.13.3 + '@types/sax': 1.2.7 + arg: 5.0.2 + sax: 1.6.1 + + slice-ansi@7.1.2: + dependencies: + ansi-styles: 6.2.3 + is-fullwidth-code-point: 5.1.0 + + slice-ansi@8.0.0: + dependencies: + ansi-styles: 6.2.3 + is-fullwidth-code-point: 5.1.0 + + smol-toml@1.7.1: {} + + source-map-js@1.2.1: {} + + source-map@0.7.6: {} + + space-separated-tokens@2.0.2: {} + + stream-replace-string@2.0.0: {} + + string-argv@0.3.2: {} + + string-width@7.2.0: + dependencies: + emoji-regex: 10.6.0 + get-east-asian-width: 1.6.0 + strip-ansi: 7.2.0 + + string-width@8.2.2: + dependencies: + get-east-asian-width: 1.6.0 + strip-ansi: 7.2.0 + + stringify-entities@4.0.4: + dependencies: + character-entities-html4: 2.1.0 + character-entities-legacy: 3.0.0 + + strip-ansi@7.2.0: + dependencies: + ansi-regex: 6.2.2 + + strnum@2.4.1: + dependencies: + anynum: 1.0.1 + + style-to-js@1.1.21: + dependencies: + style-to-object: 1.0.14 + + style-to-object@1.0.14: + dependencies: + inline-style-parser: 0.2.7 + + svgo@4.0.2: + dependencies: + commander: 11.1.0 + css-select: 5.2.2 + css-tree: 3.2.1 + css-what: 6.2.2 + csso: 5.0.5 + picocolors: 1.1.1 + sax: 1.6.1 + + swiper@12.2.0: {} + + tiny-inflate@1.0.3: {} + + tinyclip@0.1.15: {} + + tinyexec@1.3.0: {} + + tinyglobby@0.2.17: + dependencies: + fdir: 6.5.0(picomatch@4.0.5) + picomatch: 4.0.5 + + tinypool@2.1.0: {} + + totalist@3.0.1: {} + + trim-lines@3.0.1: {} + + trough@2.2.0: {} + + tslib@2.8.1: + optional: true + + typescript@6.0.3: {} + + ufo@1.6.4: {} + + ultrahtml@1.7.0: {} + + uncrypto@0.1.3: {} + + undici-types@7.18.2: {} + + undici-types@7.24.6: {} + + unified@11.0.5: + dependencies: + '@types/unist': 3.0.3 + bail: 2.0.2 + devlop: 1.1.0 + extend: 3.0.2 + is-plain-obj: 4.1.0 + trough: 2.2.0 + vfile: 6.0.3 + + unifont@0.7.4: + dependencies: + css-tree: 3.2.1 + ofetch: 1.5.1 + ohash: 2.0.11 + + unist-util-find-after@5.0.0: + dependencies: + '@types/unist': 3.0.3 + unist-util-is: 6.0.1 + + unist-util-is@6.0.1: + dependencies: + '@types/unist': 3.0.3 + + unist-util-modify-children@4.0.0: + dependencies: + '@types/unist': 3.0.3 + array-iterate: 2.0.1 + + unist-util-position-from-estree@2.0.0: + dependencies: + '@types/unist': 3.0.3 + + unist-util-position@5.0.0: + dependencies: + '@types/unist': 3.0.3 + + unist-util-remove-position@5.0.0: + dependencies: + '@types/unist': 3.0.3 + unist-util-visit: 5.1.0 + + unist-util-stringify-position@4.0.0: + dependencies: + '@types/unist': 3.0.3 + + unist-util-visit-children@3.0.0: + dependencies: + '@types/unist': 3.0.3 + + unist-util-visit-parents@6.0.2: + dependencies: + '@types/unist': 3.0.3 + unist-util-is: 6.0.1 + + unist-util-visit@5.1.0: + dependencies: + '@types/unist': 3.0.3 + unist-util-is: 6.0.1 + unist-util-visit-parents: 6.0.2 + + unstorage@1.17.5: + dependencies: + anymatch: 3.1.3 + chokidar: 5.0.0 + destr: 2.0.5 + h3: 1.15.11 + lru-cache: 11.5.2 + node-fetch-native: 1.6.7 + ofetch: 1.5.1 + ufo: 1.6.4 + + update-browserslist-db@1.3.1(browserslist@4.28.8): + dependencies: + browserslist: 4.28.8 + escalade: 3.2.0 + picocolors: 1.1.1 + + util-deprecate@1.0.2: {} + + vfile-location@5.0.3: + dependencies: + '@types/unist': 3.0.3 + vfile: 6.0.3 + + vfile-message@4.0.3: + dependencies: + '@types/unist': 3.0.3 + unist-util-stringify-position: 4.0.0 + + vfile@6.0.3: + dependencies: + '@types/unist': 3.0.3 + vfile-message: 4.0.3 + + vite@7.3.6(@types/node@25.9.5)(yaml@2.9.0): + dependencies: + esbuild: 0.28.2 + fdir: 6.5.0(picomatch@4.0.5) + picomatch: 4.0.5 + postcss: 8.5.26 + rollup: 4.62.4 + tinyglobby: 0.2.17 + optionalDependencies: + '@types/node': 25.9.5 + fsevents: 2.3.3 + yaml: 2.9.0 + + vitefu@1.1.3(vite@7.3.6(@types/node@25.9.5)(yaml@2.9.0)): + optionalDependencies: + vite: 7.3.6(@types/node@25.9.5)(yaml@2.9.0) + + web-namespaces@2.0.1: {} + + which-pm-runs@1.1.0: {} + + wrap-ansi@9.0.2: + dependencies: + ansi-styles: 6.2.3 + string-width: 7.2.0 + strip-ansi: 7.2.0 + + xml-naming@0.3.0: {} + + xxhash-wasm@1.1.0: {} + + yallist@3.1.1: {} + + yaml@2.9.0: {} + + yargs-parser@22.0.0: {} + + yocto-queue@1.2.2: {} + + zod@4.4.3: {} + + zwitch@2.0.4: {} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml new file mode 100644 index 00000000..dbb26c82 --- /dev/null +++ b/pnpm-workspace.yaml @@ -0,0 +1,3 @@ +allowBuilds: + esbuild: true + sharp: true From b4ae068085badb9fffa29371f84299118dee30a0 Mon Sep 17 00:00:00 2001 From: Afonso Jorge Ramos Date: Wed, 12 Aug 2026 15:09:22 +0200 Subject: [PATCH 06/12] docs(v3): add extension migration guide --- src/config/sidebar.ts | 4 + .../docs/development/api-wrapper/index.md | 2 +- .../docs/development/building-a-module.md | 2 + src/content/docs/development/index.md | 3 +- .../development/migrating-v2-extensions.md | 405 ++++++++++++++++++ .../docs/legacy/customization/extensions.md | 2 +- .../docs/legacy/development/extensions.md | 2 +- 7 files changed, 416 insertions(+), 4 deletions(-) create mode 100644 src/content/docs/development/migrating-v2-extensions.md diff --git a/src/config/sidebar.ts b/src/config/sidebar.ts index b7eb50a9..ec06ac73 100644 --- a/src/config/sidebar.ts +++ b/src/config/sidebar.ts @@ -32,6 +32,10 @@ export const sidebar: SidebarItem[] = [ label: 'Building a Module', href: '/docs/development/building-a-module', }, + { + label: 'Porting a v2 Extension', + href: '/docs/development/migrating-v2-extensions', + }, { label: 'Publishing a Module', href: '/docs/development/publishing' }, { label: 'Compiling', href: '/docs/development/compiling' }, { label: 'React DevTools', href: '/docs/development/react-devtools' }, diff --git a/src/content/docs/development/api-wrapper/index.md b/src/content/docs/development/api-wrapper/index.md index c1fe3eb1..055c8c55 100644 --- a/src/content/docs/development/api-wrapper/index.md +++ b/src/content/docs/development/api-wrapper/index.md @@ -6,7 +6,7 @@ description: 🧰 Everything you need to know about the Spicetify object and API :::note This reference covers the `Spicetify` global, which both v2 extensions and v3 modules use: v3 serves the same surface through its compatibility wrapper. Pages carry a warning where v3 differs, checked against a running v3 client on Spotify 1.2.94. -If you are writing a v3 module, [Building a module](/docs/development/building-a-module) is the place to start; this is the reference for what you can call once you are there. +If you are writing a new v3 module, [Building a module](/docs/development/building-a-module) is the place to start. If you have existing v2 extension code, use the [migration guide](/docs/development/migrating-v2-extensions). This page is the reference for what you can call once you are there. ::: Making an extension from scratch can be a daunting task. Luckily, Spicetify provides a powerful API Wrapper that makes it easy to interact with Spotify's internal APIs as well as provide out-of-the-box methods to help you easily create extensions. diff --git a/src/content/docs/development/building-a-module.md b/src/content/docs/development/building-a-module.md index ef156849..fbec62e7 100644 --- a/src/content/docs/development/building-a-module.md +++ b/src/content/docs/development/building-a-module.md @@ -6,6 +6,8 @@ sidebar_position: 2 A module is the one thing you build for Spicetify v3. It can add a button, register a whole page, restyle the client, or all three. This page is the path from nothing to something running in your client; [the module standard](https://github.com/spicetify/modules/blob/main/docs/module-standard.md) is the contract it has to meet. +Already have a classic extension? Follow [Porting a v2 extension to v3](/docs/development/migrating-v2-extensions) for the lifecycle, cleanup, UI, classmap, and packaging changes. + ## Scaffold and run ```bash diff --git a/src/content/docs/development/index.md b/src/content/docs/development/index.md index e3a994e7..6a7b9b68 100644 --- a/src/content/docs/development/index.md +++ b/src/content/docs/development/index.md @@ -9,6 +9,7 @@ Everything you can add to Spotify in v3 is a module: a theme, an extension, a wh ## Building modules - [Building a module](/docs/development/building-a-module) takes you from `npm create spicetify-module` to something running in your client, through a dev loop that pushes changes in about a second. +- [Porting a v2 extension](/docs/development/migrating-v2-extensions) maps the classic IIFE, wrapper UI, listeners, styles and packaging onto the v3 lifecycle. - [Publishing a module](/docs/development/publishing) covers submitting it to the store and what CI checks before it merges. - [The API reference](/docs/development/api-wrapper) documents the `Spicetify` global your module talks to. @@ -26,4 +27,4 @@ The rules a module has to follow live in the [module standard](https://github.co ## Coming from v2 -Extensions, custom apps and Spicetify Creator are v2 concepts. They still work with v2 and their guides are in the [legacy section](/docs/legacy); in v3 all three are modules. [What changes in v3](/docs/whats-new) maps the old model onto the new one. +Extensions, custom apps and Spicetify Creator are v2 concepts. They still work with v2 and their guides are in the [legacy section](/docs/legacy); in v3 all three are modules. [What changes in v3](/docs/whats-new) maps the old model onto the new one, and the [extension migration guide](/docs/development/migrating-v2-extensions) walks through a real port. diff --git a/src/content/docs/development/migrating-v2-extensions.md b/src/content/docs/development/migrating-v2-extensions.md new file mode 100644 index 00000000..4e9a908e --- /dev/null +++ b/src/content/docs/development/migrating-v2-extensions.md @@ -0,0 +1,405 @@ +--- +title: Porting a v2 extension to v3 +description: Move a classic Spicetify extension into the v3 module lifecycle. +sidebar_position: 3 +--- + +A v2 extension does not need to be rewritten all at once. The `Spicetify` global is still available in v3, so playback, navigation, URI, storage, notification, and many menu APIs can move over largely unchanged. What must change is the code around them: how the add-on starts, owns UI and side effects, cleans up, refers to Spotify classes, and ships. + +This guide takes a classic single-file extension and turns it into a module. For a new module, start with [Building a module](/docs/development/building-a-module) instead. + +## The migration in one table + +| v2 extension | v3 module | +| --- | --- | +| One `.js` file configured in `config-xpui.ini` | A directory with `metadata.json` and JS/CSS entries | +| Self-running IIFE | An exported `load(ctx)` lifecycle entry | +| Poll until `Spicetify` is ready | Start inside the loader; bound any later DOM wait | +| Side effects last until Spotify exits | Every side effect has an unload disposer | +| `Topbar.Button` and `Playbar.Button` own their placement | stdlib registers and `registrar.placeButton()` own placement and cleanup | +| A `