diff --git a/README.md b/README.md
index 696c656..0f4187c 100644
--- a/README.md
+++ b/README.md
@@ -131,6 +131,19 @@ You can verify all of this: the code is source-available (see
[`src/auth.ts`](src/auth.ts)), and releases ship with npm provenance. Full
details and revocation steps are in [SECURITY.md](SECURITY.md).
+## Documentation
+
+Full documentation lives in [`docs/`](docs/) — a self-contained docs site
+(React Router v7 + content-collections, built from the
+[code-forge docs template](https://github.com/code-forge-io/docs)). The content
+is under [`docs/content/`](docs/content/). To run it locally:
+
+```bash
+cd docs
+pnpm install
+pnpm run dev
+```
+
## License
deploykit is **source-available** under the [Business Source License 1.1](LICENSE) (`BUSL-1.1`).
diff --git a/biome.json b/biome.json
index 9c75726..a4fe85d 100644
--- a/biome.json
+++ b/biome.json
@@ -6,7 +6,7 @@
"useIgnoreFile": true
},
"files": {
- "includes": ["**", "!!**/dist", "!!**/.claude"]
+ "includes": ["**", "!!**/dist", "!!**/.claude", "!!docs"]
},
"formatter": {
"enabled": true,
diff --git a/docs/.dockerignore b/docs/.dockerignore
new file mode 100644
index 0000000..afd6a06
--- /dev/null
+++ b/docs/.dockerignore
@@ -0,0 +1,87 @@
+node_modules
+public/build
+build
+dist
+out
+coverage
+.history
+.react-router
+
+# Other Coverage tools
+*.lcov
+
+# macOS
+.DS_*
+
+# Cache Directories and files
+.cache
+.yarn*
+.env*
+!.env.example
+.swp*
+.turbo
+.npm
+.stylelintcache
+*.tsbuildinfo
+.node_repl_history
+
+# Lock files from other package managers
+package-lock.json
+yarn.lock
+
+# General tempory files and directories
+t?mp
+.t?mp
+*.t?mp
+
+# Docusaurus cache and generated files
+.docusaurus
+
+# Output of 'npm pack'
+*.tgz
+*.tar
+*.tar.gz
+*.tar.bz2
+*.tbz
+*.zip
+
+# Runtime data
+pids
+*.pid
+*.seed
+*.pid.lock
+
+# Diagnostic reports (https://nodejs.org/api/report.html)
+report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
+
+# Logs
+logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+lerna-debug.log*
+.pnpm-debug.log*
+vite.config.ts.*
+
+# Playwright various test reports
+test-results
+playwright-report
+blob-report
+
+
+# Editors
+.idea/workspace.xml
+.idea/usage.statistics.xml
+.idea/shelf
+
+# Dont commit sqlite database files
+*.db
+*.sqlite
+*.sqlite3
+*.db-journal
+
+
+# Content collections output files
+.content-collections
+
diff --git a/docs/.env.example b/docs/.env.example
new file mode 100644
index 0000000..886f5db
--- /dev/null
+++ b/docs/.env.example
@@ -0,0 +1,4 @@
+GITHUB_OWNER="abrulic" # Your username or organization name (Optional. For edit/report an issue for the documentation page)
+GITHUB_REPO="deploykit" # Repository name (Optional. For edit/report an issue for the documentation page)
+APP_ROOT_PATH="/path/to/your/app" # Optional. Default is `process.cwd()`
+GITHUB_REPO_URL="https://github.com/abrulic/deploykit" # Optional. If you want to have GitHub icon link in the header or footer
diff --git a/docs/.github/PULL_REQUEST_TEMPLATE.md b/docs/.github/PULL_REQUEST_TEMPLATE.md
new file mode 100644
index 0000000..ef94499
--- /dev/null
+++ b/docs/.github/PULL_REQUEST_TEMPLATE.md
@@ -0,0 +1,42 @@
+Fixes #
+
+# Description
+
+Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context.
+List any dependencies that are required for this change.
+
+## Type of change
+
+Please mark relevant options with an `x` in the brackets.
+
+- [ ] Bug fix (non-breaking change which fixes an issue)
+- [ ] New feature (non-breaking change which adds functionality)
+- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
+- [ ] This change requires a documentation update
+- [ ] Algorithm update - updates algorithm documentation/questions/answers etc.
+- [ ] Other (please describe):
+
+# How Has This Been Tested?
+
+Please describe the tests that you ran to verify your changes. Provide instructions so we can reproduce. Please also
+list any relevant details for your test configuration
+
+- [ ] Integration tests
+- [ ] Unit tests
+- [ ] Manual tests
+- [ ] No tests required
+
+# Reviewer checklist
+
+Mark everything that needs to be checked before merging the PR.
+
+- [ ] Check if the UI is working as expected and is satisfactory
+- [ ] Check if the code is well documented
+- [ ] Check if the behavior is what is expected
+- [ ] Check if the code is well tested
+- [ ] Check if the code is readable and well formatted
+- [ ] Additional checks (document below if any)
+
+# Screenshots (if appropriate):
+
+# Questions (if appropriate):
diff --git a/docs/.github/workflows/ci.yml b/docs/.github/workflows/ci.yml
new file mode 100644
index 0000000..a53e919
--- /dev/null
+++ b/docs/.github/workflows/ci.yml
@@ -0,0 +1,127 @@
+name: 🚀 Validation Pipeline
+concurrency:
+ group: ${{ github.repository }}-${{ github.workflow }}-${{ github.ref }}
+ cancel-in-progress: true
+on:
+ pull_request:
+ branches: [main]
+
+permissions:
+ actions: write
+ contents: read
+ # Required to put a comment into the pull-request
+ pull-requests: write
+jobs:
+ lint:
+ name: ⬣ Biome lint
+ runs-on: ubuntu-latest
+ steps:
+ - name: ⬇️ Checkout repo
+ uses: actions/checkout@v4
+ - name: Setup Biome
+ uses: biomejs/setup-biome@v2
+ - name: Run Biome
+ run: biome ci .
+
+ validate:
+ name: 🔎 Validate
+ runs-on: ubuntu-latest
+ steps:
+ - name: 🛑 Cancel Previous Runs
+ uses: styfle/cancel-workflow-action@0.12.1
+ - name: ⬇️ Checkout repo
+ uses: actions/checkout@v4
+ - name: ⎔ Setup node
+ uses: actions/setup-node@v4
+ with:
+ node-version-file: "package.json"
+ - name: Install pnpm
+ uses: pnpm/action-setup@v4
+ - name: Install dependencies
+ run: pnpm install
+ - run: pnpm install --prefer-offline --frozen-lockfile
+ - run: pnpm exec playwright install chromium --with-deps
+ - name: 🔎 Test
+ run: pnpm run test
+ - name: ✂️ Check unused code
+ run: pnpm run check:unused
+
+ build-docs:
+ name: ⬆️ Build Docs
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ with:
+ ref: ${{ github.head_ref }}
+ fetch-depth: 0
+
+ - uses: pnpm/action-setup@v4
+ - uses: actions/setup-node@v4
+ with:
+ node-version-file: "package.json"
+ cache: pnpm
+
+ - name: Install deps
+ run: pnpm install --prefer-offline --frozen-lockfile
+
+ - name: Generate docs
+ env:
+ APP_ENV: production
+ run: pnpm run generate:docs
+
+ - name: Pack generated docs (tarball)
+ run: |
+ tar -czf docs-generated.tgz generated-docs
+ ls -lh docs-generated.tgz
+
+ - name: Upload generated docs (tgz)
+ uses: actions/upload-artifact@v4
+ with:
+ name: docs-generated-tgz
+ path: docs-generated.tgz
+ if-no-files-found: error
+
+ - name: Upload versions file
+ uses: actions/upload-artifact@v4
+ with:
+ name: docs-versions
+ path: app/utils/versions.ts
+ if-no-files-found: error
+
+ deploy-docs-pr-preview:
+ name: 🚀 Deploy Docs
+ needs: [build-docs]
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Download generated docs (tgz)
+ uses: actions/download-artifact@v4
+ with:
+ name: docs-generated-tgz
+ path: .
+
+ - name: Unpack generated docs
+ run: |
+ tar -xzf docs-generated.tgz
+ ls -laR generated-docs | sed -n '1,200p'
+ - name: Download versions file
+ uses: actions/download-artifact@v4
+ with:
+ name: docs-versions
+ path: app/utils/
+
+ - uses: forge-42/fly-deploy@v1.0.0-rc.2
+ id: deploy
+ env:
+ FLY_ORG: ${{ vars.FLY_ORG }}
+ FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }}
+ FLY_REGION: ${{ vars.FLY_REGION }}
+ with:
+ app_name: ${{ github.event.repository.name }}-${{ github.event.number }}
+ use_isolated_workspace: true
+ env_vars: |
+ APP_ENV=production
+ GITHUB_OWNER=${{ github.repository_owner }}
+ GITHUB_REPO=${{ github.event.repository.name }}
+ GITHUB_REPO_URL=https://github.com/${{ github.repository }}
diff --git a/docs/.github/workflows/pr-close.yml b/docs/.github/workflows/pr-close.yml
new file mode 100644
index 0000000..d241fda
--- /dev/null
+++ b/docs/.github/workflows/pr-close.yml
@@ -0,0 +1,25 @@
+name: 🧹 PR Close
+
+concurrency:
+ group: ${{ github.repository }}-${{ github.workflow }}-${{ github.ref }}
+ cancel-in-progress: true
+
+on:
+ pull_request:
+ branches: [main]
+ types: closed
+
+jobs:
+
+ destroy-pr-preview:
+ name: 🧹 Destroy PR Preview
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ - uses: forge-42/fly-destroy@v1.0.0-rc.2
+ id: destroy
+ env:
+ FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }}
+ FLY_ORG: ${{ vars.FLY_ORG }}
+ with:
+ app_name: ${{github.event.repository.name}}-${{ github.event.number }}
diff --git a/docs/.github/workflows/publish-documentation.yml b/docs/.github/workflows/publish-documentation.yml
new file mode 100644
index 0000000..9bb67bd
--- /dev/null
+++ b/docs/.github/workflows/publish-documentation.yml
@@ -0,0 +1,93 @@
+name: 📚🚀 Build documentation on release
+
+on:
+ release:
+ types: [published]
+ workflow_dispatch: {}
+
+concurrency:
+ group: docs-build-${{ github.workflow }}-${{ github.ref }}
+ cancel-in-progress: true
+
+jobs:
+ build-docs:
+ name: Build Docs
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+ with:
+ fetch-depth: 0
+
+ - name: Setup pnpm
+ uses: pnpm/action-setup@v4
+
+ - name: Setup Node
+ uses: actions/setup-node@v4
+ with:
+ node-version-file: "package.json"
+ cache: pnpm
+
+ - name: Install deps
+ run: pnpm install --prefer-offline --frozen-lockfile
+
+ - name: Generate docs
+ env:
+ APP_ENV: production
+ run: pnpm run generate:docs
+
+ - name: Pack generated docs (tarball)
+ run: |
+ tar -czf docs-generated.tgz generated-docs
+ ls -lh docs-generated.tgz
+ - name: Upload generated docs (tgz)
+ uses: actions/upload-artifact@v4
+ with:
+ name: docs-generated-tgz
+ path: docs-generated.tgz
+ if-no-files-found: error
+
+ - name: Upload versions file
+ uses: actions/upload-artifact@v4
+ with:
+ name: docs-versions
+ path: app/utils/versions.ts
+ if-no-files-found: error
+
+ deploy-docs-on-release:
+ needs: [build-docs]
+ name: Deploy Docs
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Download generated docs (tgz)
+ uses: actions/download-artifact@v4
+ with:
+ name: docs-generated-tgz
+ path: .
+
+ - name: Unpack generated docs into docs/
+ run: |
+ tar -xzf docs-generated.tgz
+ ls -laR generated-docs | sed -n '1,200p'
+ - name: Download versions file
+ uses: actions/download-artifact@v4
+ with:
+ name: docs-versions
+ path: docs/app/utils
+
+ - uses: forge-42/fly-deploy@v1.0.0-rc.2
+ id: deploy
+ env:
+ FLY_ORG: ${{ vars.FLY_ORG }}
+ FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }}
+ FLY_REGION: ${{ vars.FLY_REGION }}
+ with:
+ app_name: ${{ vars.FLY_APP_NAME || format('{0}-{1}', github.event.repository.name, github.ref_name) }}
+ use_isolated_workspace: true
+ env_vars: |
+ APP_ENV=production
+ GITHUB_OWNER=${{ github.repository_owner }}
+ GITHUB_REPO=${{ github.event.repository.name }}
+ GITHUB_REPO_URL=https://github.com/${{ github.repository }}
diff --git a/docs/.gitignore b/docs/.gitignore
new file mode 100644
index 0000000..df59db5
--- /dev/null
+++ b/docs/.gitignore
@@ -0,0 +1,89 @@
+node_modules
+public/build
+build
+dist
+out
+coverage
+.history
+.react-router
+
+# Other Coverage tools
+*.lcov
+
+# macOS
+.DS_*
+
+# Cache Directories and files
+.cache
+.yarn*
+.env*
+!.env.example
+.swp*
+.turbo
+.npm
+.stylelintcache
+*.tsbuildinfo
+.node_repl_history
+
+# Lock files from other package managers
+package-lock.json
+yarn.lock
+
+# General tempory files and directories
+t?mp
+.t?mp
+*.t?mp
+
+# Docusaurus cache and generated files
+.docusaurus
+
+# Output of 'npm pack'
+*.tgz
+*.tar
+*.tar.gz
+*.tar.bz2
+*.tbz
+*.zip
+
+# Runtime data
+pids
+*.pid
+*.seed
+*.pid.lock
+
+# Diagnostic reports (https://nodejs.org/api/report.html)
+report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
+
+# Logs
+logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+lerna-debug.log*
+.pnpm-debug.log*
+vite.config.ts.*
+
+# Playwright various test reports
+test-results
+playwright-report
+blob-report
+
+
+# Editors
+.idea/workspace.xml
+.idea/usage.statistics.xml
+.idea/shelf
+
+# Dont commit sqlite database files
+*.db
+*.sqlite
+*.sqlite3
+*.db-journal
+
+
+# Content collections output files
+.content-collections
+
+# Output base directory of the documentation
+generated-docs/
diff --git a/docs/.npmrc b/docs/.npmrc
new file mode 100644
index 0000000..9e299db
--- /dev/null
+++ b/docs/.npmrc
@@ -0,0 +1,6 @@
+enable-pre-post-scripts=true
+side-effects-cache=false
+save-exact=true
+audit=false
+fund=false
+progress=false
\ No newline at end of file
diff --git a/docs/.vscode/extensions.json b/docs/.vscode/extensions.json
new file mode 100644
index 0000000..06e7434
--- /dev/null
+++ b/docs/.vscode/extensions.json
@@ -0,0 +1,3 @@
+{
+ "recommendations": ["codeforge.remix-forge", "biomejs.biome"]
+}
diff --git a/docs/.vscode/settings.json b/docs/.vscode/settings.json
new file mode 100644
index 0000000..85f2f2c
--- /dev/null
+++ b/docs/.vscode/settings.json
@@ -0,0 +1,47 @@
+{
+ "editor.formatOnSave": true,
+ "editor.formatOnType": false,
+ "editor.renderWhitespace": "all",
+ "editor.rulers": [120, 160],
+ "editor.codeActionsOnSave": {
+ "source.fixAll": "always",
+ "source.organizeImports": "never",
+ "source.organizeImports.biome": "always",
+ "quickfix.biome": "always"
+ },
+ "eslint.enable": false,
+ "prettier.enable": false,
+ "editor.insertSpaces": false,
+ "editor.detectIndentation": false,
+ "editor.tabSize": 2,
+ "editor.trimAutoWhitespace": true,
+ "workbench.colorCustomizations": {
+ "editorWhitespace.foreground": "#333"
+ },
+ "files.trimTrailingWhitespace": true,
+ "files.trimTrailingWhitespaceInRegexAndStrings": true,
+ "files.trimFinalNewlines": true,
+ "[yaml]": {
+ "editor.defaultFormatter": "redhat.vscode-yaml"
+ },
+ "biome.enabled": true,
+ "editor.defaultFormatter": "biomejs.biome",
+ "[javascript][typescript][typescriptreact][javascriptreact][json][jsonc][vue][astro][svelte][css][graphql]": {
+ "editor.defaultFormatter": "biomejs.biome"
+ },
+ "typescript.tsdk": "node_modules/typescript/lib",
+ "explorer.fileNesting.patterns": {
+ "*.ts": "${basename}.*.${extname}",
+ ".env": ".env.*",
+ "*.tsx": "${basename}.*.${extname},${basename}.*.ts",
+ "package.json": "*.json, *.yml, *.config.js, *.config.ts, *.yaml, *.workspace.ts",
+ "readme*": "AUTHORS, Authors, BACKERS*, Backers*, CHANGELOG*, CITATION*, CODEOWNERS, CODE_OF_CONDUCT*, CONTRIBUTING*, CONTRIBUTORS, COPYING*, CREDITS, Changelog*, Citation*, Code_Of_Conduct*, Codeowners, Contributing*, Contributors, Copying*, Credits, GOVERNANCE.MD, Governance.md, HISTORY.MD, History.md, LICENSE*, License*, MAINTAINERS, Maintainers, README-*, README_*, RELEASE_NOTES*, ROADMAP.MD, Readme-*, Readme_*, Release_Notes*, Roadmap.md, SECURITY.MD, SPONSORS*, Security.md, Sponsors*, authors, backers*, changelog*, citation*, code_of_conduct*, codeowners, contributing*, contributors, copying*, credits, governance.md, history.md, license*, maintainers, readme-*, readme_*, release_notes*, roadmap.md, security.md, sponsors*",
+ "Readme*": "AUTHORS, Authors, BACKERS*, Backers*, CHANGELOG*, CITATION*, CODEOWNERS, CODE_OF_CONDUCT*, CONTRIBUTING*, CONTRIBUTORS, COPYING*, CREDITS, Changelog*, Citation*, Code_Of_Conduct*, Codeowners, Contributing*, Contributors, Copying*, Credits, GOVERNANCE.MD, Governance.md, HISTORY.MD, History.md, LICENSE*, License*, MAINTAINERS, Maintainers, README-*, README_*, RELEASE_NOTES*, ROADMAP.MD, Readme-*, Readme_*, Release_Notes*, Roadmap.md, SECURITY.MD, SPONSORS*, Security.md, Sponsors*, authors, backers*, changelog*, citation*, code_of_conduct*, codeowners, contributing*, contributors, copying*, credits, governance.md, history.md, license*, maintainers, readme-*, readme_*, release_notes*, roadmap.md, security.md, sponsors*",
+ "README*": "AUTHORS, Authors, BACKERS*, Backers*, CHANGELOG*, CITATION*, CODEOWNERS, CODE_OF_CONDUCT*, CONTRIBUTING*, CONTRIBUTORS, COPYING*, CREDITS, Changelog*, Citation*, Code_Of_Conduct*, Codeowners, Contributing*, Contributors, Copying*, Credits, GOVERNANCE.MD, Governance.md, HISTORY.MD, History.md, LICENSE*, License*, MAINTAINERS, Maintainers, README-*, README_*, RELEASE_NOTES*, ROADMAP.MD, Readme-*, Readme_*, Release_Notes*, Roadmap.md, SECURITY.MD, SPONSORS*, Security.md, Sponsors*, authors, backers*, changelog*, citation*, code_of_conduct*, codeowners, contributing*, contributors, copying*, credits, governance.md, history.md, license*, maintainers, readme-*, readme_*, release_notes*, roadmap.md, security.md, sponsors*",
+ "Dockerfile": "*.dockerfile, .devcontainer.*, .dockerignore, captain-definition, compose.*, docker-compose.*, dockerfile*"
+ },
+ "[typescriptreact]": {
+ "editor.defaultFormatter": "biomejs.biome"
+ },
+ "editor.formatOnPaste": true
+}
diff --git a/docs/CODE_OF_CONDUCT.md b/docs/CODE_OF_CONDUCT.md
new file mode 100644
index 0000000..9dc8168
--- /dev/null
+++ b/docs/CODE_OF_CONDUCT.md
@@ -0,0 +1,128 @@
+# Contributor Covenant Code of Conduct
+
+## Our Pledge
+
+We as members, contributors, and leaders pledge to make participation in our
+community a harassment-free experience for everyone, regardless of age, body
+size, visible or invisible disability, ethnicity, sex characteristics, gender
+identity and expression, level of experience, education, socio-economic status,
+nationality, personal appearance, race, religion, or sexual identity
+and orientation.
+
+We pledge to act and interact in ways that contribute to an open, welcoming,
+diverse, inclusive, and healthy community.
+
+## Our Standards
+
+Examples of behavior that contributes to a positive environment for our
+community include:
+
+- Demonstrating empathy and kindness toward other people
+- Being respectful of differing opinions, viewpoints, and experiences
+- Giving and gracefully accepting constructive feedback
+- Accepting responsibility and apologizing to those affected by our mistakes,
+ and learning from the experience
+- Focusing on what is best not just for us as individuals, but for the
+ overall community
+
+Examples of unacceptable behavior include:
+
+- The use of sexualized language or imagery, and sexual attention or
+ advances of any kind
+- Trolling, insulting or derogatory comments, and personal or political attacks
+- Public or private harassment
+- Publishing others' private information, such as a physical or email
+ address, without their explicit permission
+- Other conduct which could reasonably be considered inappropriate in a
+ professional setting
+
+## Enforcement Responsibilities
+
+Community leaders are responsible for clarifying and enforcing our standards of
+acceptable behavior and will take appropriate and fair corrective action in
+response to any behavior that they deem inappropriate, threatening, offensive,
+or harmful.
+
+Community leaders have the right and responsibility to remove, edit, or reject
+comments, commits, code, wiki edits, issues, and other contributions that are
+not aligned to this Code of Conduct, and will communicate reasons for moderation
+decisions when appropriate.
+
+## Scope
+
+This Code of Conduct applies within all community spaces, and also applies when
+an individual is officially representing the community in public spaces.
+Examples of representing our community include using an official e-mail address,
+posting via an official social media account, or acting as an appointed
+representative at an online or offline event.
+
+## Enforcement
+
+Instances of abusive, harassing, or otherwise unacceptable behavior may be
+reported to the community leaders responsible for enforcement at
+.
+All complaints will be reviewed and investigated promptly and fairly.
+
+All community leaders are obligated to respect the privacy and security of the
+reporter of any incident.
+
+## Enforcement Guidelines
+
+Community leaders will follow these Community Impact Guidelines in determining
+the consequences for any action they deem in violation of this Code of Conduct:
+
+### 1. Correction
+
+**Community Impact**: Use of inappropriate language or other behavior deemed
+unprofessional or unwelcome in the community.
+
+**Consequence**: A private, written warning from community leaders, providing
+clarity around the nature of the violation and an explanation of why the
+behavior was inappropriate. A public apology may be requested.
+
+### 2. Warning
+
+**Community Impact**: A violation through a single incident or series
+of actions.
+
+**Consequence**: A warning with consequences for continued behavior. No
+interaction with the people involved, including unsolicited interaction with
+those enforcing the Code of Conduct, for a specified period of time. This
+includes avoiding interactions in community spaces as well as external channels
+like social media. Violating these terms may lead to a temporary or
+permanent ban.
+
+### 3. Temporary Ban
+
+**Community Impact**: A serious violation of community standards, including
+sustained inappropriate behavior.
+
+**Consequence**: A temporary ban from any sort of interaction or public
+communication with the community for a specified period of time. No public or
+private interaction with the people involved, including unsolicited interaction
+with those enforcing the Code of Conduct, is allowed during this period.
+Violating these terms may lead to a permanent ban.
+
+### 4. Permanent Ban
+
+**Community Impact**: Demonstrating a pattern of violation of community
+standards, including sustained inappropriate behavior, harassment of an
+individual, or aggression toward or disparagement of classes of individuals.
+
+**Consequence**: A permanent ban from any sort of public interaction within
+the community.
+
+## Attribution
+
+This Code of Conduct is adapted from the [Contributor Covenant][homepage],
+version 2.0, available at
+https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
+
+Community Impact Guidelines were inspired by [Mozilla's code of conduct
+enforcement ladder](https://github.com/mozilla/diversity).
+
+[homepage]: https://www.contributor-covenant.org
+
+For answers to common questions about this code of conduct, see the FAQ at
+https://www.contributor-covenant.org/faq. Translations are available at
+https://www.contributor-covenant.org/translations.
\ No newline at end of file
diff --git a/docs/Dockerfile b/docs/Dockerfile
new file mode 100644
index 0000000..ba5788d
--- /dev/null
+++ b/docs/Dockerfile
@@ -0,0 +1,38 @@
+# syntax = docker/dockerfile:1.4
+
+ARG NODE_VERSION=22
+FROM node:${NODE_VERSION}-slim AS base
+
+LABEL fly_launch_runtime="Node.js"
+WORKDIR /app
+ENV NODE_ENV=production
+
+ARG PNPM_VERSION=10.18.0
+RUN npm install -g pnpm@$PNPM_VERSION
+
+# --- Build stage ---
+# We consume the docs generated by the CI
+FROM base AS build
+
+RUN apt-get update -qq && \
+ apt-get install --no-install-recommends -y build-essential node-gyp pkg-config python-is-python3 && \
+ rm -rf /var/lib/apt/lists/*
+
+COPY .npmrc package.json pnpm-lock.yaml ./
+RUN pnpm install --prod=false --frozen-lockfile
+
+
+COPY . .
+
+RUN pnpm run build
+
+# Prune dev deps
+RUN pnpm prune --prod
+
+# --- Runtime stage ---
+FROM base
+
+COPY --from=build /app /app
+
+EXPOSE 3000
+CMD ["pnpm","run","start"]
\ No newline at end of file
diff --git a/docs/LICENSE b/docs/LICENSE
new file mode 100644
index 0000000..5a34b88
--- /dev/null
+++ b/docs/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2025 Forge 42
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/docs/README.md b/docs/README.md
new file mode 100644
index 0000000..7b90106
--- /dev/null
+++ b/docs/README.md
@@ -0,0 +1,66 @@
+# deploykit docs
+
+The documentation site for [deploykit](../README.md), built with the
+[code-forge docs template](https://github.com/code-forge-io/docs)
+(React Router v7 + [content-collections](https://github.com/sdorra/content-collections)).
+
+This is a **self-contained subproject**: it has its own `package.json`,
+`node_modules`, and tooling, and is not part of the root deploykit package build.
+
+## Develop
+
+```bash
+cd docs
+pnpm install
+pnpm run dev
+```
+
+The dev server serves live content from the `content/` folder with hot reload.
+
+## Content
+
+All documentation lives in [`content/`](./content) as `.md` / `.mdx` files.
+The sidebar is generated automatically from the folder structure:
+
+```
+content/
+├── _index.mdx # landing page
+├── 01-introduction.mdx
+├── 02-getting-started/
+│ ├── index.md # section title
+│ ├── 01-installation.mdx
+│ ├── 02-quick-start.mdx
+│ └── 03-what-it-generates.mdx
+├── 03-core-concepts/
+├── 04-commands/
+├── 05-guides/
+└── 06-reference/
+```
+
+- Numeric prefixes (`01-`, `02-`) control ordering; they're stripped from the URL.
+- Every section folder needs an `index.md` whose `title` becomes the sidebar label.
+- Each `.mdx` page needs frontmatter: `title`, `summary`, `description`.
+
+## Build
+
+```bash
+pnpm run build # production build
+pnpm run start # serve the production build
+pnpm run typecheck # tsc
+pnpm run test # vitest
+```
+
+## Configuration
+
+- `.env.example` → copy to `.env`. `GITHUB_OWNER` / `GITHUB_REPO` / `GITHUB_REPO_URL`
+ drive the "edit this page" / "report an issue" links and the header GitHub icon.
+- Branding lives in `app/routes/index.tsx` (landing page), `app/utils/seo.ts`
+ (site name / OG image), and `app/routes/documentation-layout.tsx` (header logo).
+
+## Deployment
+
+The template ships a `Dockerfile`, a `fly.toml` (app `deploykit-docs`), and
+example GitHub Actions workflows under `.github/workflows/`. These are **not**
+wired into the root repo's CI. To publish the docs, review those workflows,
+set the `FLY_API_TOKEN` secret, and adapt paths as needed — or host the
+`build/` output anywhere that runs a Node server.
diff --git a/docs/app/components/backdrop.tsx b/docs/app/components/backdrop.tsx
new file mode 100644
index 0000000..2e49cf4
--- /dev/null
+++ b/docs/app/components/backdrop.tsx
@@ -0,0 +1,16 @@
+import { cn } from "~/utils/css"
+
+export const Backdrop = ({ onClose, className }: { onClose: () => void; className?: string }) => (
+ // biome-ignore lint/a11y/useKeyWithClickEvents: We don't need keyboard events for backdrop
+
{
+ if (e.target === e.currentTarget) {
+ onClose()
+ }
+ }}
+ />
+)
diff --git a/docs/app/components/code-block/code-block-diff.ts b/docs/app/components/code-block/code-block-diff.ts
new file mode 100644
index 0000000..0812efb
--- /dev/null
+++ b/docs/app/components/code-block/code-block-diff.ts
@@ -0,0 +1,45 @@
+/**
+ * This module provides utilities for processing and styling lines of a text diff.
+ */
+const DIFF_STYLES = {
+ added: {
+ backgroundColor: "var(--color-diff-added-bg)",
+ borderLeft: "2px solid",
+ borderLeftColor: "var(--color-diff-added-border)",
+ indicator: "+",
+ },
+ removed: {
+ backgroundColor: "var(--color-diff-removed-bg)",
+ borderLeft: "2px solid",
+ borderLeftColor: "var(--color-diff-removed-border)",
+ indicator: "-",
+ },
+ normal: {
+ backgroundColor: "transparent",
+ borderLeft: "none",
+ borderLeftColor: "transparent",
+ indicator: "",
+ },
+} as const
+
+type DiffType = keyof typeof DIFF_STYLES
+
+const DIFF_PATTERNS = {
+ "+ ": "added",
+ "- ": "removed",
+} as const
+
+type DiffPatternPrefix = keyof typeof DIFF_PATTERNS
+
+const isDiffPatternPrefix = (prefix: string): prefix is DiffPatternPrefix => {
+ return prefix in DIFF_PATTERNS
+}
+
+export const getDiffType = (line: string): DiffType => {
+ const prefix = line.trimStart().slice(0, 2)
+ return isDiffPatternPrefix(prefix) ? DIFF_PATTERNS[prefix] : "normal"
+}
+
+export const cleanDiffLine = (line: string) => line.replace(/^(\s*)[+-] /, "$1")
+
+export const getDiffStyles = (diffType: DiffType) => DIFF_STYLES[diffType]
diff --git a/docs/app/components/code-block/code-block-elements.tsx b/docs/app/components/code-block/code-block-elements.tsx
new file mode 100644
index 0000000..3efe019
--- /dev/null
+++ b/docs/app/components/code-block/code-block-elements.tsx
@@ -0,0 +1,66 @@
+import type { ComponentPropsWithoutRef } from "react"
+import { cn } from "~/utils/css"
+import { createLineData } from "./code-block-parser"
+import { getTokenColor, isTokenType, type tokenize } from "./code-block-syntax-highlighter"
+
+const TokenElement = ({ token }: { token: ReturnType
[0] }) => {
+ const { type, value } = token
+ const color = isTokenType(type) ? getTokenColor(type) : ""
+
+ return {value}
+}
+
+const DiffIndicator = ({ indicator }: { indicator: string }) => (
+
+ {indicator}
+
+)
+
+const LineElement = ({ line }: { line: string }) => {
+ const { tokens, styles, isNormalDiff } = createLineData(line)
+
+ return (
+
+
+ {!isNormalDiff && }
+
+ {tokens.map((token, index) => (
+
+ ))}
+
+
+
+ )
+}
+
+const CodeElement = ({ lines }: { lines: string[] }) => (
+
+ {lines.map((line, index) => (
+
+ ))}
+
+)
+
+interface PreElementProps extends Omit, "children"> {
+ lines: string[]
+ className?: string
+}
+
+export const PreElement = ({ lines, className = "", ...props }: PreElementProps) => (
+
+
+
+)
diff --git a/docs/app/components/code-block/code-block-parser.ts b/docs/app/components/code-block/code-block-parser.ts
new file mode 100644
index 0000000..ec64f3d
--- /dev/null
+++ b/docs/app/components/code-block/code-block-parser.ts
@@ -0,0 +1,53 @@
+import { cleanDiffLine, getDiffStyles, getDiffType } from "./code-block-diff"
+import { tokenize } from "./code-block-syntax-highlighter"
+
+interface CodeBlockChild {
+ props?: {
+ children?: string
+ }
+}
+
+export const extractCodeContent = (children: string | CodeBlockChild) => {
+ const code = typeof children === "string" ? children : (children?.props?.children ?? "")
+ return { code }
+}
+
+export const processLines = (content: string) => {
+ const lines = content.split("\n")
+ return filterEmptyLines(lines)
+}
+
+const filterEmptyLines = (lines: string[]) => {
+ return lines.filter((line, index, array) => {
+ const isLastLine = index === array.length - 1
+ const isEmpty = line.trim() === ""
+ return !(isEmpty && isLastLine)
+ })
+}
+
+export const createLineData = (line: string) => {
+ const diffType = getDiffType(line)
+ const cleanLine = cleanDiffLine(line)
+ const tokens = tokenize(cleanLine)
+ const styles = getDiffStyles(diffType)
+ const isNormalDiff = diffType === "normal"
+
+ return {
+ diffType,
+ cleanLine,
+ tokens,
+ styles,
+ isNormalDiff,
+ }
+}
+
+export const processCopyContent = (content: string): { code: string } => {
+ // removes diff markers from content
+ const code = content
+ .split("\n")
+ .filter((line) => !line.trimStart().startsWith("- "))
+ .map((line) => line.replace(/^(\s*)\+ /, "$1"))
+ .join("\n")
+
+ return { code }
+}
diff --git a/docs/app/components/code-block/code-block-syntax-highlighter.ts b/docs/app/components/code-block/code-block-syntax-highlighter.ts
new file mode 100644
index 0000000..11980b5
--- /dev/null
+++ b/docs/app/components/code-block/code-block-syntax-highlighter.ts
@@ -0,0 +1,157 @@
+/**
+ * Tokenization utility for syntax highlighting code snippets.
+ * This utils will produce syntax-highlighted JSX output using theme colors.
+ */
+
+type TokenType = "keyword" | "string" | "number" | "comment" | "operator" | "punctuation" | "function" | "text"
+
+const MASTER_REGEX = new RegExp(
+ [
+ // whitespace
+ "\\s+",
+ // single-line comment
+ "\\/\\/[^\\n\\r]*(?=\\n|$)",
+ // multi-line comment
+ "\\/\\*[\\s\\S]*?\\*\\/",
+ // hash comment at start of line
+ "^\\s*#.*$",
+ // backtick inline code
+ "\\`(?:[^`\\\\]|\\\\.)*\\`",
+ // strings
+ "(['\"])(?:(?!\\1)[^\\\\]|\\\\.)*\\1",
+ // numbers
+ "\\d+\\.?\\d*",
+ // identifiers
+ "[a-zA-Z_$][a-zA-Z0-9_$]*",
+ // arrow function
+ "=>",
+ // operators & punctuation
+ "===|!==|<=|>=|==|!=|&&|\\|\\||\\+\\+|--|[+\\-*%=<>!?:(){}\\[\\];,.]|\\/(?![/*])|[+\\-*/%]=",
+ ].join("|"),
+ "gm"
+)
+
+const KEYWORDS = [
+ "import",
+ "export",
+ "default",
+ "from",
+ "const",
+ "let",
+ "var",
+ "function",
+ "return",
+ "if",
+ "else",
+ "for",
+ "while",
+ "do",
+ "switch",
+ "case",
+ "break",
+ "continue",
+ "try",
+ "catch",
+ "finally",
+ "throw",
+ "new",
+ "class",
+ "extends",
+ "interface",
+ "type",
+ "public",
+ "private",
+ "protected",
+ "static",
+ "async",
+ "await",
+ "true",
+ "false",
+ "null",
+ "undefined",
+ "typeof",
+ "instanceof",
+]
+
+const OPERATORS = [
+ "+",
+ "-",
+ "*",
+ "/",
+ "=",
+ "==",
+ "===",
+ "!=",
+ "!==",
+ "<",
+ ">",
+ "<=",
+ ">=",
+ "&&",
+ "||",
+ "!",
+ "?",
+ ":",
+ "++",
+ "--",
+ "+=",
+ "-=",
+ "*=",
+ "/=",
+ "=>",
+]
+
+const isKeyword = (value: string) => KEYWORDS.includes(value)
+const isOperator = (value: string) => OPERATORS.includes(value)
+const isFunction = (value: string) => /^[A-Z]/.test(value)
+const isWhitespace = (value: string) => /^\s/.test(value)
+const isComment = (v: string) => v.startsWith("//") || v.startsWith("/*") || /^\s*#/.test(v)
+const isString = (value: string) => /^['"`]/.test(value)
+const isNumber = (value: string) => /^\d/.test(value)
+const isIdentifier = (value: string) => /^[a-zA-Z_$]/.test(value)
+
+const classifyIdentifier = (value: string) => {
+ return isKeyword(value) ? "keyword" : isFunction(value) ? "function" : "text"
+}
+
+const classifyToken = (value: string) => {
+ switch (true) {
+ case isWhitespace(value):
+ return "text"
+ case isComment(value):
+ return "comment"
+ case isString(value):
+ return "string"
+ case isNumber(value):
+ return "number"
+ case isIdentifier(value):
+ return classifyIdentifier(value)
+ case isOperator(value):
+ return "operator"
+ default:
+ return "punctuation"
+ }
+}
+
+export const tokenize = (code: string) =>
+ Array.from(code.matchAll(MASTER_REGEX), (match) => ({
+ type: classifyToken(match[0]),
+ value: match[0],
+ }))
+
+const TOKEN_COLORS = {
+ keyword: "var(--color-code-keyword)",
+ string: "var(--color-code-string)",
+ number: "var(--color-code-number)",
+ comment: "var(--color-code-comment)",
+ operator: "var(--color-code-operator)",
+ punctuation: "var(--color-code-punctuation)",
+ function: "var(--color-code-function)",
+ text: "var(--color-code-block-text)",
+} as const
+
+export const getTokenColor = (type: TokenType) => TOKEN_COLORS[type]
+
+export function isTokenType(value: unknown): value is TokenType {
+ return typeof value === "string" && value in TOKEN_COLORS
+}
diff --git a/docs/app/components/code-block/code-block.tsx b/docs/app/components/code-block/code-block.tsx
new file mode 100644
index 0000000..1ecc8c8
--- /dev/null
+++ b/docs/app/components/code-block/code-block.tsx
@@ -0,0 +1,20 @@
+import type { ComponentPropsWithoutRef } from "react"
+import { PreElement } from "./code-block-elements"
+import { extractCodeContent, processLines } from "./code-block-parser"
+import { CopyButton } from "./copy-button"
+
+interface CodeBlockProps extends Omit, "children"> {
+ children: string
+}
+
+export const CodeBlock = ({ children, className = "", ...props }: CodeBlockProps) => {
+ const { code } = extractCodeContent(children)
+ const lines = processLines(code)
+
+ return (
+
+ )
+}
diff --git a/docs/app/components/code-block/copy-button.tsx b/docs/app/components/code-block/copy-button.tsx
new file mode 100644
index 0000000..79e9c19
--- /dev/null
+++ b/docs/app/components/code-block/copy-button.tsx
@@ -0,0 +1,38 @@
+import { useState } from "react"
+import { useTranslation } from "react-i18next"
+import { Icon } from "~/ui/icon/icon"
+import { cn } from "~/utils/css"
+import { processCopyContent } from "./code-block-parser"
+
+export const CopyButton = ({ lines }: { lines: string[] }) => {
+ const [copyState, setCopyState] = useState<"copy" | "copied">("copy")
+ const disabled = copyState === "copied"
+ const { t } = useTranslation()
+
+ const handleCopy = async () => {
+ const reconstructedContent = lines.join("\n")
+ const { code } = processCopyContent(reconstructedContent)
+
+ await navigator.clipboard.writeText(code)
+ setCopyState("copied")
+ setTimeout(() => setCopyState("copy"), 2000)
+ }
+
+ return (
+
+
+ {t(copyState === "copy" ? "buttons.copy" : "buttons.copied")}
+
+ )
+}
diff --git a/docs/app/components/code-block/tests/code-block-diff.test.ts b/docs/app/components/code-block/tests/code-block-diff.test.ts
new file mode 100644
index 0000000..0386e80
--- /dev/null
+++ b/docs/app/components/code-block/tests/code-block-diff.test.ts
@@ -0,0 +1,75 @@
+import { describe, expect, it } from "vitest"
+import { cleanDiffLine, getDiffStyles, getDiffType } from "../code-block-diff"
+
+describe("getDiffType test suite", () => {
+ it("should return 'added' for lines starting with '+ '", () => {
+ expect(getDiffType("+ something")).toBe("added")
+ })
+
+ it("should return 'removed' for lines starting with '- '", () => {
+ expect(getDiffType("- something")).toBe("removed")
+ })
+
+ it("should return 'normal' for lines without a diff prefix", () => {
+ expect(getDiffType(" unchanged line")).toBe("normal")
+ })
+
+ it("should handle leading whitespace before '+ '", () => {
+ expect(getDiffType(" + spaced")).toBe("added")
+ })
+
+ it("should handle leading whitespace before '- '", () => {
+ expect(getDiffType(" - spaced")).toBe("removed")
+ })
+
+ it("should return 'normal' for '+' without space after", () => {
+ expect(getDiffType("+no-space")).toBe("normal")
+ })
+
+ it("should return 'normal' for '-' without space after", () => {
+ expect(getDiffType("-no-space")).toBe("normal")
+ })
+})
+
+describe("cleanDiffLine test suite", () => {
+ it("should remove '+ ' from start while preserving indentation", () => {
+ expect(cleanDiffLine("+ added")).toBe("added")
+ expect(cleanDiffLine(" + added")).toBe(" added")
+ })
+
+ it("should remove '- ' from start while preserving indentation", () => {
+ expect(cleanDiffLine("- removed")).toBe("removed")
+ expect(cleanDiffLine(" - removed")).toBe(" removed")
+ })
+
+ it("should not change lines without diff prefix", () => {
+ expect(cleanDiffLine("unchanged")).toBe("unchanged")
+ expect(cleanDiffLine(" unchanged")).toBe(" unchanged")
+ })
+})
+
+describe("getDiffStyles test suite", () => {
+ it.each(["added", "removed", "normal"] as const)("should have backgroundColor and indicator for '%s'", (diffType) => {
+ const styles = getDiffStyles(diffType)
+ expect(styles).toHaveProperty("backgroundColor")
+ expect(styles).toHaveProperty("indicator")
+ })
+
+ it("should return correct backgroundColor and indicator for 'added'", () => {
+ const styles = getDiffStyles("added")
+ expect(styles.backgroundColor).toBe("var(--color-diff-added-bg)")
+ expect(styles.indicator).toBe("+")
+ })
+
+ it("should return correct backgroundColor and indicator for 'removed'", () => {
+ const styles = getDiffStyles("removed")
+ expect(styles.backgroundColor).toBe("var(--color-diff-removed-bg)")
+ expect(styles.indicator).toBe("-")
+ })
+
+ it("should return correct backgroundColor and indicator for 'normal'", () => {
+ const styles = getDiffStyles("normal")
+ expect(styles.backgroundColor).toBe("transparent")
+ expect(styles.indicator).toBe("")
+ })
+})
diff --git a/docs/app/components/code-block/tests/code-block-parser.test.ts b/docs/app/components/code-block/tests/code-block-parser.test.ts
new file mode 100644
index 0000000..a32db06
--- /dev/null
+++ b/docs/app/components/code-block/tests/code-block-parser.test.ts
@@ -0,0 +1,67 @@
+import { extractCodeContent, processCopyContent, processLines } from "../code-block-parser"
+
+describe("extractCodeContent test suite", () => {
+ it("should return code when children is a string", () => {
+ expect(extractCodeContent("console.log('hello')")).toEqual({
+ code: "console.log('hello')",
+ })
+ })
+
+ it("should return code from props.children when children is an object", () => {
+ expect(extractCodeContent({ props: { children: "const x = 1" } })).toEqual({
+ code: "const x = 1",
+ })
+ })
+
+ it("should return empty string if children has no props or children", () => {
+ // biome-ignore lint/suspicious/noExplicitAny: in tests we may use any type
+ expect(extractCodeContent({} as any)).toEqual({ code: "" })
+ })
+})
+
+describe("processLines test suite", () => {
+ it("should split lines by newline", () => {
+ expect(processLines("line1\nline2")).toEqual(["line1", "line2"])
+ })
+
+ it("should remove trailing empty line", () => {
+ expect(processLines("line1\n")).toEqual(["line1"])
+ })
+
+ it("should keep empty lines in the middle", () => {
+ expect(processLines("a\n\nb")).toEqual(["a", "", "b"])
+ })
+
+ it("should return empty array for empty string", () => {
+ expect(processLines("")).toEqual([])
+ })
+})
+
+describe("processCopyContent test suite", () => {
+ it("should remove removed lines (starting with '- ')", () => {
+ const result = processCopyContent("- removed\nunchanged")
+ expect(result.code).toBe("unchanged")
+ })
+
+ it("should strip '+ ' from added lines but keep indentation", () => {
+ const result = processCopyContent("+ added\n unchanged")
+ expect(result.code).toBe("added\n unchanged")
+ })
+
+ it("should handle mixed added, removed, and unchanged lines", () => {
+ const content = `
+- removed
++ added
+ unchanged
+`
+ const result = processCopyContent(content)
+ expect(result.code).toContain("added")
+ expect(result.code).toContain("unchanged")
+ expect(result.code).not.toContain("removed")
+ })
+
+ it("should return empty string if all lines are removed", () => {
+ const result = processCopyContent("- a\n- b")
+ expect(result.code).toBe("")
+ })
+})
diff --git a/docs/app/components/code-block/tests/code-block-syntax-highlighter.test.ts b/docs/app/components/code-block/tests/code-block-syntax-highlighter.test.ts
new file mode 100644
index 0000000..d2cf1f5
--- /dev/null
+++ b/docs/app/components/code-block/tests/code-block-syntax-highlighter.test.ts
@@ -0,0 +1,110 @@
+import { getTokenColor, isTokenType, tokenize } from "../code-block-syntax-highlighter"
+
+describe("tokenize test suite", () => {
+ it("should tokenize keywords", () => {
+ const tokens = tokenize("const let var function return if else")
+ expect(tokens.map((t) => t.type)).toContain("keyword")
+ expect(tokens.some((t) => t.value === "const")).toBe(true)
+ })
+
+ it("should tokenize strings (single and double quotes)", () => {
+ const tokens = tokenize(`'hello' "world"`)
+ expect(tokens.filter((t) => t.type === "string")).toHaveLength(2)
+ })
+
+ it("should tokenize numbers (integers and floats)", () => {
+ const tokens = tokenize("42 3.14")
+ expect(tokens.filter((t) => t.type === "number")).toHaveLength(2)
+ })
+
+ it("should tokenize single-line comments", () => {
+ const tokens = tokenize("// comment here")
+ expect(tokens[0]).toEqual({ type: "comment", value: "// comment here" })
+ })
+
+ it("should tokenize multi-line comments", () => {
+ const tokens = tokenize("/* multi\nline\ncomment */")
+ expect(tokens[0].type).toBe("comment")
+ })
+
+ it("should tokenize operators", () => {
+ const tokens = tokenize("a + b - c * d / e == f && g || h")
+ expect(tokens.filter((t) => t.type === "operator").length).toBeGreaterThan(0)
+ })
+
+ it("should tokenize punctuation", () => {
+ const tokens = tokenize("{ } ( ) [ ] ; , .")
+ expect(tokens.filter((t) => t.type === "punctuation").length).toBeGreaterThan(0)
+ })
+
+ it("should classify whitespace as text", () => {
+ const tokens = tokenize(" \n\t")
+ expect(tokens.every((t) => t.type === "text")).toBe(true)
+ })
+
+ it("should classify lowercase identifiers as text when not keywords", () => {
+ const tokens = tokenize("myVariable anotherThing")
+ const nonWhitespaceTextTokens = tokens.filter((t) => t.type === "text" && t.value.trim() !== "")
+ expect(nonWhitespaceTextTokens.length).toBe(2)
+ })
+ it("should handle empty input", () => {
+ expect(tokenize("")).toEqual([])
+ })
+
+ it("should handle mixed code sample", () => {
+ const code = `
+ // comment
+ const x = 42;
+ function Test() {
+ return "hello";
+ }
+ `
+ const tokens = tokenize(code)
+ expect(tokens.some((t) => t.type === "keyword")).toBe(true)
+ expect(tokens.some((t) => t.type === "function")).toBe(true)
+ expect(tokens.some((t) => t.type === "string")).toBe(true)
+ expect(tokens.some((t) => t.type === "comment")).toBe(true)
+ expect(tokens.some((t) => t.type === "number")).toBe(true)
+ })
+})
+
+describe("getTokenColor", () => {
+ it("should return a valid CSS variable for each TokenType", () => {
+ const tokenTypes = [
+ "keyword",
+ "string",
+ "number",
+ "comment",
+ "operator",
+ "punctuation",
+ "function",
+ "text",
+ ] as const
+
+ for (const type of tokenTypes) {
+ const color = getTokenColor(type)
+ expect(color).toMatch(/^var\(--color-code-/)
+ }
+ })
+})
+
+describe("isTokenType", () => {
+ it("should return true for valid token types", () => {
+ expect(isTokenType("keyword")).toBe(true)
+ expect(isTokenType("string")).toBe(true)
+ expect(isTokenType("function")).toBe(true)
+ })
+
+ it("should return false for invalid strings", () => {
+ expect(isTokenType("not-a-type")).toBe(false)
+ expect(isTokenType("")).toBe(false)
+ expect(isTokenType("KEYWORD")).toBe(false)
+ })
+
+ it("should return false for non-string values", () => {
+ expect(isTokenType(undefined)).toBe(false)
+ expect(isTokenType(null)).toBe(false)
+ expect(isTokenType(42)).toBe(false)
+ expect(isTokenType({})).toBe(false)
+ })
+})
diff --git a/docs/app/components/command-k/components/command-k.tsx b/docs/app/components/command-k/components/command-k.tsx
new file mode 100644
index 0000000..57b3986
--- /dev/null
+++ b/docs/app/components/command-k/components/command-k.tsx
@@ -0,0 +1,135 @@
+import { useRef, useState } from "react"
+import { useTranslation } from "react-i18next"
+import { useNavigate } from "react-router"
+import { Modal } from "~/components/modal"
+import type { Version } from "~/utils/version-resolvers"
+import { useKeyboardNavigation } from "../hooks/use-keyboard-navigation"
+import { useModalState } from "../hooks/use-modal-state"
+import { useSearch } from "../hooks/use-search"
+import { useSearchHistory } from "../hooks/use-search-history"
+import type { HistoryItem, MatchType, SearchResult } from "../search-types"
+import { EmptyState } from "./empty-state"
+import { ResultsFooter } from "./results-footer"
+import { SearchHistory } from "./search-history"
+import { SearchInput } from "./search-input"
+import { SearchResultRow } from "./search-result"
+import { TriggerButton } from "./trigger-button"
+
+interface CommandPaletteProps {
+ placeholder?: string
+ version: Version
+}
+
+export const CommandK = ({ placeholder, version }: CommandPaletteProps) => {
+ const { t } = useTranslation()
+ const navigate = useNavigate()
+ const inputRef = useRef(null)
+ const [query, setQuery] = useState("")
+ const { isOpen, openModal, closeModal } = useModalState()
+ const { history, addToHistory, clearHistory, removeFromHistory } = useSearchHistory(version)
+ const { results, search } = useSearch({ version })
+
+ const hasQuery = !!query.trim()
+ const hasResults = !!results.length
+ const hasHistory = !!history.length
+ const searchPlaceholder = placeholder ?? t("placeholders.search_documentation")
+
+ const handleClose = () => {
+ closeModal()
+ setQuery("")
+ search("")
+ }
+
+ const navigateToPage = (id: string) => {
+ const path = [version, id]
+ .filter(Boolean)
+ .map((s) => s.replace(/^\/+|\/+$/g, ""))
+ .join("/")
+
+ navigate(`/${path}`)
+ }
+
+ const handleResultSelect = (result: SearchResult) => {
+ if (!isOpen) return
+ const rowItem = result.item
+ const matchType: MatchType = result.refIndex === 0 ? "heading" : "paragraph"
+ const historyItem = {
+ ...rowItem,
+ type: matchType,
+ highlightedText: result.highlightedText,
+ }
+
+ addToHistory(historyItem)
+ navigateToPage(rowItem.id)
+ handleClose()
+ }
+
+ const handleHistorySelect = (item: HistoryItem) => {
+ navigateToPage(item.id)
+ handleClose()
+ }
+
+ const handleToggle = () => {
+ isOpen ? handleClose() : openModal()
+ }
+
+ const { selectedIndex } = useKeyboardNavigation({
+ isOpen,
+ results,
+ onSelect: handleResultSelect,
+ onClose: handleClose,
+ onToggle: handleToggle,
+ })
+
+ if (!isOpen) {
+ return
+ }
+
+ const renderBody = () => {
+ if (hasQuery) {
+ if (!hasResults) return
+
+ return results.map((result, index) => (
+ handleResultSelect(result)}
+ matchType={result.refIndex === 0 ? "heading" : "paragraph"}
+ />
+ ))
+ }
+
+ if (hasHistory) {
+ return (
+
+ )
+ }
+
+ return
+ }
+
+ return (
+ inputRef.current} ariaLabel={searchPlaceholder}>
+ {
+ setQuery(val)
+ search(val.trim())
+ }}
+ placeholder={searchPlaceholder}
+ />
+
+ {renderBody()}
+
+
+
+ )
+}
diff --git a/docs/app/components/command-k/components/empty-state.tsx b/docs/app/components/command-k/components/empty-state.tsx
new file mode 100644
index 0000000..fc79b79
--- /dev/null
+++ b/docs/app/components/command-k/components/empty-state.tsx
@@ -0,0 +1,29 @@
+import { useTranslation } from "react-i18next"
+import { KeyboardHint } from "./keyboard-hint"
+import { ResultsFooterNote } from "./results-footer-note"
+
+export const EmptyState = ({ query }: { query?: string }) => {
+ const { t } = useTranslation()
+ if (query) {
+ return (
+
+
+ {t("text.no_results_for")} "{query}"
+
+
{t("text.adjust_search")}
+
+ )
+ }
+
+ return (
+
+
{t("text.start_typing_to_search")}
+
+
+
+
+
+
+
+ )
+}
diff --git a/docs/app/components/command-k/components/keyboard-hint.tsx b/docs/app/components/command-k/components/keyboard-hint.tsx
new file mode 100644
index 0000000..7233ae5
--- /dev/null
+++ b/docs/app/components/command-k/components/keyboard-hint.tsx
@@ -0,0 +1,21 @@
+import { Kbd } from "~/ui/kbd"
+import { cn } from "~/utils/css"
+
+interface KeyboardHintProps {
+ keys: string | string[]
+ label: string
+ className?: string
+}
+
+export const KeyboardHint = ({ keys, label, className }: KeyboardHintProps) => {
+ const keyArray = Array.isArray(keys) ? keys : [keys]
+
+ return (
+
+ {keyArray.map((key) => (
+ {key}
+ ))}
+ {label}
+
+ )
+}
diff --git a/docs/app/components/command-k/components/results-footer-note.tsx b/docs/app/components/command-k/components/results-footer-note.tsx
new file mode 100644
index 0000000..1c2dd41
--- /dev/null
+++ b/docs/app/components/command-k/components/results-footer-note.tsx
@@ -0,0 +1,15 @@
+import { useTranslation } from "react-i18next"
+
+export const ResultsFooterNote = () => {
+ const { t } = useTranslation()
+ return (
+
+ {t("p.search_by")}{" "}
+
+
+ Forge 42
+
+
+
+ )
+}
diff --git a/docs/app/components/command-k/components/results-footer.tsx b/docs/app/components/command-k/components/results-footer.tsx
new file mode 100644
index 0000000..4aef83a
--- /dev/null
+++ b/docs/app/components/command-k/components/results-footer.tsx
@@ -0,0 +1,28 @@
+import { useTranslation } from "react-i18next"
+import { cn } from "~/utils/css"
+import { KeyboardHint } from "./keyboard-hint"
+import { ResultsFooterNote } from "./results-footer-note"
+
+export const ResultsFooter = ({
+ resultsCount,
+ query,
+}: {
+ resultsCount: number
+ query: string
+}) => {
+ const { t } = useTranslation()
+ if (!query || resultsCount === 0) return null
+
+ return (
+
+
+
{t("text.result", { count: resultsCount })}
+
+
+
+
+
+
+
+ )
+}
diff --git a/docs/app/components/command-k/components/search-history.tsx b/docs/app/components/command-k/components/search-history.tsx
new file mode 100644
index 0000000..e227513
--- /dev/null
+++ b/docs/app/components/command-k/components/search-history.tsx
@@ -0,0 +1,122 @@
+import { useTranslation } from "react-i18next"
+import { Icon } from "~/ui/icon/icon"
+import { cn } from "~/utils/css"
+import type { HistoryItem } from "../search-types"
+import { SearchResultRow } from "./search-result"
+interface SearchHistoryProps {
+ history: HistoryItem[]
+ onSelect: (item: HistoryItem) => void
+ onRemove: (id: string) => void
+ onClear: () => void
+}
+
+const SearchHistoryHeader = ({ onClear }: Pick) => {
+ const { t } = useTranslation()
+ return (
+
+
+
+ {t("text.recent_searches")}
+
+
+
+ )
+}
+
+const ClearHistoryButton = ({ onClear }: Pick) => {
+ const { t } = useTranslation()
+ return (
+
+
+ {t("buttons.clear")}
+
+ )
+}
+
+const RemoveItemButton = ({
+ onRemove,
+ id,
+}: {
+ onRemove: Pick["onRemove"]
+ id: string
+}) => (
+ {
+ e.stopPropagation()
+ onRemove(id)
+ }}
+ className={cn(
+ "-translate-y-1/2 absolute top-1/2 right-2 flex h-6 w-6 items-center justify-center rounded-full border opacity-0 transition-all duration-150 group-hover:opacity-100",
+ "border-[var(--color-history-remove-border)] bg-[var(--color-history-remove-bg)] text-[var(--color-history-remove-text)]",
+ "hover:border-[var(--color-history-remove-hover-border)] hover:text-[var(--color-history-remove-hover-text)]"
+ )}
+ title="Remove from history"
+ aria-label={"Remove from history"}
+ >
+
+
+)
+
+const HistoryItemRow = ({
+ item,
+ index,
+ onSelect,
+ onRemove,
+}: {
+ item: HistoryItem
+ index: number
+ onSelect: Pick["onSelect"]
+ onRemove: Pick["onRemove"]
+}) => (
+
+ onSelect(item)}
+ matchType={item.type ?? "heading"}
+ />
+
+
+)
+
+const HistoryItemsList = ({
+ history,
+ onSelect,
+ onRemove,
+}: {
+ history: HistoryItem[]
+ onSelect: Pick["onSelect"]
+ onRemove: Pick["onRemove"]
+}) => (
+
+ {history.map((item, index) => (
+
+ ))}
+
+)
+
+export const SearchHistory = ({ history, onSelect, onRemove, onClear }: SearchHistoryProps) => {
+ if (history.length === 0) return null
+ return (
+
+
+
+
+ )
+}
diff --git a/docs/app/components/command-k/components/search-input.tsx b/docs/app/components/command-k/components/search-input.tsx
new file mode 100644
index 0000000..5dcaaa6
--- /dev/null
+++ b/docs/app/components/command-k/components/search-input.tsx
@@ -0,0 +1,47 @@
+import type { Ref } from "react"
+import { Icon } from "~/ui/icon/icon"
+import { cn } from "~/utils/css"
+
+interface SearchInputProps {
+ value: string
+ onChange: (value: string) => void
+ placeholder: string
+ ref?: Ref
+}
+
+export function SearchInput({ value, onChange, placeholder, ref }: SearchInputProps) {
+ return (
+
+
+
onChange(e.target.value)}
+ placeholder={placeholder}
+ className={cn(
+ "flex-1 bg-transparent text-lg leading-6 outline-none",
+ "text-[var(--color-input-text)] placeholder-[var(--color-input-placeholder)]"
+ )}
+ autoComplete="off"
+ autoCorrect="off"
+ autoCapitalize="off"
+ spellCheck="false"
+ />
+
+
+ ESC
+
+
+
+ )
+}
diff --git a/docs/app/components/command-k/components/search-result.tsx b/docs/app/components/command-k/components/search-result.tsx
new file mode 100644
index 0000000..03bf909
--- /dev/null
+++ b/docs/app/components/command-k/components/search-result.tsx
@@ -0,0 +1,86 @@
+import { Icon } from "~/ui/icon/icon"
+import { cn } from "~/utils/css"
+import type { MatchType, SearchRecord } from "../search-types"
+
+interface SearchResultProps {
+ item: SearchRecord
+ highlightedText: string
+ isSelected: boolean
+ onClick: () => void
+ matchType: MatchType
+}
+
+const ResultIcon = ({
+ isSelected,
+ matchType,
+}: {
+ isSelected: boolean
+ matchType: MatchType
+}) => {
+ const iconName = matchType === "heading" ? "Hash" : "Pilcrow"
+
+ return (
+
+
+
+ )
+}
+
+const ResultTitle = ({
+ title,
+ highlightedText,
+ isSelected,
+}: {
+ title: string
+ highlightedText: string
+ isSelected: boolean
+}) => (
+
+ {/* biome-ignore lint/security/noDangerouslySetInnerHtml: rendering text */}
+
+
+)
+
+const ResultMetadata = ({ item, matchType }: Pick) => (
+
+ {item.title}
+ {matchType === "paragraph" && item.subtitle ? > {item.subtitle} : null}
+
+)
+
+const ResultContent = ({ item, highlightedText, isSelected, matchType }: Omit) => (
+
+
+
+
+)
+
+const useButtonStyles = (isSelected: boolean) =>
+ cn(
+ "flex w-full items-start gap-3 border-r-2 px-4 py-3 text-left transition-all duration-150",
+ "hover:bg-[var(--color-result-hover)] focus:outline-none focus:ring-2 focus:ring-[var(--color-trigger-focus-ring)]",
+ isSelected
+ ? "border-[var(--color-result-selected-border)] bg-[var(--color-result-selected)] shadow-sm"
+ : "border-transparent"
+ )
+
+export const SearchResultRow = ({ item, highlightedText, isSelected, onClick, matchType }: SearchResultProps) => {
+ const buttonStyles = useButtonStyles(isSelected)
+
+ return (
+
+
+
+
+ )
+}
diff --git a/docs/app/components/command-k/components/trigger-button.tsx b/docs/app/components/command-k/components/trigger-button.tsx
new file mode 100644
index 0000000..5d1ba8e
--- /dev/null
+++ b/docs/app/components/command-k/components/trigger-button.tsx
@@ -0,0 +1,45 @@
+import { Icon } from "~/ui/icon/icon"
+import { cn } from "~/utils/css"
+
+export const TriggerButton = ({
+ onOpen,
+ placeholder,
+}: {
+ onOpen: () => void
+ placeholder: string
+}) => (
+
+
+ {placeholder}
+
+
+ ⌘
+
+
+ K
+
+
+
+)
diff --git a/docs/app/components/command-k/create-search-index.ts b/docs/app/components/command-k/create-search-index.ts
new file mode 100644
index 0000000..f410db0
--- /dev/null
+++ b/docs/app/components/command-k/create-search-index.ts
@@ -0,0 +1,127 @@
+import type { Page } from "content-collections-types"
+import slug from "slug"
+import { getPageSlug } from "~/utils/get-page-slug"
+
+function cleanParagraph(raw: string) {
+ return (
+ raw
+ // strip inline code, bold, italics
+ .replace(/`([^`]+)`/g, "$1")
+ .replace(/\*\*([^*]+)\*\*/g, "$1")
+ .replace(/\*([^*]+)\*/g, "$1")
+ .replace(/_(.+?)_/g, "$1")
+ // strip markdown links [text](url)
+ .replace(/\[([^\]]+)\]\([^)]+\)/g, "$1")
+ // strip mdx attributes { ... } inline
+ .replace(/\{[^}]*\}/g, "")
+ // list bullets / ordered list markers at line start
+ .replace(/^\s*[-*+]\s+/gm, "")
+ .replace(/^\s*\d+\.\s+/gm, "")
+ // collapse whitespace
+ .replace(/\n{2,}/g, "\n")
+ .replace(/[ \t]+/g, " ")
+ .trim()
+ )
+}
+
+function stripCodeFences(src: string) {
+ return src.replace(/```[\s\S]*?```/g, "")
+}
+
+function splitIntoParagraphs(src: string) {
+ return src
+ .split(/\n\s*\n/g)
+ .map(cleanParagraph)
+ .filter((p) => p.length > 0)
+}
+
+const extractHeadingData = (match: RegExpMatchArray) => {
+ const [fullMatch, hashes, text] = match
+ return {
+ level: hashes.length,
+ text,
+ index: match.index || 0,
+ length: fullMatch.length,
+ }
+}
+
+function extractHeadingSections(rawMdx: string) {
+ const src = stripCodeFences(rawMdx)
+ const headingRegex = /^(#{1,6})\s+(.+?)\s*$/gm
+ const matches = Array.from(src.matchAll(headingRegex), extractHeadingData)
+
+ const usedAnchors = new Set()
+
+ const createUniqueAnchor = (baseAnchor: string) => {
+ let unique = baseAnchor
+ let n = 2
+ while (usedAnchors.has(unique)) {
+ unique = `${baseAnchor}-${n++}`
+ }
+ usedAnchors.add(unique)
+ return unique
+ }
+
+ const cleanHeadingText = (text: string) =>
+ text
+ .replace(/`([^`]+)`/g, "$1")
+ .replace(/\*\*([^*]+)\*\*/g, "$1")
+ .replace(/\*([^*]+)\*/g, "$1")
+ .replace(/\[([^\]]+)\]\([^)]+\)/g, "$1")
+ .replace(/\{[^}]*\}/g, "")
+ .trim()
+
+ if (matches.length === 0) {
+ const paragraphs = splitIntoParagraphs(src)
+ return paragraphs.length ? [{ heading: "_intro", anchor: "_intro", paragraphs }] : []
+ }
+
+ const sections = []
+
+ // we are adding intro section if content exists before first heading
+ const introBlock = src.slice(0, matches[0].index).trim()
+ if (introBlock) {
+ const introParas = splitIntoParagraphs(introBlock)
+ if (introParas.length) {
+ sections.push({ heading: "_intro", anchor: "_intro", paragraphs: introParas })
+ }
+ }
+
+ matches.forEach((match, i) => {
+ const nextMatch = matches[i + 1]
+ const block = src.slice(match.index + match.length, nextMatch?.index).trim()
+
+ const rawHeading = cleanHeadingText(match.text)
+ const baseAnchor = slug(rawHeading) || "_section"
+ const anchor = createUniqueAnchor(baseAnchor)
+ const paragraphs = splitIntoParagraphs(block)
+
+ sections.push({
+ heading: rawHeading,
+ anchor,
+ paragraphs,
+ })
+ })
+
+ return sections
+}
+
+export function createSearchIndex(pages: Page[]) {
+ return pages
+ .filter((page) => page.slug !== "_index")
+ .flatMap((page) => {
+ const pageSlug = getPageSlug(page)
+ const pageUrl = pageSlug.startsWith("/") ? pageSlug : `/${pageSlug}`
+ const sections = extractHeadingSections(page.rawMdx)
+ return sections.map((section) => {
+ const heading = section.heading === "_intro" ? page.title : section.heading
+
+ return {
+ id: `${pageUrl}#${section.anchor}`,
+ title: page.title,
+ subtitle: heading,
+ paragraphs: [heading, ...section.paragraphs],
+ }
+ })
+ })
+}
diff --git a/docs/app/components/command-k/hooks/use-debounce.ts b/docs/app/components/command-k/hooks/use-debounce.ts
new file mode 100644
index 0000000..1a01372
--- /dev/null
+++ b/docs/app/components/command-k/hooks/use-debounce.ts
@@ -0,0 +1,12 @@
+import { useEffect, useState } from "react"
+
+export function useDebounce(value: T, delay = 250) {
+ const [debouncedValue, setDebouncedValue] = useState(value)
+
+ useEffect(() => {
+ const id = setTimeout(() => setDebouncedValue(value), delay)
+ return () => clearTimeout(id)
+ }, [value, delay])
+
+ return debouncedValue
+}
diff --git a/docs/app/components/command-k/hooks/use-fuzzy-search.ts b/docs/app/components/command-k/hooks/use-fuzzy-search.ts
new file mode 100644
index 0000000..a7b3f6c
--- /dev/null
+++ b/docs/app/components/command-k/hooks/use-fuzzy-search.ts
@@ -0,0 +1,76 @@
+import type { FuzzySearchOptions, SearchRecord, SearchResult } from "../search-types"
+
+const DEFAULTS = {
+ threshold: 0.8, // results must score ≥ 0.8 to be considered relevant
+ minMatchCharLength: 2, // queries shorter than 2 chars are ignored
+}
+
+const clamp = (n: number, min: number, max: number) => (n < min ? min : n > max ? max : n)
+const toSearchable = (s: string) => s.toLowerCase().trim()
+const escapeRegExp = (s: string) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")
+
+const scoreMatchQuality = (query: string, text: string) => {
+ const q = toSearchable(query)
+ const t = toSearchable(text)
+ if (q.length < DEFAULTS.minMatchCharLength) return 0 // ignore very short queries
+ if (t === q) return 1 // exact match -> strongest
+ if (t.startsWith(q)) return 0.95 // starts with query -> very strong
+ if (t.includes(q)) return 0.85 // contains query -> weaker
+ return 0 // no match
+}
+
+const highlightSnippet = (text: string, query: string, maxLen = 120) => {
+ const trimmed = text.trim()
+ const q = toSearchable(query)
+ const idx = trimmed.toLowerCase().indexOf(q)
+
+ if (idx === -1) {
+ // if no match, just return truncated text
+ return trimmed.length > maxLen ? `${trimmed.slice(0, maxLen)}...` : trimmed
+ }
+
+ const half = Math.floor(maxLen / 2)
+ const start = Math.max(0, idx - half)
+ const end = Math.min(trimmed.length, idx + q.length + half)
+ const snippet = trimmed.slice(start, end)
+
+ const safe = escapeRegExp(q)
+ const marked = snippet.replace(
+ new RegExp(`(${safe})`, "gi"),
+ `$1 `
+ )
+
+ return `${start > 0 ? "..." : ""}${marked}${end < trimmed.length ? "..." : ""}`
+}
+
+export function useFuzzySearch(items: SearchRecord[], query: string, options?: FuzzySearchOptions) {
+ const threshold = clamp(options?.threshold ?? DEFAULTS.threshold, 0, 1)
+ const minLen = Math.max(0, options?.minMatchCharLength ?? DEFAULTS.minMatchCharLength)
+
+ const raw = query?.trim()
+ if (!raw || raw.length < minLen) return []
+
+ const results: SearchResult[] = []
+
+ for (const item of items) {
+ const paragraphs: ReadonlyArray = item.paragraphs ?? []
+
+ paragraphs.forEach((paragraph, paragraphIndex) => {
+ if (!paragraph) return
+
+ const score = scoreMatchQuality(raw, paragraph)
+
+ if (score >= threshold) {
+ results.push({
+ item,
+ score: clamp(score, 0, 1),
+ matchedText: paragraph,
+ highlightedText: highlightSnippet(paragraph, raw),
+ refIndex: paragraphIndex,
+ })
+ }
+ })
+ }
+
+ return results.sort((a, b) => (b.score !== a.score ? b.score - a.score : a.refIndex - b.refIndex))
+}
diff --git a/docs/app/components/command-k/hooks/use-keyboard-navigation.ts b/docs/app/components/command-k/hooks/use-keyboard-navigation.ts
new file mode 100644
index 0000000..fb4d480
--- /dev/null
+++ b/docs/app/components/command-k/hooks/use-keyboard-navigation.ts
@@ -0,0 +1,73 @@
+import { useEffect, useState } from "react"
+import type { SearchResult } from "../search-types"
+
+const KEYBOARD_SHORTCUTS = {
+ TOGGLE: "k",
+ ESCAPE: "Escape",
+ ARROW_DOWN: "ArrowDown",
+ ARROW_UP: "ArrowUp",
+ ENTER: "Enter",
+ TAB: "Tab",
+} as const
+
+interface UseKeyboardNavigationProps {
+ isOpen: boolean
+ results: SearchResult[]
+ onSelect: (result: SearchResult) => void
+ onClose: () => void
+ onToggle: () => void
+}
+
+export const useKeyboardNavigation = ({ isOpen, results, onSelect, onClose, onToggle }: UseKeyboardNavigationProps) => {
+ const [selectedIndex, setSelectedIndex] = useState(0)
+
+ useEffect(() => {
+ setSelectedIndex(0)
+ }, [])
+
+ useEffect(() => {
+ const handleKeyDown = (e: KeyboardEvent) => {
+ if ((e.metaKey || e.ctrlKey) && e.key === KEYBOARD_SHORTCUTS.TOGGLE) {
+ e.preventDefault()
+ onToggle()
+ return
+ }
+
+ if (e.key === KEYBOARD_SHORTCUTS.ESCAPE) {
+ onClose()
+ return
+ }
+
+ if (!isOpen) return
+
+ switch (e.key) {
+ case KEYBOARD_SHORTCUTS.ARROW_DOWN:
+ e.preventDefault()
+ setSelectedIndex((prev) => Math.min(prev + 1, results.length - 1))
+ break
+
+ case KEYBOARD_SHORTCUTS.ARROW_UP:
+ e.preventDefault()
+ setSelectedIndex((prev) => Math.max(prev - 1, 0))
+ break
+
+ case KEYBOARD_SHORTCUTS.ENTER:
+ e.preventDefault()
+ if (results[selectedIndex]) {
+ onSelect(results[selectedIndex])
+ }
+ break
+
+ case KEYBOARD_SHORTCUTS.TAB:
+ e.preventDefault()
+ setSelectedIndex((prev) => (prev + 1) % results.length)
+ break
+ }
+ }
+
+ document.addEventListener("keydown", handleKeyDown)
+ return () => document.removeEventListener("keydown", handleKeyDown)
+ }, [isOpen, results, selectedIndex, onSelect, onClose, onToggle])
+
+ return { selectedIndex }
+}
diff --git a/docs/app/components/command-k/hooks/use-modal-state.ts b/docs/app/components/command-k/hooks/use-modal-state.ts
new file mode 100644
index 0000000..68429e0
--- /dev/null
+++ b/docs/app/components/command-k/hooks/use-modal-state.ts
@@ -0,0 +1,16 @@
+import { useState } from "react"
+
+export const useModalState = (controlledIsOpen?: boolean, onOpenChange?: (open: boolean) => void) => {
+ const [internalIsOpen, setInternalIsOpen] = useState(false)
+
+ const isOpen = controlledIsOpen ?? internalIsOpen
+
+ const setIsOpen = (open: boolean) => {
+ onOpenChange ? onOpenChange(open) : setInternalIsOpen(open)
+ }
+
+ const openModal = () => setIsOpen(true)
+ const closeModal = () => setIsOpen(false)
+
+ return { isOpen, openModal, closeModal }
+}
diff --git a/docs/app/components/command-k/hooks/use-search-history.ts b/docs/app/components/command-k/hooks/use-search-history.ts
new file mode 100644
index 0000000..092a639
--- /dev/null
+++ b/docs/app/components/command-k/hooks/use-search-history.ts
@@ -0,0 +1,71 @@
+import { useEffect, useState } from "react"
+import { COMMAND_K_SEARCH_HISTORY, getStorageItem, removeStorageItem, setStorageItem } from "~/utils/local-storage"
+import { normalizeVersion } from "~/utils/version-resolvers"
+import type { HistoryItem } from "../search-types"
+
+const MAX_HISTORY_ITEMS = 10
+
+function keyFor(version: string) {
+ const { version: v } = normalizeVersion(version)
+ return `${COMMAND_K_SEARCH_HISTORY}-${v}`
+}
+
+export const useSearchHistory = (version: string) => {
+ const storageKey = keyFor(version)
+ const [history, setHistory] = useState([])
+
+ useEffect(() => {
+ try {
+ const stored = getStorageItem(storageKey)
+ if (!stored) {
+ setHistory([])
+ return
+ }
+ const parsed = JSON.parse(stored)
+ setHistory(Array.isArray(parsed) ? parsed : [])
+ } catch (err) {
+ // biome-ignore lint/suspicious/noConsole: keep for debugging
+ console.warn("Failed to load search history:", err)
+ setHistory([])
+ }
+ }, [storageKey])
+
+ useEffect(() => {
+ try {
+ setStorageItem(storageKey, JSON.stringify(history))
+ } catch (err) {
+ // biome-ignore lint/suspicious/noConsole: keep for debugging
+ console.warn("Failed to save search history:", err)
+ }
+ }, [history, storageKey])
+
+ const addToHistory = (item: HistoryItem) => {
+ setHistory((prev) => {
+ const idx = prev.findIndex((h) => h.id === item.id)
+ if (idx >= 0) {
+ const existing = prev[idx]
+ const updated = {
+ ...existing,
+ type: item.type ?? existing.type,
+ title: item.title ?? existing.title,
+ subtitle: item.subtitle ?? existing.subtitle,
+ paragraphs: item.paragraphs ?? existing.paragraphs,
+ highlightedText: item.highlightedText ?? existing.highlightedText,
+ }
+ return [updated, ...prev.slice(0, idx), ...prev.slice(idx + 1)].slice(0, MAX_HISTORY_ITEMS)
+ }
+ return [item, ...prev].slice(0, MAX_HISTORY_ITEMS)
+ })
+ }
+
+ const clearHistory = () => {
+ setHistory([])
+ removeStorageItem(storageKey)
+ }
+
+ const removeFromHistory = (itemId: string) => {
+ setHistory((prev) => prev.filter((item) => item.id !== itemId))
+ }
+
+ return { history, addToHistory, clearHistory, removeFromHistory }
+}
diff --git a/docs/app/components/command-k/hooks/use-search.ts b/docs/app/components/command-k/hooks/use-search.ts
new file mode 100644
index 0000000..607b86e
--- /dev/null
+++ b/docs/app/components/command-k/hooks/use-search.ts
@@ -0,0 +1,62 @@
+import { useEffect, useRef, useState } from "react"
+import { useFetcher } from "react-router"
+import z from "zod"
+import type { Version } from "~/utils/version-resolvers"
+import { versions } from "~/utils/versions"
+import type { SearchResult } from "../search-types"
+import { useDebounce } from "./use-debounce"
+
+export const commandKSearchParamsSchema = z.object({
+ query: z.string(),
+ version: z.enum(versions),
+})
+
+export type CommandKSearchParams = z.infer
+
+function createCommandKSearchParams(params: Record) {
+ const result = commandKSearchParamsSchema.safeParse(params)
+ if (!result.success) {
+ // biome-ignore lint/suspicious/noConsole: keep for debugging
+ console.error("Invalid parameters:", result.error)
+ return { params: null }
+ }
+
+ return { params: new URLSearchParams(result.data) }
+}
+
+const debounceMs = 250
+const minChars = 1
+
+export function useSearch({ version }: { version: Version }) {
+ const fetcher = useFetcher<{ results: SearchResult[] }>()
+ const [query, setQuery] = useState("")
+ const debouncedQuery = useDebounce(query, debounceMs)
+ const lastLoadedRef = useRef(null)
+
+ const results = query.trim() ? (fetcher.data?.results ?? []) : []
+
+ function search(q: string) {
+ setQuery(q)
+ }
+
+ useEffect(() => {
+ const trimmed = debouncedQuery.trim()
+ if (!trimmed || trimmed.length < minChars) {
+ lastLoadedRef.current = null
+ return
+ }
+
+ if (lastLoadedRef.current === trimmed) return
+ lastLoadedRef.current = trimmed
+
+ const { params } = createCommandKSearchParams({ query: trimmed, version })
+ if (!params) return
+
+ fetcher.load(`/search?${params.toString()}`)
+ }, [debouncedQuery, version, fetcher])
+
+ return {
+ results,
+ search,
+ }
+}
diff --git a/docs/app/components/command-k/search-types.ts b/docs/app/components/command-k/search-types.ts
new file mode 100644
index 0000000..f0bfcdd
--- /dev/null
+++ b/docs/app/components/command-k/search-types.ts
@@ -0,0 +1,26 @@
+export interface SearchRecord {
+ id: string //e.g "/configuration/editor#name" where name is the heading inside of the editor page under the configuration section
+ title: string // page title
+ subtitle: string // title of the "sections" inside the page
+ paragraphs: string[] // for that id (section of the current page) get all paragraphs as an array of strings
+}
+
+export interface SearchResult {
+ item: SearchRecord
+ score: number
+ matchedText: string
+ highlightedText: string
+ refIndex: number // 0 if heading, >0 if actual paragraph
+}
+
+export interface FuzzySearchOptions {
+ threshold: number
+ minMatchCharLength: number
+}
+
+export type MatchType = "heading" | "paragraph"
+
+export interface HistoryItem extends SearchRecord {
+ type?: MatchType
+ highlightedText?: string
+}
diff --git a/docs/app/components/github-contribute-links.tsx b/docs/app/components/github-contribute-links.tsx
new file mode 100644
index 0000000..02fb3a6
--- /dev/null
+++ b/docs/app/components/github-contribute-links.tsx
@@ -0,0 +1,28 @@
+import { useTranslation } from "react-i18next"
+import { useRouteLoaderData } from "react-router"
+import { createGitHubContributionLinks } from "~/utils/create-github-contribution-links"
+
+const linkStyles = "hover:text-[var(--color-text-accent)] hover:underline"
+
+export default function GithubContributeLinks({ pagePath }: { pagePath: string }) {
+ const { clientEnv } = useRouteLoaderData("root")
+ const { t } = useTranslation()
+
+ const { GITHUB_OWNER, GITHUB_REPO } = clientEnv
+
+ if (!GITHUB_OWNER || !GITHUB_REPO) {
+ return null
+ }
+
+ const { issueUrl, editUrl } = createGitHubContributionLinks({ pagePath, owner: GITHUB_OWNER, repo: GITHUB_REPO })
+ return (
+
+ )
+}
diff --git a/docs/app/components/header.tsx b/docs/app/components/header.tsx
new file mode 100644
index 0000000..cb521a4
--- /dev/null
+++ b/docs/app/components/header.tsx
@@ -0,0 +1,19 @@
+import { cn } from "~/utils/css"
+
+interface HeaderProps {
+ children: React.ReactNode
+ className?: string
+}
+
+export const Header = ({ children, className }: HeaderProps) => {
+ return (
+
+ )
+}
diff --git a/docs/app/components/icon-link.tsx b/docs/app/components/icon-link.tsx
new file mode 100644
index 0000000..3d64596
--- /dev/null
+++ b/docs/app/components/icon-link.tsx
@@ -0,0 +1,28 @@
+import type { ComponentProps } from "react"
+import { Icon } from "~/ui/icon/icon"
+import type { IconName } from "~/ui/icon/icons/types"
+import { cn } from "~/utils/css"
+
+interface IconLinkProps extends ComponentProps<"a"> {
+ name: IconName
+}
+
+export const IconLink = ({ name, className, ...props }: IconLinkProps) => {
+ const { href } = props
+ const isExternal = typeof href === "string" && /^https?:\/\//i.test(href)
+ return (
+
+
+
+ )
+}
diff --git a/docs/app/components/logo.tsx b/docs/app/components/logo.tsx
new file mode 100644
index 0000000..7362223
--- /dev/null
+++ b/docs/app/components/logo.tsx
@@ -0,0 +1,15 @@
+import type { ReactNode } from "react"
+import { href, useNavigate } from "react-router"
+
+export const Logo = ({ children }: { children: ReactNode }) => {
+ const navigate = useNavigate()
+ return (
+ // biome-ignore lint/a11y/useKeyWithClickEvents: we don't need keyboard access for this
+ navigate(href("/:version?/home"))}
+ className="relative block cursor-pointer font-semibold font-space text-[var(--color-text-active)] text-lg md:text-2xl xl:text-3xl"
+ >
+ {children}
+
+ )
+}
diff --git a/docs/app/components/mdx-wrapper.tsx b/docs/app/components/mdx-wrapper.tsx
new file mode 100644
index 0000000..0d29f10
--- /dev/null
+++ b/docs/app/components/mdx-wrapper.tsx
@@ -0,0 +1,26 @@
+import { MDXContent } from "@content-collections/mdx/react"
+import { Anchor } from "~/ui/anchor-tag"
+import { InfoAlert } from "~/ui/info-alert"
+import { InlineCode } from "~/ui/inline-code"
+import { ListItem } from "~/ui/list-item"
+import { OrderedList } from "~/ui/ordered-list"
+import { Strong } from "~/ui/strong-text"
+import { WarningAlert } from "~/ui/warning-alert"
+import { CodeBlock } from "./code-block/code-block"
+
+export const MDXWrapper = ({ content }: { content: string }) => (
+
+)
diff --git a/docs/app/components/modal.tsx b/docs/app/components/modal.tsx
new file mode 100644
index 0000000..6c2eef6
--- /dev/null
+++ b/docs/app/components/modal.tsx
@@ -0,0 +1,98 @@
+import { type ReactNode, useEffect, useRef } from "react"
+import { useScrollLock } from "~/hooks/use-scroll-lock"
+import { cn } from "~/utils/css"
+import { Backdrop } from "./backdrop"
+
+interface ModalProps {
+ isOpen: boolean
+ onClose: () => void
+ children: ReactNode
+ className?: string
+ getInitialFocus?: () => HTMLElement | null
+ restoreFocus?: boolean
+ ariaLabel?: string
+}
+
+export const Modal = ({
+ isOpen,
+ onClose,
+ children,
+ className,
+ getInitialFocus,
+ restoreFocus = true,
+ ariaLabel,
+}: ModalProps) => {
+ const modalRef = useRef(null)
+ const previouslyFocusedRef = useRef(null)
+
+ useScrollLock(isOpen)
+
+ useEffect(() => {
+ if (!isOpen) return
+ previouslyFocusedRef.current = document.activeElement as HTMLElement | null
+ return () => {
+ if (restoreFocus) previouslyFocusedRef.current?.focus?.()
+ }
+ }, [isOpen, restoreFocus])
+
+ useEffect(() => {
+ if (!isOpen) return
+ const id = requestAnimationFrame(() => {
+ const candidate = getInitialFocus?.()
+ if (candidate) {
+ candidate.focus()
+ return
+ }
+ const root = modalRef.current
+ if (!root) return
+ const firstFocusable = root.querySelector(
+ 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
+ )
+ firstFocusable?.focus()
+ })
+ return () => cancelAnimationFrame(id)
+ }, [isOpen, getInitialFocus])
+
+ useEffect(() => {
+ if (!isOpen) return
+ const onKey = (e: KeyboardEvent) => {
+ if (e.key === "Escape") onClose()
+ }
+ window.addEventListener("keydown", onKey)
+ return () => window.removeEventListener("keydown", onKey)
+ }, [isOpen, onClose])
+
+ const handleOverlayPointerDown = (e: React.PointerEvent) => {
+ const root = modalRef.current
+ if (!root) return
+ if (!root.contains(e.target as Node)) onClose()
+ }
+
+ if (!isOpen) return null
+
+ return (
+ <>
+
+
+
+
e.stopPropagation()}
+ >
+ {children}
+
+
+
+ >
+ )
+}
diff --git a/docs/app/components/page-mdx-article.tsx b/docs/app/components/page-mdx-article.tsx
new file mode 100644
index 0000000..538cfdb
--- /dev/null
+++ b/docs/app/components/page-mdx-article.tsx
@@ -0,0 +1,21 @@
+import type { Page } from "content-collections-types"
+import { Title } from "~/ui/title"
+import { MDXWrapper } from "./mdx-wrapper"
+
+export default function PageMdxArticle({ page }: { page: Page }) {
+ return (
+
+
+
+ {page.title}
+
+ {page.description && (
+
+ {page.description}
+
+ )}
+
+
+
+ )
+}
diff --git a/docs/app/components/page-navigation.tsx b/docs/app/components/page-navigation.tsx
new file mode 100644
index 0000000..dde6f3d
--- /dev/null
+++ b/docs/app/components/page-navigation.tsx
@@ -0,0 +1,75 @@
+import clsx from "clsx"
+import { useTranslation } from "react-i18next"
+import { Link } from "react-router"
+import { Icon } from "~/ui/icon/icon"
+import { cn } from "~/utils/css"
+
+interface PageNavigationItem {
+ title: string
+ to: string
+}
+
+interface PageNavigationProps {
+ previous?: PageNavigationItem
+ next?: PageNavigationItem
+}
+
+interface PageNavigationLinkProps {
+ item: PageNavigationItem
+ direction: "previous" | "next"
+ label: string
+}
+
+function PageNavigationLink({ item, direction, label }: PageNavigationLinkProps) {
+ const isPrevious = direction === "previous"
+
+ return (
+
+
{label}
+
+ {isPrevious &&
}
+ {item.title}
+ {!isPrevious &&
}
+
+
+ )
+}
+
+/**
+ * A pagination navigation component that displays "Previous" and "Next" links with
+ * accessible labels, styled arrows, and localized link text.
+ *
+ * It accepts optional `previous` and `next` props, each containing a `title` and `to` URL.
+ * When present, the component renders navigational links with arrow indicators.
+ *
+ * Example usage:
+ *
+ *
+ * @param previous - Optional previous page link data with title and path.
+ * @param next - Optional next page link data with title and path.
+ */
+export function PageNavigation({ previous, next }: PageNavigationProps) {
+ const { t } = useTranslation()
+
+ return (
+
+ {previous ? :
}
+
+ {next ? :
}
+
+ )
+}
diff --git a/docs/app/components/sidebar/build-breadcrumbs.ts b/docs/app/components/sidebar/build-breadcrumbs.ts
new file mode 100644
index 0000000..3a8ebe5
--- /dev/null
+++ b/docs/app/components/sidebar/build-breadcrumbs.ts
@@ -0,0 +1,41 @@
+import type { Page } from "content-collections-types"
+import type { SidebarSection } from "~/utils/create-sidebar-tree"
+import { buildDocPathFromSlug } from "~/utils/path-builders"
+
+export const buildBreadcrumbs = (
+ items: SidebarSection[],
+ pathname: string,
+ documentationPages: Pick[] = []
+) => {
+ // for standalone pages: /:filename
+ for (const page of documentationPages) {
+ const docPath = buildDocPathFromSlug(page.slug)
+ if (docPath === pathname) {
+ return [page.title]
+ }
+ }
+
+ // for sectioned pages: /:section/:subsection?/:filename
+ let trail: string[] = []
+
+ const walk = (section: SidebarSection, acc: string[]): boolean => {
+ for (const doc of section.documentationPages) {
+ const docPath = buildDocPathFromSlug(doc.slug)
+ if (docPath === pathname) {
+ trail = [...acc, section.title, doc.title]
+ return true
+ }
+ }
+
+ for (const sub of section.subsections) {
+ if (walk(sub, [...acc, section.title])) return true
+ }
+ return false
+ }
+
+ for (const root of items) {
+ if (walk(root, [])) break
+ }
+
+ return trail
+}
diff --git a/docs/app/components/sidebar/desktop-sidebar.tsx b/docs/app/components/sidebar/desktop-sidebar.tsx
new file mode 100644
index 0000000..e774ff2
--- /dev/null
+++ b/docs/app/components/sidebar/desktop-sidebar.tsx
@@ -0,0 +1,14 @@
+import type { SidebarTree } from "~/utils/create-sidebar-tree"
+import { cn } from "~/utils/css"
+import { SidebarContent } from "./sidebar-content"
+
+export const DesktopSidebarPanel = ({ sidebarTree, className }: { sidebarTree: SidebarTree; className: string }) => (
+
+
+
+)
diff --git a/docs/app/components/sidebar/mobile-sidebar-context.tsx b/docs/app/components/sidebar/mobile-sidebar-context.tsx
new file mode 100644
index 0000000..6573f75
--- /dev/null
+++ b/docs/app/components/sidebar/mobile-sidebar-context.tsx
@@ -0,0 +1,29 @@
+import { createContext, useContext, useState } from "react"
+
+interface MobileSidebarContextValue {
+ isOpen: boolean
+ open: () => void
+ close: () => void
+ toggle: () => void
+}
+
+const MobileSidebarContext = createContext(null)
+
+export const MobileSidebarProvider = ({ children }: { children: React.ReactNode }) => {
+ const [isOpen, setOpen] = useState(false)
+
+ const value: MobileSidebarContextValue = {
+ isOpen,
+ open: () => setOpen(true),
+ close: () => setOpen(false),
+ toggle: () => setOpen((prev) => !prev),
+ }
+
+ return {children}
+}
+
+export const useMobileSidebar = () => {
+ const ctx = useContext(MobileSidebarContext)
+ if (!ctx) throw new Error("Missing MobileSidebarProvider")
+ return ctx
+}
diff --git a/docs/app/components/sidebar/mobile-sidebar.tsx b/docs/app/components/sidebar/mobile-sidebar.tsx
new file mode 100644
index 0000000..d5a5b97
--- /dev/null
+++ b/docs/app/components/sidebar/mobile-sidebar.tsx
@@ -0,0 +1,96 @@
+import { useParams } from "react-router"
+import { useDocumentationLayoutLoaderData } from "~/hooks/use-documentation-layout-loader-data"
+import { BreadcrumbItem, Breadcrumbs } from "~/ui/breadcrumbs"
+import { IconButton } from "~/ui/icon-button"
+import { Icon } from "~/ui/icon/icon"
+import type { SidebarTree } from "~/utils/create-sidebar-tree"
+import { cn } from "~/utils/css"
+import { buildBreadcrumbs } from "./build-breadcrumbs"
+import { useMobileSidebar } from "./mobile-sidebar-context"
+import { SidebarContent } from "./sidebar-content"
+
+const MobileSidebarMenuButton = () => {
+ const { open } = useMobileSidebar()
+
+ return (
+
+ )
+}
+
+export const MobileSidebarHeader = () => {
+ const params = useParams()
+ const {
+ sidebarTree: { sections, documentationPages },
+ } = useDocumentationLayoutLoaderData()
+ const { section, subsection, filename } = params
+ const currentPath = `/${[section, subsection, filename].filter(Boolean).join("/")}`
+ const breadcrumbs = buildBreadcrumbs(sections, currentPath, documentationPages)
+ return (
+
+
+
+ {breadcrumbs.map((item) => (
+ {item}
+ ))}
+
+
+ )
+}
+
+export const MobileSidebarOverlay = () => {
+ const { isOpen, close } = useMobileSidebar()
+
+ return (
+ // biome-ignore lint/a11y/useKeyWithClickEvents: We don't need keyboard support for this overlay
+
+ )
+}
+
+const MobileSidebarCloseButton = () => {
+ const { close } = useMobileSidebar()
+
+ return (
+
+
+
+ )
+}
+
+export const MobileSidebarPanel = ({
+ sidebarTree,
+ className,
+}: {
+ sidebarTree: SidebarTree
+ className: string
+}) => {
+ const { close, isOpen } = useMobileSidebar()
+ return (
+
+
+
+
+ )
+}
diff --git a/docs/app/components/sidebar/sidebar-content.tsx b/docs/app/components/sidebar/sidebar-content.tsx
new file mode 100644
index 0000000..dd020df
--- /dev/null
+++ b/docs/app/components/sidebar/sidebar-content.tsx
@@ -0,0 +1,44 @@
+import { useMobileView } from "~/hooks/use-mobile-view"
+import { Accordion } from "~/ui/accordion"
+import type { SidebarTree } from "~/utils/create-sidebar-tree"
+import { buildStandaloneTo } from "~/utils/path-builders"
+import { useCurrentVersion } from "~/utils/version-resolvers"
+import { DocumentationNavLink, SectionItem } from "./sidebar-items"
+
+export const SidebarContent = ({
+ sidebarTree,
+ onClose,
+}: {
+ sidebarTree: SidebarTree
+ onClose?: () => void
+}) => {
+ const { isMobile } = useMobileView()
+ const handle = isMobile ? onClose : undefined
+ const { sections, documentationPages } = sidebarTree
+ const version = useCurrentVersion()
+ return (
+
+ {documentationPages.length > 0 && (
+
+ {documentationPages.map((p) => (
+
+ ))}
+
+ )}
+
+
+ {sections.map((item) => (
+
+ ))}
+
+
+ )
+}
diff --git a/docs/app/components/sidebar/sidebar-items.tsx b/docs/app/components/sidebar/sidebar-items.tsx
new file mode 100644
index 0000000..987812f
--- /dev/null
+++ b/docs/app/components/sidebar/sidebar-items.tsx
@@ -0,0 +1,111 @@
+import { NavLink } from "react-router"
+import { AccordionItem } from "~/ui/accordion"
+import { buildSectionedTo } from "~/utils/path-builders"
+import { useCurrentVersion } from "~/utils/version-resolvers"
+import type { SidebarSection } from "./sidebar"
+
+const getIndentClass = (depth: number) => {
+ const indentMap = { 0: "ml-4", 1: "ml-7", 2: "ml-10" }
+ return indentMap[depth as keyof typeof indentMap] || "ml-10"
+}
+
+type DocumentationNavLinkProps = {
+ title: string
+ to: string
+ depth?: number
+ onClick?: () => void
+ className?: string
+}
+
+export function DocumentationNavLink({ title, to, depth = 0, onClick, className }: DocumentationNavLinkProps) {
+ const indentClass = getIndentClass(depth)
+ return (
+
+ `block rounded-md px-3 py-0.5 text-sm md:text-base ${indentClass} ${className}
+ ${isPending ? "text-[var(--color-text-hover)]" : ""}
+ ${
+ isActive
+ ? "bg-[var(--color-background-active)] font-medium text-[var(--color-text-active)]"
+ : "text-[var(--color-text-normal)] hover:text-[var(--color-text-hover)]"
+ }`
+ }
+ >
+ {title}
+
+ )
+}
+
+interface SectionItemProps {
+ item: SidebarSection
+ depth?: number
+ onItemClick?: () => void
+ className?: string
+}
+
+const SectionTitle = ({ title }: { title: string }) => {
+ return (
+
+ {title}
+
+ )
+}
+
+export const SectionItem = ({ item, depth = 0, onItemClick, className = "my-1" }: SectionItemProps) => {
+ const isTopLevel = depth === 0
+ const version = useCurrentVersion()
+ const content = (
+
+ {item.documentationPages.length > 0 && (
+
+ {item.documentationPages.map((doc) => (
+
+ ))}
+
+ )}
+
+ {item.subsections.length > 0 && (
+
+ {item.subsections.map((subsection) => (
+
+ ))}
+
+ )}
+
+ )
+
+ if (isTopLevel) {
+ return (
+
+ )
+ }
+
+ return (
+
+
+ {content}
+
+ )
+}
diff --git a/docs/app/components/sidebar/sidebar.tsx b/docs/app/components/sidebar/sidebar.tsx
new file mode 100644
index 0000000..b5a2bf8
--- /dev/null
+++ b/docs/app/components/sidebar/sidebar.tsx
@@ -0,0 +1,32 @@
+import type { SidebarTree } from "~/utils/create-sidebar-tree"
+import { cn } from "~/utils/css"
+import { DesktopSidebarPanel } from "./desktop-sidebar"
+import { MobileSidebarHeader, MobileSidebarOverlay, MobileSidebarPanel } from "./mobile-sidebar"
+import { MobileSidebarProvider } from "./mobile-sidebar-context"
+
+export type SidebarSection = {
+ title: string
+ slug: string
+ subsections: SidebarSection[]
+ documentationPages: { title: string; slug: string }[]
+}
+
+interface SidebarProps {
+ sidebarTree: SidebarTree
+ className?: string
+}
+
+export const Sidebar = ({ sidebarTree, className = "" }: SidebarProps) => {
+ return (
+ <>
+
+
+
+
+
+
+
+
+ >
+ )
+}
diff --git a/docs/app/components/sidebar/tests/build-breadcrumbs.test.ts b/docs/app/components/sidebar/tests/build-breadcrumbs.test.ts
new file mode 100644
index 0000000..f1c6451
--- /dev/null
+++ b/docs/app/components/sidebar/tests/build-breadcrumbs.test.ts
@@ -0,0 +1,105 @@
+import type { Page } from "content-collections-types"
+import type { SidebarSection } from "~/utils/create-sidebar-tree"
+import { buildBreadcrumbs } from "../build-breadcrumbs"
+
+const makePage = (slug: string, title: string, section = slug.split("/")[0] ?? ""): Page => ({
+ content: "",
+ slug,
+ section,
+ rawMdx: "",
+ title,
+ summary: "",
+ description: "",
+ _meta: {
+ filePath: "",
+ fileName: "",
+ directory: "",
+ path: "",
+ extension: ".mdx",
+ },
+})
+
+const makeSection = (overrides: Partial = {}): SidebarSection => ({
+ title: "",
+ slug: "",
+ documentationPages: [],
+ subsections: [],
+ ...overrides,
+})
+
+const makeStandalone = (slug: string, title: string): Pick => ({
+ slug,
+ title,
+})
+
+describe("buildBreadcrumbs", () => {
+ it("returns [] when pathname doesn't match any doc", () => {
+ const items: SidebarSection[] = [
+ makeSection({
+ title: "Getting Started",
+ slug: "getting-started",
+ documentationPages: [makePage("getting-started/intro", "Intro")],
+ }),
+ ]
+
+ expect(buildBreadcrumbs(items, "/getting-started/unknown")).toEqual([])
+ })
+
+ it("returns [section, doc] for a top-level doc within a section", () => {
+ const items: SidebarSection[] = [
+ makeSection({
+ title: "Getting Started",
+ slug: "getting-started",
+ documentationPages: [makePage("getting-started/intro", "Intro")],
+ }),
+ ]
+
+ expect(buildBreadcrumbs(items, "/getting-started/intro")).toEqual(["Getting Started", "Intro"])
+ })
+
+ it("returns full trail for a nested doc (root → sub → doc)", () => {
+ const items: SidebarSection[] = [
+ makeSection({
+ title: "Configuration",
+ slug: "configuration",
+ subsections: [
+ makeSection({
+ title: "Advanced",
+ slug: "configuration/advanced",
+ documentationPages: [makePage("configuration/advanced/tuning", "Tuning")],
+ }),
+ ],
+ documentationPages: [makePage("configuration/setup", "Setup")],
+ }),
+ ]
+
+ expect(buildBreadcrumbs(items, "/configuration/advanced/tuning")).toEqual(["Configuration", "Advanced", "Tuning"])
+ })
+
+ it("returns [] for an empty sidebar", () => {
+ const items: SidebarSection[] = []
+ expect(buildBreadcrumbs(items, "/any-path")).toEqual([])
+ })
+
+ it("returns [doc] for a standalone top-level doc", () => {
+ const items: SidebarSection[] = []
+ const standalone = [makeStandalone("changelog", "Changelog")]
+
+ expect(buildBreadcrumbs(items, "/changelog", standalone)).toEqual(["Changelog"])
+ })
+
+ it("matches sectioned path when pathname is sectioned, and standalone when pathname is standalone", () => {
+ const items: SidebarSection[] = [
+ makeSection({
+ title: "Guides",
+ slug: "guides",
+ documentationPages: [makePage("guides/quickstart", "Quickstart")],
+ }),
+ ]
+ const standalone = [makeStandalone("quickstart", "Quickstart")]
+
+ expect(buildBreadcrumbs(items, "/guides/quickstart", standalone)).toEqual(["Guides", "Quickstart"])
+
+ expect(buildBreadcrumbs(items, "/quickstart", standalone)).toEqual(["Quickstart"])
+ })
+})
diff --git a/docs/app/components/table-of-content.tsx b/docs/app/components/table-of-content.tsx
new file mode 100644
index 0000000..7fe0809
--- /dev/null
+++ b/docs/app/components/table-of-content.tsx
@@ -0,0 +1,148 @@
+import { useCallback, useEffect, useRef } from "react"
+import { Link, useLocation, useNavigate } from "react-router"
+import { useActiveHeadingId } from "~/hooks/use-active-heading-id"
+import type { HeadingItem } from "~/utils/extract-heading-tree-from-mdx"
+import { scrollIntoView } from "~/utils/scroll-into-view"
+
+interface TableOfContentsProps {
+ items: HeadingItem[]
+ className?: string
+}
+
+interface TocItemProps {
+ item: HeadingItem
+ depth?: number
+ activeId: string | null
+ onItemClick: (slug: string) => Promise
+}
+
+const BASE_PADDING = 12
+const DEPTH_MULTIPLIER = 16
+
+const calculatePadding = (depth: number) => BASE_PADDING + depth * DEPTH_MULTIPLIER
+
+const getItemClassName = (depth: number, isActive: boolean) => {
+ return [
+ "block py-1.5 text-sm md:text-base hover:text-[var(--color-text-hover)]",
+ depth === 0 && "font-medium",
+ isActive ? "text-[var(--color-text-accent)]" : "text-[var(--color-text-active)]",
+ ]
+ .filter(Boolean)
+ .join(" ")
+}
+
+const TocItem = ({ item, depth = 0, activeId, onItemClick }: TocItemProps) => {
+ const paddingLeft = calculatePadding(depth)
+ const isActive = activeId === item.slug
+ const className = getItemClassName(depth, isActive)
+
+ const handleClick = useCallback(
+ async (e: React.MouseEvent) => {
+ e.preventDefault()
+ await onItemClick(item.slug)
+ },
+ [item.slug, onItemClick]
+ )
+
+ return (
+
+
+ {item.title}
+
+ {item.children.length > 0 && (
+
+ {item.children.map((child) => (
+
+ ))}
+
+ )}
+
+ )
+}
+
+const Navigation = ({
+ items,
+ activeId,
+ onItemClick,
+}: {
+ items: HeadingItem[]
+ activeId: string | null
+ onItemClick: (slug: string) => Promise
+}) => {
+ const navRef = useRef(null)
+
+ useEffect(() => {
+ if (!activeId) return
+ const nav = navRef.current
+ if (!nav) return
+
+ const el = nav.querySelector(`[data-toc-slug="${CSS.escape(activeId)}"]`)
+ if (!el) return
+
+ const padding = 24
+ const elTop = el.offsetTop
+ const elBottom = elTop + el.offsetHeight
+ const viewTop = nav.scrollTop
+ const viewBottom = viewTop + nav.clientHeight
+
+ if (elTop < viewTop + padding) {
+ nav.scrollTo({ top: Math.max(elTop - padding, 0), behavior: "smooth" })
+ } else if (elBottom > viewBottom - padding) {
+ nav.scrollTo({ top: elBottom - nav.clientHeight + padding, behavior: "smooth" })
+ }
+ }, [activeId])
+
+ return (
+
+
+ {items.map((item) => (
+
+ ))}
+
+
+ )
+}
+
+const TableOfContentsHeader = () => (
+
+ On this page
+
+)
+
+export const TableOfContents = ({ items }: TableOfContentsProps) => {
+ const location = useLocation()
+ const navigate = useNavigate()
+ const { activeId, setManualActiveId } = useActiveHeadingId()
+
+ const handleItemClick = async (slug: string) => {
+ setManualActiveId(slug)
+
+ const newHash = `#${slug}`
+ if (location.hash !== newHash) {
+ navigate(`${location.pathname}${newHash}`, { replace: true })
+ }
+
+ const fakeEvent = { preventDefault: () => {} } as React.MouseEvent
+ await scrollIntoView(fakeEvent, slug)
+ }
+
+ if (items.length === 0) return null
+
+ return (
+ <>
+
+
+ >
+ )
+}
diff --git a/docs/app/components/theme-toggle.tsx b/docs/app/components/theme-toggle.tsx
new file mode 100644
index 0000000..0b0ea47
--- /dev/null
+++ b/docs/app/components/theme-toggle.tsx
@@ -0,0 +1,31 @@
+import { useLayoutEffect, useState } from "react"
+import { IconButton } from "~/ui/icon-button"
+import { applyTheme, getCurrentTheme } from "~/utils/theme"
+
+export function ThemeToggle() {
+ const [theme, setTheme] = useState<"light" | "dark" | null>(null)
+
+ useLayoutEffect(() => {
+ setTheme(getCurrentTheme())
+ }, [])
+
+ const toggle = () => {
+ if (!theme) return
+ const next = theme === "dark" ? "light" : "dark"
+ applyTheme(next)
+ setTheme(next)
+ }
+
+ if (theme === null) {
+ return
+ }
+
+ const isDarkTheme = theme === "dark"
+ return (
+
+ )
+}
diff --git a/docs/app/components/versions-dropdown.tsx b/docs/app/components/versions-dropdown.tsx
new file mode 100644
index 0000000..a6b0d1b
--- /dev/null
+++ b/docs/app/components/versions-dropdown.tsx
@@ -0,0 +1,55 @@
+import { useState } from "react"
+import { useNavigate } from "react-router"
+import { Icon } from "~/ui/icon/icon"
+import { homepageUrlWithVersion, isKnownVersion, useCurrentVersion } from "~/utils/version-resolvers"
+import { versions } from "~/utils/versions"
+
+export function VersionDropdown() {
+ const navigate = useNavigate()
+ const currentVersion = useCurrentVersion()
+ const [selectedVersion, setSelectedVersion] = useState(currentVersion)
+
+ function onChange(e: React.ChangeEvent) {
+ const next = e.target.value
+ if (next === currentVersion) return
+
+ setSelectedVersion(isKnownVersion(next) ? next : currentVersion)
+
+ const to = homepageUrlWithVersion(next)
+ const nav = () => {
+ navigate(to)
+ e.target.blur()
+ }
+ if (document.startViewTransition) document.startViewTransition(nav)
+ else nav()
+ }
+
+ return (
+
+
+ {versions.map((v) => (
+
+ {v}
+
+ ))}
+
+
+
+
+ )
+}
diff --git a/docs/app/entry.client.tsx b/docs/app/entry.client.tsx
new file mode 100644
index 0000000..e9ab99b
--- /dev/null
+++ b/docs/app/entry.client.tsx
@@ -0,0 +1,53 @@
+import i18next from "i18next"
+import LanguageDetector from "i18next-browser-languagedetector"
+import Backend from "i18next-http-backend"
+import { StrictMode, startTransition } from "react"
+import { hydrateRoot } from "react-dom/client"
+import { I18nextProvider, initReactI18next } from "react-i18next"
+import { HydratedRouter } from "react-router/dom"
+import { getInitialNamespaces } from "remix-i18next/client"
+import i18n from "~/localization/i18n"
+
+async function hydrate() {
+ // eslint-disable-next-line import/no-named-as-default-member
+ await i18next
+ .use(initReactI18next) // Tell i18next to use the react-i18next plugin
+ .use(LanguageDetector) // Setup a client-side language detector
+ .use(Backend) // Setup your backend
+ .init({
+ ...i18n, // spread the configuration
+ // This function detects the namespaces your routes rendered while SSR use
+ ns: getInitialNamespaces(),
+ backend: {
+ loadPath: "/resource/locales?lng={{lng}}&ns={{ns}}",
+ },
+ detection: {
+ // Here only enable htmlTag detection, we'll detect the language only
+ // server-side with remix-i18next, by using the `` attribute
+ // we can communicate to the client the language detected server-side
+ order: ["htmlTag"],
+ // Because we only use htmlTag, there's no reason to cache the language
+ // on the browser, so we disable it
+ caches: [],
+ },
+ })
+
+ startTransition(() => {
+ hydrateRoot(
+ document,
+
+
+
+
+
+ )
+ })
+}
+
+if (window.requestIdleCallback) {
+ window.requestIdleCallback(hydrate)
+} else {
+ // Safari doesn't support requestIdleCallback
+ // https://caniuse.com/requestidlecallback
+ window.setTimeout(hydrate, 1)
+}
diff --git a/docs/app/entry.server.tsx b/docs/app/entry.server.tsx
new file mode 100644
index 0000000..4e466ef
--- /dev/null
+++ b/docs/app/entry.server.tsx
@@ -0,0 +1,79 @@
+import { PassThrough } from "node:stream"
+import { createReadableStreamFromReadable } from "@react-router/node"
+import { createInstance } from "i18next"
+import { isbot } from "isbot"
+import { renderToPipeableStream } from "react-dom/server"
+import { I18nextProvider, initReactI18next } from "react-i18next"
+import { type AppLoadContext, type EntryContext, ServerRouter } from "react-router"
+import i18n from "./localization/i18n" // your i18n configuration file
+import i18nextOpts from "./localization/i18n.server"
+import { resources } from "./localization/resource"
+import { preloadSearchIndexes } from "./server/search-index"
+import { preloadContentCollections } from "./utils/load-content"
+
+// Reject all pending promises from handler functions after 10 seconds
+export const streamTimeout = 10000
+
+await preloadContentCollections()
+await preloadSearchIndexes()
+
+export default async function handleRequest(
+ request: Request,
+ responseStatusCode: number,
+ responseHeaders: Headers,
+ context: EntryContext,
+ appContext: AppLoadContext
+) {
+ const callbackName = isbot(request.headers.get("user-agent")) ? "onAllReady" : "onShellReady"
+ const instance = createInstance()
+ const lng = appContext.lang
+ // biome-ignore lint/suspicious/noExplicitAny:
+ const ns = i18nextOpts.getRouteNamespaces(context as any)
+
+ await instance
+ .use(initReactI18next) // Tell our instance to use react-i18next
+ .init({
+ ...i18n, // spread the configuration
+ lng, // The locale we detected above
+ ns, // The namespaces the routes about to render wants to use
+ resources,
+ })
+
+ return new Promise((resolve, reject) => {
+ let didError = false
+
+ const { pipe, abort } = renderToPipeableStream(
+
+
+ ,
+ {
+ [callbackName]: () => {
+ const body = new PassThrough()
+ const stream = createReadableStreamFromReadable(body)
+ responseHeaders.set("Content-Type", "text/html")
+
+ resolve(
+ // @ts-expect-error - We purposely do not define the body as existent so it's not used inside loaders as it's injected there as well
+ appContext.body(stream, {
+ headers: responseHeaders,
+ status: didError ? 500 : responseStatusCode,
+ })
+ )
+
+ pipe(body)
+ },
+ onShellError(error: unknown) {
+ reject(error)
+ },
+ onError(error: unknown) {
+ didError = true
+ // biome-ignore lint/suspicious/noConsole: We console log the error
+ console.error(error)
+ },
+ }
+ )
+ // Abort the streaming render pass after 11 seconds so to allow the rejected
+ // boundaries to be flushed
+ setTimeout(abort, streamTimeout + 1000)
+ })
+}
diff --git a/docs/app/env.server.ts b/docs/app/env.server.ts
new file mode 100644
index 0000000..61d3ae2
--- /dev/null
+++ b/docs/app/env.server.ts
@@ -0,0 +1,67 @@
+import { z } from "zod"
+
+const envSchema = z.object({
+ NODE_ENV: z.enum(["development", "production", "test"]).default("development"),
+ APP_ENV: z.enum(["development", "staging", "production"]).default("development"),
+ GITHUB_OWNER: z.string()?.optional(), // optional - for edit/report an issue for the documentation page
+ GITHUB_REPO: z.string()?.optional(), // optional - for edit/report an issue for the documentation page,
+ GITHUB_REPO_URL: z.string().optional(), // optional - for navigation to the github repository page
+})
+
+type ServerEnv = z.infer
+let env: ServerEnv
+
+/**
+ * Initializes and parses given environment variables using zod
+ * @returns Initialized env vars
+ */
+function initEnv() {
+ // biome-ignore lint/nursery/noProcessEnv: This should be the only place to use process.env directly
+ const envData = envSchema.safeParse(process.env)
+
+ if (!envData.success) {
+ // biome-ignore lint/suspicious/noConsole: We want this to be logged
+ console.error("❌ Invalid environment variables:", envData.error.flatten().fieldErrors)
+ throw new Error("Invalid environment variables")
+ }
+
+ env = envData.data
+ Object.freeze(env)
+
+ // Do not log the message when running tests
+ if (env.NODE_ENV !== "test") {
+ // biome-ignore lint/suspicious/noConsole: We want this to be logged
+ console.log("✅ Environment variables loaded successfully")
+ }
+ return env
+}
+
+export function getServerEnv() {
+ if (env) return env
+ return initEnv()
+}
+
+/**
+ * Helper function which returns a subset of the environment vars which are safe expose to the client.
+ * Dont expose any secrets or sensitive data here.
+ * Otherwise you would expose your server vars to the client if you returned them from here as this is
+ * directly sent in the root to the client and set on the window.env
+ * @returns Subset of the whole process.env to be passed to the client and used there
+ */
+export function getClientEnv() {
+ const serverEnv = getServerEnv()
+ return {
+ NODE_ENV: serverEnv.NODE_ENV,
+ GITHUB_OWNER: serverEnv.GITHUB_OWNER,
+ GITHUB_REPO: serverEnv.GITHUB_REPO,
+ GITHUB_REPO_URL: serverEnv.GITHUB_REPO_URL,
+ }
+}
+
+type ClientEnvVars = ReturnType
+
+declare global {
+ interface Window {
+ env: ClientEnvVars
+ }
+}
diff --git a/docs/app/hooks/use-active-heading-id.ts b/docs/app/hooks/use-active-heading-id.ts
new file mode 100644
index 0000000..b73163b
--- /dev/null
+++ b/docs/app/hooks/use-active-heading-id.ts
@@ -0,0 +1,77 @@
+import { useCallback, useEffect, useRef, useState } from "react"
+import { useLocation } from "react-router"
+
+// tracks the currently active heading on the page using IntersectionObserver and URL hash
+export function useActiveHeadingId(selector = "h2[id], h3[id], h4[id]") {
+ const [activeId, setActiveId] = useState(null)
+ const activeIdRef = useRef(null)
+ const isManualRef = useRef(false)
+ const timeoutRef = useRef(undefined)
+
+ const location = useLocation()
+
+ useEffect(() => {
+ activeIdRef.current = activeId
+ }, [activeId])
+
+ const setManualActiveId = useCallback((id: string) => {
+ setActiveId(id)
+ isManualRef.current = true
+ if (timeoutRef.current) window.clearTimeout(timeoutRef.current)
+ timeoutRef.current = window.setTimeout(() => {
+ isManualRef.current = false
+ }, 1000)
+ }, [])
+
+ // biome-ignore lint/correctness/useExhaustiveDependencies: location.pathname shoud be in the dependency array
+ useEffect(() => {
+ const headings = Array.from(document.querySelectorAll(selector))
+ if (!headings.length) {
+ setActiveId(null)
+ return
+ }
+
+ const initialHash = window.location.hash.slice(1)
+ if (initialHash && document.getElementById(initialHash)) {
+ setActiveId(initialHash)
+ }
+
+ const observer = new IntersectionObserver(
+ (entries) => {
+ if (isManualRef.current) return
+
+ const visible = new Set(entries.filter((e) => e.isIntersecting).map((e) => e.target))
+
+ const firstVisible = headings.find((h) => visible.has(h))
+
+ if (firstVisible && firstVisible.id !== activeIdRef.current) {
+ setActiveId(firstVisible.id)
+ }
+ },
+ {
+ rootMargin: "0% 0% -60% 0%",
+ threshold: 0,
+ }
+ )
+ for (const heading of headings) {
+ observer.observe(heading)
+ }
+
+ const handleHashChange = () => {
+ const id = window.location.hash.slice(1)
+ if (id && document.getElementById(id)) {
+ setManualActiveId(id)
+ }
+ }
+
+ window.addEventListener("hashchange", handleHashChange)
+
+ return () => {
+ observer.disconnect()
+ window.removeEventListener("hashchange", handleHashChange)
+ if (timeoutRef.current) window.clearTimeout(timeoutRef.current)
+ }
+ }, [selector, location.pathname, setManualActiveId])
+
+ return { activeId, setManualActiveId }
+}
diff --git a/docs/app/hooks/use-documentation-layout-loader-data.ts b/docs/app/hooks/use-documentation-layout-loader-data.ts
new file mode 100644
index 0000000..79dca34
--- /dev/null
+++ b/docs/app/hooks/use-documentation-layout-loader-data.ts
@@ -0,0 +1,12 @@
+import { useRouteLoaderData } from "react-router"
+import type { Route } from "../routes/+types/documentation-layout"
+
+export const useDocumentationLayoutLoaderData = () => {
+ const data = useRouteLoaderData("routes/documentation-layout")
+ if (!data) {
+ throw new Error(
+ "useDocumentationLayoutLoaderData must be used inside a route that is a child of 'documentation-layout' route"
+ )
+ }
+ return data
+}
diff --git a/docs/app/hooks/use-mobile-view.ts b/docs/app/hooks/use-mobile-view.ts
new file mode 100644
index 0000000..cbd48b3
--- /dev/null
+++ b/docs/app/hooks/use-mobile-view.ts
@@ -0,0 +1,21 @@
+import { useLayoutEffect, useState } from "react"
+
+/**
+ * Hook to determine if the current viewport is considered mobile (below `breakpoint` px).
+ * Returns true if mobile, false otherwise.
+ */
+export function useMobileView(breakpoint = 1280) {
+ const [isMobile, setIsMobile] = useState(() =>
+ typeof window === "undefined" ? null : window.innerWidth < breakpoint
+ )
+
+ useLayoutEffect(() => {
+ const mql = window.matchMedia(`(max-width: ${breakpoint}px)`)
+ const onChange = (e: MediaQueryListEvent) => setIsMobile(e.matches)
+ setIsMobile(mql.matches)
+ mql.addEventListener("change", onChange)
+ return () => mql.removeEventListener("change", onChange)
+ }, [breakpoint])
+
+ return { isMobile }
+}
diff --git a/docs/app/hooks/use-previous-next-pages.ts b/docs/app/hooks/use-previous-next-pages.ts
new file mode 100644
index 0000000..a90cdbc
--- /dev/null
+++ b/docs/app/hooks/use-previous-next-pages.ts
@@ -0,0 +1,46 @@
+import { href, useLocation } from "react-router"
+import type { SidebarTree } from "~/utils/create-sidebar-tree"
+import { flattenSidebarItems } from "~/utils/flatten-sidebar"
+import { splitSlug } from "~/utils/split-slug"
+import { useCurrentVersion } from "~/utils/version-resolvers"
+
+function buildDocHref(slug: string, version: string) {
+ const parts = slug.split("/").filter(Boolean)
+ if (parts.length === 1) {
+ const filename = parts[0]
+ return href("/:version/:section?/:subsection?/:filename", { version, filename })
+ }
+ const { section, subsection, filename } = splitSlug(slug)
+ return href("/:version/:section?/:subsection?/:filename", {
+ version,
+ section,
+ subsection,
+ filename,
+ })
+}
+
+export function usePreviousNextPages(sidebarTree: SidebarTree) {
+ const { pathname } = useLocation()
+ const version = useCurrentVersion()
+ const { sections, documentationPages } = sidebarTree
+
+ const flatFromSections = flattenSidebarItems(sections)
+ const flatStandalone = documentationPages.map((p) => ({ title: p.title, slug: p.slug }))
+ const flatPages = [...flatStandalone, ...flatFromSections]
+
+ const currentIndex = flatPages.findIndex((p) => pathname.endsWith(p.slug))
+
+ const getNavItem = (index: number) => {
+ const item = flatPages[index]
+ if (!item) return undefined
+ return {
+ title: item.title,
+ to: buildDocHref(item.slug, version),
+ }
+ }
+
+ return {
+ previous: getNavItem(currentIndex - 1),
+ next: getNavItem(currentIndex + 1),
+ }
+}
diff --git a/docs/app/hooks/use-scroll-lock.ts b/docs/app/hooks/use-scroll-lock.ts
new file mode 100644
index 0000000..51da69e
--- /dev/null
+++ b/docs/app/hooks/use-scroll-lock.ts
@@ -0,0 +1,39 @@
+import { useEffect, useRef } from "react"
+
+/**
+ * Locks the body scroll when `isActive` is true.
+ * Uses position: fixed + scroll restoration (avoids layout shift).
+ */
+export function useScrollLock(isActive: boolean) {
+ const scrollYRef = useRef(0)
+
+ useEffect(() => {
+ if (!isActive) return
+
+ scrollYRef.current = window.scrollY
+ const body = document.body
+ const html = document.documentElement
+
+ const prevBodyStyle = {
+ position: body.style.position,
+ top: body.style.top,
+ width: body.style.width,
+ }
+ const prevHtmlOverscroll = html.style.overscrollBehavior
+
+ body.style.position = "fixed"
+ body.style.top = `-${scrollYRef.current}px`
+ body.style.width = "100%"
+
+ html.style.overscrollBehavior = "contain"
+
+ return () => {
+ body.style.position = prevBodyStyle.position
+ body.style.top = prevBodyStyle.top
+ body.style.width = prevBodyStyle.width
+ html.style.overscrollBehavior = prevHtmlOverscroll
+
+ window.scrollTo(0, scrollYRef.current)
+ }
+ }, [isActive])
+}
diff --git a/docs/app/localization/i18n.server.test.ts b/docs/app/localization/i18n.server.test.ts
new file mode 100644
index 0000000..01bb3f1
--- /dev/null
+++ b/docs/app/localization/i18n.server.test.ts
@@ -0,0 +1,21 @@
+import remixI18n from "./i18n.server"
+
+describe("Remix I18n", () => {
+ it("returns the correct default language from the request", async () => {
+ const request = new Request("http://localhost:3000")
+ const defaultLanguage = await remixI18n.getLocale(request)
+ expect(defaultLanguage).toBe("en")
+ })
+
+ it("returns the correct default language from the request if search param lang is invalid", async () => {
+ const request = new Request("http://localhost:3000?lng=invalid")
+ const defaultLanguage = await remixI18n.getLocale(request)
+ expect(defaultLanguage).toBe("en")
+ })
+
+ it("returns the correct language when specified in the search params from the request", async () => {
+ const request = new Request("http://localhost:3000?lng=bs")
+ const defaultLanguage = await remixI18n.getLocale(request)
+ expect(defaultLanguage).toBe("bs")
+ })
+})
diff --git a/docs/app/localization/i18n.server.ts b/docs/app/localization/i18n.server.ts
new file mode 100644
index 0000000..f92e0b7
--- /dev/null
+++ b/docs/app/localization/i18n.server.ts
@@ -0,0 +1,18 @@
+import { RemixI18Next } from "remix-i18next/server"
+import i18n from "~/localization/i18n" // your i18n configuration file
+import { resources } from "./resource"
+
+const i18next = new RemixI18Next({
+ detection: {
+ supportedLanguages: i18n.supportedLngs,
+ fallbackLanguage: i18n.fallbackLng,
+ },
+ // This is the configuration for i18next used
+ // when translating messages server-side only
+ i18next: {
+ ...i18n,
+ resources,
+ },
+})
+
+export default i18next
diff --git a/docs/app/localization/i18n.ts b/docs/app/localization/i18n.ts
new file mode 100644
index 0000000..e42f330
--- /dev/null
+++ b/docs/app/localization/i18n.ts
@@ -0,0 +1,12 @@
+import type { InitOptions } from "i18next"
+import { supportedLanguages } from "./resource"
+
+export default {
+ // This is the list of languages your application supports
+ supportedLngs: supportedLanguages,
+ // This is the language you want to use in case
+ // if the user language is not in the supportedLngs
+ fallbackLng: "en",
+ // The default namespace of i18next is "translation", but you can customize it here
+ defaultNS: "common",
+} satisfies Omit
diff --git a/docs/app/localization/resource.ts b/docs/app/localization/resource.ts
new file mode 100644
index 0000000..39301ae
--- /dev/null
+++ b/docs/app/localization/resource.ts
@@ -0,0 +1,30 @@
+import bosnian from "../../resources/locales/bs/common.json"
+import english from "../../resources/locales/en/common.json"
+
+const languages = ["en", "bs"] as const
+export const supportedLanguages = [...languages]
+export type Language = (typeof languages)[number]
+
+type Resource = {
+ common: typeof english
+}
+
+export type Namespace = keyof Resource
+
+export const resources: Record = {
+ en: {
+ common: english,
+ },
+ bs: {
+ common: bosnian,
+ },
+}
+
+declare module "i18next" {
+ export interface CustomTypeOptions {
+ defaultNS: "common"
+ fallbackNS: "common"
+ // custom resources type
+ resources: Resource
+ }
+}
diff --git a/docs/app/root.tsx b/docs/app/root.tsx
new file mode 100644
index 0000000..5ca7444
--- /dev/null
+++ b/docs/app/root.tsx
@@ -0,0 +1,189 @@
+import { useEffect, useLayoutEffect, useState } from "react"
+import { useTranslation } from "react-i18next"
+import {
+ Link,
+ Links,
+ Meta,
+ Outlet,
+ Scripts,
+ ScrollRestoration,
+ isRouteErrorResponse,
+ useNavigate,
+ useRouteError,
+} from "react-router"
+import type { LinksFunction } from "react-router"
+import { useChangeLanguage } from "remix-i18next/react"
+import type { Route } from "./+types/root"
+import { ClientHintCheck, getHints } from "./services/client-hints"
+import tailwindcss from "./tailwind.css?url"
+import { fonts } from "./utils/fonts"
+import { getDomain } from "./utils/get-domain"
+import { THEME, getStorageItem, setStorageItem } from "./utils/local-storage"
+import { getSystemTheme } from "./utils/theme"
+import { normalizeVersion } from "./utils/version-resolvers"
+
+export async function loader({ context, request, params }: Route.LoaderArgs) {
+ const { lang, clientEnv } = context
+ const hints = getHints(request)
+ const { version } = params
+ const { version: normalizedVersion } = normalizeVersion(version)
+ const { domain } = getDomain(request)
+ return { lang, clientEnv, hints, version: normalizedVersion, domain }
+}
+
+export const links: LinksFunction = () => [{ rel: "stylesheet", href: tailwindcss }]
+
+export const handle = {
+ i18n: "common",
+}
+
+export default function App({ loaderData }: Route.ComponentProps) {
+ const { lang, clientEnv } = loaderData
+ useChangeLanguage(lang)
+ const fontFaceRules = fonts
+ .map(
+ (font) => `
+ @font-face {
+ font-family: "${font.fontFamily}";
+ font-style: ${font.fontStyle};
+ font-weight: ${font.fontWeight};
+ src: url(${font.src}) format("truetype");
+ font-display: swap;
+ }
+ `
+ )
+ .join("\n")
+ return (
+ <>
+
+ {/* biome-ignore lint/security/noDangerouslySetInnerHtml: We set the window.env variable to the client env */}
+
+ {/* biome-ignore lint/security/noDangerouslySetInnerHtml: fonts loading*/}
+
+ >
+ )
+}
+
+export const Layout = ({ children }: { children: React.ReactNode }) => {
+ const { i18n } = useTranslation()
+ const [theme, setTheme] = useState(() => {
+ if (typeof window === "undefined" || !window.localStorage) {
+ return "dark"
+ }
+ return getStorageItem(THEME) || getSystemTheme()
+ })
+
+ useLayoutEffect(() => {
+ const storedTheme = getStorageItem(THEME)
+ if (storedTheme) {
+ setTheme(storedTheme)
+ }
+ }, [])
+
+ useEffect(() => {
+ setStorageItem(THEME, theme)
+ }, [theme])
+
+ return (
+
+
+
+
+
+
+
+
+
+
+ {children}
+
+
+
+
+ )
+}
+
+export const ErrorBoundary = () => {
+ const navigate = useNavigate()
+ const error = useRouteError()
+ const { t } = useTranslation()
+ // Constrain the generic type so we don't provide a non-existent key
+ const statusCode = () => {
+ if (!isRouteErrorResponse(error)) {
+ return "500"
+ }
+ // Supported error code messages
+ switch (error.status) {
+ case 200:
+ return "200"
+ case 403:
+ return "403"
+ case 404:
+ return "404"
+ default:
+ return "500"
+ }
+ }
+ const errorStatusCode = statusCode()
+ if (errorStatusCode === "404") {
+ return (
+
+
+
+
+ {t(`error.${errorStatusCode}.title`)}
+
+
+ {t(`error.${errorStatusCode}.description`)}
+
+
+
+ navigate(-1)}
+ className="rounded-lg border border-[var(--color-border)] bg-[var(--color-background)] px-5 py-2 font-medium text-[var(--color-text-normal)] text-sm transition-colors hover:bg-[var(--color-background)] hover:text-[var(--color-text-hover)]"
+ >
+ {t("buttons.back")}
+
+
+ {t("buttons.home")}
+
+
+
+
+
+ )
+ }
+ return (
+
+
+
+
+ {t(`error.${errorStatusCode}.title`)}
+
+
+ {t(`error.${errorStatusCode}.description`)}
+
+
+
+
+ )
+}
diff --git a/docs/app/routes.ts b/docs/app/routes.ts
new file mode 100644
index 0000000..fa4da1f
--- /dev/null
+++ b/docs/app/routes.ts
@@ -0,0 +1,16 @@
+import { type RouteConfig, index, layout, route } from "@react-router/dev/routes"
+
+export default [
+ index("routes/index.tsx"),
+ route("search", "routes/search.ts"),
+ layout("routes/documentation-layout.tsx", [
+ route(":version?/home", "routes/documentation-homepage.tsx"),
+ route(":version/:section?/:subsection?/:filename", "routes/documentation-page.tsx"),
+ ]),
+ route("sitemap-index.xml", "routes/sitemap-index[.]xml.ts"),
+ route("robots.txt", "routes/robots[.]txt.ts"),
+ route("resource/*", "routes/resource.locales.ts"),
+ route("$", "routes/$.tsx"),
+ route("sitemap/:lang.xml", "routes/sitemap.$lang[.]xml.ts"),
+ route(":version?/llms.txt", "routes/llms[.]txt.ts"),
+] satisfies RouteConfig
diff --git a/docs/app/routes/$.tsx b/docs/app/routes/$.tsx
new file mode 100644
index 0000000..4e266b0
--- /dev/null
+++ b/docs/app/routes/$.tsx
@@ -0,0 +1,43 @@
+import { useTranslation } from "react-i18next"
+import { useNavigate } from "react-router"
+import { Link } from "~/ui/link"
+import type { Route } from "./+types/$"
+
+export const loader = async ({ params }: Route.LoaderArgs) => {
+ const slug = params["*"]
+ return new Response(`Page with slug \"${slug}\" not found!`, { status: 404 })
+}
+export default function Route404() {
+ const navigate = useNavigate()
+ const { t } = useTranslation()
+ return (
+
+
+
+
+ {t("error.404.title")}
+
+
+ {t("error.404.description")}
+
+
+
+ navigate(-1)}
+ className="rounded-lg border border-[var(--color-border)] bg-[var(--color-background)] px-5 py-2 font-medium text-[var(--color-text-normal)] text-sm transition-colors hover:bg-[var(--color-background)] hover:text-[var(--color-text-hover)]"
+ >
+ {t("buttons.back")}
+
+
+ {t("buttons.home")}
+
+
+
+
+
+ )
+}
diff --git a/docs/app/routes/documentation-homepage.tsx b/docs/app/routes/documentation-homepage.tsx
new file mode 100644
index 0000000..e27d006
--- /dev/null
+++ b/docs/app/routes/documentation-homepage.tsx
@@ -0,0 +1,44 @@
+import GithubContributeLinks from "~/components/github-contribute-links"
+import PageMdxArticle from "~/components/page-mdx-article"
+import { getDomain } from "~/utils/get-domain"
+import { getContent } from "~/utils/load-content"
+import { generateMetaFields } from "~/utils/seo"
+import { resolveVersionForHomepage } from "~/utils/version-resolvers"
+import type { Route } from "./+types/documentation-homepage"
+
+export const meta = ({ data }: Route.MetaArgs) => {
+ const { page, domain, version } = data
+ const title = page.title
+ const description = page.description
+ return generateMetaFields({
+ domain,
+ path: `/${version}/home`,
+ title: `${title} · deploykit`,
+ description,
+ })
+}
+
+export async function loader({ params, request }: Route.LoaderArgs) {
+ const { version } = resolveVersionForHomepage(params.version)
+ const { allPages } = await getContent(version)
+ const page = allPages.find((p) => p._meta.path === "_index")
+ if (!page) throw new Response("Not Found", { status: 404 })
+ const { domain } = getDomain(request)
+ return { page, version, domain }
+}
+
+export default function DocumentationHomepage({ loaderData }: Route.ComponentProps) {
+ const { page } = loaderData
+ return (
+
+ )
+}
diff --git a/docs/app/routes/documentation-layout.tsx b/docs/app/routes/documentation-layout.tsx
new file mode 100644
index 0000000..e62845a
--- /dev/null
+++ b/docs/app/routes/documentation-layout.tsx
@@ -0,0 +1,46 @@
+import { Outlet, useRouteLoaderData } from "react-router"
+import { CommandK } from "~/components/command-k/components/command-k"
+import { Header } from "~/components/header"
+import { IconLink } from "~/components/icon-link"
+import { Logo } from "~/components/logo"
+import { Sidebar } from "~/components/sidebar/sidebar"
+import { ThemeToggle } from "~/components/theme-toggle"
+import { VersionDropdown } from "~/components/versions-dropdown"
+import { createSidebarTree } from "~/utils/create-sidebar-tree"
+import { resolveVersionForLayout } from "~/utils/version-resolvers"
+import type { Route } from "./+types/documentation-layout"
+
+export async function loader({ params, request }: Route.LoaderArgs) {
+ const { version } = resolveVersionForLayout(params.version, request)
+ const sidebarTree = await createSidebarTree(version)
+ return { sidebarTree, version }
+}
+export default function DocumentationLayout({ loaderData }: Route.ComponentProps) {
+ const { sidebarTree, version } = loaderData
+ const { clientEnv } = useRouteLoaderData("root")
+ const { GITHUB_REPO_URL } = clientEnv
+ return (
+
+ )
+}
diff --git a/docs/app/routes/documentation-page.tsx b/docs/app/routes/documentation-page.tsx
new file mode 100644
index 0000000..57ed96f
--- /dev/null
+++ b/docs/app/routes/documentation-page.tsx
@@ -0,0 +1,63 @@
+import GithubContributeLinks from "~/components/github-contribute-links"
+import PageMdxArticle from "~/components/page-mdx-article"
+import { PageNavigation } from "~/components/page-navigation"
+import { TableOfContents } from "~/components/table-of-content"
+import { useDocumentationLayoutLoaderData } from "~/hooks/use-documentation-layout-loader-data"
+import { usePreviousNextPages } from "~/hooks/use-previous-next-pages"
+import { extractHeadingTreeFromMarkdown } from "~/utils/extract-heading-tree-from-mdx"
+import { getDomain } from "~/utils/get-domain"
+import { getContent } from "~/utils/load-content"
+import { buildDocPathFromSlug, buildDocSlug } from "~/utils/path-builders"
+import { generateMetaFields } from "~/utils/seo"
+import { normalizeVersion } from "~/utils/version-resolvers"
+import type { Route } from "./+types/documentation-page"
+
+export const meta = ({ data }: Route.MetaArgs) => {
+ const { page, domain, version } = data
+ const docPath = buildDocPathFromSlug(page.slug)
+ const fullPath = `/${[version, docPath.replace(/^\//, "")].filter(Boolean).join("/")}`
+
+ return generateMetaFields({
+ domain,
+ path: fullPath,
+ title: `${page.title} · deploykit`,
+ description: page.description,
+ })
+}
+
+export async function loader({ params, request }: Route.LoaderArgs) {
+ const { version: v, section, subsection, filename } = params
+ if (!filename) throw new Response("Not Found", { status: 404 })
+
+ const { version } = normalizeVersion(v)
+ const slug = buildDocSlug({ section, subsection, filename })
+
+ const { allPages } = await getContent(version)
+ const page = allPages.find((p) => p.slug === slug)
+ if (!page) throw new Response("Not Found", { status: 404 })
+
+ const { domain } = getDomain(request)
+ return { page, version, domain }
+}
+
+export default function DocumentationPage({ loaderData }: Route.ComponentProps) {
+ const { page } = loaderData
+ const { sidebarTree } = useDocumentationLayoutLoaderData()
+ const { previous, next } = usePreviousNextPages(sidebarTree)
+ const toc = extractHeadingTreeFromMarkdown(page.rawMdx)
+
+ return (
+
+ )
+}
diff --git a/docs/app/routes/index.tsx b/docs/app/routes/index.tsx
new file mode 100644
index 0000000..8f974b4
--- /dev/null
+++ b/docs/app/routes/index.tsx
@@ -0,0 +1,159 @@
+import { href, useNavigate } from "react-router"
+import { Header } from "~/components/header"
+import { Logo } from "~/components/logo"
+import { Icon } from "~/ui/icon/icon"
+import { getDomain } from "~/utils/get-domain"
+import { generateMetaFields } from "~/utils/seo"
+import { getLatestVersion } from "~/utils/version-resolvers"
+import type { Route } from "./+types"
+
+export const meta = ({ data }: Route.MetaArgs) => {
+ const { domain } = data
+ return generateMetaFields({
+ domain,
+ path: "/",
+ title: "deploykit",
+ description:
+ "Automate CI/CD for Turbo and Nx monorepos deploying to Fly.io. One command generates Dockerfiles, fly.toml files and a GitHub Actions workflow — landed as a reviewable PR you own.",
+ })
+}
+
+export async function loader({ request }: Route.LoaderArgs) {
+ const { domain } = getDomain(request)
+ return { domain }
+}
+
+type CardDef = {
+ icon: React.ComponentProps["name"]
+ title: string
+ body: string
+ href?: string
+}
+
+const CARDS: CardDef[] = [
+ {
+ icon: "Zap",
+ title: "One command",
+ body: "Run `deploykit init`: it reads your workspace graph, asks a few pre-filled questions, and shows a plan before writing anything.",
+ },
+ {
+ icon: "FileText",
+ title: "Files you own",
+ body: "Generates multi-stage Dockerfiles, per-app fly.toml, and a GitHub Actions workflow — landed as a reviewable PR, not hidden magic.",
+ },
+ {
+ icon: "Rocket",
+ title: "Preview · staging · production",
+ body: "Every PR gets its own deployed URL, staging deploys on merge to main, and production sits behind a manual approval gate.",
+ },
+ {
+ icon: "ShieldCheck",
+ title: "Health-checked rollbacks",
+ body: "Fly waits on an HTTP health check before shifting traffic and keeps old machines on failure, so a bad deploy rolls itself back.",
+ },
+ {
+ icon: "Clock",
+ title: "Roll back a release",
+ body: "`deploykit rollback` lists prior Fly releases for an environment and redeploys the image you pick, after showing the exact command.",
+ },
+ {
+ icon: "Code",
+ title: "Turbo & Nx aware",
+ body: "Detects your package manager, framework, ports, internal deps, Prisma schemas and env-var names — turbo-prune based builds included.",
+ },
+]
+
+function Card({ icon, title, body, href }: CardDef) {
+ return (
+
+
+
+
+ {title}
+ {body}
+ {href ? (
+
+ Learn more
+
+ ) : null}
+
+ )
+}
+
+// FIXME Customize this page
+export default function Index() {
+ const navigate = useNavigate()
+
+ return (
+
+
+
+
+
+
+
+ Version {getLatestVersion()} now available
+
+
+
+ CI/CD for your monorepo{" "}
+
+ in one command
+
+
+
+
+ deploykit reads your Turbo or Nx workspace and generates the Dockerfiles, fly.toml files and GitHub Actions
+ workflow to deploy every app to Fly.io — preview, staging and production — landed as a PR you review and own.
+
+
+
+ {CARDS.map((c) => (
+
+ ))}
+
+
+
+
navigate(href("/:version?/home"))}
+ className="flex items-center gap-2 rounded-lg bg-[#2c8794] px-6 py-3 font-medium text-white transition-colors hover:bg-[#329baa]"
+ >
+
+ Get started
+
+
+
+
+ View on GitHub
+
+
+
+
+ Docs built with the{" "}
+
+ code-forge docs template
+
+
+
+
+
+ )
+}
diff --git a/docs/app/routes/llms[.]txt.ts b/docs/app/routes/llms[.]txt.ts
new file mode 100644
index 0000000..83228a1
--- /dev/null
+++ b/docs/app/routes/llms[.]txt.ts
@@ -0,0 +1,22 @@
+import { href, redirect } from "react-router"
+import { renderLlmsTxt } from "~/utils/llms-txt-builder"
+import { getLatestVersion, normalizeVersion } from "~/utils/version-resolvers"
+import type { Route } from "./+types/llms[.]txt"
+
+export async function loader({ request, params }: Route.LoaderArgs) {
+ const { version: paramVersion } = params
+ if (!paramVersion) {
+ const latest = getLatestVersion()
+ return redirect(href("/:version?/llms.txt", { version: latest }))
+ }
+
+ const { version } = normalizeVersion(paramVersion)
+ const body = await renderLlmsTxt({
+ request,
+ version,
+ title: "deploykit",
+ tagline: "Official documentation and guides.",
+ })
+
+ return new Response(body, { headers: { "Content-Type": "text/plain; charset=utf-8" } })
+}
diff --git a/docs/app/routes/resource.locales.ts b/docs/app/routes/resource.locales.ts
new file mode 100644
index 0000000..8e9f194
--- /dev/null
+++ b/docs/app/routes/resource.locales.ts
@@ -0,0 +1,40 @@
+import { cacheHeader } from "pretty-cache-header"
+import { z } from "zod"
+import { type Language, type Namespace, resources } from "~/localization/resource"
+import type { Route } from "./+types/resource.locales"
+
+export async function loader({ request, context }: Route.LoaderArgs) {
+ const { isProductionDeployment } = context
+ const url = new URL(request.url)
+
+ const lng = z
+ .string()
+ .refine((lng): lng is Language => Object.keys(resources).includes(lng))
+ .parse(url.searchParams.get("lng"))
+
+ const namespaces = resources[lng as Language]
+
+ const ns = z
+ .string()
+ .refine((ns): ns is Namespace => {
+ return Object.keys(resources[lng as Language]).includes(ns)
+ })
+ .parse(url.searchParams.get("ns"))
+
+ const headers = new Headers()
+
+ // On production, we want to add cache headers to the response
+ if (isProductionDeployment) {
+ headers.set(
+ "Cache-Control",
+ cacheHeader({
+ maxAge: "5m",
+ sMaxage: "1d",
+ staleWhileRevalidate: "7d",
+ staleIfError: "7d",
+ })
+ )
+ }
+
+ return Response.json(namespaces[ns as Namespace], { headers })
+}
diff --git a/docs/app/routes/robots[.]txt.ts b/docs/app/routes/robots[.]txt.ts
new file mode 100644
index 0000000..548d1e9
--- /dev/null
+++ b/docs/app/routes/robots[.]txt.ts
@@ -0,0 +1,21 @@
+import { generateRobotsTxt } from "@forge42/seo-tools/robots"
+
+import { createDomain } from "~/utils/http"
+import type { Route } from "./+types/robots[.]txt"
+
+export async function loader({ request, context }: Route.LoaderArgs) {
+ const { isProductionDeployment } = context
+ const domain = createDomain(request)
+ const robotsTxt = generateRobotsTxt([
+ {
+ userAgent: "*",
+ [isProductionDeployment ? "allow" : "disallow"]: ["/"],
+ sitemap: [`${domain}/sitemap-index.xml`],
+ },
+ ])
+ return new Response(robotsTxt, {
+ headers: {
+ "Content-Type": "text/plain",
+ },
+ })
+}
diff --git a/docs/app/routes/search.ts b/docs/app/routes/search.ts
new file mode 100644
index 0000000..20daec4
--- /dev/null
+++ b/docs/app/routes/search.ts
@@ -0,0 +1,27 @@
+import { commandKSearchParamsSchema } from "~/components/command-k/hooks/use-search"
+import { fuzzySearch } from "~/server/search-index"
+import { parseSearchParams } from "~/utils/parse-search-params"
+import type { Route } from "./+types/search"
+
+export async function loader({ request }: Route.LoaderArgs) {
+ const { params } = parseSearchParams(request, commandKSearchParamsSchema)
+ if (!params) {
+ throw new Response("Bad Request", { status: 400 })
+ }
+
+ const { query, version } = params
+ if (!query) {
+ return { results: [] }
+ }
+
+ try {
+ const results = await fuzzySearch({ query: query.trim(), version })
+ return {
+ results,
+ }
+ } catch (error) {
+ // biome-ignore lint/suspicious/noConsole: keep for debugging
+ console.error("Search error:", error)
+ return { results: [] }
+ }
+}
diff --git a/docs/app/routes/sitemap-index[.]xml.ts b/docs/app/routes/sitemap-index[.]xml.ts
new file mode 100644
index 0000000..c60c878
--- /dev/null
+++ b/docs/app/routes/sitemap-index[.]xml.ts
@@ -0,0 +1,23 @@
+import { generateSitemapIndex } from "@forge42/seo-tools/sitemap"
+import { createDomain } from "~/utils/http"
+import type { Route } from "./+types/sitemap-index[.]xml"
+
+export const loader = async ({ request }: Route.LoaderArgs) => {
+ const domain = createDomain(request)
+ const sitemaps = generateSitemapIndex([
+ {
+ url: `${domain}/sitemap/en.xml`,
+ lastmod: "2024-07-17",
+ },
+ {
+ url: `${domain}/sitemap/bs.xml`,
+ lastmod: "2024-07-17",
+ },
+ ])
+
+ return new Response(sitemaps, {
+ headers: {
+ "Content-Type": "application/xml; charset=utf-8",
+ },
+ })
+}
diff --git a/docs/app/routes/sitemap.$lang[.]xml.ts b/docs/app/routes/sitemap.$lang[.]xml.ts
new file mode 100644
index 0000000..813426b
--- /dev/null
+++ b/docs/app/routes/sitemap.$lang[.]xml.ts
@@ -0,0 +1,27 @@
+import { generateRemixSitemap } from "@forge42/seo-tools/remix/sitemap"
+import { createDomain } from "~/utils/http"
+import type { Route } from "./+types/sitemap.$lang[.]xml"
+
+export const loader = async ({ request, params }: Route.LoaderArgs) => {
+ const domain = createDomain(request)
+
+ // @ts-expect-error - This import exists but is not picked up by the typescript compiler because it's a remix internal
+ const { routes } = await import("virtual:react-router/server-build")
+
+ const sitemap = await generateRemixSitemap({
+ domain,
+ routes,
+ ignore: ["/resource/*"],
+ // Transforms the url before adding it to the sitemap
+ urlTransformer: (url) => `${url}?lng=${params.lang}`,
+ sitemapData: {
+ lang: params.lang,
+ },
+ })
+
+ return new Response(sitemap, {
+ headers: {
+ "Content-Type": "application/xml; charset=utf-8",
+ },
+ })
+}
diff --git a/docs/app/server/context.ts b/docs/app/server/context.ts
new file mode 100644
index 0000000..ddf43b2
--- /dev/null
+++ b/docs/app/server/context.ts
@@ -0,0 +1,31 @@
+import type { Context } from "hono"
+import { i18next } from "remix-hono/i18next"
+import { getClientEnv, getServerEnv } from "~/env.server"
+
+export const getLoadContext = async (c: Context) => {
+ // get the locale from the context
+ const locale = i18next.getLocale(c)
+ // get t function for the default namespace
+ const t = await i18next.getFixedT(c)
+ // get the server environment
+ const env = getServerEnv()
+
+ return {
+ lang: locale,
+ t,
+ isProductionDeployment: env.APP_ENV === "production",
+ env,
+ clientEnv: getClientEnv(),
+ // We do not add this to AppLoadContext type because it's not needed in the loaders, but it's used above to handle requests
+ body: c.body,
+ }
+}
+
+interface LoadContext extends Awaited> {}
+
+/**
+ * Declare our loaders and actions context type
+ */
+declare module "react-router" {
+ interface AppLoadContext extends Omit {}
+}
diff --git a/docs/app/server/index.ts b/docs/app/server/index.ts
new file mode 100644
index 0000000..a81ff3d
--- /dev/null
+++ b/docs/app/server/index.ts
@@ -0,0 +1,12 @@
+import { createHonoServer } from "react-router-hono-server/node"
+import { i18next } from "remix-hono/i18next"
+import i18nextOpts from "../localization/i18n.server"
+import { getLoadContext } from "./context"
+
+export default await createHonoServer({
+ configure(server) {
+ server.use("*", i18next(i18nextOpts))
+ },
+ defaultLogger: false,
+ getLoadContext,
+})
diff --git a/docs/app/server/search-index.ts b/docs/app/server/search-index.ts
new file mode 100644
index 0000000..e1296b8
--- /dev/null
+++ b/docs/app/server/search-index.ts
@@ -0,0 +1,36 @@
+import { createSearchIndex } from "~/components/command-k/create-search-index"
+import { useFuzzySearch } from "~/components/command-k/hooks/use-fuzzy-search"
+import type { CommandKSearchParams } from "~/components/command-k/hooks/use-search"
+import type { SearchRecord } from "~/components/command-k/search-types"
+import { loadContentCollections } from "~/utils/load-content-collections"
+import type { Version } from "~/utils/version-resolvers"
+import { versions } from "~/utils/versions"
+
+const searchIndexes: Map = new Map()
+
+export async function preloadSearchIndexes() {
+ await Promise.all(
+ versions.map(async (version) => {
+ if (!searchIndexes.has(version)) {
+ const { allPages } = await loadContentCollections(version)
+ const searchIndex = createSearchIndex(allPages)
+
+ searchIndexes.set(version, searchIndex)
+ }
+ })
+ )
+}
+
+async function getSearchIndex(version: Version) {
+ const index = searchIndexes.get(version)
+ if (!index) {
+ throw new Error(`Search index for version "${version}" could not be retrieved.`)
+ }
+
+ return index
+}
+
+export async function fuzzySearch({ query, version }: CommandKSearchParams) {
+ const searchIndex = await getSearchIndex(version)
+ return useFuzzySearch(searchIndex, query)
+}
diff --git a/docs/app/services/client-hints.tsx b/docs/app/services/client-hints.tsx
new file mode 100644
index 0000000..3281347
--- /dev/null
+++ b/docs/app/services/client-hints.tsx
@@ -0,0 +1,47 @@
+import { getHintUtils } from "@epic-web/client-hints"
+import { clientHint as colorSchemeHint, subscribeToSchemeChange } from "@epic-web/client-hints/color-scheme"
+import { clientHint as reducedMotionHint, subscribeToMotionChange } from "@epic-web/client-hints/reduced-motion"
+import { clientHint as timeZoneHint } from "@epic-web/client-hints/time-zone"
+import { useEffect } from "react"
+import { useRevalidator, useRouteLoaderData } from "react-router"
+import type { Route } from "../+types/root"
+
+export const { getHints, getClientHintCheckScript } = getHintUtils({
+ theme: colorSchemeHint,
+ timeZone: timeZoneHint,
+ reducedMotion: reducedMotionHint,
+ // add other hints here
+})
+
+/**
+ * @public
+ * Utility function used to get the time zone for the current users browser on either the client or the server.
+ * */
+export const getTimeZone = (request?: Request) => getHints(request).timeZone
+
+/**
+ * @public
+ * Utility used to get the client hints for the current users browser.
+ * */
+export function useHints() {
+ const requestInfo = useRouteLoaderData("root")
+ return requestInfo?.hints
+}
+/**
+ * Utility component used to check the client hints on the client and send them to the server.
+ */
+export function ClientHintCheck({ nonce }: { nonce?: string }) {
+ const { revalidate } = useRevalidator()
+ useEffect(() => subscribeToSchemeChange(() => revalidate()), [revalidate])
+ useEffect(() => subscribeToMotionChange(() => revalidate()), [revalidate])
+
+ return (
+
+ )
+}
diff --git a/docs/app/tailwind.css b/docs/app/tailwind.css
new file mode 100644
index 0000000..a670a12
--- /dev/null
+++ b/docs/app/tailwind.css
@@ -0,0 +1,300 @@
+@import "tailwindcss";
+@plugin "@tailwindcss/typography";
+
+@theme {
+ --font-dyna-puff: "Dyna Puff", sans-serif;
+ --font-inter: "Inter", sans-serif;
+ --font-space: "Space", sans-serif;
+
+ /* Animation Variables */
+ --animation-duration: 40s;
+ --animation-direction: forwards;
+
+ /* Animation Definitions */
+ --animate-scroll: scroll var(--animation-duration, 40s) var(--animation-direction, forwards) linear infinite;
+ --animate-spotlight: spotlight 2s ease .75s 1 forwards;
+ --animate-meteor-effect: meteor 5s linear infinite;
+}
+
+@keyframes scroll {
+ to {
+ transform: translate(calc(-50% - 0.5rem));
+ }
+}
+
+@keyframes meteor {
+ 0% {
+ transform: rotate(215deg) translateX(0);
+ opacity: 1;
+ }
+ 70% {
+ opacity: 1;
+ }
+ 100% {
+ transform: rotate(215deg) translateX(-1900px);
+ opacity: 0;
+ }
+}
+
+@keyframes spotlight {
+ 0% {
+ opacity: 0;
+ transform: translate(-72%, -62%) scale(0.5);
+ }
+ 100% {
+ opacity: 1;
+ transform: translate(-50%, -40%) scale(1);
+ }
+}
+
+@layer base {
+ h1,
+ h2,
+ h3 {
+ scroll-margin-top: 6rem;
+ }
+
+ :root {
+ --header-height: 5rem;
+ --color-background: #fafafa;
+ --color-border: #e5e7eb;
+ --color-text-normal: #2d3748;
+ --color-text-muted: #647388;
+ --color-text-hover: #1a202c;
+ --color-text-active: #000000;
+ --color-background-active: #f3f4f6;
+ --color-text-accent: #646464;
+
+ --color-code-inline-text: #04825b;
+ --color-code-inline-bg: #f3f4f6;
+
+ --color-code-block-bg: #fcfcfc;
+ --color-code-block-text: #334155;
+ --color-code-copy-bg: #fcfcfc;
+ --color-code-copy-hover-bg: #f0f0f0;
+ --color-code-copy-text: #1d1d1d;
+
+ --color-code-keyword: #0369a1;
+ --color-code-string: #04825b;
+ --color-code-number: #c84b0a;
+ --color-code-comment: #7f90a7;
+ --color-code-operator: #be185d;
+ --color-code-punctuation: #64748b;
+ --color-code-function: #7c3aed;
+
+ --color-diff-added-bg: rgba(34, 197, 94, 0.1);
+ --color-diff-added-border: rgba(34, 197, 94, 0.3);
+ --color-diff-removed-bg: rgba(239, 68, 68, 0.1);
+ --color-diff-removed-border: rgba(239, 68, 68, 0.3);
+ --color-diff-indicator: #08162b;
+
+ --color-info-bg: #eff6ff;
+ --color-info-border: #bfdbfe;
+ --color-info-text: #1e40af;
+ --color-info-icon: #3b82f6;
+
+ --color-warning-bg: #fefce8;
+ --color-warning-border: #fde68a;
+ --color-warning-text: #92400e;
+ --color-warning-icon: #f59e0b;
+
+ --color-modal-backdrop: rgba(17, 24, 39, 0.5);
+ --color-modal-bg: #ffffff;
+ --color-modal-border: #e5e7eb;
+ --color-modal-shadow: rgba(0, 0, 0, 0.25);
+
+ --color-input-bg: rgba(249, 250, 251, 0.5);
+ --color-input-border: #e5e7eb;
+ --color-input-text: #111827;
+ --color-input-placeholder: #6b7280;
+ --color-input-icon: #9ca3af;
+
+ --color-result-hover: #f9fafb;
+ --color-result-selected: #eff6ff;
+ --color-result-selected-border: #3b82f6;
+ --color-result-selected-text: #1e3a8a;
+ --color-result-text: #111827;
+ --color-result-meta: #6b7280;
+ --color-result-icon: #9ca3af;
+ --color-result-icon-selected: #3b82f6;
+ --color-result-arrow: #d1d5db;
+
+ --color-breadcrumb-bg: #f3f4f6;
+ --color-breadcrumb-text: #686f7d;
+
+ --color-footer-bg: #f9fafb;
+ --color-footer-border: #e5e7eb;
+ --color-footer-text: #6b7280;
+ --color-footer-kbd-bg: #ffffff;
+ --color-footer-kbd-border: #e5e7eb;
+
+ --color-history-header-bg: rgba(249, 250, 251, 0.5);
+ --color-history-header-border: #e5e7eb;
+ --color-history-header-text: #374151;
+ --color-history-clear-hover-bg: #fef2f2;
+ --color-history-clear-hover-text: #dc2626;
+ --color-history-remove-bg: #ffffff;
+ --color-history-remove-border: #e5e7eb;
+ --color-history-remove-text: #9ca3af;
+ --color-history-remove-hover-border: #fecaca;
+ --color-history-remove-hover-text: #ef4444;
+
+ --color-empty-icon-bg: #f3f4f6;
+ --color-empty-icon: #9ca3af;
+ --color-empty-text: #6b7280;
+ --color-empty-text-muted: #9ca3af;
+ --color-empty-icon-accent: #3b82f6;
+
+ --color-kbd-bg: #f3f4f6;
+ --color-kbd-border: #d1d5db;
+ --color-kbd-text: #6b7280;
+
+ --color-trigger-bg: #ffffff;
+ --color-trigger-border: #e5e7eb;
+ --color-trigger-text: #6b7280;
+ --color-trigger-hover-bg: #f9fafb;
+ --color-trigger-hover-border: #d1d5db;
+ --color-trigger-hover-text: #4b5563;
+ --color-trigger-focus-border: #93c5fd;
+ --color-trigger-focus-ring: rgba(59, 130, 246, 0.2);
+
+ --color-highlight-bg: #fef3c7;
+ --color-highlight-text: #92400e;
+
+ --color-scrollbar-track: #f0f0f0;
+ --color-scrollbar-thumb: #718096;
+ }
+
+ [data-theme="dark"] {
+ --color-background: #0f0f0f;
+ --color-border: #1b1f2e;
+ --color-text-normal: #e6edf3;
+ --color-text-muted: #7d8590;
+ --color-text-hover: #cfcfcf;
+ --color-text-active: #ffffff;
+ --color-background-active: #1a1a1a;
+ --color-text-accent: #878787;
+
+ --color-code-inline-text: #34d399;
+ --color-code-inline-bg: #1a1a1a;
+
+ --color-code-block-bg: #1a1a1a;
+ --color-code-block-text: #e2e8f0;
+ --color-code-copy-bg: #414141;
+ --color-code-copy-hover-bg: #303030;
+ --color-code-copy-text: #fbfbfb;
+
+ --color-code-keyword: #60a5fa;
+ --color-code-string: #34d399;
+ --color-code-number: #fb923c;
+ --color-code-comment: #73839a;
+ --color-code-operator: #f472b6;
+ --color-code-punctuation: #cbd5e1;
+ --color-code-function: #a78bfa;
+
+ --color-diff-added-bg: rgba(34, 197, 94, 0.15);
+ --color-diff-added-border: rgba(34, 197, 94, 0.4);
+ --color-diff-removed-bg: rgba(239, 68, 68, 0.15);
+ --color-diff-removed-border: rgba(239, 68, 68, 0.4);
+ --color-diff-indicator: #64748b;
+
+ --color-info-bg: rgba(59, 130, 246, 0.1);
+ --color-info-border: rgba(59, 130, 246, 0.2);
+ --color-info-text: #93c5fd;
+ --color-info-icon: #60a5fa;
+
+ --color-warning-bg: rgba(245, 158, 11, 0.1);
+ --color-warning-border: rgba(245, 158, 11, 0.2);
+ --color-warning-text: #fbbf24;
+ --color-warning-icon: #f59e0b;
+
+ --color-modal-backdrop: rgba(10, 13, 17, 0.5);
+ --color-modal-bg: rgb(15, 15, 15);
+ --color-modal-border: #374151;
+ --color-modal-shadow: rgba(0, 0, 0, 0.5);
+
+ --color-input-bg: rgba(31, 41, 55, 0.5);
+ --color-input-border: #374151;
+ --color-input-text: #f9fafb;
+ --color-input-placeholder: #9ca3af;
+ --color-input-icon: #6b7280;
+
+ --color-result-hover: rgba(31, 41, 55, 0.5);
+ --color-result-selected: rgba(59, 130, 246, 0.2);
+ --color-result-selected-border: #3b82f6;
+ --color-result-selected-text: #93c5fd;
+ --color-result-text: #f9fafb;
+ --color-result-meta: #9ca3af;
+ --color-result-icon: #6b7280;
+ --color-result-icon-selected: #60a5fa;
+ --color-result-arrow: #4b5563;
+
+ --color-breadcrumb-bg: #0f0f0f;
+ --color-breadcrumb-text: #6b7280;
+
+ --color-footer-bg: #0f0f0f;
+ --color-footer-border: #374151;
+ --color-footer-text: #9ca3af;
+ --color-footer-kbd-bg: #374151;
+ --color-footer-kbd-border: #4b5563;
+
+ --color-history-header-bg: rgba(31, 41, 55, 0.5);
+ --color-history-header-border: #374151;
+ --color-history-header-text: #d1d5db;
+ --color-history-clear-hover-bg: rgba(185, 28, 28, 0.2);
+ --color-history-clear-hover-text: #f87171;
+ --color-history-remove-bg: #0f0f0f;
+ --color-history-remove-border: #374151;
+ --color-history-remove-text: #6b7280;
+ --color-history-remove-hover-border: rgba(185, 28, 28, 0.8);
+ --color-history-remove-hover-text: #f87171;
+
+ --color-empty-icon-bg: #0f0f0f;
+ --color-empty-icon: #6b7280;
+ --color-empty-text: #9ca3af;
+ --color-empty-text-muted: #6b7280;
+ --color-empty-icon-accent: #60a5fa;
+
+ --color-kbd-bg: #0f0f0f;
+ --color-kbd-border: #4b5563;
+ --color-kbd-text: #9ca3af;
+
+ --color-trigger-bg: #0f0f0f;
+ --color-trigger-border: #374151;
+ --color-trigger-text: #9ca3af;
+ --color-trigger-hover-bg: #374151;
+ --color-trigger-hover-border: #4b5563;
+ --color-trigger-hover-text: #d1d5db;
+ --color-trigger-focus-border: #60a5fa;
+ --color-trigger-focus-ring: rgba(96, 165, 250, 0.2);
+
+ --color-highlight-bg: rgba(245, 158, 11, 0.5);
+ --color-highlight-text: #fbbf24;
+
+ --color-scrollbar-track: #0f0f0f;
+ --color-scrollbar-thumb: #e6edf3;
+ }
+
+ .scrollbar::-webkit-scrollbar {
+ height: 5px;
+ width: 4px;
+ }
+
+ .scrollbar::-webkit-scrollbar-thumb {
+ background-color: var(--color-scrollbar-thumb);
+ /* border-radius: 9999px; */
+ }
+
+ .scrollbar::-webkit-scrollbar-track {
+ background-color: var(--color-scrollbar-track);
+ }
+
+ *::-webkit-scrollbar-thumb {
+ cursor: default;
+ }
+
+ .prose :not(pre) > code::before,
+ .prose :not(pre) > code::after {
+ content: none !important;
+ }
+}
diff --git a/docs/app/ui/accordion.tsx b/docs/app/ui/accordion.tsx
new file mode 100644
index 0000000..bf37dbb
--- /dev/null
+++ b/docs/app/ui/accordion.tsx
@@ -0,0 +1,111 @@
+import { type ReactNode, useState } from "react"
+import { cn } from "../utils/css"
+import { Icon } from "./icon/icon"
+import { Title, type ValidTitleElements } from "./title"
+
+interface AccordionItemProps {
+ title?: string
+ titleElement?: keyof typeof ValidTitleElements
+ titleClassName?: string
+ content: ReactNode
+ defaultOpen?: boolean
+}
+
+interface AccordionProps {
+ children: ReactNode
+ className?: string
+}
+
+const AccordionContent = ({ isOpen, children }: { isOpen: boolean; children: ReactNode }) => {
+ return (
+
+ )
+}
+
+/**
+ * An expandable and collapsible content section used within the `Accordion` component.
+ *
+ * The title is rendered using the `Title` component and can be customized via `titleElement`
+ * (e.g. `"h2"`, `"h3"`, etc.) and styled with `titleClassName`.
+ * Content visibility toggles when the heading is clicked. Supports smooth transitions.
+ *
+ * @param title - The heading text for the accordion item.
+ * @param titleElement - The HTML heading element tag to render (`h1` through `h5`).
+ * @param titleClassName - Optional classes to customize the title's appearance.
+ * @param content - The content to show/hide when toggling the accordion.
+ * @param defaultOpen - Whether the item should be open by default.
+ *
+ * @example
+ * ```tsx
+ *
+ * Run `npm install`
+ * Configure your environment
+ *
+ * }
+ * />
+ * ```
+ */
+export const AccordionItem = ({
+ title,
+ titleElement,
+ titleClassName,
+ content,
+ defaultOpen = false,
+}: AccordionItemProps) => {
+ const [isOpen, setIsOpen] = useState(defaultOpen)
+
+ const buttonClasses =
+ "flex gap-2 items-center w-full px-2 transition-transform duration-200 text-[var(--color-text-normal)] hover:text-[var(--color-text-hover)] hover:cursor-pointer rounded-md"
+
+ const iconClasses = "w-4 h-4 transition-transform duration-300"
+
+ return (
+
+ {/* biome-ignore lint/a11y/useKeyWithClickEvents:we don't need key with click events */}
+
setIsOpen(!isOpen)} aria-expanded={isOpen}>
+
+
+ {title}
+
+
+
{content}
+
+ )
+}
+
+/**
+ * A container component for grouping multiple `AccordionItem`s.
+ * Typically used to structure expandable sections of content.
+ *
+ * @param children - The `AccordionItem` components to render inside the accordion.
+ * @param className - Optional additional classes for the accordion wrapper.
+ *
+ * @example
+ * ```tsx
+ *
+ * This is an accordion item.}
+ * />
+ * To toggle sections of content easily.}
+ * defaultOpen
+ * />
+ *
+ * ```
+ */
+export const Accordion = ({ children, className }: AccordionProps) => {
+ return {children}
+}
diff --git a/docs/app/ui/alert.tsx b/docs/app/ui/alert.tsx
new file mode 100644
index 0000000..5ec3905
--- /dev/null
+++ b/docs/app/ui/alert.tsx
@@ -0,0 +1,80 @@
+import type { ReactNode } from "react"
+import { useTranslation } from "react-i18next"
+import { cn } from "~/utils/css"
+import { Icon } from "./icon/icon"
+
+interface AlertProps {
+ children: ReactNode
+ title?: string
+ variant: "info" | "warning"
+ className?: string
+}
+
+export const Alert = ({ children, title, variant, className = "" }: AlertProps) => {
+ const { t } = useTranslation()
+ const getVariantStyles = () => {
+ switch (variant) {
+ case "info":
+ return {
+ container: "bg-[var(--color-info-bg)] border-[var(--color-info-border)] border-l-4",
+ title: "text-[var(--color-info-text)]",
+ content: "text-[var(--color-info-text)]",
+ icon: "text-[var(--color-info-icon)]",
+ }
+ case "warning":
+ return {
+ container: "bg-[var(--color-warning-bg)] border-[var(--color-warning-border)] border-l-4",
+ title: "text-[var(--color-warning-text)]",
+ content: "text-[var(--color-warning-text)]",
+ icon: "text-[var(--color-warning-icon)]",
+ }
+ default:
+ return {
+ container: "",
+ title: "",
+ content: "",
+ icon: "",
+ }
+ }
+ }
+
+ const getIcon = () => {
+ switch (variant) {
+ case "info":
+ return
+ case "warning":
+ return
+ default:
+ return null
+ }
+ }
+
+ const styles = getVariantStyles()
+ const defaultTitle = variant === "info" ? t("titles.good_to_know") : t("titles.warning")
+
+ return (
+
+
+
{getIcon()}
+
+ {title || defaultTitle}
+
+
+
+
+ {children}
+
+
+ )
+}
diff --git a/docs/app/ui/anchor-tag.tsx b/docs/app/ui/anchor-tag.tsx
new file mode 100644
index 0000000..809c78d
--- /dev/null
+++ b/docs/app/ui/anchor-tag.tsx
@@ -0,0 +1,9 @@
+import type { ComponentPropsWithoutRef } from "react"
+import { cn } from "~/utils/css"
+
+export const Anchor = (props: ComponentPropsWithoutRef<"a">) => {
+ const { className, target, rel, ...rest } = props
+ const safeRel = target === "_blank" ? (rel ?? "noopener noreferrer") : rel
+
+ return
+}
diff --git a/docs/app/ui/breadcrumbs.tsx b/docs/app/ui/breadcrumbs.tsx
new file mode 100644
index 0000000..01bbbbd
--- /dev/null
+++ b/docs/app/ui/breadcrumbs.tsx
@@ -0,0 +1,55 @@
+import { Children, type ReactNode, isValidElement } from "react"
+import { Link } from "react-router"
+import { cn } from "../utils/css"
+import { Icon } from "./icon/icon"
+
+interface BreadcrumbsProps {
+ children: ReactNode
+ className?: string
+}
+
+interface BreadcrumbItemProps {
+ children: ReactNode
+ href?: string
+ isActive?: boolean
+ className?: string
+}
+
+export const BreadcrumbItem = ({ children, href, isActive = false, className }: BreadcrumbItemProps) => {
+ const classes = cn(
+ "text-ellipsis text-start font-medium text-[var(--color-text-normal)]",
+ isActive && "pointer-events-none font-semibold text-[var(--color-text-active)]",
+ className
+ )
+
+ if (href && !isActive) {
+ return (
+
+ {children}
+
+ )
+ }
+
+ return (
+ {children}
+ )
+}
+
+export const Breadcrumbs = ({ children, className }: BreadcrumbsProps) => {
+ const items = Children.toArray(children).filter(
+ (child) => isValidElement(child) && child.type === BreadcrumbItem
+ ) as React.ReactElement[]
+
+ return (
+
+
+ {items.map((child, index) => (
+
+ {index > 0 && }
+ {child}
+
+ ))}
+
+
+ )
+}
diff --git a/docs/app/ui/icon-button.tsx b/docs/app/ui/icon-button.tsx
new file mode 100644
index 0000000..928750c
--- /dev/null
+++ b/docs/app/ui/icon-button.tsx
@@ -0,0 +1,24 @@
+import type { ComponentProps } from "react"
+import { cn } from "~/utils/css"
+import { Icon } from "./icon/icon"
+import type { IconName } from "./icon/icons/types"
+
+interface IconButtonProps extends ComponentProps<"button"> {
+ name: IconName
+ className?: string
+}
+
+export const IconButton = ({ name, className, ...props }: IconButtonProps) => {
+ return (
+
+
+
+ )
+}
diff --git a/docs/app/ui/icon/icon.tsx b/docs/app/ui/icon/icon.tsx
new file mode 100644
index 0000000..c946236
--- /dev/null
+++ b/docs/app/ui/icon/icon.tsx
@@ -0,0 +1,45 @@
+import type { SVGProps } from "react"
+import { cn } from "~/utils/css"
+import spriteHref from "./icons/icon.svg"
+import type { IconName } from "./icons/types"
+
+enum IconSize {
+ xs = "12",
+ sm = "16",
+ md = "24",
+ lg = "32",
+ xl = "40",
+}
+
+type IconSizes = keyof typeof IconSize
+
+interface IconProps extends SVGProps {
+ name: IconName
+ testId?: string
+ className?: string
+ size?: IconSizes
+}
+
+/**
+ * Icon component wrapper for SVG icons.
+ * @returns SVG icon as a react component
+ */
+export const Icon = ({ name, testId, className, size = "md", ...props }: IconProps) => {
+ const iconSize = IconSize[size]
+ const iconClasses = cn("inline-block flex-shrink-0", className)
+ return (
+
+ {name}
+
+
+ )
+}
diff --git a/docs/app/ui/icon/icons/icon.svg b/docs/app/ui/icon/icons/icon.svg
new file mode 100644
index 0000000..564b19d
--- /dev/null
+++ b/docs/app/ui/icon/icons/icon.svg
@@ -0,0 +1,32 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/docs/app/ui/icon/icons/types.ts b/docs/app/ui/icon/icons/types.ts
new file mode 100644
index 0000000..d210123
--- /dev/null
+++ b/docs/app/ui/icon/icons/types.ts
@@ -0,0 +1,33 @@
+// This file is generated by icon spritesheet generator
+
+export const iconNames = [
+ "Zap",
+ "X",
+ "TriangleAlert",
+ "Trash2",
+ "Sun",
+ "SunMoon",
+ "ShieldCheck",
+ "Search",
+ "Rocket",
+ "Pilcrow",
+ "Palette",
+ "Moon",
+ "Menu",
+ "Info",
+ "Hash",
+ "Github",
+ "Ghost",
+ "FileText",
+ "Code",
+ "Clock",
+ "ClipboardCopy",
+ "ClipboardCheck",
+ "ChevronRight",
+ "ChevronDown",
+ "Bot",
+ "ArrowRight",
+ "ArrowLeft",
+] as const
+
+export type IconName = (typeof iconNames)[number]
diff --git a/docs/app/ui/info-alert.tsx b/docs/app/ui/info-alert.tsx
new file mode 100644
index 0000000..a8bec48
--- /dev/null
+++ b/docs/app/ui/info-alert.tsx
@@ -0,0 +1,16 @@
+import type { ReactNode } from "react"
+import { Alert } from "./alert"
+
+interface InfoAlertProps {
+ children: ReactNode
+ title?: string
+ className?: string
+}
+
+export const InfoAlert = ({ children, title, className }: InfoAlertProps) => {
+ return (
+
+ {children}
+
+ )
+}
diff --git a/docs/app/ui/inline-code.tsx b/docs/app/ui/inline-code.tsx
new file mode 100644
index 0000000..42903f8
--- /dev/null
+++ b/docs/app/ui/inline-code.tsx
@@ -0,0 +1,20 @@
+import type { ComponentPropsWithoutRef } from "react"
+
+/**
+ * A styled wrapper around the native element, used to display inline code snippets with consistent styling.
+ *
+ * Useful for rendering short code expressions, variable names, or commands within paragraphs or markdown content.
+ *
+ * Example usage:
+ *
+ * Install it using npm install forge42/base-stack .
+ *
+ */
+export const InlineCode = (props: ComponentPropsWithoutRef<"code">) => {
+ return (
+
+ )
+}
diff --git a/docs/app/ui/kbd.tsx b/docs/app/ui/kbd.tsx
new file mode 100644
index 0000000..5116532
--- /dev/null
+++ b/docs/app/ui/kbd.tsx
@@ -0,0 +1,21 @@
+import type { ReactNode } from "react"
+import { cn } from "~/utils/css"
+
+export function Kbd({
+ children,
+ className,
+}: {
+ children: ReactNode
+ className?: string
+}) {
+ return (
+
+ {children}
+
+ )
+}
diff --git a/docs/app/ui/link/index.ts b/docs/app/ui/link/index.ts
new file mode 100644
index 0000000..6ada303
--- /dev/null
+++ b/docs/app/ui/link/index.ts
@@ -0,0 +1 @@
+export * from "./link"
diff --git a/docs/app/ui/link/link.tsx b/docs/app/ui/link/link.tsx
new file mode 100644
index 0000000..aff0e34
--- /dev/null
+++ b/docs/app/ui/link/link.tsx
@@ -0,0 +1,20 @@
+import { Link as ReactRouterLink, type LinkProps as ReactRouterLinkProps } from "react-router"
+import type { Language } from "~/localization/resource"
+import { useEnhancedTo } from "./useEnhancedTo"
+
+interface LinkProps extends ReactRouterLinkProps {
+ keepSearchParams?: boolean
+ language?: Language
+}
+
+export const Link = ({
+ prefetch = "intent",
+ viewTransition = true,
+ keepSearchParams = false,
+ to,
+ language,
+ ...props
+}: LinkProps) => {
+ const enhancedTo = useEnhancedTo({ language, to, keepSearchParams })
+ return
+}
diff --git a/docs/app/ui/link/useEnhancedTo.ts b/docs/app/ui/link/useEnhancedTo.ts
new file mode 100644
index 0000000..4f6afac
--- /dev/null
+++ b/docs/app/ui/link/useEnhancedTo.ts
@@ -0,0 +1,52 @@
+import { useMemo } from "react"
+import { type To, useSearchParams } from "react-router"
+import type { Language } from "~/localization/resource"
+
+/**
+ * Enhances the default to prop by adding the language to the search params and conditionally keeping the search params
+ * @param language The language to use over the search param language
+ * @param to The new location to navigate to
+ * @param keepSearchParams Whether to keep the search params or not
+ *
+ * @example
+ * ```tsx
+ * // override the language
+ * function Component(){
+ * const enhancedTo = useEnhancedTo({ language: "en", to: "/" })
+ * return // Will navigate to /?lng=en even if the current url contains a different lanugage
+ * }
+ *
+ * function Component(){
+ * const enhancedTo = useEnhancedTo({ to: "/" })
+ * return // Will navigate to /?lng=X where X is the current language in the url search params, or just to / if no language is found
+ * }
+ *
+ * function Component(){
+ * const enhancedTo = useEnhancedTo({ to: "/", keepSearchParams: true })
+ * return // Will navigate to /?params=from_the_url_search_params&lng=en
+ * }
+ * ```
+ */
+export const useEnhancedTo = ({
+ language,
+ to,
+ keepSearchParams,
+}: { language?: Language; to: To; keepSearchParams?: boolean }) => {
+ const [params] = useSearchParams()
+ const { lng, ...searchParams } = Object.fromEntries(params.entries())
+ // allow language override for language switcher or manually setting the language in specific cases
+ const lang = language ?? params.get("lng")
+ const newSearchParams = new URLSearchParams(searchParams)
+ const searchString = newSearchParams.toString()
+ const hasSearchParams = searchString.length > 0
+ const appendSearchParams = lang || hasSearchParams
+ const newPath = useMemo(
+ () =>
+ to +
+ (appendSearchParams
+ ? `?${keepSearchParams && hasSearchParams ? `${searchString}${lang ? "&" : ""}` : ""}${lang ? `lng=${lang}` : ""}`
+ : ""),
+ [to, appendSearchParams, keepSearchParams, hasSearchParams, searchString, lang]
+ )
+ return newPath
+}
diff --git a/docs/app/ui/list-item.tsx b/docs/app/ui/list-item.tsx
new file mode 100644
index 0000000..f93b204
--- /dev/null
+++ b/docs/app/ui/list-item.tsx
@@ -0,0 +1,14 @@
+import type { ComponentPropsWithoutRef } from "react"
+import { cn } from "~/utils/css"
+
+export const ListItem = (props: ComponentPropsWithoutRef<"li">) => {
+ return (
+ li]:ml-2 [&>li]:marker:font-medium",
+ props.className
+ )}
+ />
+ )
+}
diff --git a/docs/app/ui/ordered-list.tsx b/docs/app/ui/ordered-list.tsx
new file mode 100644
index 0000000..d3ff4cb
--- /dev/null
+++ b/docs/app/ui/ordered-list.tsx
@@ -0,0 +1,25 @@
+import type { ComponentPropsWithoutRef } from "react"
+import { cn } from "~/utils/css"
+
+/**
+ * A styled wrapper around the native element, used to render ordered lists
+ * with consistent spacing, indentation, and text styling.
+ *
+ * Example usage:
+ *
+ * Clone the repository
+ * Install dependencies
+ * Run the development server
+ *
+ */
+export const OrderedList = (props: ComponentPropsWithoutRef<"ol">) => {
+ return (
+ li]:ml-2 [&>li]:marker:font-medium",
+ props.className
+ )}
+ />
+ )
+}
diff --git a/docs/app/ui/strong-text.tsx b/docs/app/ui/strong-text.tsx
new file mode 100644
index 0000000..7c27482
--- /dev/null
+++ b/docs/app/ui/strong-text.tsx
@@ -0,0 +1,6 @@
+import type { ComponentPropsWithoutRef } from "react"
+import { cn } from "~/utils/css"
+
+export const Strong = (props: ComponentPropsWithoutRef<"strong">) => {
+ return
+}
diff --git a/docs/app/ui/title.tsx b/docs/app/ui/title.tsx
new file mode 100644
index 0000000..f96d08a
--- /dev/null
+++ b/docs/app/ui/title.tsx
@@ -0,0 +1,53 @@
+import { cn } from "../utils/css"
+
+export const ValidTitleElements = {
+ h1: "text-2xl sm:text-3xl md:text-4xl",
+ h2: "text-xl sm:text-2xl md:text-3xl",
+ h3: "text-lg sm:text-xl md:text-2xl",
+ h4: "text-base sm:text-lg md:text-xl",
+ h5: "text-sm sm:text-base md:text-lg",
+ h6: "text-xs sm:text-sm md:text-base",
+} as const
+
+interface TitleProps extends React.HTMLAttributes {
+ children: React.ReactNode
+ as: keyof typeof ValidTitleElements
+ className?: string
+}
+
+/**
+ * A reusable, styled heading component for consistent typography across the project.
+ *
+ * The `Title` component renders a heading element (`h1` through `h6`) with
+ * predefined responsive font sizes, weights, and line heights based on a design scale.
+ * It ensures consistent visual hierarchy and styling throughout the app.
+ *
+ * You can customize the appearance further using the `className` prop, and the rendered
+ * element type is controlled via the `as` prop.
+ *
+ * @param as - The HTML heading element to render (`h1` through `h6`). Required.
+ * @param children - The title content to display inside the heading.
+ * @param className - Optional additional Tailwind or custom classes to override or extend the default styles.
+ * @param props - Any additional valid HTML attributes for the heading element.
+ *
+ * @example
+ * ```tsx
+ *
+ * Getting Started
+ *
+ * ```
+ *
+ * @returns A JSX element with consistent project-specific title styling.
+ */
+const Title = ({ children, as, className, ...props }: TitleProps) => {
+ const Component = as
+ const titleClasses = cn(ValidTitleElements[as], "text-[var(--color-text-normal)]", className)
+
+ return (
+
+ {children}
+
+ )
+}
+
+export { Title }
diff --git a/docs/app/ui/warning-alert.tsx b/docs/app/ui/warning-alert.tsx
new file mode 100644
index 0000000..36849f5
--- /dev/null
+++ b/docs/app/ui/warning-alert.tsx
@@ -0,0 +1,16 @@
+import type { ReactNode } from "react"
+import { Alert } from "./alert"
+
+interface WarningAlertProps {
+ children: ReactNode
+ title?: string
+ className?: string
+}
+
+export const WarningAlert = ({ children, title, className }: WarningAlertProps) => {
+ return (
+
+ {children}
+
+ )
+}
diff --git a/docs/app/utils/create-github-contribution-links.ts b/docs/app/utils/create-github-contribution-links.ts
new file mode 100644
index 0000000..825f2a6
--- /dev/null
+++ b/docs/app/utils/create-github-contribution-links.ts
@@ -0,0 +1,26 @@
+interface GitHubContributionLinkOptions {
+ pagePath: string
+ owner: string
+ repo: string
+}
+
+export function createGitHubContributionLinks({ pagePath, owner, repo }: GitHubContributionLinkOptions) {
+ const githubBase = `https://github.com/${owner}/${repo}`
+ const editUrl = `${githubBase}/edit/main/content/${pagePath}`
+
+ const issueTitle = `Issue with the "${pagePath}" doc`
+ const issueBody = `I found an issue with this document.
+
+**Title:** ${pagePath}
+**Source:** ${githubBase}/blob/main/content/${pagePath}
+
+### Describe the issue
+
+
+### Additional info
+`
+
+ const issueUrl = `${githubBase}/issues/new?title=${encodeURIComponent(issueTitle)}&body=${encodeURIComponent(issueBody)}`
+
+ return { editUrl, issueUrl }
+}
diff --git a/docs/app/utils/create-sidebar-tree.ts b/docs/app/utils/create-sidebar-tree.ts
new file mode 100644
index 0000000..127b8d0
--- /dev/null
+++ b/docs/app/utils/create-sidebar-tree.ts
@@ -0,0 +1,59 @@
+import type { Page } from "content-collections-types"
+import { getContent } from "./load-content"
+import type { Version } from "./version-resolvers"
+
+export type SidebarTree = {
+ sections: SidebarSection[]
+ documentationPages: Page[]
+}
+
+export type SidebarSection = {
+ title: string
+ slug: string
+ subsections: SidebarSection[]
+ documentationPages: Page[]
+}
+
+const parentOf = (slug: string) => {
+ const i = slug.lastIndexOf("/")
+ return i === -1 ? "" : slug.slice(0, i)
+}
+
+export async function createSidebarTree(version: Version) {
+ const { allPages, allSections } = await getContent(version)
+
+ const sectionMap = new Map()
+ for (const s of allSections) {
+ sectionMap.set(s.slug, {
+ ...s,
+ subsections: [],
+ documentationPages: [],
+ })
+ }
+
+ for (const node of sectionMap.values()) {
+ const p = parentOf(node.slug)
+ const parent = sectionMap.get(p)
+ if (parent) parent.subsections.push(node)
+ }
+
+ const documentationPages: Page[] = []
+ for (const page of allPages) {
+ const parts = page.slug.split("/").filter(Boolean)
+
+ if (parts.length < 2) {
+ if (page.slug !== "index" && !page.slug.startsWith("_")) {
+ documentationPages.push({ ...page, slug: page.slug, title: page.title })
+ }
+ continue
+ }
+
+ const parentSlug = parts.length >= 3 ? parts.slice(0, 2).join("/") : parts[0]
+ const parent = sectionMap.get(parentSlug)
+ if (parent) parent.documentationPages.push({ ...page, slug: page.slug, title: page.title })
+ }
+
+ const sections = [...sectionMap.values()].filter((s) => !sectionMap.has(parentOf(s.slug)))
+
+ return { sections, documentationPages }
+}
diff --git a/docs/app/utils/css.ts b/docs/app/utils/css.ts
new file mode 100644
index 0000000..bb075a8
--- /dev/null
+++ b/docs/app/utils/css.ts
@@ -0,0 +1,4 @@
+import clsx, { type ClassValue } from "clsx"
+import { twMerge } from "tailwind-merge"
+
+export const cn = (...inputs: ClassValue[]) => twMerge(clsx(inputs))
diff --git a/docs/app/utils/extract-heading-tree-from-mdx.ts b/docs/app/utils/extract-heading-tree-from-mdx.ts
new file mode 100644
index 0000000..4ca2735
--- /dev/null
+++ b/docs/app/utils/extract-heading-tree-from-mdx.ts
@@ -0,0 +1,127 @@
+import slugify from "slug"
+
+export type HeadingItem = {
+ slug: string
+ title: string
+ level: number
+ children: HeadingItem[]
+}
+
+const ATX_RE = /^\s*(#{1,6})\s+(.+?)\s*$/
+const FENCE_RE = /^\s*(`{3,}|~{3,})(.*)$/
+const SETEXT_UNDERLINE_RE = /^\s*(=+|-+)\s*$/
+const FRONTMATTER_DELIM_RE = /^\s*---\s*$/
+
+const cleanMarkdown = (text: string) =>
+ text
+ .replace(/`([^`]+)`/g, "$1") // inline code
+ .replace(/\*\*([^*]+)\*\*/g, "$1") // bold
+ .replace(/(^|[^*])\*([^*]+)\*(?!\*)/g, "$1$2") // italics
+ .replace(/\[([^\]]+)\]\([^)]+\)/g, "$1") // links -> text
+ .replace(/\{[^}]*\}/g, "") // MDX/remark directives
+ .replace(/<\/?[^>]+(>|$)/g, "") // HTML tags
+ .trim()
+
+function skipFrontmatter(lines: string[], start = 0) {
+ if (!lines[start]?.match(FRONTMATTER_DELIM_RE)) return start
+ let i = start + 1
+ while (i < lines.length && !lines[i].match(FRONTMATTER_DELIM_RE)) i++
+ return i < lines.length ? i + 1 : start
+}
+
+type FenceState = { inFence: boolean; marker: "`" | "~" | "" }
+
+function fenceStep(state: FenceState, line: string): FenceState {
+ const m = line.match(FENCE_RE)
+ if (!m) return state
+ const marker = (m[1][0] === "`" ? "`" : "~") as "`" | "~"
+ if (!state.inFence) return { inFence: true, marker }
+ return marker === state.marker ? { inFence: false, marker: "" } : state
+}
+
+function parseAtx(line: string): { level: number; text: string } | null {
+ const m = line.match(ATX_RE)
+ if (!m) return null
+ return { level: m[1].length, text: m[2] }
+}
+
+function parseSetext(curr: string, next: string): { level: number; text: string } | null {
+ if (!/\S/.test(curr)) return null
+ if (!SETEXT_UNDERLINE_RE.test(next)) return null
+ const level = next.trim().startsWith("=") ? 1 : 2
+ return { level, text: curr }
+}
+
+function makeNode(level: number, rawTitle: string): HeadingItem | null {
+ const title = cleanMarkdown(rawTitle)
+ if (!title) return null
+ return {
+ title,
+ slug: slugify(title, { lower: true }),
+ level,
+ children: [],
+ }
+}
+
+function pushNode(root: HeadingItem[], stack: HeadingItem[], node: HeadingItem) {
+ while (stack.length && stack[stack.length - 1].level >= node.level) {
+ stack.pop()
+ }
+ if (stack.length === 0) root.push(node)
+ else stack[stack.length - 1].children.push(node)
+ stack.push(node)
+}
+
+export function extractHeadingTreeFromMarkdown(content: string, maxDepth = 3) {
+ const lines = content.split("\n")
+ const root: HeadingItem[] = []
+ const stack: HeadingItem[] = []
+ let i = skipFrontmatter(lines)
+ let fence: FenceState = { inFence: false, marker: "" }
+
+ while (i < lines.length) {
+ const line = lines[i]
+
+ // fence handling
+ const nextFence = fenceStep(fence, line)
+ const justEnteredFence = !fence.inFence && nextFence.inFence
+ const justExitedFence = fence.inFence && !nextFence.inFence
+ fence = nextFence
+ if (justEnteredFence || fence.inFence) {
+ i++
+ continue
+ }
+ if (justExitedFence) {
+ i++
+ continue
+ }
+
+ // ATX: "# ...", "## ...", ...
+ const atx = parseAtx(line)
+ if (atx) {
+ if (atx.level <= maxDepth) {
+ const node = makeNode(atx.level, atx.text)
+ if (node) pushNode(root, stack, node)
+ }
+ i++
+ continue
+ }
+
+ // Setext: "Title" + ("===" | "---")
+ if (i + 1 < lines.length) {
+ const setext = parseSetext(line, lines[i + 1])
+ if (setext) {
+ if (setext.level <= maxDepth) {
+ const node = makeNode(setext.level, setext.text)
+ if (node) pushNode(root, stack, node)
+ }
+ i += 2
+ continue
+ }
+ }
+
+ i++
+ }
+
+ return root
+}
diff --git a/docs/app/utils/flatten-sidebar.ts b/docs/app/utils/flatten-sidebar.ts
new file mode 100644
index 0000000..bb56199
--- /dev/null
+++ b/docs/app/utils/flatten-sidebar.ts
@@ -0,0 +1,10 @@
+import type { SidebarSection } from "~/components/sidebar/sidebar"
+
+export function flattenSidebarItems(sections: SidebarSection[]) {
+ const collectPages = (section: SidebarSection): { title: string; slug: string }[] => [
+ ...section.documentationPages,
+ ...section.subsections.flatMap(collectPages),
+ ]
+
+ return sections.flatMap(collectPages)
+}
diff --git a/docs/app/utils/fonts.ts b/docs/app/utils/fonts.ts
new file mode 100644
index 0000000..643780a
--- /dev/null
+++ b/docs/app/utils/fonts.ts
@@ -0,0 +1,164 @@
+import dynaPuffBold from "../../resources/fonts/dyna-puff/DynaPuff-Bold.ttf"
+import dynaPuffMedium from "../../resources/fonts/dyna-puff/DynaPuff-Medium.ttf"
+import dynaPuffRegular from "../../resources/fonts/dyna-puff/DynaPuff-Regular.ttf"
+import dynaPuffSemiBold from "../../resources/fonts/dyna-puff/DynaPuff-SemiBold.ttf"
+import interBlack from "../../resources/fonts/inter/Inter-Black.ttf"
+import interBlackItalic from "../../resources/fonts/inter/Inter-BlackItalic.ttf"
+import interBold from "../../resources/fonts/inter/Inter-Bold.ttf"
+import interBoldItalic from "../../resources/fonts/inter/Inter-BoldItalic.ttf"
+import interExtraBold from "../../resources/fonts/inter/Inter-ExtraBold.ttf"
+import interExtraBoldItalic from "../../resources/fonts/inter/Inter-ExtraBoldItalic.ttf"
+import interExtraLight from "../../resources/fonts/inter/Inter-ExtraLight.ttf"
+import interExtraLightItalic from "../../resources/fonts/inter/Inter-ExtraLightItalic.ttf"
+import interItalic from "../../resources/fonts/inter/Inter-Italic.ttf"
+import interLight from "../../resources/fonts/inter/Inter-Light.ttf"
+import interLightItalic from "../../resources/fonts/inter/Inter-LightItalic.ttf"
+import interMedium from "../../resources/fonts/inter/Inter-Medium.ttf"
+import interMediumItalic from "../../resources/fonts/inter/Inter-MediumItalic.ttf"
+import interRegular from "../../resources/fonts/inter/Inter-Regular.ttf"
+import interSemiBold from "../../resources/fonts/inter/Inter-SemiBold.ttf"
+import interSemiBoldItalic from "../../resources/fonts/inter/Inter-SemiBoldItalic.ttf"
+import interThin from "../../resources/fonts/inter/Inter-Thin.ttf"
+import interThinItalic from "../../resources/fonts/inter/Inter-ThinItalic.ttf"
+import spaceNormal from "../../resources/fonts/space/Space.woff2"
+
+export const fonts = [
+ {
+ fontFamily: "Dyna Puff",
+ fontStyle: "normal",
+ fontWeight: 400,
+ src: dynaPuffRegular,
+ },
+ {
+ fontFamily: "Dyna Puff",
+ fontStyle: "normal",
+ fontWeight: 700,
+ src: dynaPuffBold,
+ },
+ {
+ fontFamily: "Dyna Puff",
+ fontStyle: "normal",
+ fontWeight: 500,
+ src: dynaPuffMedium,
+ },
+ {
+ fontFamily: "Dyna Puff",
+ fontStyle: "normal",
+ fontWeight: 600,
+ src: dynaPuffSemiBold,
+ },
+ {
+ fontFamily: "Inter",
+ fontStyle: "normal",
+ fontWeight: 400,
+ src: interRegular,
+ },
+ {
+ fontFamily: "Inter",
+ fontStyle: "normal",
+ fontWeight: 700,
+ src: interBold,
+ },
+ {
+ fontFamily: "Inter",
+ fontStyle: "normal",
+ fontWeight: 900,
+ src: interBlack,
+ },
+ {
+ fontFamily: "Inter",
+ fontStyle: "normal",
+ fontWeight: 200,
+ src: interExtraLight,
+ },
+ {
+ fontFamily: "Inter",
+ fontStyle: "normal",
+ fontWeight: 300,
+ src: interLight,
+ },
+ {
+ fontFamily: "Inter",
+ fontStyle: "normal",
+ fontWeight: 500,
+ src: interMedium,
+ },
+ {
+ fontFamily: "Inter",
+ fontStyle: "normal",
+ fontWeight: 600,
+ src: interSemiBold,
+ },
+ {
+ fontFamily: "Inter",
+ fontStyle: "normal",
+ fontWeight: 800,
+ src: interExtraBold,
+ },
+ {
+ fontFamily: "Inter",
+ fontStyle: "normal",
+ fontWeight: 100,
+ src: interThin,
+ },
+ {
+ fontFamily: "Inter",
+ fontStyle: "italic",
+ fontWeight: 400,
+ src: interItalic,
+ },
+ {
+ fontFamily: "Inter",
+ fontStyle: "italic",
+ fontWeight: 700,
+ src: interBoldItalic,
+ },
+ {
+ fontFamily: "Inter",
+ fontStyle: "italic",
+ fontWeight: 900,
+ src: interBlackItalic,
+ },
+ {
+ fontFamily: "Inter",
+ fontStyle: "italic",
+ fontWeight: 200,
+ src: interExtraLightItalic,
+ },
+ {
+ fontFamily: "Inter",
+ fontStyle: "italic",
+ fontWeight: 300,
+ src: interLightItalic,
+ },
+ {
+ fontFamily: "Inter",
+ fontStyle: "italic",
+ fontWeight: 500,
+ src: interMediumItalic,
+ },
+ {
+ fontFamily: "Inter",
+ fontStyle: "italic",
+ fontWeight: 600,
+ src: interSemiBoldItalic,
+ },
+ {
+ fontFamily: "Inter",
+ fontStyle: "italic",
+ fontWeight: 800,
+ src: interExtraBoldItalic,
+ },
+ {
+ fontFamily: "Inter",
+ fontStyle: "italic",
+ fontWeight: 100,
+ src: interThinItalic,
+ },
+ {
+ fontFamily: "Space",
+ fontStyle: "normal",
+ fontWeight: 400,
+ src: spaceNormal,
+ },
+]
diff --git a/docs/app/utils/get-domain.ts b/docs/app/utils/get-domain.ts
new file mode 100644
index 0000000..0ac184f
--- /dev/null
+++ b/docs/app/utils/get-domain.ts
@@ -0,0 +1,5 @@
+export function getDomain(request: Request) {
+ const url = new URL(request.url)
+ const domain = url.origin
+ return { domain }
+}
diff --git a/docs/app/utils/get-page-slug.tsx b/docs/app/utils/get-page-slug.tsx
new file mode 100644
index 0000000..b35a9eb
--- /dev/null
+++ b/docs/app/utils/get-page-slug.tsx
@@ -0,0 +1,5 @@
+import type { Page } from "content-collections-types"
+
+export function getPageSlug(page: Page) {
+ return page._meta.path === "_index" ? "/" : page.slug
+}
diff --git a/docs/app/utils/http.ts b/docs/app/utils/http.ts
new file mode 100644
index 0000000..20303dc
--- /dev/null
+++ b/docs/app/utils/http.ts
@@ -0,0 +1,23 @@
+/**
+ * Helper utility used to extract the domain from the request even if it's
+ * behind a proxy. This is useful for sitemaps and other things.
+ * @param request Request object
+ * @returns Current domain
+ */
+export const createDomain = (request: Request) => {
+ const headers = request.headers
+ const maybeProto = headers.get("x-forwarded-proto")
+ const maybeHost = headers.get("host")
+ const url = new URL(request.url)
+ // If the request is behind a proxy, we need to use the x-forwarded-proto and host headers
+ // to get the correct domain
+ if (maybeProto) {
+ return `${maybeProto}://${maybeHost ?? url.host}`
+ }
+ // If we are in local development, return the localhost
+ if (url.hostname === "localhost") {
+ return `http://${url.host}`
+ }
+ // If we are in production, return the production domain
+ return `https://${url.host}`
+}
diff --git a/docs/app/utils/llms-txt-builder.ts b/docs/app/utils/llms-txt-builder.ts
new file mode 100644
index 0000000..1e76b19
--- /dev/null
+++ b/docs/app/utils/llms-txt-builder.ts
@@ -0,0 +1,54 @@
+import { createDomain } from "~/utils/http"
+import type { Page, Section } from "../../content-collections"
+import { getContent } from "./load-content"
+import { type Version, pageUrlWithVersion } from "./version-resolvers"
+
+function buildSectionTitles(sections: Section[]) {
+ return new Map(sections.map((s) => [s.slug.split("/").pop() || "", s.title]))
+}
+
+function groupPagesByFolder(pages: Page[]) {
+ return pages.reduce((groups, p) => {
+ const id = p.section ?? p._meta.path?.split("/")[0]
+ if (!id) return groups
+ const list = groups.get(id) ?? []
+ if (!groups.has(id)) groups.set(id, list)
+ list.push(p)
+ return groups
+ }, new Map())
+}
+
+function renderVersionBlock(domain: string, version: string, pages: Page[], sections: Section[]) {
+ if (!pages.length) return `## ${version}\n\n_No pages found._`
+
+ const sectionTitles = buildSectionTitles(sections)
+ const groups = groupPagesByFolder(pages)
+
+ const renderPageLink = (p: Page) => {
+ const url = pageUrlWithVersion(domain, version, p.slug)
+ const note = p.description
+ return `- [${p.title}](${url})${note ? `: ${note}` : ""}`
+ }
+
+ const renderSection = ([id, list]: [string, Page[]]) => {
+ const label = sectionTitles.get(id) ?? id
+ return `### ${label}\n\n${list.map(renderPageLink).join("\n")}`
+ }
+
+ return `\n## ${version}\n\n${Array.from(groups.entries()).map(renderSection).join("\n\n")}`
+}
+
+export async function renderLlmsTxt(opts: {
+ request: Request
+ version: Version
+ title: string
+ tagline: string
+}) {
+ const { request, version, title, tagline } = opts
+ const domain = createDomain(request)
+
+ const { allPages, allSections } = await getContent(version)
+ const content = renderVersionBlock(domain, version, allPages, allSections)
+
+ return [`# ${title}`, `> ${tagline}`, content, ""].join("\n")
+}
diff --git a/docs/app/utils/load-content-collections.ts b/docs/app/utils/load-content-collections.ts
new file mode 100644
index 0000000..4f9f1af
--- /dev/null
+++ b/docs/app/utils/load-content-collections.ts
@@ -0,0 +1,37 @@
+import path, { resolve } from "node:path"
+import { pathToFileURL } from "node:url"
+import type { Page } from "content-collections-types"
+import type { Section } from "content-collections-types"
+import { getServerEnv } from "~/env.server"
+import type { Version } from "./version-resolvers"
+
+/**
+ * Load content-collections outputs
+ * Always read from generated-docs
+ * If no tags/releases exist → fallback to main branch (production) or current (development)
+ */
+export async function loadContentCollections(version: Version) {
+ const { NODE_ENV } = getServerEnv()
+ const projectRoot = process.cwd()
+ // locally we use the actual content-collections source for DX and hot-reloads
+ if (NODE_ENV === "development") {
+ const { allPages, allSections } = await import("content-collections")
+ return { allPages, allSections }
+ }
+ const genBase = resolve(projectRoot, "generated-docs", version, ".content-collections", "generated")
+
+ const pagesPath = pathToFileURL(path.join(genBase, "allPages.js")).href
+
+ const sectionsPath = pathToFileURL(path.join(genBase, "allSections.js")).href
+
+ const pagesMod = await import(/* @vite-ignore */ pagesPath)
+ const sectionsMod = await import(/* @vite-ignore */ sectionsPath)
+
+ const allPages = pagesMod.default as Page[]
+ const allSections = sectionsMod.default as Section[]
+ if (!Array.isArray(allPages) || !Array.isArray(allSections)) {
+ throw new Error(`Generated modules must default-export arrays (allPages/allSections) for version ${version}.`)
+ }
+
+ return { allPages, allSections }
+}
diff --git a/docs/app/utils/load-content.ts b/docs/app/utils/load-content.ts
new file mode 100644
index 0000000..beb44bf
--- /dev/null
+++ b/docs/app/utils/load-content.ts
@@ -0,0 +1,25 @@
+import type { Section } from "content-collections-types"
+import type { Page } from "content-collections-types"
+import { loadContentCollections } from "~/utils/load-content-collections"
+import type { Version } from "~/utils/version-resolvers"
+import { versions } from "./versions"
+
+const content: Map = new Map()
+
+export async function preloadContentCollections() {
+ for (const version of versions) {
+ if (!content.has(version)) {
+ const { allPages, allSections } = await loadContentCollections(version)
+ content.set(version, { allPages, allSections })
+ }
+ }
+}
+
+export async function getContent(version: Version) {
+ const contentForVersion = content.get(version)
+ if (!contentForVersion) {
+ throw new Error(`Content for version "${version}" could not be retrieved.`)
+ }
+ const { allPages, allSections } = await loadContentCollections(version)
+ return { allPages, allSections }
+}
diff --git a/docs/app/utils/local-storage.ts b/docs/app/utils/local-storage.ts
new file mode 100644
index 0000000..ea72753
--- /dev/null
+++ b/docs/app/utils/local-storage.ts
@@ -0,0 +1,12 @@
+export const getStorageItem = (key: string) => localStorage.getItem(key)
+export const setStorageItem = (key: string, value: string) => {
+ try {
+ localStorage.setItem(key, value)
+ } catch (_e) {
+ return
+ }
+}
+
+export const removeStorageItem = (key: string) => localStorage.removeItem(key)
+export const THEME = "theme"
+export const COMMAND_K_SEARCH_HISTORY = "command-k-search-history"
diff --git a/docs/app/utils/parse-search-params.ts b/docs/app/utils/parse-search-params.ts
new file mode 100644
index 0000000..f1d3bb8
--- /dev/null
+++ b/docs/app/utils/parse-search-params.ts
@@ -0,0 +1,15 @@
+import type z from "zod"
+
+export function parseSearchParams(request: Request, schema: T) {
+ const url = new URL(request.url)
+ const params = Object.fromEntries(url.searchParams.entries())
+ const result = schema.safeParse(params)
+
+ if (!result.success) {
+ // biome-ignore lint/suspicious/noConsole: keep for debugging
+ console.error("Invalid query parameters:", result.error)
+ return { params: null }
+ }
+
+ return { params: result.data }
+}
diff --git a/docs/app/utils/path-builders.ts b/docs/app/utils/path-builders.ts
new file mode 100644
index 0000000..5c32bb6
--- /dev/null
+++ b/docs/app/utils/path-builders.ts
@@ -0,0 +1,45 @@
+import { href } from "react-router"
+import { splitSlug } from "./split-slug"
+
+export function buildDocSlug({
+ section,
+ subsection,
+ filename,
+}: {
+ section?: string
+ subsection?: string
+ filename: string
+}) {
+ const seg = [section, subsection, filename].map((s) => (s ?? "").trim()).filter(Boolean)
+ return seg.join("/")
+}
+
+export function buildDocPathFromSlug(slug: string) {
+ const parts = slug.split("/").filter(Boolean)
+
+ if (parts.length === 1) {
+ return `/${parts[0]}`
+ }
+
+ const { section, subsection, filename } = splitSlug(slug)
+ return `/${[section, subsection, filename].filter(Boolean).join("/")}`
+}
+
+function getFilenameFromSlug(slug: string) {
+ return slug.split("/").filter(Boolean).at(-1) ?? slug
+}
+
+export function buildStandaloneTo(version: string, slug: string) {
+ const filename = getFilenameFromSlug(slug)
+ return href("/:version/:section?/:subsection?/:filename", { version, filename })
+}
+
+export function buildSectionedTo(version: string, slug: string) {
+ const { section, subsection, filename } = splitSlug(slug)
+ return href("/:version/:section?/:subsection?/:filename", {
+ version,
+ section,
+ subsection,
+ filename,
+ })
+}
diff --git a/docs/app/utils/scroll-into-view.ts b/docs/app/utils/scroll-into-view.ts
new file mode 100644
index 0000000..27e19d8
--- /dev/null
+++ b/docs/app/utils/scroll-into-view.ts
@@ -0,0 +1,21 @@
+export function scrollIntoView(e: React.MouseEvent, id: string, offset = -80, behavior: ScrollBehavior = "smooth") {
+ e.preventDefault()
+
+ const element = document.getElementById(id)
+ if (!element) return Promise.resolve()
+
+ const targetY = element.getBoundingClientRect().top + window.scrollY + offset
+
+ window.scrollTo({ top: targetY, behavior })
+
+ if (behavior !== "smooth") {
+ return Promise.resolve()
+ }
+
+ const distance = Math.abs(window.scrollY - targetY)
+ const duration = Math.min(distance / 2, 1000)
+
+ return new Promise((resolve) => {
+ setTimeout(resolve, duration)
+ })
+}
diff --git a/docs/app/utils/seo.ts b/docs/app/utils/seo.ts
new file mode 100644
index 0000000..f037a19
--- /dev/null
+++ b/docs/app/utils/seo.ts
@@ -0,0 +1,30 @@
+import { generateMeta } from "@forge42/seo-tools/remix/metadata"
+import type { MetaDescriptor } from "react-router"
+import PackageLogo from "/static/images/package-logo-1200x630.png"
+
+interface MetaFields {
+ domain: string
+ title: string
+ description: string
+ path: string
+ additionalData?: MetaDescriptor[]
+}
+
+export function generateMetaFields({ domain, title, description, path, additionalData }: MetaFields) {
+ const fullUrl = `${domain}${path}`
+
+ return generateMeta(
+ {
+ title,
+ description,
+ url: fullUrl,
+ siteName: "deploykit",
+ image: PackageLogo,
+ },
+ [
+ // Open Graph
+ { property: "og:type", content: "website" },
+ ...(additionalData ?? []),
+ ]
+ )
+}
diff --git a/docs/app/utils/split-slug.ts b/docs/app/utils/split-slug.ts
new file mode 100644
index 0000000..42e911f
--- /dev/null
+++ b/docs/app/utils/split-slug.ts
@@ -0,0 +1,16 @@
+export function splitSlug(slug: string) {
+ const parts = slug.split("/").filter(Boolean)
+ if (parts.length === 2) {
+ const [section, filename] = parts
+ return { section, filename }
+ }
+
+ if (parts.length === 3) {
+ const [section, subsection, filename] = parts
+ return { section, subsection, filename }
+ }
+
+ throw new Error(
+ `Invalid slug format: expected "section/page" or "section/subsection/page" but got ${parts.length} segments — slug: ${slug}`
+ )
+}
diff --git a/docs/app/utils/tests/css.test.ts b/docs/app/utils/tests/css.test.ts
new file mode 100644
index 0000000..78abdfc
--- /dev/null
+++ b/docs/app/utils/tests/css.test.ts
@@ -0,0 +1,39 @@
+import { cn } from "../css"
+
+describe("cn", () => {
+ it("should merge classes", () => {
+ // Arrange
+ const classes = ["class1", "class2"]
+ // Act
+ const result = cn(...classes)
+ // Assert
+ expect(result).toBe("class1 class2")
+ })
+
+ it("should merge classes with undefined", () => {
+ // Arrange
+ const classes = ["class1", undefined, "class2"]
+ // Act
+ const result = cn(...classes)
+ // Assert
+ expect(result).toBe("class1 class2")
+ })
+
+ it("should merge classes with empty string", () => {
+ // Arrange
+ const classes = ["class1", "", "class2"]
+ // Act
+ const result = cn(...classes)
+ // Assert
+ expect(result).toBe("class1 class2")
+ })
+
+ it("should remove duplicates", () => {
+ // Arrange
+ const classes = ["mb-1", "mb-2"]
+ // Act
+ const result = cn(...classes)
+ // Assert
+ expect(result).toBe("mb-2")
+ })
+})
diff --git a/docs/app/utils/tests/local-storage.test.ts b/docs/app/utils/tests/local-storage.test.ts
new file mode 100644
index 0000000..e3efc61
--- /dev/null
+++ b/docs/app/utils/tests/local-storage.test.ts
@@ -0,0 +1,62 @@
+import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"
+import { THEME, getStorageItem, setStorageItem } from "../local-storage"
+
+let store: Record
+let fake!: Storage
+
+function installFakeLocalStorage() {
+ store = {}
+ fake = {
+ getItem: vi.fn((k: string) => (k in store ? store[k] : null)),
+ setItem: vi.fn((k: string, v: string) => {
+ store[k] = String(v)
+ }),
+ removeItem: vi.fn((k: string) => {
+ delete store[k]
+ }),
+ clear: vi.fn(() => {
+ store = {}
+ }),
+ key: vi.fn((i: number) => Object.keys(store)[i] ?? null),
+ get length() {
+ return Object.keys(store).length
+ },
+ } as unknown as Storage
+
+ if (typeof window === "undefined") {
+ globalThis.localStorage = fake
+ return
+ }
+
+ vi.spyOn(window, "localStorage", "get").mockReturnValue(fake)
+}
+
+describe("local storage test suite", () => {
+ beforeEach(() => {
+ vi.restoreAllMocks()
+ installFakeLocalStorage()
+ localStorage.clear()
+ })
+
+ afterEach(() => {
+ vi.restoreAllMocks()
+ })
+
+ describe("getStorageItem", () => {
+ it("returns the stored value for the given key", () => {
+ localStorage.setItem(THEME, "dark")
+ expect(getStorageItem(THEME)).toBe("dark")
+ })
+
+ it("returns null if the key is not found", () => {
+ expect(getStorageItem("nonexistent")).toBeNull()
+ })
+ })
+
+ describe("setStorageItem", () => {
+ it("stores the value for the given key", () => {
+ setStorageItem(THEME, "light")
+ expect(localStorage.getItem(THEME)).toBe("light")
+ })
+ })
+})
diff --git a/docs/app/utils/theme.ts b/docs/app/utils/theme.ts
new file mode 100644
index 0000000..dcb932a
--- /dev/null
+++ b/docs/app/utils/theme.ts
@@ -0,0 +1,17 @@
+import { THEME, setStorageItem } from "./local-storage"
+
+export function getSystemTheme(): "light" | "dark" {
+ if (typeof window === "undefined") return "light"
+ return window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light"
+}
+
+export function getCurrentTheme(): "light" | "dark" {
+ if (typeof document === "undefined") return "light"
+ const theme = document.documentElement.getAttribute("data-theme")
+ return theme === "dark" ? "dark" : "light"
+}
+
+export function applyTheme(theme: "light" | "dark") {
+ document.documentElement.setAttribute("data-theme", theme)
+ setStorageItem(THEME, theme)
+}
diff --git a/docs/app/utils/version-resolvers.ts b/docs/app/utils/version-resolvers.ts
new file mode 100644
index 0000000..0984707
--- /dev/null
+++ b/docs/app/utils/version-resolvers.ts
@@ -0,0 +1,51 @@
+import { href, redirect, useRouteLoaderData } from "react-router"
+import type { loader } from "~/root"
+import { versions } from "./versions"
+
+export type Version = (typeof versions)[number]
+
+export const getLatestVersion = () => versions[0]
+
+export const isKnownVersion = (v: string | undefined): v is Version =>
+ typeof v === "string" && versions.includes(v as Version)
+
+function isUnknownVersion(v?: string) {
+ return typeof v === "string" && !isKnownVersion(v)
+}
+
+export function normalizeVersion(v?: string) {
+ return { version: isKnownVersion(v) ? v : getLatestVersion() }
+}
+
+export function useCurrentVersion() {
+ const data = useRouteLoaderData("root")
+ return data?.version ?? versions[0]
+}
+
+export function resolveVersionForHomepage(version?: string) {
+ if (isUnknownVersion(version) || getLatestVersion() === version) {
+ throw redirect("/home")
+ }
+ return normalizeVersion(version)
+}
+
+function homepageUrl(base: string, version: string) {
+ return `${base}/${version}/home`
+}
+
+export function pageUrlWithVersion(base: string, version: string, slug: string) {
+ return slug === "_index" ? homepageUrl(base, version) : `${base}/${version}/${slug}`
+}
+
+function firstPathSegment(request: Request) {
+ return new URL(request.url).pathname.split("/").filter(Boolean)[0]
+}
+
+export function resolveVersionForLayout(version: string | undefined, request: Request) {
+ if (isKnownVersion(version)) return { version }
+ const first = firstPathSegment(request)
+ return normalizeVersion(first)
+}
+
+export const homepageUrlWithVersion = (v: string) =>
+ v === getLatestVersion() ? href("/:version?/home") : href("/:version?/home", { version: v })
diff --git a/docs/app/utils/versions.ts b/docs/app/utils/versions.ts
new file mode 100644
index 0000000..1fba735
--- /dev/null
+++ b/docs/app/utils/versions.ts
@@ -0,0 +1,4 @@
+// Auto-generated file. Do not edit manually.
+export const versions = [
+ "latest"
+] as const
diff --git a/docs/biome.json b/docs/biome.json
new file mode 100644
index 0000000..86c179d
--- /dev/null
+++ b/docs/biome.json
@@ -0,0 +1,68 @@
+{
+ "$schema": "./node_modules/@biomejs/biome/configuration_schema.json",
+ "files": {
+ "ignore": ["app/library/icon/**/*"]
+ },
+ "vcs": {
+ "enabled": true,
+ "clientKind": "git",
+ "defaultBranch": "main",
+ "useIgnoreFile": true
+ },
+ "formatter": {
+ "enabled": true,
+ "lineWidth": 120
+ },
+ "organizeImports": {
+ "enabled": true
+ },
+ "linter": {
+ "enabled": true,
+ "rules": {
+ "recommended": true,
+ "suspicious": {
+ "recommended": true,
+ "noConsole": "error"
+ },
+ "style": {
+ "recommended": true
+ },
+ "complexity": {
+ "recommended": true
+ },
+ "security": {
+ "recommended": true
+ },
+ "performance": {
+ "recommended": true
+ },
+ "correctness": {
+ "recommended": true,
+ "noUnusedImports": "error",
+ "noUnusedVariables": "error",
+ "noUnusedLabels": "error",
+ "noUnusedFunctionParameters": "error"
+ },
+ "a11y": {
+ "recommended": true
+ },
+ "nursery": {
+ "recommended": true,
+ "noProcessEnv": "error",
+ "useSortedClasses": {
+ "level": "error",
+ "fix": "safe",
+ "options": {
+ "functions": ["cn", "cva", "tw", "clsx", "twMerge"]
+ }
+ }
+ }
+ }
+ },
+ "javascript": {
+ "formatter": {
+ "semicolons": "asNeeded",
+ "trailingCommas": "es5"
+ }
+ }
+}
diff --git a/docs/content-collections.ts b/docs/content-collections.ts
new file mode 100644
index 0000000..b528172
--- /dev/null
+++ b/docs/content-collections.ts
@@ -0,0 +1,101 @@
+import { posix } from "node:path"
+import { defineCollection, defineConfig } from "@content-collections/core"
+import { compileMDX } from "@content-collections/mdx"
+import rehypeSlug from "rehype-slug"
+import { z } from "zod"
+
+const sectionSchema = z.object({
+ title: z.string(),
+})
+const pageSchema = z.object({
+ title: z.string(),
+ summary: z.string(),
+ description: z.string(),
+})
+const metaSchema = z.object({
+ filePath: z.string(),
+ fileName: z.string(),
+ directory: z.string(),
+ path: z.string(),
+ extension: z.string(),
+})
+
+const outputBaseSchema = z.object({
+ slug: z.string(),
+ _meta: metaSchema,
+})
+
+const sectionOutputSchema = sectionSchema.extend(outputBaseSchema.shape)
+
+const pageOutputSchema = pageSchema.extend({
+ ...outputBaseSchema.shape,
+ section: z.string().optional(),
+ rawMdx: z.string(),
+ content: z.string(),
+})
+
+export type Section = z.infer
+export type Page = z.infer
+
+/**
+ * Removes leading number prefixes like "01-", "02-" from each path segment.
+ */
+const cleanSlug = (p: string) =>
+ toPosix(p)
+ .split("/")
+ .filter(Boolean)
+ .map((seg) => seg.replace(/^\d{2,}-/, ""))
+ .join("/")
+
+const toPosix = (s: string) => {
+ return posix.normalize(s.replace(/\\/g, "/"))
+}
+const stripExt = (s: string) => s.replace(/\.(md|mdx)$/i, "")
+const stripTrailingIndex = (s: string) => s.replace(/\/index$/i, "")
+
+/*
+ * This collection defines a documentation section shown in the sidebar of the package documentation.
+ *
+ * Each section is represented by a directory in the `content` folder and must contain an `index.md` file
+ * with metadata (title).
+ */
+const section = defineCollection({
+ name: "section",
+ directory: "content",
+ include: "**/index.md",
+ schema: sectionSchema,
+ transform: (document) => {
+ const slug = stripTrailingIndex(cleanSlug(document._meta.path))
+ return { ...document, slug }
+ },
+})
+
+/*
+ * This collection defines an individual documentation page within the package documentation.
+ *
+ * Pages are `.mdx` files located inside section folders or their subdirectories.
+ */
+const page = defineCollection({
+ name: "page",
+ directory: "content",
+ include: "**/**/*.mdx",
+ schema: pageSchema,
+ transform: async (document, context) => {
+ const cleanedSlug = cleanSlug(document._meta.path)
+ const slug = stripExt(cleanedSlug)
+ const content = await compileMDX(context, document, { rehypePlugins: [rehypeSlug] })
+ const rawMdx = document.content.replace(/^---\s*[\r\n](.*?|\r|\n)---/, "").trim()
+
+ return {
+ ...document,
+ content,
+ slug,
+ section: slug.split("/")[0] ?? "",
+ rawMdx,
+ }
+ },
+})
+
+export default defineConfig({
+ collections: [section, page],
+})
diff --git a/docs/content/01-introduction.mdx b/docs/content/01-introduction.mdx
new file mode 100644
index 0000000..03e9c9d
--- /dev/null
+++ b/docs/content/01-introduction.mdx
@@ -0,0 +1,44 @@
+---
+title: "Introduction"
+summary: "What deploykit is, who it's for, and the problem it solves"
+description: "deploykit turns a Turbo or Nx monorepo into a fully wired Fly.io deployment pipeline — Dockerfiles, fly.toml files and a GitHub Actions workflow — generated as a reviewable PR you own and can edit."
+---
+
+## The problem
+
+Wiring up CI/CD for a monorepo is repetitive, error-prone work. For each deployable app you need a Dockerfile that prunes the workspace correctly, a `fly.toml`, the right build args and runtime secrets, and a GitHub Actions workflow that only redeploys the apps a change actually touches — across preview, staging and production. Getting the Docker layer caching, the `turbo prune` / Nx graph, and the per-environment triggers right takes hours, and the result rots as the monorepo grows.
+
+## What deploykit does
+
+deploykit reads your workspace and does that wiring for you. One command:
+
+```bash
+npx deploykit init
+```
+
+produces:
+
+- Multi-stage, **`turbo-prune`-based** Dockerfiles (or Nx-graph-based) for each deployable app.
+- A `.dockerignore` per app.
+- A per-app `fly.toml` with an HTTP health check.
+- A single `.github/workflows/deploy.yml` that routes changes to preview / teardown / staging / production.
+- A `deploykit.config.ts` that captures every decision and regenerates all of the above.
+
+Everything is written into your repo as ordinary files. You review them, edit them, and commit them like any other code — there is no proprietary runtime and nothing phones home.
+
+## Who it's for
+
+- Teams running a **Turbo** or **Nx** monorepo.
+- Deploying Node/web apps (Next, Remix, React Router, Astro, Vite SPAs, plain Node servers, or static builds) to **Fly.io**.
+- Using **GitHub Actions** for CI.
+
+## Design principles
+
+- **You own the output.** deploykit generates files, not a black box. Delete deploykit tomorrow and your pipeline keeps working.
+- **Nothing happens without confirmation.** `init` shows a full plan and writes/provisions nothing until you say yes. `--dry-run` prints the plan and stops.
+- **The config is the source of truth.** `deploykit.config.ts` drives every generated artifact; re-run `deploykit generate` any time to rebuild them.
+- **Safe by default.** deploykit will not run database migrations for you (a bad one is irreversible), health-checks every deploy so bad releases roll themselves back, and never writes secret *values* — only their names.
+
+## Next steps
+
+Head to **[Getting Started → Installation](/getting-started/installation)** to check prerequisites and run your first `init`.
diff --git a/docs/content/02-getting-started/01-installation.mdx b/docs/content/02-getting-started/01-installation.mdx
new file mode 100644
index 0000000..440cb80
--- /dev/null
+++ b/docs/content/02-getting-started/01-installation.mdx
@@ -0,0 +1,58 @@
+---
+title: "Installation"
+summary: "Prerequisites and how to run deploykit"
+description: "What you need before running deploykit — a Turbo or Nx monorepo, git, the GitHub CLI and flyctl — and the two ways to run it (npx or a dev dependency)."
+---
+
+## Prerequisites
+
+deploykit checks these for you in its **preflight** phase, but you'll want them ready:
+
+| Requirement | Why |
+|-------------|-----|
+| A **git** repository | deploykit branches, commits and opens PRs. |
+| A **Turbo** or **Nx** monorepo | It reads your workspace graph to find deployable apps. |
+| **Node.js 20+** | deploykit's runtime. |
+| A supported package manager | **pnpm**, **yarn**, **npm** or **bun** — detected automatically. |
+| [**GitHub CLI** (`gh`)](https://cli.github.com/) | Used to open the PR and set repository secrets / environments. |
+| [**flyctl**](https://fly.io/docs/flyctl/install/) | Used to provision Fly apps and deploy. |
+
+You only need `gh` and `flyctl` if you opt into provisioning, opening a PR, or deploying. A plain `--dry-run` needs neither.
+
+## Running deploykit
+
+You don't have to install anything — run it straight from npm:
+
+```bash
+npx deploykit init
+```
+
+### As a dev dependency (optional)
+
+If you'd rather pin a version and get a repo-local binary:
+
+```bash
+# pnpm
+pnpm add -D @alminabrulic/deploykit
+
+# npm
+npm install -D @alminabrulic/deploykit
+
+# yarn
+yarn add -D @alminabrulic/deploykit
+```
+
+Then run it via your package manager:
+
+```bash
+pnpm deploykit init
+```
+
+## Verify
+
+```bash
+npx deploykit --version
+npx deploykit --help
+```
+
+`--help` lists every command and flag. When you're ready, continue to the **[Quick Start](/getting-started/quick-start)**.
diff --git a/docs/content/02-getting-started/02-quick-start.mdx b/docs/content/02-getting-started/02-quick-start.mdx
new file mode 100644
index 0000000..b070c58
--- /dev/null
+++ b/docs/content/02-getting-started/02-quick-start.mdx
@@ -0,0 +1,77 @@
+---
+title: "Quick Start"
+summary: "Run deploykit init and understand its five phases"
+description: "Walk through deploykit init end to end — the preflight, detect, ask, plan and emit phases — plus the non-interactive form for scripting and CI."
+---
+
+## One command
+
+From the root of your monorepo:
+
+```bash
+npx deploykit init
+```
+
+`init` runs five phases, and **nothing is written or provisioned until you confirm the plan**.
+
+### 1. Preflight
+
+Verifies you're in a git repo with a Turbo or Nx monorepo, and that `gh` / `flyctl` are available if you'll need them.
+
+### 2. Detect
+
+Reads your package manager, workspace packages, each app's framework, its port, its internal workspace dependencies, any Prisma schemas, and the environment-variable names it references.
+
+### 3. Ask
+
+A handful of questions, **each pre-filled from detection**: which apps to deploy, which environments to configure, your Fly org and region(s), and (optionally) custom domains.
+
+### 4. Plan
+
+Shows exactly what will be written and provisioned. This is your review gate.
+
+### 5. Emit
+
+Writes the files and — only if you opt in — provisions Fly apps, sets GitHub secrets/environments, and opens a PR.
+
+## Non-interactive
+
+For CI or scripting, accept the detected defaults with `--yes`:
+
+```bash
+# Print the plan and stop — writes nothing
+deploykit init --yes --org my-org --region iad --dry-run
+
+# Write the files
+deploykit init --yes --org my-org --region iad
+
+# Also provision Fly apps + the FLY_API_TOKEN secret, and open a PR
+deploykit init --yes --org my-org --region iad --provision --pr
+```
+
+Common flags:
+
+| Flag | Description |
+|------|-------------|
+| `--yes`, `-y` | Accept detected defaults, no prompts. |
+| `--org ` | Fly organization slug. |
+| `--region ` | Fly region(s), comma-separated. First is primary; the rest are extra stateless regions. |
+| `--envs ` | Which environments to configure (`preview,staging,production`). |
+| `--dry-run` | Detect and print the plan, but write nothing. |
+| `--provision` | Create Fly apps and set the `FLY_API_TOKEN` GitHub secret. |
+| `--deploy` | Deploy the staging app(s) at the end of the run. |
+| `--pr` | Commit generated files on a branch and open a PR. |
+| `--force` | Overwrite existing generated files instead of skipping. |
+| `--cwd ` | Run against a different directory. |
+
+See the **[CLI Reference](/reference/cli-flags)** for the full list.
+
+## After init
+
+Once the PR is open (or the files are committed), your pipeline is live:
+
+- Open a PR → a preview app is deployed and its URL is commented on the PR.
+- Merge to `main` → staging deploys.
+- Approve the production gate → production deploys.
+
+Next, see **[What it generates](/getting-started/what-it-generates)** to understand each file.
diff --git a/docs/content/02-getting-started/03-what-it-generates.mdx b/docs/content/02-getting-started/03-what-it-generates.mdx
new file mode 100644
index 0000000..97f02e9
--- /dev/null
+++ b/docs/content/02-getting-started/03-what-it-generates.mdx
@@ -0,0 +1,76 @@
+---
+title: "What it generates"
+summary: "The files deploykit writes into your repo"
+description: "A tour of every file deploykit generates — per-app Dockerfiles, .dockerignore, fly.toml, the deploy.yml GitHub Actions workflow, and the deploykit.config.ts source of truth."
+---
+
+## The output
+
+A typical run writes:
+
+```
+apps//Dockerfile multi-stage, turbo-prune (or Nx-graph) based
+apps//.dockerignore
+apps//fly.toml per-app Fly config with a health check
+.github/workflows/deploy.yml changes → preview / teardown / staging / production
+deploykit.config.ts source of truth for every decision
+```
+
+## Per-app `Dockerfile`
+
+A multi-stage build. For Turbo repos it uses `turbo prune` to produce a minimal, cache-friendly context containing only the target app and its internal dependencies; for Nx it uses the project graph. The runner stage is chosen from how the app is served:
+
+- **server** apps run a long-running process — by default your app's own `start` script (which resolves `node_modules/.bin` and honors the script), or an explicit exec-form command.
+- **static** apps serve their built output directory, optionally with SPA history fallback.
+
+If an app depends on a package that ships a **Prisma** schema, the Dockerfile runs `prisma generate` for it before the build.
+
+## Per-app `.dockerignore`
+
+Keeps the build context lean and cache-friendly.
+
+## Per-app `fly.toml`
+
+Includes an HTTP **health check** (`/` by default). Fly waits for it before shifting traffic to a new release and keeps the old machines running if it fails — so a bad deploy rolls itself back. Set `healthCheckPath` per app in `deploykit.config.ts` for an API that 404s at `/`.
+
+## `.github/workflows/deploy.yml`
+
+A single workflow that:
+
+- Detects which apps a change touches (via each app's watch paths — its own directory plus every internal dependency's directory).
+- On a **pull request**, deploys a **preview** app per changed app and comments its URL; on PR close, tears the preview down.
+- On **push to `main`**, deploys **staging**.
+- Deploys **production** behind a manual approval gate (a GitHub Environment protection rule).
+
+Secrets are wired through GitHub secrets → `flyctl secrets set` (runtime) and `flyctl deploy --build-arg` (build-time), by name only.
+
+## `deploykit.config.ts`
+
+The **single source of truth**. Every Dockerfile, `fly.toml` and workflow is regenerable from it, and you own it in your repo. Edit it and run `deploykit generate` to rebuild everything. See the **[Config Reference](/reference/config-reference)** for every field.
+
+```ts
+import { defineConfig } from "@alminabrulic/deploykit"
+
+export default defineConfig({
+ tool: "turbo",
+ packageManager: "pnpm",
+ nodeVersion: "20",
+ provider: { type: "fly", org: "my-org", region: "iad" },
+ apps: {
+ web: {
+ root: "apps/web",
+ packageName: "@acme/web",
+ framework: "next",
+ port: 3000,
+ internalDeps: ["@acme/ui"],
+ watchPaths: ["apps/web/**", "packages/ui/**"],
+ environments: {
+ preview: { name: "acme-web-pr-{pr}", trigger: "pr" },
+ staging: { name: "acme-web-staging", trigger: "push:main" },
+ production: { name: "acme-web-prod", trigger: "manual" },
+ },
+ secrets: ["DATABASE_URL"],
+ },
+ },
+})
+```
diff --git a/docs/content/02-getting-started/index.md b/docs/content/02-getting-started/index.md
new file mode 100644
index 0000000..f45421e
--- /dev/null
+++ b/docs/content/02-getting-started/index.md
@@ -0,0 +1,3 @@
+---
+title: "Getting Started"
+---
diff --git a/docs/content/03-core-concepts/01-how-it-works.mdx b/docs/content/03-core-concepts/01-how-it-works.mdx
new file mode 100644
index 0000000..780296d
--- /dev/null
+++ b/docs/content/03-core-concepts/01-how-it-works.mdx
@@ -0,0 +1,41 @@
+---
+title: "How it works"
+summary: "Detection, the plan gate, and regeneration"
+description: "The mental model behind deploykit — how it detects your workspace, why nothing happens before you confirm the plan, and how the config file lets you regenerate every artifact."
+---
+
+## Detection
+
+deploykit's power comes from reading your workspace instead of asking you to describe it. During the **detect** phase it works out:
+
+- **Monorepo tool** — Turbo or Nx.
+- **Package manager** — pnpm, yarn, npm or bun.
+- **Deployable apps** — the workspace packages that are actually apps (not libraries).
+- **Framework per app** — Next, Remix, React Router, Astro, Vite, a plain Node server, or a static build. This is a *hint* that drives default ports and the runner shape.
+- **How each app is served** — a long-running `server` process, or `static` files.
+- **Port** — from the framework's convention, overridable.
+- **Internal dependencies** — which workspace packages each app imports. This determines the Docker prune context *and* the workflow's watch paths.
+- **Prisma schemas** — packages in an app's dependency closure that ship a `schema.prisma`.
+- **Env-var names** — runtime secrets and build-time variables the app references (names only, never values).
+
+Every answer in the **ask** phase is pre-filled from this, so most runs are a few keystrokes.
+
+## The plan gate
+
+deploykit is deliberately conservative: the **plan** phase shows you exactly what will be written and provisioned, and **nothing happens until you confirm**. Use `--dry-run` to print the plan and stop. This makes it safe to run in any repo just to see what it would do.
+
+## Regeneration
+
+The generated `deploykit.config.ts` is the **single source of truth**. Every Dockerfile, `fly.toml` and workflow is a pure function of it. That means:
+
+- You can hand-edit the config and run **[`deploykit generate`](/commands/generate)** to rebuild the artifacts.
+- Diffs are meaningful — a config change produces a predictable file change.
+- deploykit is removable — the config and generated files are yours; nothing depends on deploykit at deploy time.
+
+## What deploykit does *not* do
+
+- It does **not** run database migrations — see **[Database migrations](/guides/database-migrations)**.
+- It does **not** store secret values — only their names flow into the config and workflow.
+- It does **not** model database locality for multi-region — extra regions are for **stateless** apps.
+
+These are deliberate scope boundaries: each is a place where doing the convenient thing could cause irreversible damage or leak data.
diff --git a/docs/content/03-core-concepts/02-environments.mdx b/docs/content/03-core-concepts/02-environments.mdx
new file mode 100644
index 0000000..090f64b
--- /dev/null
+++ b/docs/content/03-core-concepts/02-environments.mdx
@@ -0,0 +1,41 @@
+---
+title: "Environments"
+summary: "Preview, staging and production — triggers and guardrails"
+description: "How deploykit models the three deployment environments: PR preview apps with automatic teardown, staging on merge to main, and production behind a manual approval gate."
+---
+
+deploykit configures up to three environments per app. Choose which ones with `--envs` (default: all three).
+
+## Preview
+
+- **Trigger:** a pull request.
+- Every PR gets its **own deployed app**, named with a `{pr}` placeholder (e.g. `acme-web-pr-42`).
+- The preview URL is **commented on the PR**.
+- When the PR **closes**, the preview app is **torn down** automatically.
+- Previews stay **single-region** even if you configure extra regions.
+
+Previews give reviewers a live, isolated environment for every change without manual setup or leftover apps.
+
+## Staging
+
+- **Trigger:** push to `main` (i.e. merge).
+- A single, concrete app (e.g. `acme-web-staging`).
+- Deploys automatically once a PR lands, so `main` always has a running staging environment.
+
+## Production
+
+- **Trigger:** manual.
+- Sits behind a **GitHub Environment protection rule** — a required reviewer must approve the deploy.
+- Same generated artifacts as staging; the difference is the approval gate and the app name/secrets.
+
+## How triggers map
+
+| Environment | Trigger | Fly app name | Teardown |
+|-------------|---------|--------------|----------|
+| preview | `pr` | `…-pr-{pr}` | on PR close |
+| staging | `push:main` | `…-staging` | — |
+| production | `manual` | `…-prod` | — |
+
+## Secrets per environment
+
+Each environment resolves its own secret **values** from GitHub secrets at deploy time — deploykit only records the **names** the app needs. Runtime secrets are applied with `flyctl secrets set`; build-time variables (client-exposed prefixes like `NEXT_PUBLIC_`/`VITE_`, and every variable of a static app) are passed as `--build-arg` during `docker build`. See **[Provisioning & secrets](/guides/provisioning-and-secrets)**.
diff --git a/docs/content/03-core-concepts/03-config-file.mdx b/docs/content/03-core-concepts/03-config-file.mdx
new file mode 100644
index 0000000..1fdfde8
--- /dev/null
+++ b/docs/content/03-core-concepts/03-config-file.mdx
@@ -0,0 +1,76 @@
+---
+title: "The config file"
+summary: "deploykit.config.ts is the source of truth"
+description: "Understand deploykit.config.ts — the typed configuration file that captures every deployment decision and regenerates all Dockerfiles, fly.toml files and workflows."
+---
+
+## The single source of truth
+
+`deploykit.config.ts` captures every decision deploykit made, in a typed, hand-editable file that lives in your repo. Every generated artifact is a pure function of it — edit the config and run **[`deploykit generate`](/commands/generate)** to rebuild everything.
+
+```ts
+import { defineConfig } from "@alminabrulic/deploykit"
+
+export default defineConfig({
+ tool: "turbo",
+ packageManager: "pnpm",
+ nodeVersion: "20",
+ namePrefix: "acme",
+ provider: {
+ type: "fly",
+ org: "my-org",
+ region: "iad",
+ },
+ apps: {
+ web: {
+ root: "apps/web",
+ packageName: "@acme/web",
+ framework: "next",
+ serve: "server",
+ port: 3000,
+ healthCheckPath: "/",
+ internalDeps: ["@acme/ui"],
+ watchPaths: ["apps/web/**", "packages/ui/**"],
+ environments: {
+ preview: { name: "acme-web-pr-{pr}", trigger: "pr" },
+ staging: { name: "acme-web-staging", trigger: "push:main" },
+ production: { name: "acme-web-prod", trigger: "manual" },
+ },
+ secrets: ["DATABASE_URL"],
+ buildEnv: ["NEXT_PUBLIC_API_URL"],
+ },
+ },
+})
+```
+
+## Top-level fields
+
+| Field | Meaning |
+|-------|---------|
+| `tool` | `turbo` or `nx`. |
+| `packageManager` | `pnpm`, `yarn`, `npm` or `bun`. |
+| `nodeVersion` | Node major used in the generated Dockerfiles, e.g. `"20"`. |
+| `namePrefix` | Prefix for every generated Fly app name. Fly app names are **globally unique**, so bare names like `web-staging` are usually taken. |
+| `provider` | The Fly org, primary `region`, and optional extra `regions`. |
+| `apps` | Deployable apps keyed by short name. |
+| `cloudflare` | Optional DNS/CDN wiring for custom domains. |
+
+## Key per-app fields
+
+| Field | Meaning |
+|-------|---------|
+| `root` | Workspace-relative directory, e.g. `apps/web`. |
+| `framework` | Detection hint (drives default port + runner shape). |
+| `serve` | `server` (long-running process) or `static` (serve built files). |
+| `port` | Internal port the container listens on. |
+| `healthCheckPath` | HTTP path Fly polls for release health (default `/`). |
+| `internalDeps` | Internal workspace packages this app depends on. |
+| `watchPaths` | Globs that trigger a redeploy (its dir + each dep's dir). |
+| `environments` | Per-environment Fly app name + trigger (+ optional hostname). |
+| `secrets` | **Runtime** env-var names (wired via `flyctl secrets set`). |
+| `buildEnv` | **Build-time** var names (baked in via `--build-arg`). |
+| `prisma` | Prisma packages whose client is generated at build time. |
+
+The **[Config Reference](/reference/config-reference)** documents every field, including the Cloudflare and Prisma sub-shapes.
+
+> **Tip:** because the config is the source of truth, prefer editing it and running `deploykit generate` over hand-editing a Dockerfile or `fly.toml` — otherwise the next `generate` will overwrite your manual change.
diff --git a/docs/content/03-core-concepts/index.md b/docs/content/03-core-concepts/index.md
new file mode 100644
index 0000000..5aea62d
--- /dev/null
+++ b/docs/content/03-core-concepts/index.md
@@ -0,0 +1,3 @@
+---
+title: "Core Concepts"
+---
diff --git a/docs/content/04-commands/01-init.mdx b/docs/content/04-commands/01-init.mdx
new file mode 100644
index 0000000..c80cbef
--- /dev/null
+++ b/docs/content/04-commands/01-init.mdx
@@ -0,0 +1,57 @@
+---
+title: "deploykit init"
+summary: "Detect the monorepo and set everything up"
+description: "Reference for deploykit init — the five-phase command that detects your monorepo, asks a few pre-filled questions, shows a plan, and emits Dockerfiles, fly.toml files, a workflow and the config file."
+---
+
+## Synopsis
+
+```bash
+deploykit init [options]
+```
+
+Detects the monorepo and sets everything up. Runs five phases — **preflight → detect → ask → plan → emit** — and writes or provisions nothing until you confirm the plan.
+
+## What it produces
+
+- `apps//Dockerfile` and `.dockerignore`
+- `apps//fly.toml`
+- `.github/workflows/deploy.yml`
+- `deploykit.config.ts`
+
+Optionally (when you opt in): provisioned Fly apps, the `FLY_API_TOKEN` GitHub secret, GitHub environments, a deploy of staging, and an opened PR.
+
+## Options
+
+| Flag | Description |
+|------|-------------|
+| `-y`, `--yes` | Accept detected defaults, no prompts. |
+| `--org ` | Fly organization slug. |
+| `--region ` | Fly region(s), comma-separated; first is primary, the rest are extra **stateless** regions (default: `iad`). |
+| `--envs ` | Environments to configure: `preview,staging,production` (default: all). |
+| `--dry-run` | Detect and print the plan, write nothing. |
+| `--provision` | Force provisioning in `--yes` mode (Fly apps, `FLY_API_TOKEN`, GitHub environments). Interactive runs offer it inline. |
+| `--deploy` | Deploy the staging app(s) to Fly at the end of the run. |
+| `--pr` | Commit generated files on a branch and open a PR. |
+| `--force` | Overwrite existing generated files instead of skipping. |
+| `--cwd ` | Run against a different directory. |
+
+## Examples
+
+```bash
+# Fully interactive
+deploykit init
+
+# Non-interactive dry run
+deploykit init --yes --org my-org --region iad --dry-run
+
+# Only preview + staging
+deploykit init --yes --org my-org --envs preview,staging
+
+# Multi-region primary + extras
+deploykit init --region iad,lhr,fra
+```
+
+## Re-running
+
+`init` skips files that already exist unless you pass `--force`. To rebuild artifacts from an edited config without re-detecting, use **[`deploykit generate`](/commands/generate)** instead.
diff --git a/docs/content/04-commands/02-generate.mdx b/docs/content/04-commands/02-generate.mdx
new file mode 100644
index 0000000..290465a
--- /dev/null
+++ b/docs/content/04-commands/02-generate.mdx
@@ -0,0 +1,39 @@
+---
+title: "deploykit generate"
+summary: "Regenerate artifacts from deploykit.config.ts"
+description: "Reference for deploykit generate — rebuilds every Dockerfile, fly.toml and the workflow from the existing deploykit.config.ts, overwriting them."
+---
+
+## Synopsis
+
+```bash
+deploykit generate [options]
+```
+
+Regenerates the Dockerfiles, `fly.toml` files and the workflow **from `deploykit.config.ts`**, overwriting them. Use this after you edit the config — it's the counterpart to `init`, without the detect/ask/provision steps.
+
+## When to use it
+
+- You edited `deploykit.config.ts` (added an app, changed a port, set `healthCheckPath`, added regions, etc.) and want the generated files to match.
+- You upgraded deploykit and want to pick up improvements to the generated output.
+- You want deterministic, reviewable regeneration in CI.
+
+Because every artifact is a pure function of the config, `generate` is safe to run repeatedly — the output only changes when the config does.
+
+## Options
+
+`generate` shares the relevant flags with `init`:
+
+| Flag | Description |
+|------|-------------|
+| `--force` | Overwrite existing generated files (generate overwrites by design). |
+| `--cwd ` | Run against a different directory. |
+
+## Example
+
+```bash
+# Edit deploykit.config.ts, then:
+deploykit generate
+```
+
+> **Heads up:** `generate` overwrites the generated files. If you hand-edited a Dockerfile or `fly.toml`, move that change into `deploykit.config.ts` first, or it will be lost.
diff --git a/docs/content/04-commands/03-rollback.mdx b/docs/content/04-commands/03-rollback.mdx
new file mode 100644
index 0000000..182ab62
--- /dev/null
+++ b/docs/content/04-commands/03-rollback.mdx
@@ -0,0 +1,47 @@
+---
+title: "deploykit rollback"
+summary: "Redeploy a prior image for one environment"
+description: "Reference for deploykit rollback — lists an environment's past Fly releases, shows the exact flyctl deploy command, and redeploys the image you pick. Rolls back the app image only, not database migrations."
+---
+
+## Synopsis
+
+```bash
+deploykit rollback [options]
+```
+
+When a release deployed cleanly but turned out bad, `rollback` redeploys a **previous image** for one environment's Fly app. It lists the environment's Fly releases, lets you pick one, shows the exact `flyctl deploy --image …` it will run, and asks before doing it.
+
+## How it works
+
+1. Reads `deploykit.config.ts` to resolve the target app + environment's Fly app name.
+2. Runs `flyctl releases --json` and normalizes the list (defensively across flyctl versions).
+3. Lets you pick a prior release (or takes `--to `).
+4. Prints the exact `flyctl deploy --image [` command.
+5. Asks for confirmation, then runs it.
+
+Only **staging** and **production** can be rolled back — they have concrete Fly app names. Preview apps use a per-PR placeholder name and aren't rollback targets.
+
+## Options
+
+| Flag | Description |
+|------|-------------|
+| `--app ]` | App to roll back (defaults to the sole app if there's only one). |
+| `--env ` | Environment: `staging` or `production`. |
+| `--to ` | Release version to redeploy (non-interactive). |
+| `-y`, `--yes` | Skip the confirmation prompt (use with `--to`). |
+| `--cwd ` | Run against a different directory. |
+
+## Examples
+
+```bash
+# Interactive: pick a release for production
+deploykit rollback --app web --env production
+
+# Scripted: redeploy version 41 without prompts
+deploykit rollback --app web --env production --to 41 --yes
+```
+
+## Important: image only
+
+Rollback redeploys the **app image only** — it does **not** undo database migrations. An older image may not run correctly against a schema a newer release already migrated. If a bad release included a migration, plan the data path separately. See **[Database migrations](/guides/database-migrations)**.
diff --git a/docs/content/04-commands/index.md b/docs/content/04-commands/index.md
new file mode 100644
index 0000000..66fd9f0
--- /dev/null
+++ b/docs/content/04-commands/index.md
@@ -0,0 +1,3 @@
+---
+title: "Commands"
+---
diff --git a/docs/content/05-guides/01-health-checks-and-rollbacks.mdx b/docs/content/05-guides/01-health-checks-and-rollbacks.mdx
new file mode 100644
index 0000000..569659a
--- /dev/null
+++ b/docs/content/05-guides/01-health-checks-and-rollbacks.mdx
@@ -0,0 +1,52 @@
+---
+title: "Health checks & automatic rollback"
+summary: "How a failing health check keeps a bad deploy from taking traffic"
+description: "Every generated fly.toml includes an HTTP health check. Fly waits for it before shifting traffic and keeps old machines running on failure, so a bad deploy rolls itself back. Configure the path per app."
+---
+
+## Two kinds of rollback
+
+deploykit gives you rollback protection at two levels:
+
+1. **Automatic, at deploy time** — a failing health check stops a bad release from ever taking traffic. This is built into every generated `fly.toml`.
+2. **Manual, after the fact** — a release that deployed cleanly but turned out bad can be reverted with **[`deploykit rollback`](/commands/rollback)**.
+
+This guide covers the first.
+
+## The health check
+
+Each generated `fly.toml` includes an HTTP health check — by default on `/`:
+
+- Fly **waits** for the check to pass before shifting traffic to the new release.
+- If it **fails**, Fly keeps the **old machines** running and serving.
+
+So a deploy that boots broken never takes production traffic — it rolls itself back to the previous release with no action from you.
+
+## Choosing the path
+
+The default (`/`) works for most web apps, which answer it with a 2xx/3xx. But an **API that 404s at `/`** would fail the check and wedge the deploy. Set a lightweight endpoint per app in `deploykit.config.ts`:
+
+```ts
+apps: {
+ api: {
+ // …
+ healthCheckPath: "/health",
+ },
+}
+```
+
+Then regenerate:
+
+```bash
+deploykit generate
+```
+
+## Picking a good endpoint
+
+- Make it **cheap** — no heavy DB work; it's polled frequently.
+- Make it **honest** — return non-2xx only when the app genuinely can't serve, so real failures are caught but transient dependency blips don't block every deploy.
+- Keep it **unauthenticated** (or trivially reachable) so Fly can hit it.
+
+## When a deploy still goes bad
+
+If a release passes its health check but is functionally wrong (bad logic, wrong config), the automatic guard won't catch it — that's what **[`deploykit rollback`](/commands/rollback)** is for.
diff --git a/docs/content/05-guides/02-multiple-regions.mdx b/docs/content/05-guides/02-multiple-regions.mdx
new file mode 100644
index 0000000..bac08c4
--- /dev/null
+++ b/docs/content/05-guides/02-multiple-regions.mdx
@@ -0,0 +1,47 @@
+---
+title: "Multiple regions"
+summary: "Scale stateless apps into extra Fly regions"
+description: "Pass more than one region and the extras become stateless regions the app scales into after each staging/production deploy. For stateless apps only — deploykit does not model database locality."
+---
+
+## Adding regions
+
+Pass more than one region to `init`, or set `regions` under `provider` in the config. The **first** region is the **primary**; the rest are extras:
+
+```bash
+deploykit init --region iad,lhr,fra # primary iad, plus lhr and fra
+```
+
+```ts
+provider: {
+ type: "fly",
+ org: "my-org",
+ region: "iad",
+ regions: ["iad", "lhr", "fra"],
+}
+```
+
+## What happens
+
+After each **staging/production** deploy, deploykit scales one machine into every extra region:
+
+```bash
+flyctl scale count 1 --region
+```
+
+**Previews stay single-region** — extra regions apply only to staging and production.
+
+## Stateless apps only
+
+> ⚠️ This is for **stateless** apps. deploykit does **not** model database locality: a machine in a far region still talks to whatever single-region `DATABASE_URL` you set, so expect **high write latency** from distant regions.
+
+If your app is stateful, don't add regions without your own strategy — read replicas, `fly-replay`, or region-aware routing. Those are **out of scope** for deploykit; it will happily scale you wide, but it won't make your database fast from three continents.
+
+## Good fits
+
+- Stateless SSR/edge-ish frontends.
+- APIs that are read-mostly against a globally replicated data store you manage yourself.
+
+## Removing a region
+
+Edit `provider.regions` in the config and run `deploykit generate`. Note that scaling *down* existing machines in a dropped region is a Fly operation you run yourself (`flyctl scale count 0 --region `); deploykit only scales up after deploys.
diff --git a/docs/content/05-guides/03-database-migrations.mdx b/docs/content/05-guides/03-database-migrations.mdx
new file mode 100644
index 0000000..4790346
--- /dev/null
+++ b/docs/content/05-guides/03-database-migrations.mdx
@@ -0,0 +1,44 @@
+---
+title: "Database migrations"
+summary: "Why deploykit won't run migrations for you — and the hook it writes instead"
+description: "deploykit does not run database migrations because a bad one causes irreversible data loss. When it detects a Prisma schema it writes a commented-out release_command hook into fly.toml that you opt into."
+---
+
+## Why deploykit doesn't run migrations
+
+A bad migration causes **irreversible data loss**, and owning that decision is out of scope for a code generator. So deploykit will **never run migrations for you** by default.
+
+This is also why **[rollback](/commands/rollback)** is image-only: rolling an app back to an older image does **not** undo a migration a newer release already applied, and an older image may not run against the newer schema.
+
+## The commented-out hook
+
+When deploykit detects a **Prisma** schema in an app, it writes a **commented-out** release hook into that app's `fly.toml`:
+
+```toml
+# [deploy]
+# release_command = "(cd packages/db && npx prisma migrate deploy --schema ./prisma/schema.prisma)"
+```
+
+Fly runs a `release_command` once per deploy, before the new machines take traffic. deploykit leaves it commented so it's a deliberate, reviewed opt-in.
+
+## Enabling it
+
+If you've decided your migrations are safe to run automatically on deploy:
+
+1. Uncomment the `[deploy]` block in the app's `fly.toml`.
+2. Make sure the command's working directory and schema path match your repo.
+3. Ensure the runtime image can reach the database (the `DATABASE_URL` secret is set).
+
+```toml
+[deploy]
+ release_command = "(cd packages/db && npx prisma migrate deploy --schema ./prisma/schema.prisma)"
+```
+
+## Prisma client generation
+
+Separately from *migrations*, deploykit handles Prisma **client generation** for you. Under pnpm 10 / Prisma 7 the client isn't generated on install, so the Dockerfile runs `prisma generate` before the build for every Prisma package in an app's dependency closure. That's about producing a runnable image — it does **not** touch your database.
+
+## Safer patterns
+
+- **Expand/contract** — deploy schema-additive migrations first, ship code that works with both shapes, then remove old columns in a later release. This keeps old and new images compatible so rollback stays safe.
+- **Gate destructive migrations** behind the production approval gate and run them deliberately, not on every push.
diff --git a/docs/content/05-guides/04-custom-domains-cloudflare.mdx b/docs/content/05-guides/04-custom-domains-cloudflare.mdx
new file mode 100644
index 0000000..121d356
--- /dev/null
+++ b/docs/content/05-guides/04-custom-domains-cloudflare.mdx
@@ -0,0 +1,74 @@
+---
+title: "Custom domains (Cloudflare)"
+summary: "Wire staging/production hostnames through Cloudflare DNS + Fly certs"
+description: "Optionally serve custom domains for staging and production. deploykit issues a Fly certificate and wires the Cloudflare DNS records, with a token that carries the exact zone-read + DNS-edit permissions it needs."
+---
+
+## What this does
+
+By default your apps are reachable on `*.fly.dev`. Opt into custom domains and deploykit will, for each staging/production hostname you provide:
+
+- issue a **Fly certificate** for the hostname, and
+- wire the matching **Cloudflare DNS** record.
+
+Previews always stay on `*.fly.dev` — only staging and production get custom domains.
+
+## During `init`
+
+If you configure staging or production, deploykit asks: **"Wire up custom domains through Cloudflare?"** If you say yes it:
+
+1. Resolves a **Cloudflare API token** (see below).
+2. Lets you pick the **zone** (the registrable domain that owns the hostnames, e.g. `example.com`).
+3. Collects a **hostname per environment** (blank to skip).
+
+If no token is available, deploykit still records the domains in your config and **skips provisioning** until `CLOUDFLARE_API_TOKEN` is set — nothing is left half-done.
+
+## The API token
+
+deploykit needs **Zone read + DNS edit** permissions. It prints a Cloudflare dashboard link with exactly those permissions **pre-selected** — you just confirm and create the token. It's resolved in this order:
+
+1. `CLOUDFLARE_API_TOKEN` environment variable
+2. a saved credentials file (`.deploykit/credentials`)
+3. an interactive, masked prompt
+
+## Config shape
+
+Custom domains add a `cloudflare` block and per-environment `hostname` fields:
+
+```ts
+export default defineConfig({
+ // …
+ apps: {
+ web: {
+ // …
+ environments: {
+ staging: { name: "acme-web-staging", trigger: "push:main", hostname: "staging.example.com" },
+ production: { name: "acme-web-prod", trigger: "manual", hostname: "app.example.com" },
+ },
+ },
+ },
+ cloudflare: {
+ zone: "example.com",
+ proxied: true,
+ ssl: "strict",
+ alwaysUseHttps: true,
+ minTlsVersion: "1.2",
+ security: true,
+ cache: true,
+ },
+})
+```
+
+| Field | Meaning |
+|-------|---------|
+| `zone` | Registrable zone that owns the hostnames. |
+| `proxied` | Route traffic through Cloudflare's proxy (orange cloud) vs DNS-only. |
+| `ssl` | Zone SSL mode: `off` / `flexible` / `full` / `strict`. Use **`strict`** with proxied Fly apps to avoid redirect loops. |
+| `alwaysUseHttps` | Turn on the edge "Always Use HTTPS" redirect. |
+| `minTlsVersion` | Minimum TLS version at the edge (`1.0`–`1.3`). |
+| `security` | Apply a security baseline (security level + bot fight mode, managed WAF best-effort). |
+| `cache` | Add cache rules for static assets (+ browser cache TTL). |
+
+## SSL tip
+
+If your Fly app is **proxied** through Cloudflare, use `ssl: "strict"`. `flexible` terminates TLS at the edge and talks HTTP to Fly, which — combined with Fly's own HTTPS redirect — produces redirect loops.
diff --git a/docs/content/05-guides/05-provisioning-and-secrets.mdx b/docs/content/05-guides/05-provisioning-and-secrets.mdx
new file mode 100644
index 0000000..f41dcb6
--- /dev/null
+++ b/docs/content/05-guides/05-provisioning-and-secrets.mdx
@@ -0,0 +1,54 @@
+---
+title: "Provisioning & secrets"
+summary: "How deploykit creates Fly apps, tokens and GitHub secrets"
+description: "What --provision does — create Fly apps, mint a least-privilege org-scoped deploy token, set FLY_API_TOKEN and GitHub environments — and how runtime vs build-time secrets flow by name only."
+---
+
+## Provisioning
+
+Provisioning is **opt-in**. Interactive runs offer it inline; non-interactive runs enable it with `--provision`. Each step is confirmed. When enabled, deploykit:
+
+- **Creates the Fly apps** for each environment (using the globally-unique, prefixed names).
+- **Mints a Fly deploy token** and stores it as the `FLY_API_TOKEN` GitHub secret.
+- **Sets up GitHub environments**, including the production approval gate.
+
+Nothing is provisioned during a `--dry-run`.
+
+## Least-privilege Fly token
+
+deploykit deliberately creates an **org-scoped deploy token** via `flyctl tokens create org`, **not** `flyctl auth token`. The difference matters:
+
+- `flyctl auth token` prints your **personal** credential — broad access to every org, and invisible on the dashboard.
+- An **org token** is least-privilege, revocable, and shows up under **Organization → Tokens** as `deploykit (GitHub Actions)`.
+
+That token becomes the `FLY_API_TOKEN` secret the workflow uses to deploy.
+
+## Secrets: names, never values
+
+deploykit records the **names** of the environment variables each app needs — never their values. Those names flow into `deploykit.config.ts` and the workflow, and are resolved from **GitHub secrets** at deploy time. There are two kinds:
+
+### Runtime secrets (`secrets`)
+
+Read by the app at runtime. Wired through GitHub secrets → `flyctl secrets set`:
+
+```ts
+secrets: ["DATABASE_URL", "SESSION_SECRET"]
+```
+
+### Build-time variables (`buildEnv`)
+
+Baked into the bundle during `docker build` — client-exposed prefixes (`NEXT_PUBLIC_`, `VITE_`, …) and **every** variable of a static app (which has no runtime to read env from). Wired through GitHub secrets → `flyctl deploy --build-arg` + Dockerfile `ARG`/`ENV`:
+
+```ts
+buildEnv: ["NEXT_PUBLIC_API_URL"]
+```
+
+> ⚠️ `buildEnv` values are **embedded in the built artifact** and shipped to the client. Never put a real secret in `buildEnv` — only public, client-safe configuration.
+
+## Setting the secret values
+
+deploykit doesn't know your secret *values*. After provisioning, set them as GitHub repository or environment secrets (e.g. with `gh secret set`), matching the names in your config. Environment-scoped secrets let staging and production use different values for the same name.
+
+## Local secrets file
+
+deploykit uses a `.deploykit/` directory for local, gitignored material — `secrets.local.env` and `credentials`. These stay on your machine; they are not committed and not the source of the CI secret values.
diff --git a/docs/content/05-guides/index.md b/docs/content/05-guides/index.md
new file mode 100644
index 0000000..3c400f4
--- /dev/null
+++ b/docs/content/05-guides/index.md
@@ -0,0 +1,3 @@
+---
+title: "Guides"
+---
diff --git a/docs/content/06-reference/01-cli-flags.mdx b/docs/content/06-reference/01-cli-flags.mdx
new file mode 100644
index 0000000..d89c1b2
--- /dev/null
+++ b/docs/content/06-reference/01-cli-flags.mdx
@@ -0,0 +1,52 @@
+---
+title: "CLI reference"
+summary: "Every command and flag"
+description: "Complete reference for the deploykit CLI — the init, generate and rollback commands and all their options, with examples."
+---
+
+## Commands
+
+```bash
+deploykit init [options] Detect the monorepo and set everything up
+deploykit generate [options] Regenerate Dockerfiles/workflow/fly.toml from
+ deploykit.config.ts (overwrites them)
+deploykit rollback [options] Redeploy a prior image for one environment's Fly
+ app (app only — does not undo DB migrations)
+```
+
+## Global options
+
+| Flag | Description |
+|------|-------------|
+| `-h`, `--help` | Show help. |
+| `-v`, `--version` | Show version. |
+| `--cwd ` | Run against a different directory. |
+
+## Options
+
+| Flag | Applies to | Description |
+|------|------------|-------------|
+| `-y`, `--yes` | init, rollback | Accept detected defaults / skip prompts. |
+| `--org ` | init | Fly organization slug. |
+| `--region ` | init | Fly region(s), comma-separated; first is primary, the rest are extra stateless regions (default: `iad`). |
+| `--envs ` | init | Environments to configure: `preview,staging,production` (default: all). |
+| `--dry-run` | init | Detect and print the plan, write nothing. |
+| `--provision` | init | Force provisioning in `--yes` mode (Fly apps, `FLY_API_TOKEN`, GitHub environments). |
+| `--deploy` | init | Deploy the staging app(s) to Fly at the end of the run. |
+| `--pr` | init | Commit generated files on a branch and open a PR. |
+| `--force` | init, generate | Overwrite existing generated files instead of skipping. |
+| `--app ` | rollback | App to roll back (defaults to the sole app). |
+| `--env ` | rollback | Environment: `staging` or `production`. |
+| `--to ` | rollback | Release version to redeploy (non-interactive). |
+
+## Examples
+
+```bash
+deploykit init
+deploykit init --yes --org my-org --region iad --dry-run
+deploykit init --yes --org my-org --envs preview,staging
+deploykit init --region iad,lhr,fra
+deploykit generate
+deploykit rollback --app web --env production
+deploykit rollback --app web --env production --to 41 --yes
+```
diff --git a/docs/content/06-reference/02-config-reference.mdx b/docs/content/06-reference/02-config-reference.mdx
new file mode 100644
index 0000000..d5aa3d2
--- /dev/null
+++ b/docs/content/06-reference/02-config-reference.mdx
@@ -0,0 +1,87 @@
+---
+title: "Config reference"
+summary: "Every field in deploykit.config.ts"
+description: "Complete field-by-field reference for deploykit.config.ts — top-level options, the provider block, per-app config, environments, Prisma targets, and the Cloudflare block."
+---
+
+`deploykit.config.ts` is the source of truth. Import `defineConfig` for editor types:
+
+```ts
+import { defineConfig } from "@alminabrulic/deploykit"
+
+export default defineConfig({ /* … */ })
+```
+
+## Top level
+
+| Field | Type | Description |
+|-------|------|-------------|
+| `tool` | `"turbo" \| "nx"` | Monorepo tool. |
+| `packageManager` | `"pnpm" \| "yarn" \| "npm" \| "bun"` | Package manager. |
+| `nodeVersion` | `string` | Node major used in generated Dockerfiles, e.g. `"20"`. |
+| `namePrefix` | `string?` | Prefix for every generated Fly app name. Fly names are **globally unique**, so a prefix (repo/org) avoids collisions. Omitted → no prefix. |
+| `provider` | `ProviderConfig` | Fly provider settings. |
+| `apps` | `Record` | Deployable apps keyed by short name. |
+| `cloudflare` | `CloudflareConfig?` | Optional DNS/CDN wiring for custom domains. |
+| `installEnv` | `Record?` | Env used to neutralize `prepare` git-hook installers that fail in the slim image (e.g. `HUSKY=0`). |
+| `nxIntegrated` | `boolean?` | Nx only: `true` = integrated repo (outputs at `dist/`); `false` = package-based. |
+
+## `provider`
+
+| Field | Type | Description |
+|-------|------|-------------|
+| `type` | `"fly"` | The provider. |
+| `org` | `string` | Fly organization slug. |
+| `region` | `string` | Primary region, e.g. `"iad"`. |
+| `regions` | `string[]?` | Extra regions to also run in (stateless apps only). See **[Multiple regions](/guides/multiple-regions)**. |
+
+## `apps[name]`
+
+| Field | Type | Description |
+|-------|------|-------------|
+| `root` | `string` | Workspace-relative dir, e.g. `apps/web`. |
+| `packageName` | `string` | `name` from the app's `package.json`. |
+| `framework` | `Framework` | `next` / `remix` / `react-router` / `astro` / `vite` / `node-server` / `static`. A detection hint. |
+| `serve` | `"static" \| "server"?` | How the runner serves the app. Omitted → derived from framework. |
+| `startCommand` | `string[]?` | Exec-form CMD for a server app. Omitted → run the app's own `start` script. |
+| `outputDir` | `string?` | Static apps: directory to serve. Omitted → derived. |
+| `spa` | `boolean?` | Static apps: serve with SPA history fallback. |
+| `prisma` | `PrismaTarget[]?` | Prisma packages whose client is generated at build time. |
+| `port` | `number` | Internal container port. |
+| `healthCheckPath` | `string?` | HTTP path Fly polls for health (default `/`). |
+| `internalDeps` | `string[]` | Internal workspace packages this app depends on. |
+| `watchPaths` | `string[]` | Globs that trigger a redeploy (its dir + each dep's dir). |
+| `environments` | `Partial>` | Per-environment config. |
+| `secrets` | `string[]` | **Runtime** env-var names (via `flyctl secrets set`). |
+| `buildEnv` | `string[]?` | **Build-time** var names (via `--build-arg`; client-exposed). |
+
+### `environments[kind]` (`AppEnvironment`)
+
+| Field | Type | Description |
+|-------|------|-------------|
+| `name` | `string` | Fly app name. Preview uses the `{pr}` placeholder. |
+| `trigger` | `"pr" \| "push:main" \| "manual"` | When this environment deploys. |
+| `hostname` | `string?` | Custom domain (staging/production only). Requires a `cloudflare` block. |
+
+### `prisma[]` (`PrismaTarget`)
+
+| Field | Type | Description |
+|-------|------|-------------|
+| `packageName` | `string` | Package name (the `--filter` / run target). |
+| `root` | `string` | Workspace-relative package dir. |
+| `schema` | `string` | Schema path relative to the package root. |
+| `hasConfig` | `boolean` | True when the package has a `prisma.config.{ts,js}` (then `--schema` is omitted). |
+
+## `cloudflare`
+
+| Field | Type | Description |
+|-------|------|-------------|
+| `zone` | `string` | Registrable zone owning the hostnames. |
+| `proxied` | `boolean` | Route through Cloudflare's proxy vs DNS-only. |
+| `ssl` | `"off" \| "flexible" \| "full" \| "strict"` | Zone SSL mode. Use `strict` with proxied Fly apps. |
+| `alwaysUseHttps` | `boolean` | Edge "Always Use HTTPS" redirect. |
+| `minTlsVersion` | `"1.0" \| "1.1" \| "1.2" \| "1.3"` | Minimum edge TLS version. |
+| `security` | `boolean` | Apply a security baseline. |
+| `cache` | `boolean` | Add static-asset cache rules. |
+
+See **[Custom domains](/guides/custom-domains-cloudflare)** for the workflow.
diff --git a/docs/content/06-reference/index.md b/docs/content/06-reference/index.md
new file mode 100644
index 0000000..d8c59ba
--- /dev/null
+++ b/docs/content/06-reference/index.md
@@ -0,0 +1,3 @@
+---
+title: "Reference"
+---
diff --git a/docs/content/_index.mdx b/docs/content/_index.mdx
new file mode 100644
index 0000000..6c1c865
--- /dev/null
+++ b/docs/content/_index.mdx
@@ -0,0 +1,43 @@
+---
+title: "deploykit"
+summary: "Automate CI/CD for Turbo and Nx monorepos deploying to Fly.io"
+description: "deploykit reads your workspace graph and generates the Dockerfiles, fly.toml files and GitHub Actions workflow to deploy every app to Fly.io — preview, staging and production — landed as a reviewable PR you own."
+---
+
+## Welcome
+
+**deploykit** automates CI/CD for **Turbo and Nx monorepos** deploying to **Fly.io**.
+
+Run one command in your monorepo and get a reviewable pull request that wires up:
+
+- **PR preview environments** — every pull request gets its own deployed app, with the URL commented on the PR, torn down automatically when the PR closes.
+- **Staging** — deploys on merge to `main`.
+- **Production** — deploys behind a manual approval gate (a GitHub Environment protection rule).
+
+deploykit reads your workspace graph, figures out which apps are deployable, and generates the Dockerfiles, `fly.toml` files and a GitHub Actions workflow — all landed as **files you own and can edit**.
+
+## Try it now
+
+```bash
+npx deploykit init
+```
+
+That's it. `init` walks through five phases — preflight, detect, ask, plan, emit — and never writes or provisions anything until you confirm the plan.
+
+## What makes it different
+
+- 🧭 **Detection-driven** — reads your package manager, workspace packages, per-app framework, ports, internal dependencies, Prisma schemas and env-var names. Every question is pre-filled from what it found.
+- 📦 **Files you own** — multi-stage, `turbo-prune`-based Dockerfiles, per-app `fly.toml`, a single `deploy.yml` workflow, and a `deploykit.config.ts` that is the source of truth for every decision. No hidden runtime, no vendor lock-in.
+- 🚦 **Three environments** — preview, staging and production, each with the right trigger and the right guardrails.
+- 🛟 **Health-checked, self-rolling-back deploys** — Fly waits on an HTTP health check before shifting traffic and keeps the old machines running if it fails, so a bad deploy rolls itself back.
+- ⏪ **One-command rollback** — `deploykit rollback` redeploys a prior image for an environment, after showing you the exact `flyctl` command it will run.
+- 🌍 **Multi-region** — scale stateless apps into extra Fly regions after each deploy.
+- 🗄️ **Migration-aware** — detects Prisma schemas and writes a commented-out release hook you opt into, rather than running destructive migrations for you.
+- 🌐 **Custom domains** — optionally wire staging/production hostnames through Cloudflare DNS + Fly certificates.
+
+## Where to next
+
+- New here? Start with **[Getting Started](/getting-started)** — install, run, and read what gets generated.
+- Want the mental model? Read **[Core Concepts](/core-concepts)** — how detection, environments and the config file fit together.
+- Looking for a specific task? Jump to the **[Guides](/guides)** — rollbacks, multi-region, migrations, custom domains, provisioning.
+- Need exact flags and fields? See the **[Reference](/reference)**.
diff --git a/docs/env.d.ts b/docs/env.d.ts
new file mode 100644
index 0000000..6eb99ae
--- /dev/null
+++ b/docs/env.d.ts
@@ -0,0 +1,3 @@
+///
+///
+///
diff --git a/docs/fly.toml b/docs/fly.toml
new file mode 100644
index 0000000..2ecc017
--- /dev/null
+++ b/docs/fly.toml
@@ -0,0 +1,20 @@
+# fly.toml app configuration file generated for docs-template-main on 2025-10-03T11:40:42+02:00
+#
+# See https://fly.io/docs/reference/configuration/ for information about how to use this file.
+#
+
+app = 'deploykit-docs'
+primary_region = 'fra'
+
+[build]
+
+[http_service]
+ internal_port = 3000
+ force_https = true
+ auto_stop_machines = 'suspend'
+ auto_start_machines = true
+ min_machines_running = 0
+ processes = ['app']
+
+[[vm]]
+ size = 'shared-cpu-1x'
diff --git a/docs/knip.json b/docs/knip.json
new file mode 100644
index 0000000..b3090e2
--- /dev/null
+++ b/docs/knip.json
@@ -0,0 +1,20 @@
+{
+ "$schema": "https://unpkg.com/knip@5/schema.json",
+ "entry": ["scripts/*.{ts,js}", "app/routes.ts", "app/server/*.ts", "app/ui/icon/Icon.tsx"],
+ "remix": true,
+ "lefthook": true,
+ "project": ["**/*.{js,cjs,mjs,jsx,ts,cts,mts,tsx}"],
+ "ignore": [
+ "app/ui/icon/icons/types.ts",
+ "react-router.config.ts",
+ "content-collections.ts",
+ "docs-config/docs.schema.ts",
+ "docs.config.ts"
+ ],
+ "ignoreDependencies": [
+ "@babel/preset-typescript",
+ "babel-plugin-react-compiler",
+ "tailwindcss",
+ "@tailwindcss/typography"
+ ]
+}
diff --git a/docs/lefthook.yml b/docs/lefthook.yml
new file mode 100644
index 0000000..fe73162
--- /dev/null
+++ b/docs/lefthook.yml
@@ -0,0 +1,12 @@
+pre-commit:
+ parallel: true
+ commands:
+ check:
+ run: pnpm run check --fix --no-errors-on-unmatched
+ stage_fixed: true
+ typecheck:
+ run: pnpm run typecheck
+ test:
+ run: pnpm run test
+ unused-code:
+ run: pnpm run check:unused
diff --git a/docs/package.json b/docs/package.json
new file mode 100644
index 0000000..56466e0
--- /dev/null
+++ b/docs/package.json
@@ -0,0 +1,107 @@
+{
+ "name": "deploykit-docs",
+ "version": "1.0.0",
+ "author": "Almina Brulic",
+ "private": true,
+ "sideEffects": false,
+ "license": "MIT",
+ "type": "module",
+ "scripts": {
+ "execute": "tsx",
+ "clean": "git clean -fdX --exclude=\"!.env\"",
+ "script": "tsx scripts/setup.ts",
+ "build": "react-router build",
+ "predev": "pnpm run typegen",
+ "dev": "react-router dev",
+ "start": "NODE_ENV=production node ./build/server/index.js",
+ "pretest": "pnpm run typegen",
+ "test": "vitest run --browser.headless",
+ "test:ui": "vitest",
+ "test:cov": "vitest run --coverage --browser.headless",
+ "pretypecheck": "pnpm run typegen",
+ "typecheck": "tsc",
+ "validate": "pnpm run check && pnpm run typecheck && pnpm run test && pnpm run check:unused",
+ "check": "biome check .",
+ "check:fix": "biome check --fix .",
+ "precheck:unused": " pnpm run typegen",
+ "check:unused": "knip --max-issues 1",
+ "check:unused:fix": "knip --fix",
+ "typegen": "react-router typegen",
+ "generate:docs": "npx tsx scripts/generate-docs.ts --branch main",
+ "content-collections:build": "content-collections build"
+ },
+ "dependencies": {
+ "@content-collections/cli": "0.1.7",
+ "@content-collections/core": "0.10.0",
+ "@content-collections/mdx": "0.2.2",
+ "@content-collections/remix-vite": "0.2.2",
+ "@epic-web/client-hints": "1.3.5",
+ "@forge42/seo-tools": "1.3.0",
+ "@react-router/node": "7.2.0",
+ "clsx": "2.1.1",
+ "hono": "4.6.20",
+ "i18next": "24.2.2",
+ "i18next-browser-languagedetector": "8.0.2",
+ "i18next-http-backend": "3.0.2",
+ "isbot": "5.1.22",
+ "pretty-cache-header": "1.0.0",
+ "react": "19.0.0",
+ "react-dom": "19.0.0",
+ "react-i18next": "15.4.0",
+ "react-router": "7.2.0",
+ "react-router-hono-server": "2.10.0",
+ "rehype-slug": "6.0.0",
+ "remix-hono": "0.0.18",
+ "remix-i18next": "7.0.2",
+ "semver": "7.7.2",
+ "slug": "11.0.0",
+ "tailwind-merge": "3.0.1",
+ "zod": "4.0.17"
+ },
+ "devDependencies": {
+ "@babel/preset-typescript": "7.26.0",
+ "@biomejs/biome": "1.9.4",
+ "@dotenvx/dotenvx": "1.34.0",
+ "@react-router/dev": "7.2.0",
+ "@tailwindcss/typography": "0.5.16",
+ "@tailwindcss/vite": "4.0.9",
+ "@testing-library/react": "16.2.0",
+ "@types/node": "22.13.1",
+ "@types/prompt": "1.1.9",
+ "@types/react": "19.0.8",
+ "@types/react-dom": "19.0.3",
+ "@types/semver": "7.7.0",
+ "@types/slug": "5.0.9",
+ "@vitest/browser": "3.2.4",
+ "@vitest/coverage-v8": "3.2.4",
+ "@vitest/ui": "3.2.4",
+ "babel-plugin-react-compiler": "19.0.0-beta-df7b47d-20241124",
+ "chalk": "5.4.1",
+ "happy-dom": "16.8.1",
+ "knip": "5.43.6",
+ "lefthook": "1.10.10",
+ "playwright": "1.50.1",
+ "prompt": "1.3.0",
+ "react-router-devtools": "5.0.4",
+ "tailwindcss": "4.0.9",
+ "tsx": "4.19.2",
+ "typescript": "5.7.3",
+ "vite": "6.2.0",
+ "vite-plugin-babel": "1.3.0",
+ "vite-plugin-icons-spritesheet": "3.0.1",
+ "vite-tsconfig-paths": "5.1.4",
+ "vitest": "3.2.4",
+ "vitest-browser-react": "1.0.1"
+ },
+ "packageManager": "pnpm@10.18.0",
+ "optionalDependencies": {
+ "@rollup/rollup-linux-x64-gnu": "^4.34.3"
+ },
+ "engines": {
+ "node": ">=22.17.0",
+ "pnpm": ">=10.18.0"
+ },
+ "pnpm": {
+ "onlyBuiltDependencies": ["@biomejs/biome", "esbuild", "lefthook", "msw"]
+ }
+}
diff --git a/docs/pnpm-lock.yaml b/docs/pnpm-lock.yaml
new file mode 100644
index 0000000..83ecbbb
--- /dev/null
+++ b/docs/pnpm-lock.yaml
@@ -0,0 +1,8289 @@
+lockfileVersion: '9.0'
+
+settings:
+ autoInstallPeers: true
+ excludeLinksFromLockfile: false
+
+importers:
+
+ .:
+ dependencies:
+ '@content-collections/cli':
+ specifier: 0.1.7
+ version: 0.1.7(@content-collections/core@0.10.0(typescript@5.7.3))
+ '@content-collections/core':
+ specifier: 0.10.0
+ version: 0.10.0(typescript@5.7.3)
+ '@content-collections/mdx':
+ specifier: 0.2.2
+ version: 0.2.2(@content-collections/core@0.10.0(typescript@5.7.3))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
+ '@content-collections/remix-vite':
+ specifier: 0.2.2
+ version: 0.2.2(@content-collections/core@0.10.0(typescript@5.7.3))(vite@6.2.0(@types/node@22.13.1)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.19.2)(yaml@2.8.1))
+ '@epic-web/client-hints':
+ specifier: 1.3.5
+ version: 1.3.5
+ '@forge42/seo-tools':
+ specifier: 1.3.0
+ version: 1.3.0(typescript@5.7.3)
+ '@react-router/node':
+ specifier: 7.2.0
+ version: 7.2.0(react-router@7.2.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(typescript@5.7.3)
+ clsx:
+ specifier: 2.1.1
+ version: 2.1.1
+ hono:
+ specifier: 4.6.20
+ version: 4.6.20
+ i18next:
+ specifier: 24.2.2
+ version: 24.2.2(typescript@5.7.3)
+ i18next-browser-languagedetector:
+ specifier: 8.0.2
+ version: 8.0.2
+ i18next-http-backend:
+ specifier: 3.0.2
+ version: 3.0.2
+ isbot:
+ specifier: 5.1.22
+ version: 5.1.22
+ pretty-cache-header:
+ specifier: 1.0.0
+ version: 1.0.0
+ react:
+ specifier: 19.0.0
+ version: 19.0.0
+ react-dom:
+ specifier: 19.0.0
+ version: 19.0.0(react@19.0.0)
+ react-i18next:
+ specifier: 15.4.0
+ version: 15.4.0(i18next@24.2.2(typescript@5.7.3))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
+ react-router:
+ specifier: 7.2.0
+ version: 7.2.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
+ react-router-hono-server:
+ specifier: 2.10.0
+ version: 2.10.0(@react-router/dev@7.2.0(@types/node@22.13.1)(babel-plugin-macros@3.1.0)(jiti@2.6.1)(lightningcss@1.30.2)(react-router@7.2.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(tsx@4.19.2)(typescript@5.7.3)(vite@6.2.0(@types/node@22.13.1)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.19.2)(yaml@2.8.1))(yaml@2.8.1))(@types/react@19.0.8)(react-router@7.2.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(vite@6.2.0(@types/node@22.13.1)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.19.2)(yaml@2.8.1))
+ rehype-slug:
+ specifier: 6.0.0
+ version: 6.0.0
+ remix-hono:
+ specifier: 0.0.18
+ version: 0.0.18(hono@4.6.20)(i18next@24.2.2(typescript@5.7.3))(pretty-cache-header@1.0.0)(react-router@7.2.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(remix-i18next@7.0.2(i18next@24.2.2(typescript@5.7.3))(react-i18next@15.4.0(i18next@24.2.2(typescript@5.7.3))(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react-router@7.2.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react@19.0.0))(zod@4.0.17)
+ remix-i18next:
+ specifier: 7.0.2
+ version: 7.0.2(i18next@24.2.2(typescript@5.7.3))(react-i18next@15.4.0(i18next@24.2.2(typescript@5.7.3))(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react-router@7.2.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react@19.0.0)
+ semver:
+ specifier: 7.7.2
+ version: 7.7.2
+ slug:
+ specifier: 11.0.0
+ version: 11.0.0
+ tailwind-merge:
+ specifier: 3.0.1
+ version: 3.0.1
+ zod:
+ specifier: 4.0.17
+ version: 4.0.17
+ devDependencies:
+ '@babel/preset-typescript':
+ specifier: 7.26.0
+ version: 7.26.0(@babel/core@7.28.4)
+ '@biomejs/biome':
+ specifier: 1.9.4
+ version: 1.9.4
+ '@dotenvx/dotenvx':
+ specifier: 1.34.0
+ version: 1.34.0
+ '@react-router/dev':
+ specifier: 7.2.0
+ version: 7.2.0(@types/node@22.13.1)(babel-plugin-macros@3.1.0)(jiti@2.6.1)(lightningcss@1.30.2)(react-router@7.2.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(tsx@4.19.2)(typescript@5.7.3)(vite@6.2.0(@types/node@22.13.1)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.19.2)(yaml@2.8.1))(yaml@2.8.1)
+ '@tailwindcss/typography':
+ specifier: 0.5.16
+ version: 0.5.16(tailwindcss@4.0.9)
+ '@tailwindcss/vite':
+ specifier: 4.0.9
+ version: 4.0.9(vite@6.2.0(@types/node@22.13.1)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.19.2)(yaml@2.8.1))
+ '@testing-library/react':
+ specifier: 16.2.0
+ version: 16.2.0(@testing-library/dom@10.4.1)(@types/react-dom@19.0.3(@types/react@19.0.8))(@types/react@19.0.8)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
+ '@types/node':
+ specifier: 22.13.1
+ version: 22.13.1
+ '@types/prompt':
+ specifier: 1.1.9
+ version: 1.1.9
+ '@types/react':
+ specifier: 19.0.8
+ version: 19.0.8
+ '@types/react-dom':
+ specifier: 19.0.3
+ version: 19.0.3(@types/react@19.0.8)
+ '@types/semver':
+ specifier: 7.7.0
+ version: 7.7.0
+ '@types/slug':
+ specifier: 5.0.9
+ version: 5.0.9
+ '@vitest/browser':
+ specifier: 3.2.4
+ version: 3.2.4(msw@2.11.3(@types/node@22.13.1)(typescript@5.7.3))(playwright@1.50.1)(vite@6.2.0(@types/node@22.13.1)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.19.2)(yaml@2.8.1))(vitest@3.2.4)
+ '@vitest/coverage-v8':
+ specifier: 3.2.4
+ version: 3.2.4(@vitest/browser@3.2.4)(vitest@3.2.4)
+ '@vitest/ui':
+ specifier: 3.2.4
+ version: 3.2.4(vitest@3.2.4)
+ babel-plugin-react-compiler:
+ specifier: 19.0.0-beta-df7b47d-20241124
+ version: 19.0.0-beta-df7b47d-20241124
+ chalk:
+ specifier: 5.4.1
+ version: 5.4.1
+ happy-dom:
+ specifier: 16.8.1
+ version: 16.8.1
+ knip:
+ specifier: 5.43.6
+ version: 5.43.6(@types/node@22.13.1)(typescript@5.7.3)
+ lefthook:
+ specifier: 1.10.10
+ version: 1.10.10
+ playwright:
+ specifier: 1.50.1
+ version: 1.50.1
+ prompt:
+ specifier: 1.3.0
+ version: 1.3.0
+ react-router-devtools:
+ specifier: 5.0.4
+ version: 5.0.4(@types/react-dom@19.0.3(@types/react@19.0.8))(@types/react@19.0.8)(react-dom@19.0.0(react@19.0.0))(react-router@7.2.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react@19.0.0)(vite@6.2.0(@types/node@22.13.1)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.19.2)(yaml@2.8.1))
+ tailwindcss:
+ specifier: 4.0.9
+ version: 4.0.9
+ tsx:
+ specifier: 4.19.2
+ version: 4.19.2
+ typescript:
+ specifier: 5.7.3
+ version: 5.7.3
+ vite:
+ specifier: 6.2.0
+ version: 6.2.0(@types/node@22.13.1)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.19.2)(yaml@2.8.1)
+ vite-plugin-babel:
+ specifier: 1.3.0
+ version: 1.3.0(@babel/core@7.28.4)(vite@6.2.0(@types/node@22.13.1)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.19.2)(yaml@2.8.1))
+ vite-plugin-icons-spritesheet:
+ specifier: 3.0.1
+ version: 3.0.1(vite@6.2.0(@types/node@22.13.1)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.19.2)(yaml@2.8.1))
+ vite-tsconfig-paths:
+ specifier: 5.1.4
+ version: 5.1.4(typescript@5.7.3)(vite@6.2.0(@types/node@22.13.1)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.19.2)(yaml@2.8.1))
+ vitest:
+ specifier: 3.2.4
+ version: 3.2.4(@types/debug@4.1.12)(@types/node@22.13.1)(@vitest/browser@3.2.4)(@vitest/ui@3.2.4)(happy-dom@16.8.1)(jiti@2.6.1)(lightningcss@1.30.2)(msw@2.11.3(@types/node@22.13.1)(typescript@5.7.3))(tsx@4.19.2)(yaml@2.8.1)
+ vitest-browser-react:
+ specifier: 1.0.1
+ version: 1.0.1(@types/react-dom@19.0.3(@types/react@19.0.8))(@types/react@19.0.8)(@vitest/browser@3.2.4)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(vitest@3.2.4)
+ optionalDependencies:
+ '@rollup/rollup-linux-x64-gnu':
+ specifier: ^4.34.3
+ version: 4.52.4
+
+packages:
+
+ '@ampproject/remapping@2.3.0':
+ resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==}
+ engines: {node: '>=6.0.0'}
+
+ '@babel/code-frame@7.27.1':
+ resolution: {integrity: sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/compat-data@7.28.4':
+ resolution: {integrity: sha512-YsmSKC29MJwf0gF8Rjjrg5LQCmyh+j/nD8/eP7f+BeoQTKYqs9RoWbjGOdy0+1Ekr68RJZMUOPVQaQisnIo4Rw==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/core@7.28.4':
+ resolution: {integrity: sha512-2BCOP7TN8M+gVDj7/ht3hsaO/B/n5oDbiAyyvnRlNOs+u1o+JWNYTQrmpuNp1/Wq2gcFrI01JAW+paEKDMx/CA==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/generator@7.28.3':
+ resolution: {integrity: sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/helper-annotate-as-pure@7.27.3':
+ resolution: {integrity: sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/helper-compilation-targets@7.27.2':
+ resolution: {integrity: sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/helper-create-class-features-plugin@7.28.3':
+ resolution: {integrity: sha512-V9f6ZFIYSLNEbuGA/92uOvYsGCJNsuA8ESZ4ldc09bWk/j8H8TKiPw8Mk1eG6olpnO0ALHJmYfZvF4MEE4gajg==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0
+
+ '@babel/helper-globals@7.28.0':
+ resolution: {integrity: sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/helper-member-expression-to-functions@7.27.1':
+ resolution: {integrity: sha512-E5chM8eWjTp/aNoVpcbfM7mLxu9XGLWYise2eBKGQomAk/Mb4XoxyqXTZbuTohbsl8EKqdlMhnDI2CCLfcs9wA==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/helper-module-imports@7.27.1':
+ resolution: {integrity: sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/helper-module-transforms@7.28.3':
+ resolution: {integrity: sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0
+
+ '@babel/helper-optimise-call-expression@7.27.1':
+ resolution: {integrity: sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/helper-plugin-utils@7.27.1':
+ resolution: {integrity: sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/helper-replace-supers@7.27.1':
+ resolution: {integrity: sha512-7EHz6qDZc8RYS5ElPoShMheWvEgERonFCs7IAonWLLUTXW59DP14bCZt89/GKyreYn8g3S83m21FelHKbeDCKA==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0
+
+ '@babel/helper-skip-transparent-expression-wrappers@7.27.1':
+ resolution: {integrity: sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/helper-string-parser@7.27.1':
+ resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/helper-validator-identifier@7.27.1':
+ resolution: {integrity: sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/helper-validator-option@7.27.1':
+ resolution: {integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/helpers@7.28.4':
+ resolution: {integrity: sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/parser@7.28.4':
+ resolution: {integrity: sha512-yZbBqeM6TkpP9du/I2pUZnJsRMGGvOuIrhjzC1AwHwW+6he4mni6Bp/m8ijn0iOuZuPI2BfkCoSRunpyjnrQKg==}
+ engines: {node: '>=6.0.0'}
+ hasBin: true
+
+ '@babel/plugin-syntax-decorators@7.27.1':
+ resolution: {integrity: sha512-YMq8Z87Lhl8EGkmb0MwYkt36QnxC+fzCgrl66ereamPlYToRpIk5nUjKUY3QKLWq8mwUB1BgbeXcTJhZOCDg5A==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@babel/plugin-syntax-jsx@7.27.1':
+ resolution: {integrity: sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@babel/plugin-syntax-typescript@7.27.1':
+ resolution: {integrity: sha512-xfYCBMxveHrRMnAWl1ZlPXOZjzkN82THFvLhQhFXFt81Z5HnN+EtUkZhv/zcKpmT3fzmWZB0ywiBrbC3vogbwQ==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@babel/plugin-transform-modules-commonjs@7.27.1':
+ resolution: {integrity: sha512-OJguuwlTYlN0gBZFRPqwOGNWssZjfIUdS7HMYtN8c1KmwpwHFBwTeFZrg9XZa+DFTitWOW5iTAG7tyCUPsCCyw==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@babel/plugin-transform-typescript@7.28.0':
+ resolution: {integrity: sha512-4AEiDEBPIZvLQaWlc9liCavE0xRM0dNca41WtBeM3jgFptfUOSG9z0uteLhq6+3rq+WB6jIvUwKDTpXEHPJ2Vg==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@babel/preset-typescript@7.26.0':
+ resolution: {integrity: sha512-NMk1IGZ5I/oHhoXEElcm+xUnL/szL6xflkFZmoEU9xj1qSJXpiS7rsspYo92B4DRCDvZn2erT5LdsCeXAKNCkg==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@babel/runtime@7.28.4':
+ resolution: {integrity: sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/template@7.27.2':
+ resolution: {integrity: sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/traverse@7.28.4':
+ resolution: {integrity: sha512-YEzuboP2qvQavAcjgQNVgsvHIDv6ZpwXvcvjmyySP2DIMuByS/6ioU5G9pYrWHM6T2YDfc7xga9iNzYOs12CFQ==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/types@7.28.4':
+ resolution: {integrity: sha512-bkFqkLhh3pMBUQQkpVgWDWq/lqzc2678eUyDlTBhRqhCHFguYYGM0Efga7tYk4TogG/3x0EEl66/OQ+WGbWB/Q==}
+ engines: {node: '>=6.9.0'}
+
+ '@bcoe/v8-coverage@1.0.2':
+ resolution: {integrity: sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==}
+ engines: {node: '>=18'}
+
+ '@biomejs/biome@1.9.4':
+ resolution: {integrity: sha512-1rkd7G70+o9KkTn5KLmDYXihGoTaIGO9PIIN2ZB7UJxFrWw04CZHPYiMRjYsaDvVV7hP1dYNRLxSANLaBFGpog==}
+ engines: {node: '>=14.21.3'}
+ hasBin: true
+
+ '@biomejs/cli-darwin-arm64@1.9.4':
+ resolution: {integrity: sha512-bFBsPWrNvkdKrNCYeAp+xo2HecOGPAy9WyNyB/jKnnedgzl4W4Hb9ZMzYNbf8dMCGmUdSavlYHiR01QaYR58cw==}
+ engines: {node: '>=14.21.3'}
+ cpu: [arm64]
+ os: [darwin]
+
+ '@biomejs/cli-darwin-x64@1.9.4':
+ resolution: {integrity: sha512-ngYBh/+bEedqkSevPVhLP4QfVPCpb+4BBe2p7Xs32dBgs7rh9nY2AIYUL6BgLw1JVXV8GlpKmb/hNiuIxfPfZg==}
+ engines: {node: '>=14.21.3'}
+ cpu: [x64]
+ os: [darwin]
+
+ '@biomejs/cli-linux-arm64-musl@1.9.4':
+ resolution: {integrity: sha512-v665Ct9WCRjGa8+kTr0CzApU0+XXtRgwmzIf1SeKSGAv+2scAlW6JR5PMFo6FzqqZ64Po79cKODKf3/AAmECqA==}
+ engines: {node: '>=14.21.3'}
+ cpu: [arm64]
+ os: [linux]
+
+ '@biomejs/cli-linux-arm64@1.9.4':
+ resolution: {integrity: sha512-fJIW0+LYujdjUgJJuwesP4EjIBl/N/TcOX3IvIHJQNsAqvV2CHIogsmA94BPG6jZATS4Hi+xv4SkBBQSt1N4/g==}
+ engines: {node: '>=14.21.3'}
+ cpu: [arm64]
+ os: [linux]
+
+ '@biomejs/cli-linux-x64-musl@1.9.4':
+ resolution: {integrity: sha512-gEhi/jSBhZ2m6wjV530Yy8+fNqG8PAinM3oV7CyO+6c3CEh16Eizm21uHVsyVBEB6RIM8JHIl6AGYCv6Q6Q9Tg==}
+ engines: {node: '>=14.21.3'}
+ cpu: [x64]
+ os: [linux]
+
+ '@biomejs/cli-linux-x64@1.9.4':
+ resolution: {integrity: sha512-lRCJv/Vi3Vlwmbd6K+oQ0KhLHMAysN8lXoCI7XeHlxaajk06u7G+UsFSO01NAs5iYuWKmVZjmiOzJ0OJmGsMwg==}
+ engines: {node: '>=14.21.3'}
+ cpu: [x64]
+ os: [linux]
+
+ '@biomejs/cli-win32-arm64@1.9.4':
+ resolution: {integrity: sha512-tlbhLk+WXZmgwoIKwHIHEBZUwxml7bRJgk0X2sPyNR3S93cdRq6XulAZRQJ17FYGGzWne0fgrXBKpl7l4M87Hg==}
+ engines: {node: '>=14.21.3'}
+ cpu: [arm64]
+ os: [win32]
+
+ '@biomejs/cli-win32-x64@1.9.4':
+ resolution: {integrity: sha512-8Y5wMhVIPaWe6jw2H+KlEm4wP/f7EW3810ZLmDlrEEy5KvBsb9ECEfu/kMWD484ijfQ8+nIi0giMgu9g1UAuuA==}
+ engines: {node: '>=14.21.3'}
+ cpu: [x64]
+ os: [win32]
+
+ '@bkrem/react-transition-group@1.3.5':
+ resolution: {integrity: sha512-lbBYhC42sxAeFEopxzd9oWdkkV0zirO5E9WyeOBxOrpXsf7m30Aj8vnbayZxFOwD9pvUQ2Pheb1gO79s0Qap3Q==}
+ peerDependencies:
+ react: ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
+ react-dom: ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
+
+ '@bundled-es-modules/cookie@2.0.1':
+ resolution: {integrity: sha512-8o+5fRPLNbjbdGRRmJj3h6Hh1AQJf2dk3qQ/5ZFb+PXkRNiSoMGGUKlsgLfrxneb72axVJyIYji64E2+nNfYyw==}
+
+ '@bundled-es-modules/statuses@1.0.1':
+ resolution: {integrity: sha512-yn7BklA5acgcBr+7w064fGV+SGIFySjCKpqjcWgBAIfrAkY+4GQTJJHQMeT3V/sgz23VTEVV8TtOmkvJAhFVfg==}
+
+ '@clerc/core@0.44.0':
+ resolution: {integrity: sha512-o8RgXNcMRoHRujSw9OPDMxqrmoNk7HG0XAZkjZgOrSyIfRXCf85VLyHGBT3XmaOrPEGY964h02ZxMVFdp8RnNQ==}
+
+ '@clerc/plugin-completions@0.44.0':
+ resolution: {integrity: sha512-r69KpaB+EcWccqe31OwK5iyJQZmgmhxJjEBL4RAGlRr2tu6MRX42AOmD3GDW+ZPHkc4D9NJdkqukLboTJlbycA==}
+ peerDependencies:
+ '@clerc/core': '*'
+
+ '@clerc/plugin-help@0.44.0':
+ resolution: {integrity: sha512-QIH+Lrk6WZtXKNxEAA4gOk7dwseS7U0jTZ0TbJfcyOoNA3fF2p48UV8c7hmKk7OhfPS5009eJRW5CVQEgBB8Ng==}
+ peerDependencies:
+ '@clerc/core': '*'
+
+ '@clerc/plugin-version@0.44.0':
+ resolution: {integrity: sha512-YETH54A0sO32oJcLABpb4P5FyhEkhIhe5oe3IXyeUj9/LMcInvKCm6x/gDMIUjTQuh0a5l4iton0A1RscAANhw==}
+ peerDependencies:
+ '@clerc/core': '*'
+
+ '@clerc/utils@0.44.0':
+ resolution: {integrity: sha512-//1zl8UgVhv1NbqsRoCWWci0Y9uBxzAVn8TqoKZchDywGQNZWK6vQI/Ms9uGe3+PZTDXedoXbVjklOINcVC2aA==}
+ peerDependencies:
+ '@clerc/core': '*'
+
+ '@colors/colors@1.5.0':
+ resolution: {integrity: sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==}
+ engines: {node: '>=0.1.90'}
+
+ '@content-collections/cli@0.1.7':
+ resolution: {integrity: sha512-dZn8vd6JSu2mXE1KYOEEq6Y7pwZ3vWjHE/LDj+9aSXNEddfXRGem2NNMO24NCuqFH/uIoVpS00Wnkj6gNqqnQw==}
+ hasBin: true
+ peerDependencies:
+ '@content-collections/core': 0.x
+
+ '@content-collections/core@0.10.0':
+ resolution: {integrity: sha512-GDBYbvhoj9lHNlarY5wr+3PoO3m9GBMjftio9NXatLuZaenY+EHHNCcbbA3J+c06Q7WBYwNoLAaMX2I5N0duAg==}
+ peerDependencies:
+ typescript: ^5.0.2
+
+ '@content-collections/integrations@0.3.0':
+ resolution: {integrity: sha512-He+TXQC94LO/1bNygTioh3J5H0K/mkFVPVkIrM5kHybprvi5bRmGa91ViZ6K6icFAzGH4jFD0iasR56fZcMGTA==}
+ peerDependencies:
+ '@content-collections/core': 0.x
+
+ '@content-collections/mdx@0.2.2':
+ resolution: {integrity: sha512-7Xx8AohrSuq1jn/k44qWIq1s666KnksGPk64nnoY/T9mFZ7fZkdEtYezBsNpzkDMMKTnf65CNIvyFHtwTD2muA==}
+ peerDependencies:
+ '@content-collections/core': 0.x
+ react: '>= 18'
+ react-dom: '>= 18'
+
+ '@content-collections/remix-vite@0.2.2':
+ resolution: {integrity: sha512-kdHJz9CMJHZcGBtJy8zfRd4zp5bSOiaKvj7hlACYLaZK8m1ABmql8giliGbXDCepKqbx1YLb0b86niZg+6aytQ==}
+ peerDependencies:
+ '@content-collections/core': ^0.x
+ vite: ^5 || ^6 || ^7
+
+ '@dotenvx/dotenvx@1.34.0':
+ resolution: {integrity: sha512-+Dp/xaI3IZ4eKv+b2vg4V89VnqLKbmJ7UZ7unnZxMu9SNLOSc2jYaXey1YHCJM+67T0pOr2Gbej3TewnuoqTWQ==}
+ hasBin: true
+
+ '@drizzle-team/brocli@0.11.0':
+ resolution: {integrity: sha512-hD3pekGiPg0WPCCGAZmusBBJsDqGUR66Y452YgQsZOnkdQ7ViEPKuyP4huUGEZQefp8g34RRodXYmJ2TbCH+tg==}
+
+ '@ecies/ciphers@0.2.4':
+ resolution: {integrity: sha512-t+iX+Wf5nRKyNzk8dviW3Ikb/280+aEJAnw9YXvCp2tYGPSkMki+NRY+8aNLmVFv3eNtMdvViPNOPxS8SZNP+w==}
+ engines: {bun: '>=1', deno: '>=2', node: '>=16'}
+ peerDependencies:
+ '@noble/ciphers': ^1.0.0
+
+ '@emotion/babel-plugin@11.13.5':
+ resolution: {integrity: sha512-pxHCpT2ex+0q+HH91/zsdHkw/lXd468DIN2zvfvLtPKLLMo6gQj7oLObq8PhkrxOZb/gGCq03S3Z7PDhS8pduQ==}
+
+ '@emotion/cache@11.14.0':
+ resolution: {integrity: sha512-L/B1lc/TViYk4DcpGxtAVbx0ZyiKM5ktoIyafGkH6zg/tj+mA+NE//aPYKG0k8kCHSHVJrpLpcAlOBEXQ3SavA==}
+
+ '@emotion/css@11.13.5':
+ resolution: {integrity: sha512-wQdD0Xhkn3Qy2VNcIzbLP9MR8TafI0MJb7BEAXKp+w4+XqErksWR4OXomuDzPsN4InLdGhVe6EYcn2ZIUCpB8w==}
+
+ '@emotion/hash@0.9.2':
+ resolution: {integrity: sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g==}
+
+ '@emotion/memoize@0.9.0':
+ resolution: {integrity: sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ==}
+
+ '@emotion/react@11.14.0':
+ resolution: {integrity: sha512-O000MLDBDdk/EohJPFUqvnp4qnHeYkVP5B0xEG0D/L7cOKP9kefu2DXn8dj74cQfsEzUqh+sr1RzFqiL1o+PpA==}
+ peerDependencies:
+ '@types/react': '*'
+ react: '>=16.8.0'
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
+ '@emotion/serialize@1.3.3':
+ resolution: {integrity: sha512-EISGqt7sSNWHGI76hC7x1CksiXPahbxEOrC5RjmFRJTqLyEK9/9hZvBbiYn70dw4wuwMKiEMCUlR6ZXTSWQqxA==}
+
+ '@emotion/sheet@1.4.0':
+ resolution: {integrity: sha512-fTBW9/8r2w3dXWYM4HCB1Rdp8NLibOw2+XELH5m5+AkWiL/KqYX6dc0kKYlaYyKjrQ6ds33MCdMPEwgs2z1rqg==}
+
+ '@emotion/unitless@0.10.0':
+ resolution: {integrity: sha512-dFoMUuQA20zvtVTuxZww6OHoJYgrzfKM1t52mVySDJnMSEa08ruEvdYQbhvyu6soU+NeLVd3yKfTfT0NeV6qGg==}
+
+ '@emotion/use-insertion-effect-with-fallbacks@1.2.0':
+ resolution: {integrity: sha512-yJMtVdH59sxi/aVJBpk9FQq+OR8ll5GT8oWd57UpeaKEVGab41JWaCFA7FRLoMLloOZF/c/wsPoe+bfGmRKgDg==}
+ peerDependencies:
+ react: '>=16.8.0'
+
+ '@emotion/utils@1.4.2':
+ resolution: {integrity: sha512-3vLclRofFziIa3J2wDh9jjbkUz9qk5Vi3IZ/FSTKViB0k+ef0fPV7dYrUIugbgupYDx7v9ud/SjrtEP8Y4xLoA==}
+
+ '@emotion/weak-memoize@0.4.0':
+ resolution: {integrity: sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg==}
+
+ '@epic-web/client-hints@1.3.5':
+ resolution: {integrity: sha512-tFIDxdU5NzN5Ak4gcDOPKkj6aF/qNMC0G+K58CTBZIx7CMSjCrxqhuiEbZBKGDAGJcsQLF5uKKlgs6mgqWmB7Q==}
+
+ '@esbuild-plugins/node-resolve@0.2.2':
+ resolution: {integrity: sha512-+t5FdX3ATQlb53UFDBRb4nqjYBz492bIrnVWvpQHpzZlu9BQL5HasMZhqc409ygUwOWCXZhrWr6NyZ6T6Y+cxw==}
+ peerDependencies:
+ esbuild: '*'
+
+ '@esbuild/aix-ppc64@0.23.1':
+ resolution: {integrity: sha512-6VhYk1diRqrhBAqpJEdjASR/+WVRtfjpqKuNw11cLiaWpAT/Uu+nokB+UJnevzy/P9C/ty6AOe0dwueMrGh/iQ==}
+ engines: {node: '>=18'}
+ cpu: [ppc64]
+ os: [aix]
+
+ '@esbuild/aix-ppc64@0.25.10':
+ resolution: {integrity: sha512-0NFWnA+7l41irNuaSVlLfgNT12caWJVLzp5eAVhZ0z1qpxbockccEt3s+149rE64VUI3Ml2zt8Nv5JVc4QXTsw==}
+ engines: {node: '>=18'}
+ cpu: [ppc64]
+ os: [aix]
+
+ '@esbuild/android-arm64@0.23.1':
+ resolution: {integrity: sha512-xw50ipykXcLstLeWH7WRdQuysJqejuAGPd30vd1i5zSyKK3WE+ijzHmLKxdiCMtH1pHz78rOg0BKSYOSB/2Khw==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [android]
+
+ '@esbuild/android-arm64@0.25.10':
+ resolution: {integrity: sha512-LSQa7eDahypv/VO6WKohZGPSJDq5OVOo3UoFR1E4t4Gj1W7zEQMUhI+lo81H+DtB+kP+tDgBp+M4oNCwp6kffg==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [android]
+
+ '@esbuild/android-arm@0.23.1':
+ resolution: {integrity: sha512-uz6/tEy2IFm9RYOyvKl88zdzZfwEfKZmnX9Cj1BHjeSGNuGLuMD1kR8y5bteYmwqKm1tj8m4cb/aKEorr6fHWQ==}
+ engines: {node: '>=18'}
+ cpu: [arm]
+ os: [android]
+
+ '@esbuild/android-arm@0.25.10':
+ resolution: {integrity: sha512-dQAxF1dW1C3zpeCDc5KqIYuZ1tgAdRXNoZP7vkBIRtKZPYe2xVr/d3SkirklCHudW1B45tGiUlz2pUWDfbDD4w==}
+ engines: {node: '>=18'}
+ cpu: [arm]
+ os: [android]
+
+ '@esbuild/android-x64@0.23.1':
+ resolution: {integrity: sha512-nlN9B69St9BwUoB+jkyU090bru8L0NA3yFvAd7k8dNsVH8bi9a8cUAUSEcEEgTp2z3dbEDGJGfP6VUnkQnlReg==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [android]
+
+ '@esbuild/android-x64@0.25.10':
+ resolution: {integrity: sha512-MiC9CWdPrfhibcXwr39p9ha1x0lZJ9KaVfvzA0Wxwz9ETX4v5CHfF09bx935nHlhi+MxhA63dKRRQLiVgSUtEg==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [android]
+
+ '@esbuild/darwin-arm64@0.23.1':
+ resolution: {integrity: sha512-YsS2e3Wtgnw7Wq53XXBLcV6JhRsEq8hkfg91ESVadIrzr9wO6jJDMZnCQbHm1Guc5t/CdDiFSSfWP58FNuvT3Q==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [darwin]
+
+ '@esbuild/darwin-arm64@0.25.10':
+ resolution: {integrity: sha512-JC74bdXcQEpW9KkV326WpZZjLguSZ3DfS8wrrvPMHgQOIEIG/sPXEN/V8IssoJhbefLRcRqw6RQH2NnpdprtMA==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [darwin]
+
+ '@esbuild/darwin-x64@0.23.1':
+ resolution: {integrity: sha512-aClqdgTDVPSEGgoCS8QDG37Gu8yc9lTHNAQlsztQ6ENetKEO//b8y31MMu2ZaPbn4kVsIABzVLXYLhCGekGDqw==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [darwin]
+
+ '@esbuild/darwin-x64@0.25.10':
+ resolution: {integrity: sha512-tguWg1olF6DGqzws97pKZ8G2L7Ig1vjDmGTwcTuYHbuU6TTjJe5FXbgs5C1BBzHbJ2bo1m3WkQDbWO2PvamRcg==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [darwin]
+
+ '@esbuild/freebsd-arm64@0.23.1':
+ resolution: {integrity: sha512-h1k6yS8/pN/NHlMl5+v4XPfikhJulk4G+tKGFIOwURBSFzE8bixw1ebjluLOjfwtLqY0kewfjLSrO6tN2MgIhA==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [freebsd]
+
+ '@esbuild/freebsd-arm64@0.25.10':
+ resolution: {integrity: sha512-3ZioSQSg1HT2N05YxeJWYR+Libe3bREVSdWhEEgExWaDtyFbbXWb49QgPvFH8u03vUPX10JhJPcz7s9t9+boWg==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [freebsd]
+
+ '@esbuild/freebsd-x64@0.23.1':
+ resolution: {integrity: sha512-lK1eJeyk1ZX8UklqFd/3A60UuZ/6UVfGT2LuGo3Wp4/z7eRTRYY+0xOu2kpClP+vMTi9wKOfXi2vjUpO1Ro76g==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [freebsd]
+
+ '@esbuild/freebsd-x64@0.25.10':
+ resolution: {integrity: sha512-LLgJfHJk014Aa4anGDbh8bmI5Lk+QidDmGzuC2D+vP7mv/GeSN+H39zOf7pN5N8p059FcOfs2bVlrRr4SK9WxA==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [freebsd]
+
+ '@esbuild/linux-arm64@0.23.1':
+ resolution: {integrity: sha512-/93bf2yxencYDnItMYV/v116zff6UyTjo4EtEQjUBeGiVpMmffDNUyD9UN2zV+V3LRV3/on4xdZ26NKzn6754g==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [linux]
+
+ '@esbuild/linux-arm64@0.25.10':
+ resolution: {integrity: sha512-5luJWN6YKBsawd5f9i4+c+geYiVEw20FVW5x0v1kEMWNq8UctFjDiMATBxLvmmHA4bf7F6hTRaJgtghFr9iziQ==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [linux]
+
+ '@esbuild/linux-arm@0.23.1':
+ resolution: {integrity: sha512-CXXkzgn+dXAPs3WBwE+Kvnrf4WECwBdfjfeYHpMeVxWE0EceB6vhWGShs6wi0IYEqMSIzdOF1XjQ/Mkm5d7ZdQ==}
+ engines: {node: '>=18'}
+ cpu: [arm]
+ os: [linux]
+
+ '@esbuild/linux-arm@0.25.10':
+ resolution: {integrity: sha512-oR31GtBTFYCqEBALI9r6WxoU/ZofZl962pouZRTEYECvNF/dtXKku8YXcJkhgK/beU+zedXfIzHijSRapJY3vg==}
+ engines: {node: '>=18'}
+ cpu: [arm]
+ os: [linux]
+
+ '@esbuild/linux-ia32@0.23.1':
+ resolution: {integrity: sha512-VTN4EuOHwXEkXzX5nTvVY4s7E/Krz7COC8xkftbbKRYAl96vPiUssGkeMELQMOnLOJ8k3BY1+ZY52tttZnHcXQ==}
+ engines: {node: '>=18'}
+ cpu: [ia32]
+ os: [linux]
+
+ '@esbuild/linux-ia32@0.25.10':
+ resolution: {integrity: sha512-NrSCx2Kim3EnnWgS4Txn0QGt0Xipoumb6z6sUtl5bOEZIVKhzfyp/Lyw4C1DIYvzeW/5mWYPBFJU3a/8Yr75DQ==}
+ engines: {node: '>=18'}
+ cpu: [ia32]
+ os: [linux]
+
+ '@esbuild/linux-loong64@0.23.1':
+ resolution: {integrity: sha512-Vx09LzEoBa5zDnieH8LSMRToj7ir/Jeq0Gu6qJ/1GcBq9GkfoEAoXvLiW1U9J1qE/Y/Oyaq33w5p2ZWrNNHNEw==}
+ engines: {node: '>=18'}
+ cpu: [loong64]
+ os: [linux]
+
+ '@esbuild/linux-loong64@0.25.10':
+ resolution: {integrity: sha512-xoSphrd4AZda8+rUDDfD9J6FUMjrkTz8itpTITM4/xgerAZZcFW7Dv+sun7333IfKxGG8gAq+3NbfEMJfiY+Eg==}
+ engines: {node: '>=18'}
+ cpu: [loong64]
+ os: [linux]
+
+ '@esbuild/linux-mips64el@0.23.1':
+ resolution: {integrity: sha512-nrFzzMQ7W4WRLNUOU5dlWAqa6yVeI0P78WKGUo7lg2HShq/yx+UYkeNSE0SSfSure0SqgnsxPvmAUu/vu0E+3Q==}
+ engines: {node: '>=18'}
+ cpu: [mips64el]
+ os: [linux]
+
+ '@esbuild/linux-mips64el@0.25.10':
+ resolution: {integrity: sha512-ab6eiuCwoMmYDyTnyptoKkVS3k8fy/1Uvq7Dj5czXI6DF2GqD2ToInBI0SHOp5/X1BdZ26RKc5+qjQNGRBelRA==}
+ engines: {node: '>=18'}
+ cpu: [mips64el]
+ os: [linux]
+
+ '@esbuild/linux-ppc64@0.23.1':
+ resolution: {integrity: sha512-dKN8fgVqd0vUIjxuJI6P/9SSSe/mB9rvA98CSH2sJnlZ/OCZWO1DJvxj8jvKTfYUdGfcq2dDxoKaC6bHuTlgcw==}
+ engines: {node: '>=18'}
+ cpu: [ppc64]
+ os: [linux]
+
+ '@esbuild/linux-ppc64@0.25.10':
+ resolution: {integrity: sha512-NLinzzOgZQsGpsTkEbdJTCanwA5/wozN9dSgEl12haXJBzMTpssebuXR42bthOF3z7zXFWH1AmvWunUCkBE4EA==}
+ engines: {node: '>=18'}
+ cpu: [ppc64]
+ os: [linux]
+
+ '@esbuild/linux-riscv64@0.23.1':
+ resolution: {integrity: sha512-5AV4Pzp80fhHL83JM6LoA6pTQVWgB1HovMBsLQ9OZWLDqVY8MVobBXNSmAJi//Csh6tcY7e7Lny2Hg1tElMjIA==}
+ engines: {node: '>=18'}
+ cpu: [riscv64]
+ os: [linux]
+
+ '@esbuild/linux-riscv64@0.25.10':
+ resolution: {integrity: sha512-FE557XdZDrtX8NMIeA8LBJX3dC2M8VGXwfrQWU7LB5SLOajfJIxmSdyL/gU1m64Zs9CBKvm4UAuBp5aJ8OgnrA==}
+ engines: {node: '>=18'}
+ cpu: [riscv64]
+ os: [linux]
+
+ '@esbuild/linux-s390x@0.23.1':
+ resolution: {integrity: sha512-9ygs73tuFCe6f6m/Tb+9LtYxWR4c9yg7zjt2cYkjDbDpV/xVn+68cQxMXCjUpYwEkze2RcU/rMnfIXNRFmSoDw==}
+ engines: {node: '>=18'}
+ cpu: [s390x]
+ os: [linux]
+
+ '@esbuild/linux-s390x@0.25.10':
+ resolution: {integrity: sha512-3BBSbgzuB9ajLoVZk0mGu+EHlBwkusRmeNYdqmznmMc9zGASFjSsxgkNsqmXugpPk00gJ0JNKh/97nxmjctdew==}
+ engines: {node: '>=18'}
+ cpu: [s390x]
+ os: [linux]
+
+ '@esbuild/linux-x64@0.23.1':
+ resolution: {integrity: sha512-EV6+ovTsEXCPAp58g2dD68LxoP/wK5pRvgy0J/HxPGB009omFPv3Yet0HiaqvrIrgPTBuC6wCH1LTOY91EO5hQ==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [linux]
+
+ '@esbuild/linux-x64@0.25.10':
+ resolution: {integrity: sha512-QSX81KhFoZGwenVyPoberggdW1nrQZSvfVDAIUXr3WqLRZGZqWk/P4T8p2SP+de2Sr5HPcvjhcJzEiulKgnxtA==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [linux]
+
+ '@esbuild/netbsd-arm64@0.25.10':
+ resolution: {integrity: sha512-AKQM3gfYfSW8XRk8DdMCzaLUFB15dTrZfnX8WXQoOUpUBQ+NaAFCP1kPS/ykbbGYz7rxn0WS48/81l9hFl3u4A==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [netbsd]
+
+ '@esbuild/netbsd-x64@0.23.1':
+ resolution: {integrity: sha512-aevEkCNu7KlPRpYLjwmdcuNz6bDFiE7Z8XC4CPqExjTvrHugh28QzUXVOZtiYghciKUacNktqxdpymplil1beA==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [netbsd]
+
+ '@esbuild/netbsd-x64@0.25.10':
+ resolution: {integrity: sha512-7RTytDPGU6fek/hWuN9qQpeGPBZFfB4zZgcz2VK2Z5VpdUxEI8JKYsg3JfO0n/Z1E/6l05n0unDCNc4HnhQGig==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [netbsd]
+
+ '@esbuild/openbsd-arm64@0.23.1':
+ resolution: {integrity: sha512-3x37szhLexNA4bXhLrCC/LImN/YtWis6WXr1VESlfVtVeoFJBRINPJ3f0a/6LV8zpikqoUg4hyXw0sFBt5Cr+Q==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [openbsd]
+
+ '@esbuild/openbsd-arm64@0.25.10':
+ resolution: {integrity: sha512-5Se0VM9Wtq797YFn+dLimf2Zx6McttsH2olUBsDml+lm0GOCRVebRWUvDtkY4BWYv/3NgzS8b/UM3jQNh5hYyw==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [openbsd]
+
+ '@esbuild/openbsd-x64@0.23.1':
+ resolution: {integrity: sha512-aY2gMmKmPhxfU+0EdnN+XNtGbjfQgwZj43k8G3fyrDM/UdZww6xrWxmDkuz2eCZchqVeABjV5BpildOrUbBTqA==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [openbsd]
+
+ '@esbuild/openbsd-x64@0.25.10':
+ resolution: {integrity: sha512-XkA4frq1TLj4bEMB+2HnI0+4RnjbuGZfet2gs/LNs5Hc7D89ZQBHQ0gL2ND6Lzu1+QVkjp3x1gIcPKzRNP8bXw==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [openbsd]
+
+ '@esbuild/openharmony-arm64@0.25.10':
+ resolution: {integrity: sha512-AVTSBhTX8Y/Fz6OmIVBip9tJzZEUcY8WLh7I59+upa5/GPhh2/aM6bvOMQySspnCCHvFi79kMtdJS1w0DXAeag==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [openharmony]
+
+ '@esbuild/sunos-x64@0.23.1':
+ resolution: {integrity: sha512-RBRT2gqEl0IKQABT4XTj78tpk9v7ehp+mazn2HbUeZl1YMdaGAQqhapjGTCe7uw7y0frDi4gS0uHzhvpFuI1sA==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [sunos]
+
+ '@esbuild/sunos-x64@0.25.10':
+ resolution: {integrity: sha512-fswk3XT0Uf2pGJmOpDB7yknqhVkJQkAQOcW/ccVOtfx05LkbWOaRAtn5SaqXypeKQra1QaEa841PgrSL9ubSPQ==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [sunos]
+
+ '@esbuild/win32-arm64@0.23.1':
+ resolution: {integrity: sha512-4O+gPR5rEBe2FpKOVyiJ7wNDPA8nGzDuJ6gN4okSA1gEOYZ67N8JPk58tkWtdtPeLz7lBnY6I5L3jdsr3S+A6A==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [win32]
+
+ '@esbuild/win32-arm64@0.25.10':
+ resolution: {integrity: sha512-ah+9b59KDTSfpaCg6VdJoOQvKjI33nTaQr4UluQwW7aEwZQsbMCfTmfEO4VyewOxx4RaDT/xCy9ra2GPWmO7Kw==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [win32]
+
+ '@esbuild/win32-ia32@0.23.1':
+ resolution: {integrity: sha512-BcaL0Vn6QwCwre3Y717nVHZbAa4UBEigzFm6VdsVdT/MbZ38xoj1X9HPkZhbmaBGUD1W8vxAfffbDe8bA6AKnQ==}
+ engines: {node: '>=18'}
+ cpu: [ia32]
+ os: [win32]
+
+ '@esbuild/win32-ia32@0.25.10':
+ resolution: {integrity: sha512-QHPDbKkrGO8/cz9LKVnJU22HOi4pxZnZhhA2HYHez5Pz4JeffhDjf85E57Oyco163GnzNCVkZK0b/n4Y0UHcSw==}
+ engines: {node: '>=18'}
+ cpu: [ia32]
+ os: [win32]
+
+ '@esbuild/win32-x64@0.23.1':
+ resolution: {integrity: sha512-BHpFFeslkWrXWyUPnbKm+xYYVYruCinGcftSBaa8zoF9hZO4BcSCFUvHVTtzpIY6YzUnYtuEhZ+C9iEXjxnasg==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [win32]
+
+ '@esbuild/win32-x64@0.25.10':
+ resolution: {integrity: sha512-9KpxSVFCu0iK1owoez6aC/s/EdUQLDN3adTxGCqxMVhrPDj6bt5dbrHDXUuq+Bs2vATFBBrQS5vdQ/Ed2P+nbw==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [win32]
+
+ '@fal-works/esbuild-plugin-global-externals@2.1.2':
+ resolution: {integrity: sha512-cEee/Z+I12mZcFJshKcCqC8tuX5hG3s+d+9nZ3LabqKF1vKdF41B92pJVCBggjAGORAeOzyyDDKrZwIkLffeOQ==}
+
+ '@floating-ui/core@1.7.3':
+ resolution: {integrity: sha512-sGnvb5dmrJaKEZ+LDIpguvdX3bDlEllmv4/ClQ9awcmCZrlx5jQyyMWFM5kBI+EyNOCDDiKk8il0zeuX3Zlg/w==}
+
+ '@floating-ui/dom@1.7.4':
+ resolution: {integrity: sha512-OOchDgh4F2CchOX94cRVqhvy7b3AFb+/rQXyswmzmGakRfkMgoWVjfnLWkRirfLEfuD4ysVW16eXzwt3jHIzKA==}
+
+ '@floating-ui/react-dom@2.1.6':
+ resolution: {integrity: sha512-4JX6rEatQEvlmgU80wZyq9RT96HZJa88q8hp0pBd+LrczeDI4o6uA2M+uvxngVHo4Ihr8uibXxH6+70zhAFrVw==}
+ peerDependencies:
+ react: '>=16.8.0'
+ react-dom: '>=16.8.0'
+
+ '@floating-ui/utils@0.2.10':
+ resolution: {integrity: sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==}
+
+ '@forge42/seo-tools@1.3.0':
+ resolution: {integrity: sha512-yxpkeyYyZhFzTpuq9rtcx6FRVZD0NTcVDS2ptrVG7nobnHQnANlLJXkY343GOocHGTygdK35Hyu/iU1nxsEGuA==}
+
+ '@hono/node-server@1.19.5':
+ resolution: {integrity: sha512-iBuhh+uaaggeAuf+TftcjZyWh2GEgZcVGXkNtskLVoWaXhnJtC5HLHrU8W1KHDoucqO1MswwglmkWLFyiDn4WQ==}
+ engines: {node: '>=18.14.1'}
+ peerDependencies:
+ hono: ^4
+
+ '@hono/node-ws@1.2.0':
+ resolution: {integrity: sha512-OBPQ8OSHBw29mj00wT/xGYtB6HY54j0fNSdVZ7gZM3TUeq0So11GXaWtFf1xWxQNfumKIsj0wRuLKWfVsO5GgQ==}
+ engines: {node: '>=18.14.1'}
+ peerDependencies:
+ '@hono/node-server': ^1.11.1
+ hono: ^4.6.0
+
+ '@hono/vite-dev-server@0.17.0':
+ resolution: {integrity: sha512-EvGOIj1MoY9uV94onXXz88yWaTxzUK+Mv8LiIEsR/9eSFoVUnHVR0B7l7iNIsxfHYRN7tbPDMWBSnD2RQun3yw==}
+ engines: {node: '>=18.14.1'}
+ peerDependencies:
+ hono: '*'
+ miniflare: '*'
+ wrangler: '*'
+ peerDependenciesMeta:
+ miniflare:
+ optional: true
+ wrangler:
+ optional: true
+
+ '@inquirer/ansi@1.0.0':
+ resolution: {integrity: sha512-JWaTfCxI1eTmJ1BIv86vUfjVatOdxwD0DAVKYevY8SazeUUZtW+tNbsdejVO1GYE0GXJW1N1ahmiC3TFd+7wZA==}
+ engines: {node: '>=18'}
+
+ '@inquirer/confirm@5.1.18':
+ resolution: {integrity: sha512-MilmWOzHa3Ks11tzvuAmFoAd/wRuaP3SwlT1IZhyMke31FKLxPiuDWcGXhU+PKveNOpAc4axzAgrgxuIJJRmLw==}
+ engines: {node: '>=18'}
+ peerDependencies:
+ '@types/node': '>=18'
+ peerDependenciesMeta:
+ '@types/node':
+ optional: true
+
+ '@inquirer/core@10.2.2':
+ resolution: {integrity: sha512-yXq/4QUnk4sHMtmbd7irwiepjB8jXU0kkFRL4nr/aDBA2mDz13cMakEWdDwX3eSCTkk03kwcndD1zfRAIlELxA==}
+ engines: {node: '>=18'}
+ peerDependencies:
+ '@types/node': '>=18'
+ peerDependenciesMeta:
+ '@types/node':
+ optional: true
+
+ '@inquirer/figures@1.0.13':
+ resolution: {integrity: sha512-lGPVU3yO9ZNqA7vTYz26jny41lE7yoQansmqdMLBEfqaGsmdg7V3W9mK9Pvb5IL4EVZ9GnSDGMO/cJXud5dMaw==}
+ engines: {node: '>=18'}
+
+ '@inquirer/type@3.0.8':
+ resolution: {integrity: sha512-lg9Whz8onIHRthWaN1Q9EGLa/0LFJjyM8mEUbL1eTi6yMGvBf8gvyDLtxSXztQsxMvhxxNpJYrwa1YHdq+w4Jw==}
+ engines: {node: '>=18'}
+ peerDependencies:
+ '@types/node': '>=18'
+ peerDependenciesMeta:
+ '@types/node':
+ optional: true
+
+ '@isaacs/balanced-match@4.0.1':
+ resolution: {integrity: sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==}
+ engines: {node: 20 || >=22}
+
+ '@isaacs/brace-expansion@5.0.0':
+ resolution: {integrity: sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA==}
+ engines: {node: 20 || >=22}
+
+ '@isaacs/cliui@8.0.2':
+ resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==}
+ engines: {node: '>=12'}
+
+ '@istanbuljs/schema@0.1.3':
+ resolution: {integrity: sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==}
+ engines: {node: '>=8'}
+
+ '@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/esbuild@3.1.1':
+ resolution: {integrity: sha512-NS35VhTdvKNj5/B1JSD5W3kN1R0WDHgk+zCWq+tSChQw5L2Bgeiz7yyZPFrc5LWuPVOxE1xMbJr82bO9VVzmfQ==}
+ peerDependencies:
+ esbuild: '>=0.14.0'
+
+ '@mdx-js/mdx@3.1.1':
+ resolution: {integrity: sha512-f6ZO2ifpwAQIpzGWaBQT2TXxPv6z3RBzQKpVftEWN78Vl/YweF1uwussDx8ECAXVtr3Rs89fKyG9YlzUs9DyGQ==}
+
+ '@mjackson/node-fetch-server@0.2.0':
+ resolution: {integrity: sha512-EMlH1e30yzmTpGLQjlFmaDAjyOeZhng1/XCd7DExR8PNAnG/G1tyruZxEoUe11ClnwGhGrtsdnyyUx1frSzjng==}
+
+ '@mswjs/interceptors@0.39.7':
+ resolution: {integrity: sha512-sURvQbbKsq5f8INV54YJgJEdk8oxBanqkTiXXd33rKmofFCwZLhLRszPduMZ9TA9b8/1CHc/IJmOlBHJk2Q5AQ==}
+ engines: {node: '>=18'}
+
+ '@noble/ciphers@1.3.0':
+ resolution: {integrity: sha512-2I0gnIVPtfnMw9ee9h1dJG7tp81+8Ob3OJb3Mv37rx5L40/b0i7djjCVvGOVqc9AEIQyvyu1i6ypKdFw8R8gQw==}
+ engines: {node: ^14.21.3 || >=16}
+
+ '@noble/curves@1.9.7':
+ resolution: {integrity: sha512-gbKGcRUYIjA3/zCCNaWDciTMFI0dCkvou3TL8Zmy5Nc7sJ47a0jtOeZoTaMxkuqRo9cRhjOdZJXegxYE5FN/xw==}
+ engines: {node: ^14.21.3 || >=16}
+
+ '@noble/hashes@1.8.0':
+ resolution: {integrity: sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==}
+ engines: {node: ^14.21.3 || >=16}
+
+ '@nodelib/fs.scandir@2.1.5':
+ resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==}
+ engines: {node: '>= 8'}
+
+ '@nodelib/fs.scandir@4.0.1':
+ resolution: {integrity: sha512-vAkI715yhnmiPupY+dq+xenu5Tdf2TBQ66jLvBIcCddtz+5Q8LbMKaf9CIJJreez8fQ8fgaY+RaywQx8RJIWpw==}
+ engines: {node: '>=18.18.0'}
+
+ '@nodelib/fs.stat@2.0.5':
+ resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==}
+ engines: {node: '>= 8'}
+
+ '@nodelib/fs.stat@4.0.0':
+ resolution: {integrity: sha512-ctr6bByzksKRCV0bavi8WoQevU6plSp2IkllIsEqaiKe2mwNNnaluhnRhcsgGZHrrHk57B3lf95MkLMO3STYcg==}
+ engines: {node: '>=18.18.0'}
+
+ '@nodelib/fs.walk@1.2.8':
+ resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==}
+ engines: {node: '>= 8'}
+
+ '@nodelib/fs.walk@3.0.1':
+ resolution: {integrity: sha512-nIh/M6Kh3ZtOmlY00DaUYB4xeeV6F3/ts1l29iwl3/cfyY/OuCfUx+v08zgx8TKPTifXRcjjqVQ4KB2zOYSbyw==}
+ engines: {node: '>=18.18.0'}
+
+ '@npmcli/git@4.1.0':
+ resolution: {integrity: sha512-9hwoB3gStVfa0N31ymBmrX+GuDGdVA/QWShZVqE0HK2Af+7QGGrCTbZia/SW0ImUTjTne7SP91qxDmtXvDHRPQ==}
+ engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}
+
+ '@npmcli/package-json@4.0.1':
+ resolution: {integrity: sha512-lRCEGdHZomFsURroh522YvA/2cVb9oPIJrjHanCJZkiasz1BzcnLr3tBJhlV7S86MBJBuAQ33is2D60YitZL2Q==}
+ engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}
+
+ '@npmcli/promise-spawn@6.0.2':
+ resolution: {integrity: sha512-gGq0NJkIGSwdbUt4yhdF8ZrmkGKVz9vAdVzpOfnom+V8PLSmSOVhZwbNvZZS1EYcJN5hzzKBxmmVVAInM6HQLg==}
+ engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}
+
+ '@one-ini/wasm@0.1.1':
+ resolution: {integrity: sha512-XuySG1E38YScSJoMlqovLru4KTUNSjgVTIjyh7qMX6aNN5HY5Ct5LhRJdxO79JtTzKfzV/bnWpz+zquYrISsvw==}
+
+ '@open-draft/deferred-promise@2.2.0':
+ resolution: {integrity: sha512-CecwLWx3rhxVQF6V4bAgPS5t+So2sTbPgAzafKkVizyi7tlwpcFpdFqq+wqF2OwNBmqFuu6tOyouTuxgpMfzmA==}
+
+ '@open-draft/logger@0.3.0':
+ resolution: {integrity: sha512-X2g45fzhxH238HKO4xbSr7+wBS8Fvw6ixhTDuvLd5mqh6bJJCFAPwU9mPDxbcrRtfxv4u5IHCEH77BmxvXmmxQ==}
+
+ '@open-draft/until@2.1.0':
+ resolution: {integrity: sha512-U69T3ItWHvLwGg5eJ0n3I62nWuE6ilHlmz7zM0npLBRvPRd7e6NYmg54vvRtP5mZG7kZqZCFVdsTWo7BPtBujg==}
+
+ '@pkgjs/parseargs@0.11.0':
+ resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==}
+ engines: {node: '>=14'}
+
+ '@polka/url@1.0.0-next.29':
+ resolution: {integrity: sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==}
+
+ '@radix-ui/number@1.1.1':
+ resolution: {integrity: sha512-MkKCwxlXTgz6CFoJx3pCwn07GKp36+aZyu/u2Ln2VrA5DcdyCZkASEDBTd8x5whTQQL5CiYf4prXKLcgQdv29g==}
+
+ '@radix-ui/primitive@1.1.3':
+ resolution: {integrity: sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==}
+
+ '@radix-ui/react-accordion@1.2.12':
+ resolution: {integrity: sha512-T4nygeh9YE9dLRPhAHSeOZi7HBXo+0kYIPJXayZfvWOWA0+n3dESrZbjfDPUABkUNym6Hd+f2IR113To8D2GPA==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+
+ '@radix-ui/react-arrow@1.1.7':
+ resolution: {integrity: sha512-F+M1tLhO+mlQaOWspE8Wstg+z6PwxwRd8oQ8IXceWz92kfAmalTRf0EjrouQeo7QssEPfCn05B4Ihs1K9WQ/7w==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+
+ '@radix-ui/react-collapsible@1.1.12':
+ resolution: {integrity: sha512-Uu+mSh4agx2ib1uIGPP4/CKNULyajb3p92LsVXmH2EHVMTfZWpll88XJ0j4W0z3f8NK1eYl1+Mf/szHPmcHzyA==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+
+ '@radix-ui/react-collection@1.1.7':
+ resolution: {integrity: sha512-Fh9rGN0MoI4ZFUNyfFVNU4y9LUz93u9/0K+yLgA2bwRojxM8JU1DyvvMBabnZPBgMWREAJvU2jjVzq+LrFUglw==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+
+ '@radix-ui/react-compose-refs@1.1.2':
+ resolution: {integrity: sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
+ '@radix-ui/react-context@1.1.2':
+ resolution: {integrity: sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
+ '@radix-ui/react-direction@1.1.1':
+ resolution: {integrity: sha512-1UEWRX6jnOA2y4H5WczZ44gOOjTEmlqv1uNW4GAJEO5+bauCBhv8snY65Iw5/VOS/ghKN9gr2KjnLKxrsvoMVw==}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
+ '@radix-ui/react-dismissable-layer@1.1.11':
+ resolution: {integrity: sha512-Nqcp+t5cTB8BinFkZgXiMJniQH0PsUt2k51FUhbdfeKvc4ACcG2uQniY/8+h1Yv6Kza4Q7lD7PQV0z0oicE0Mg==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+
+ '@radix-ui/react-focus-guards@1.1.3':
+ resolution: {integrity: sha512-0rFg/Rj2Q62NCm62jZw0QX7a3sz6QCQU0LpZdNrJX8byRGaGVTqbrW9jAoIAHyMQqsNpeZ81YgSizOt5WXq0Pw==}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
+ '@radix-ui/react-focus-scope@1.1.7':
+ resolution: {integrity: sha512-t2ODlkXBQyn7jkl6TNaw/MtVEVvIGelJDCG41Okq/KwUsJBwQ4XVZsHAVUkK4mBv3ewiAS3PGuUWuY2BoK4ZUw==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+
+ '@radix-ui/react-id@1.1.1':
+ resolution: {integrity: sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg==}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
+ '@radix-ui/react-popper@1.2.8':
+ resolution: {integrity: sha512-0NJQ4LFFUuWkE7Oxf0htBKS6zLkkjBH+hM1uk7Ng705ReR8m/uelduy1DBo0PyBXPKVnBA6YBlU94MBGXrSBCw==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+
+ '@radix-ui/react-portal@1.1.9':
+ resolution: {integrity: sha512-bpIxvq03if6UNwXZ+HTK71JLh4APvnXntDc6XOX8UVq4XQOVl7lwok0AvIl+b8zgCw3fSaVTZMpAPPagXbKmHQ==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+
+ '@radix-ui/react-presence@1.1.5':
+ resolution: {integrity: sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+
+ '@radix-ui/react-primitive@2.1.3':
+ resolution: {integrity: sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+
+ '@radix-ui/react-select@2.2.6':
+ resolution: {integrity: sha512-I30RydO+bnn2PQztvo25tswPH+wFBjehVGtmagkU78yMdwTwVf12wnAOF+AeP8S2N8xD+5UPbGhkUfPyvT+mwQ==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+
+ '@radix-ui/react-slot@1.2.3':
+ resolution: {integrity: sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
+ '@radix-ui/react-use-callback-ref@1.1.1':
+ resolution: {integrity: sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg==}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
+ '@radix-ui/react-use-controllable-state@1.2.2':
+ resolution: {integrity: sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg==}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
+ '@radix-ui/react-use-effect-event@0.0.2':
+ resolution: {integrity: sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA==}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
+ '@radix-ui/react-use-escape-keydown@1.1.1':
+ resolution: {integrity: sha512-Il0+boE7w/XebUHyBjroE+DbByORGR9KKmITzbR7MyQ4akpORYP/ZmbhAr0DG7RmmBqoOnZdy2QlvajJ2QA59g==}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
+ '@radix-ui/react-use-layout-effect@1.1.1':
+ resolution: {integrity: sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
+ '@radix-ui/react-use-previous@1.1.1':
+ resolution: {integrity: sha512-2dHfToCj/pzca2Ck724OZ5L0EVrr3eHRNsG/b3xQJLA2hZpVCS99bLAX+hm1IHXDEnzU6by5z/5MIY794/a8NQ==}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
+ '@radix-ui/react-use-rect@1.1.1':
+ resolution: {integrity: sha512-QTYuDesS0VtuHNNvMh+CjlKJ4LJickCMUAqjlE3+j8w+RlRpwyX3apEQKGFzbZGdo7XNG1tXa+bQqIE7HIXT2w==}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
+ '@radix-ui/react-use-size@1.1.1':
+ resolution: {integrity: sha512-ewrXRDTAqAXlkl6t/fkXWNAhFX9I+CkKlw6zjEwk86RSPKwZr3xpBRso655aqYafwtnbpHLj6toFzmd6xdVptQ==}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
+ '@radix-ui/react-visually-hidden@1.2.3':
+ resolution: {integrity: sha512-pzJq12tEaaIhqjbzpCuv/OypJY/BPavOofm+dbab+MHLajy277+1lLm6JFcGgF5eskJ6mquGirhXY2GD/8u8Ug==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+
+ '@radix-ui/rect@1.1.1':
+ resolution: {integrity: sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==}
+
+ '@react-router/dev@7.2.0':
+ resolution: {integrity: sha512-GzSNGeWuhx6sMsnidCQAlCAephibUMC61xIAdsc6hBXWCJe/T9wUrvtnh2Xbcpr7BRZJtJN4UhI472ZURA6m9w==}
+ engines: {node: '>=20.0.0'}
+ hasBin: true
+ peerDependencies:
+ '@react-router/serve': ^7.2.0
+ react-router: ^7.2.0
+ typescript: ^5.1.0
+ vite: ^5.1.0 || ^6.0.0
+ wrangler: ^3.28.2
+ peerDependenciesMeta:
+ '@react-router/serve':
+ optional: true
+ typescript:
+ optional: true
+ wrangler:
+ optional: true
+
+ '@react-router/node@7.2.0':
+ resolution: {integrity: sha512-CqBHLwvvV4BB8htmaSwT+SOwX9B4RVOIiEdTlaIp12sNVCGSYDIEGbv3T4Wxeq8p5ynNfhNcdBeXtZ6ZPWVozA==}
+ engines: {node: '>=20.0.0'}
+ peerDependencies:
+ react-router: 7.2.0
+ typescript: ^5.1.0
+ peerDependenciesMeta:
+ typescript:
+ optional: true
+
+ '@remix-run/router@1.23.0':
+ resolution: {integrity: sha512-O3rHJzAQKamUz1fvE0Qaw0xSFqsA/yafi2iqeE0pvdFtCO1viYx8QL6f3Ln/aCCTLxs68SLf0KPM9eSeM8yBnA==}
+ engines: {node: '>=14.0.0'}
+
+ '@remix-run/server-runtime@2.17.1':
+ resolution: {integrity: sha512-d1Vp9FxX4KafB111vP2E5C1fmWzPI+gHZ674L1drq+N8Bp9U6FBspi7GAZSU5K5Kxa4T6UF+aE1gK6pVi9R8sw==}
+ engines: {node: '>=18.0.0'}
+ peerDependencies:
+ typescript: ^5.1.0
+ peerDependenciesMeta:
+ typescript:
+ optional: true
+
+ '@rollup/rollup-android-arm-eabi@4.52.4':
+ resolution: {integrity: sha512-BTm2qKNnWIQ5auf4deoetINJm2JzvihvGb9R6K/ETwKLql/Bb3Eg2H1FBp1gUb4YGbydMA3jcmQTR73q7J+GAA==}
+ cpu: [arm]
+ os: [android]
+
+ '@rollup/rollup-android-arm64@4.52.4':
+ resolution: {integrity: sha512-P9LDQiC5vpgGFgz7GSM6dKPCiqR3XYN1WwJKA4/BUVDjHpYsf3iBEmVz62uyq20NGYbiGPR5cNHI7T1HqxNs2w==}
+ cpu: [arm64]
+ os: [android]
+
+ '@rollup/rollup-darwin-arm64@4.52.4':
+ resolution: {integrity: sha512-QRWSW+bVccAvZF6cbNZBJwAehmvG9NwfWHwMy4GbWi/BQIA/laTIktebT2ipVjNncqE6GLPxOok5hsECgAxGZg==}
+ cpu: [arm64]
+ os: [darwin]
+
+ '@rollup/rollup-darwin-x64@4.52.4':
+ resolution: {integrity: sha512-hZgP05pResAkRJxL1b+7yxCnXPGsXU0fG9Yfd6dUaoGk+FhdPKCJ5L1Sumyxn8kvw8Qi5PvQ8ulenUbRjzeCTw==}
+ cpu: [x64]
+ os: [darwin]
+
+ '@rollup/rollup-freebsd-arm64@4.52.4':
+ resolution: {integrity: sha512-xmc30VshuBNUd58Xk4TKAEcRZHaXlV+tCxIXELiE9sQuK3kG8ZFgSPi57UBJt8/ogfhAF5Oz4ZSUBN77weM+mQ==}
+ cpu: [arm64]
+ os: [freebsd]
+
+ '@rollup/rollup-freebsd-x64@4.52.4':
+ resolution: {integrity: sha512-WdSLpZFjOEqNZGmHflxyifolwAiZmDQzuOzIq9L27ButpCVpD7KzTRtEG1I0wMPFyiyUdOO+4t8GvrnBLQSwpw==}
+ cpu: [x64]
+ os: [freebsd]
+
+ '@rollup/rollup-linux-arm-gnueabihf@4.52.4':
+ resolution: {integrity: sha512-xRiOu9Of1FZ4SxVbB0iEDXc4ddIcjCv2aj03dmW8UrZIW7aIQ9jVJdLBIhxBI+MaTnGAKyvMwPwQnoOEvP7FgQ==}
+ cpu: [arm]
+ os: [linux]
+
+ '@rollup/rollup-linux-arm-musleabihf@4.52.4':
+ resolution: {integrity: sha512-FbhM2p9TJAmEIEhIgzR4soUcsW49e9veAQCziwbR+XWB2zqJ12b4i/+hel9yLiD8pLncDH4fKIPIbt5238341Q==}
+ cpu: [arm]
+ os: [linux]
+
+ '@rollup/rollup-linux-arm64-gnu@4.18.1':
+ resolution: {integrity: sha512-8mwmGD668m8WaGbthrEYZ9CBmPug2QPGWxhJxh/vCgBjro5o96gL04WLlg5BA233OCWLqERy4YUzX3bJGXaJgQ==}
+ cpu: [arm64]
+ os: [linux]
+
+ '@rollup/rollup-linux-arm64-gnu@4.52.4':
+ resolution: {integrity: sha512-4n4gVwhPHR9q/g8lKCyz0yuaD0MvDf7dV4f9tHt0C73Mp8h38UCtSCSE6R9iBlTbXlmA8CjpsZoujhszefqueg==}
+ cpu: [arm64]
+ os: [linux]
+
+ '@rollup/rollup-linux-arm64-musl@4.52.4':
+ resolution: {integrity: sha512-u0n17nGA0nvi/11gcZKsjkLj1QIpAuPFQbR48Subo7SmZJnGxDpspyw2kbpuoQnyK+9pwf3pAoEXerJs/8Mi9g==}
+ cpu: [arm64]
+ os: [linux]
+
+ '@rollup/rollup-linux-loong64-gnu@4.52.4':
+ resolution: {integrity: sha512-0G2c2lpYtbTuXo8KEJkDkClE/+/2AFPdPAbmaHoE870foRFs4pBrDehilMcrSScrN/fB/1HTaWO4bqw+ewBzMQ==}
+ cpu: [loong64]
+ os: [linux]
+
+ '@rollup/rollup-linux-ppc64-gnu@4.52.4':
+ resolution: {integrity: sha512-teSACug1GyZHmPDv14VNbvZFX779UqWTsd7KtTM9JIZRDI5NUwYSIS30kzI8m06gOPB//jtpqlhmraQ68b5X2g==}
+ cpu: [ppc64]
+ os: [linux]
+
+ '@rollup/rollup-linux-riscv64-gnu@4.52.4':
+ resolution: {integrity: sha512-/MOEW3aHjjs1p4Pw1Xk4+3egRevx8Ji9N6HUIA1Ifh8Q+cg9dremvFCUbOX2Zebz80BwJIgCBUemjqhU5XI5Eg==}
+ cpu: [riscv64]
+ os: [linux]
+
+ '@rollup/rollup-linux-riscv64-musl@4.52.4':
+ resolution: {integrity: sha512-1HHmsRyh845QDpEWzOFtMCph5Ts+9+yllCrREuBR/vg2RogAQGGBRC8lDPrPOMnrdOJ+mt1WLMOC2Kao/UwcvA==}
+ cpu: [riscv64]
+ os: [linux]
+
+ '@rollup/rollup-linux-s390x-gnu@4.52.4':
+ resolution: {integrity: sha512-seoeZp4L/6D1MUyjWkOMRU6/iLmCU2EjbMTyAG4oIOs1/I82Y5lTeaxW0KBfkUdHAWN7j25bpkt0rjnOgAcQcA==}
+ cpu: [s390x]
+ os: [linux]
+
+ '@rollup/rollup-linux-x64-gnu@4.52.4':
+ resolution: {integrity: sha512-Wi6AXf0k0L7E2gteNsNHUs7UMwCIhsCTs6+tqQ5GPwVRWMaflqGec4Sd8n6+FNFDw9vGcReqk2KzBDhCa1DLYg==}
+ cpu: [x64]
+ os: [linux]
+
+ '@rollup/rollup-linux-x64-musl@4.52.4':
+ resolution: {integrity: sha512-dtBZYjDmCQ9hW+WgEkaffvRRCKm767wWhxsFW3Lw86VXz/uJRuD438/XvbZT//B96Vs8oTA8Q4A0AfHbrxP9zw==}
+ cpu: [x64]
+ os: [linux]
+
+ '@rollup/rollup-openharmony-arm64@4.52.4':
+ resolution: {integrity: sha512-1ox+GqgRWqaB1RnyZXL8PD6E5f7YyRUJYnCqKpNzxzP0TkaUh112NDrR9Tt+C8rJ4x5G9Mk8PQR3o7Ku2RKqKA==}
+ cpu: [arm64]
+ os: [openharmony]
+
+ '@rollup/rollup-win32-arm64-msvc@4.18.1':
+ resolution: {integrity: sha512-W2ZNI323O/8pJdBGil1oCauuCzmVd9lDmWBBqxYZcOqWD6aWqJtVBQ1dFrF4dYpZPks6F+xCZHfzG5hYlSHZ6g==}
+ cpu: [arm64]
+ os: [win32]
+
+ '@rollup/rollup-win32-arm64-msvc@4.52.4':
+ resolution: {integrity: sha512-8GKr640PdFNXwzIE0IrkMWUNUomILLkfeHjXBi/nUvFlpZP+FA8BKGKpacjW6OUUHaNI6sUURxR2U2g78FOHWQ==}
+ cpu: [arm64]
+ os: [win32]
+
+ '@rollup/rollup-win32-ia32-msvc@4.52.4':
+ resolution: {integrity: sha512-AIy/jdJ7WtJ/F6EcfOb2GjR9UweO0n43jNObQMb6oGxkYTfLcnN7vYYpG+CN3lLxrQkzWnMOoNSHTW54pgbVxw==}
+ cpu: [ia32]
+ os: [win32]
+
+ '@rollup/rollup-win32-x64-gnu@4.52.4':
+ resolution: {integrity: sha512-UF9KfsH9yEam0UjTwAgdK0anlQ7c8/pWPU2yVjyWcF1I1thABt6WXE47cI71pGiZ8wGvxohBoLnxM04L/wj8mQ==}
+ cpu: [x64]
+ os: [win32]
+
+ '@rollup/rollup-win32-x64-msvc@4.18.1':
+ resolution: {integrity: sha512-yjk2MAkQmoaPYCSu35RLJ62+dz358nE83VfTePJRp8CG7aMg25mEJYpXFiD+NcevhX8LxD5OP5tktPXnXN7GDw==}
+ cpu: [x64]
+ os: [win32]
+
+ '@rollup/rollup-win32-x64-msvc@4.52.4':
+ resolution: {integrity: sha512-bf9PtUa0u8IXDVxzRToFQKsNCRz9qLYfR/MpECxl4mRoWYjAeFjgxj1XdZr2M/GNVpT05p+LgQOHopYDlUu6/w==}
+ cpu: [x64]
+ os: [win32]
+
+ '@snyk/github-codeowners@1.1.0':
+ resolution: {integrity: sha512-lGFf08pbkEac0NYgVf4hdANpAgApRjNByLXB+WBip3qj1iendOIyAwP2GKkKbQMNVy2r1xxDf0ssfWscoiC+Vw==}
+ engines: {node: '>=8.10'}
+ hasBin: true
+
+ '@standard-schema/spec@1.0.0':
+ resolution: {integrity: sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==}
+
+ '@tailwindcss/node@4.0.9':
+ resolution: {integrity: sha512-tOJvdI7XfJbARYhxX+0RArAhmuDcczTC46DGCEziqxzzbIaPnfYaIyRT31n4u8lROrsO7Q6u/K9bmQHL2uL1bQ==}
+
+ '@tailwindcss/oxide-android-arm64@4.0.9':
+ resolution: {integrity: sha512-YBgy6+2flE/8dbtrdotVInhMVIxnHJPbAwa7U1gX4l2ThUIaPUp18LjB9wEH8wAGMBZUb//SzLtdXXNBHPUl6Q==}
+ engines: {node: '>= 10'}
+ cpu: [arm64]
+ os: [android]
+
+ '@tailwindcss/oxide-darwin-arm64@4.0.9':
+ resolution: {integrity: sha512-pWdl4J2dIHXALgy2jVkwKBmtEb73kqIfMpYmcgESr7oPQ+lbcQ4+tlPeVXaSAmang+vglAfFpXQCOvs/aGSqlw==}
+ engines: {node: '>= 10'}
+ cpu: [arm64]
+ os: [darwin]
+
+ '@tailwindcss/oxide-darwin-x64@4.0.9':
+ resolution: {integrity: sha512-4Dq3lKp0/C7vrRSkNPtBGVebEyWt9QPPlQctxJ0H3MDyiQYvzVYf8jKow7h5QkWNe8hbatEqljMj/Y0M+ERYJg==}
+ engines: {node: '>= 10'}
+ cpu: [x64]
+ os: [darwin]
+
+ '@tailwindcss/oxide-freebsd-x64@4.0.9':
+ resolution: {integrity: sha512-k7U1RwRODta8x0uealtVt3RoWAWqA+D5FAOsvVGpYoI6ObgmnzqWW6pnVwz70tL8UZ/QXjeMyiICXyjzB6OGtQ==}
+ engines: {node: '>= 10'}
+ cpu: [x64]
+ os: [freebsd]
+
+ '@tailwindcss/oxide-linux-arm-gnueabihf@4.0.9':
+ resolution: {integrity: sha512-NDDjVweHz2zo4j+oS8y3KwKL5wGCZoXGA9ruJM982uVJLdsF8/1AeKvUwKRlMBpxHt1EdWJSAh8a0Mfhl28GlQ==}
+ engines: {node: '>= 10'}
+ cpu: [arm]
+ os: [linux]
+
+ '@tailwindcss/oxide-linux-arm64-gnu@4.0.9':
+ resolution: {integrity: sha512-jk90UZ0jzJl3Dy1BhuFfRZ2KP9wVKMXPjmCtY4U6fF2LvrjP5gWFJj5VHzfzHonJexjrGe1lMzgtjriuZkxagg==}
+ engines: {node: '>= 10'}
+ cpu: [arm64]
+ os: [linux]
+
+ '@tailwindcss/oxide-linux-arm64-musl@4.0.9':
+ resolution: {integrity: sha512-3eMjyTC6HBxh9nRgOHzrc96PYh1/jWOwHZ3Kk0JN0Kl25BJ80Lj9HEvvwVDNTgPg154LdICwuFLuhfgH9DULmg==}
+ engines: {node: '>= 10'}
+ cpu: [arm64]
+ os: [linux]
+
+ '@tailwindcss/oxide-linux-x64-gnu@4.0.9':
+ resolution: {integrity: sha512-v0D8WqI/c3WpWH1kq/HP0J899ATLdGZmENa2/emmNjubT0sWtEke9W9+wXeEoACuGAhF9i3PO5MeyditpDCiWQ==}
+ engines: {node: '>= 10'}
+ cpu: [x64]
+ os: [linux]
+
+ '@tailwindcss/oxide-linux-x64-musl@4.0.9':
+ resolution: {integrity: sha512-Kvp0TCkfeXyeehqLJr7otsc4hd/BUPfcIGrQiwsTVCfaMfjQZCG7DjI+9/QqPZha8YapLA9UoIcUILRYO7NE1Q==}
+ engines: {node: '>= 10'}
+ cpu: [x64]
+ os: [linux]
+
+ '@tailwindcss/oxide-win32-arm64-msvc@4.0.9':
+ resolution: {integrity: sha512-m3+60T/7YvWekajNq/eexjhV8z10rswcz4BC9bioJ7YaN+7K8W2AmLmG0B79H14m6UHE571qB0XsPus4n0QVgQ==}
+ engines: {node: '>= 10'}
+ cpu: [arm64]
+ os: [win32]
+
+ '@tailwindcss/oxide-win32-x64-msvc@4.0.9':
+ resolution: {integrity: sha512-dpc05mSlqkwVNOUjGu/ZXd5U1XNch1kHFJ4/cHkZFvaW1RzbHmRt24gvM8/HC6IirMxNarzVw4IXVtvrOoZtxA==}
+ engines: {node: '>= 10'}
+ cpu: [x64]
+ os: [win32]
+
+ '@tailwindcss/oxide@4.0.9':
+ resolution: {integrity: sha512-eLizHmXFqHswJONwfqi/WZjtmWZpIalpvMlNhTM99/bkHtUs6IqgI1XQ0/W5eO2HiRQcIlXUogI2ycvKhVLNcA==}
+ engines: {node: '>= 10'}
+
+ '@tailwindcss/typography@0.5.16':
+ resolution: {integrity: sha512-0wDLwCVF5V3x3b1SGXPCDcdsbDHMBe+lkFzBRaHeLvNi+nrrnZ1lA18u+OTWO8iSWU2GxUOCvlXtDuqftc1oiA==}
+ peerDependencies:
+ tailwindcss: '>=3.0.0 || insiders || >=4.0.0-alpha.20 || >=4.0.0-beta.1'
+
+ '@tailwindcss/vite@4.0.9':
+ resolution: {integrity: sha512-BIKJO+hwdIsN7V6I7SziMZIVHWWMsV/uCQKYEbeiGRDRld+TkqyRRl9+dQ0MCXbhcVr+D9T/qX2E84kT7V281g==}
+ peerDependencies:
+ vite: ^5.2.0 || ^6
+
+ '@testing-library/dom@10.4.1':
+ resolution: {integrity: sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==}
+ engines: {node: '>=18'}
+
+ '@testing-library/react@16.2.0':
+ resolution: {integrity: sha512-2cSskAvA1QNtKc8Y9VJQRv0tm3hLVgxRGDB+KYhIaPQJ1I+RHbhIXcM+zClKXzMes/wshsMVzf4B9vS4IZpqDQ==}
+ engines: {node: '>=18'}
+ peerDependencies:
+ '@testing-library/dom': ^10.0.0
+ '@types/react': ^18.0.0 || ^19.0.0
+ '@types/react-dom': ^18.0.0 || ^19.0.0
+ react: ^18.0.0 || ^19.0.0
+ react-dom: ^18.0.0 || ^19.0.0
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+
+ '@testing-library/user-event@14.6.1':
+ resolution: {integrity: sha512-vq7fv0rnt+QTXgPxr5Hjc210p6YKq2kmdziLgnsZGgLJ9e6VAShx1pACLuRjd/AS/sr7phAR58OIIpf0LlmQNw==}
+ engines: {node: '>=12', npm: '>=6'}
+ peerDependencies:
+ '@testing-library/dom': '>=7.21.4'
+
+ '@types/aria-query@5.0.4':
+ resolution: {integrity: sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==}
+
+ '@types/chai@5.2.2':
+ resolution: {integrity: sha512-8kB30R7Hwqf40JPiKhVzodJs2Qc1ZJ5zuT3uzw5Hq/dhNCl3G3l83jfpdI1e20BP348+fV7VIL/+FxaXkqBmWg==}
+
+ '@types/cookie@0.6.0':
+ resolution: {integrity: sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==}
+
+ '@types/d3-hierarchy@1.1.11':
+ resolution: {integrity: sha512-lnQiU7jV+Gyk9oQYk0GGYccuexmQPTp08E0+4BidgFdiJivjEvf+esPSdZqCZ2C7UwTWejWpqetVaU8A+eX3FA==}
+
+ '@types/debug@4.1.12':
+ resolution: {integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==}
+
+ '@types/deep-eql@4.0.2':
+ resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==}
+
+ '@types/estree-jsx@1.0.5':
+ resolution: {integrity: sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==}
+
+ '@types/estree@1.0.8':
+ resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==}
+
+ '@types/hast@3.0.4':
+ resolution: {integrity: sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==}
+
+ '@types/mdast@4.0.4':
+ resolution: {integrity: sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==}
+
+ '@types/mdx@2.0.13':
+ resolution: {integrity: sha512-+OWZQfAYyio6YkJb3HLxDrvnx6SWWDbC0zVPfBRzUk0/nqoDyf6dNxQi3eArPe8rJ473nobTMQ/8Zk+LxJ+Yuw==}
+
+ '@types/ms@2.1.0':
+ resolution: {integrity: sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==}
+
+ '@types/node@22.13.1':
+ resolution: {integrity: sha512-jK8uzQlrvXqEU91UxiK5J7pKHyzgnI1Qnl0QDHIgVGuolJhRb9EEl28Cj9b3rGR8B2lhFCtvIm5os8lFnO/1Ew==}
+
+ '@types/parse-json@4.0.2':
+ resolution: {integrity: sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==}
+
+ '@types/prompt@1.1.9':
+ resolution: {integrity: sha512-Yl0hzOWGDNCo2JHA8s7g7e9higzonlP/MssTMAIAwYmA7GugrB5WZ1DmrfrpJyJY8Y5UHFr8uaXDlxiyHt2jbg==}
+
+ '@types/react-dom@19.0.3':
+ resolution: {integrity: sha512-0Knk+HJiMP/qOZgMyNFamlIjw9OFCsyC2ZbigmEEyXXixgre6IQpm/4V+r3qH4GC1JPvRJKInw+on2rV6YZLeA==}
+ peerDependencies:
+ '@types/react': ^19.0.0
+
+ '@types/react-reconciler@0.28.9':
+ resolution: {integrity: sha512-HHM3nxyUZ3zAylX8ZEyrDNd2XZOnQ0D5XfunJF5FLQnZbHHYq4UWvW1QfelQNXv1ICNkwYhfxjwfnqivYB6bFg==}
+ peerDependencies:
+ '@types/react': '*'
+
+ '@types/react@19.0.8':
+ resolution: {integrity: sha512-9P/o1IGdfmQxrujGbIMDyYaaCykhLKc0NGCtYcECNUr9UAaDe4gwvV9bR6tvd5Br1SG0j+PBpbKr2UYY8CwqSw==}
+
+ '@types/resolve@1.20.6':
+ resolution: {integrity: sha512-A4STmOXPhMUtHH+S6ymgE2GiBSMqf4oTvcQZMcHzokuTLVYzXTB8ttjcgxOVaAp2lGwEdzZ0J+cRbbeevQj1UQ==}
+
+ '@types/revalidator@0.3.12':
+ resolution: {integrity: sha512-DsA2jHfz73JaIROVoMDd/x7nVWXBmEdDSoXB4yQlDzv/NCBkFY2fMHkyE6DGrvooLDAFe5QI6l9Wq0TgdopMtg==}
+
+ '@types/semver@7.7.0':
+ resolution: {integrity: sha512-k107IF4+Xr7UHjwDc7Cfd6PRQfbdkiRabXGRjo07b4WyPahFBZCZ1sE+BNxYIJPPg73UkfOsVOLwqVc/6ETrIA==}
+
+ '@types/slug@5.0.9':
+ resolution: {integrity: sha512-6Yp8BSplP35Esa/wOG1wLNKiqXevpQTEF/RcL/NV6BBQaMmZh4YlDwCgrrFSoUE4xAGvnKd5c+lkQJmPrBAzfQ==}
+
+ '@types/statuses@2.0.6':
+ resolution: {integrity: sha512-xMAgYwceFhRA2zY+XbEA7mxYbA093wdiW8Vu6gZPGWy9cmOyU9XesH1tNcEWsKFd5Vzrqx5T3D38PWx1FIIXkA==}
+
+ '@types/text-table@0.2.5':
+ resolution: {integrity: sha512-hcZhlNvMkQG/k1vcZ6yHOl6WAYftQ2MLfTHcYRZ2xYZFD8tGVnE3qFV0lj1smQeDSR7/yY0PyuUalauf33bJeA==}
+
+ '@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.0':
+ resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==}
+
+ '@vitest/browser@3.2.4':
+ resolution: {integrity: sha512-tJxiPrWmzH8a+w9nLKlQMzAKX/7VjFs50MWgcAj7p9XQ7AQ9/35fByFYptgPELyLw+0aixTnC4pUWV+APcZ/kw==}
+ peerDependencies:
+ playwright: '*'
+ safaridriver: '*'
+ vitest: 3.2.4
+ webdriverio: ^7.0.0 || ^8.0.0 || ^9.0.0
+ peerDependenciesMeta:
+ playwright:
+ optional: true
+ safaridriver:
+ optional: true
+ webdriverio:
+ optional: true
+
+ '@vitest/coverage-v8@3.2.4':
+ resolution: {integrity: sha512-EyF9SXU6kS5Ku/U82E259WSnvg6c8KTjppUncuNdm5QHpe17mwREHnjDzozC8x9MZ0xfBUFSaLkRv4TMA75ALQ==}
+ peerDependencies:
+ '@vitest/browser': 3.2.4
+ vitest: 3.2.4
+ peerDependenciesMeta:
+ '@vitest/browser':
+ optional: true
+
+ '@vitest/expect@3.2.4':
+ resolution: {integrity: sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==}
+
+ '@vitest/mocker@3.2.4':
+ resolution: {integrity: sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ==}
+ peerDependencies:
+ msw: ^2.4.9
+ vite: ^5.0.0 || ^6.0.0 || ^7.0.0-0
+ peerDependenciesMeta:
+ msw:
+ optional: true
+ vite:
+ optional: true
+
+ '@vitest/pretty-format@3.2.4':
+ resolution: {integrity: sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA==}
+
+ '@vitest/runner@3.2.4':
+ resolution: {integrity: sha512-oukfKT9Mk41LreEW09vt45f8wx7DordoWUZMYdY/cyAk7w5TWkTRCNZYF7sX7n2wB7jyGAl74OxgwhPgKaqDMQ==}
+
+ '@vitest/snapshot@3.2.4':
+ resolution: {integrity: sha512-dEYtS7qQP2CjU27QBC5oUOxLE/v5eLkGqPE0ZKEIDGMs4vKWe7IjgLOeauHsR0D5YuuycGRO5oSRXnwnmA78fQ==}
+
+ '@vitest/spy@3.2.4':
+ resolution: {integrity: sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw==}
+
+ '@vitest/ui@3.2.4':
+ resolution: {integrity: sha512-hGISOaP18plkzbWEcP/QvtRW1xDXF2+96HbEX6byqQhAUbiS5oH6/9JwW+QsQCIYON2bI6QZBF+2PvOmrRZ9wA==}
+ peerDependencies:
+ vitest: 3.2.4
+
+ '@vitest/utils@3.2.4':
+ resolution: {integrity: sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==}
+
+ '@web3-storage/multipart-parser@1.0.0':
+ resolution: {integrity: sha512-BEO6al7BYqcnfX15W2cnGR+Q566ACXAT9UQykORCWW80lmkpWsnEob6zJS1ZVBKsSJC8+7vJkHwlp+lXG1UCdw==}
+
+ abbrev@2.0.0:
+ resolution: {integrity: sha512-6/mh1E2u2YgEsCHdY0Yx5oW+61gZU+1vXaoiHHrpKeuRNNgFvS+/jrwHiQhB5apAf5oB7UB7E19ol2R2LKH8hQ==}
+ engines: {node: ^14.17.0 || ^16.13.0 || >=18.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.15.0:
+ resolution: {integrity: sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==}
+ engines: {node: '>=0.4.0'}
+ hasBin: true
+
+ aggregate-error@3.1.0:
+ resolution: {integrity: sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==}
+ engines: {node: '>=8'}
+
+ ansi-regex@5.0.1:
+ resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==}
+ engines: {node: '>=8'}
+
+ ansi-regex@6.2.2:
+ resolution: {integrity: sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==}
+ engines: {node: '>=12'}
+
+ ansi-styles@4.3.0:
+ resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==}
+ engines: {node: '>=8'}
+
+ ansi-styles@5.2.0:
+ resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==}
+ engines: {node: '>=10'}
+
+ ansi-styles@6.2.3:
+ resolution: {integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==}
+ engines: {node: '>=12'}
+
+ arg@5.0.2:
+ resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==}
+
+ argparse@1.0.10:
+ resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==}
+
+ argparse@2.0.1:
+ resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==}
+
+ aria-hidden@1.2.6:
+ resolution: {integrity: sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==}
+ engines: {node: '>=10'}
+
+ aria-query@5.3.0:
+ resolution: {integrity: sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==}
+
+ assertion-error@2.0.1:
+ resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==}
+ engines: {node: '>=12'}
+
+ ast-v8-to-istanbul@0.3.7:
+ resolution: {integrity: sha512-kr1Hy6YRZBkGQSb6puP+D6FQ59Cx4m0siYhAxygMCAgadiWQ6oxAxQXHOMvJx67SJ63jRoVIIg5eXzUbbct1ww==}
+
+ astring@1.9.0:
+ resolution: {integrity: sha512-LElXdjswlqjWrPpJFg1Fx4wpkOCxj1TDHlSV4PlaRxHGWko024xICaa97ZkMfs6DRKlCguiAI+rbXv5GWwXIkg==}
+ hasBin: true
+
+ async@2.6.4:
+ resolution: {integrity: sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==}
+
+ async@3.2.3:
+ resolution: {integrity: sha512-spZRyzKL5l5BZQrr/6m/SqFdBN0q3OCI0f9rjfBzCMBIP4p75P620rR3gTmaksNOhmzgdxcaxdNfMy6anrbM0g==}
+
+ babel-dead-code-elimination@1.0.10:
+ resolution: {integrity: sha512-DV5bdJZTzZ0zn0DC24v3jD7Mnidh6xhKa4GfKCbq3sfW8kaWhDdZjP3i81geA8T33tdYqWKw4D3fVv0CwEgKVA==}
+
+ babel-plugin-macros@3.1.0:
+ resolution: {integrity: sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==}
+ engines: {node: '>=10', npm: '>=6'}
+
+ babel-plugin-react-compiler@19.0.0-beta-df7b47d-20241124:
+ resolution: {integrity: sha512-93iSASR20HNsotcOTQ+KPL0zpgfRFVWL86AtXpmHp995HuMVnC9femd8Winr3GxkPEh8lEOyaw3nqY4q2HUm5w==}
+
+ bail@2.0.2:
+ resolution: {integrity: sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==}
+
+ balanced-match@1.0.2:
+ resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
+
+ baseline-browser-mapping@2.8.12:
+ resolution: {integrity: sha512-vAPMQdnyKCBtkmQA6FMCBvU9qFIppS3nzyXnEM+Lo2IAhG4Mpjv9cCxMudhgV3YdNNJv6TNqXy97dfRVL2LmaQ==}
+ hasBin: true
+
+ beautify@0.0.8:
+ resolution: {integrity: sha512-1iF6Ey2qxDkm6bPgKcoXUmwFDpoRi5IgwefQDDQBRLxlZAAYwcULoQ2IdBArXZuSsuL7AT+KvZI9xZVLeUZPRg==}
+ hasBin: true
+
+ bippy@0.3.27:
+ resolution: {integrity: sha512-0k9M+yXcgUDpMgSl+7QjYRqUk8Ud4Z18uL7m34AKkkXy7Fi79Yl8q2pdyDGOyb0uhmRzHgJUOsAIYW1qvlnGaA==}
+ peerDependencies:
+ react: '>=17.0.1'
+
+ boolbase@1.0.0:
+ resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==}
+
+ brace-expansion@2.0.2:
+ resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==}
+
+ braces@3.0.3:
+ resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==}
+ engines: {node: '>=8'}
+
+ browserify-zlib@0.1.4:
+ resolution: {integrity: sha512-19OEpq7vWgsH6WkvkBJQDFvJS1uPcbFOQ4v9CU839dO+ZZXUZO6XpE6hNCqvlIIj+4fZvRiJ6DsAQ382GwiyTQ==}
+
+ browserslist@4.26.3:
+ resolution: {integrity: sha512-lAUU+02RFBuCKQPj/P6NgjlbCnLBMp4UtgTx7vNHd3XSIJF87s9a5rA3aH2yw3GS9DqZAUbOtZdCCiZeVRqt0w==}
+ engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
+ hasBin: true
+
+ buffer-from@1.1.2:
+ resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==}
+
+ cac@6.7.14:
+ resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==}
+ engines: {node: '>=8'}
+
+ callsites@3.1.0:
+ resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==}
+ engines: {node: '>=6'}
+
+ camelcase@8.0.0:
+ resolution: {integrity: sha512-8WB3Jcas3swSvjIeA2yvCJ+Miyz5l1ZmB6HFb9R1317dt9LCQoswg/BGrmAmkWVEszSrrg4RwmO46qIm2OEnSA==}
+ engines: {node: '>=16'}
+
+ caniuse-lite@1.0.30001748:
+ resolution: {integrity: sha512-5P5UgAr0+aBmNiplks08JLw+AW/XG/SurlgZLgB1dDLfAw7EfRGxIwzPHxdSCGY/BTKDqIVyJL87cCN6s0ZR0w==}
+
+ ccount@2.0.1:
+ resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==}
+
+ chai@5.3.3:
+ resolution: {integrity: sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==}
+ engines: {node: '>=18'}
+
+ chain-function@1.0.1:
+ resolution: {integrity: sha512-SxltgMwL9uCko5/ZCLiyG2B7R9fY4pDZUw7hJ4MhirdjBLosoDqkWABi3XMucddHdLiFJMb7PD2MZifZriuMTg==}
+
+ chalk@5.4.1:
+ resolution: {integrity: sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==}
+ engines: {node: ^12.17.0 || ^14.13 || >=16.0.0}
+
+ 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==}
+
+ check-error@2.1.1:
+ resolution: {integrity: sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==}
+ engines: {node: '>= 16'}
+
+ chokidar@4.0.3:
+ resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==}
+ engines: {node: '>= 14.16.0'}
+
+ classnames@2.5.1:
+ resolution: {integrity: sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==}
+
+ clean-stack@2.2.0:
+ resolution: {integrity: sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==}
+ engines: {node: '>=6'}
+
+ cli-width@4.1.0:
+ resolution: {integrity: sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==}
+ engines: {node: '>= 12'}
+
+ cliui@8.0.1:
+ resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==}
+ engines: {node: '>=12'}
+
+ clone@1.0.4:
+ resolution: {integrity: sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==}
+ engines: {node: '>=0.8'}
+
+ clone@2.1.2:
+ resolution: {integrity: sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==}
+ engines: {node: '>=0.8'}
+
+ clsx@2.1.1:
+ resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==}
+ engines: {node: '>=6'}
+
+ collapse-white-space@2.1.0:
+ resolution: {integrity: sha512-loKTxY1zCOuG4j9f6EPnuyyYkf58RnhhWTvRoZEokgB+WbdXehfjFviyOVYkqzEWz1Q5kRiZdBYS5SwxbQYwzw==}
+
+ color-convert@2.0.1:
+ resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==}
+ engines: {node: '>=7.0.0'}
+
+ color-name@1.1.4:
+ resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}
+
+ colors@1.0.3:
+ resolution: {integrity: sha512-pFGrxThWcWQ2MsAz6RtgeWe4NK2kUE1WfsrvvlctdII745EW9I0yflqhe7++M5LEc7bV2c/9/5zc8sFcpL0Drw==}
+ engines: {node: '>=0.1.90'}
+
+ comma-separated-tokens@2.0.3:
+ resolution: {integrity: sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==}
+
+ commander@10.0.1:
+ resolution: {integrity: sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==}
+ engines: {node: '>=14'}
+
+ commander@11.1.0:
+ resolution: {integrity: sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==}
+ engines: {node: '>=16'}
+
+ commander@4.1.1:
+ resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==}
+ engines: {node: '>= 6'}
+
+ concat-stream@1.6.2:
+ resolution: {integrity: sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==}
+ engines: {'0': node >= 0.8}
+
+ config-chain@1.1.13:
+ resolution: {integrity: sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==}
+
+ convert-source-map@1.9.0:
+ resolution: {integrity: sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==}
+
+ convert-source-map@2.0.0:
+ resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==}
+
+ cookie@0.7.2:
+ resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==}
+ engines: {node: '>= 0.6'}
+
+ cookie@1.0.2:
+ resolution: {integrity: sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==}
+ engines: {node: '>=18'}
+
+ core-util-is@1.0.3:
+ resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==}
+
+ cosmiconfig@7.1.0:
+ resolution: {integrity: sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==}
+ engines: {node: '>=10'}
+
+ cross-fetch@4.0.0:
+ resolution: {integrity: sha512-e4a5N8lVvuLgAWgnCrLr2PP0YyDOTHa9H/Rj54dirp61qXnNq46m82bRhNqIA5VccJtWBvPTFRV3TtvHUKPB1g==}
+
+ cross-spawn@7.0.6:
+ resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==}
+ engines: {node: '>= 8'}
+
+ css-select@5.2.2:
+ resolution: {integrity: sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==}
+
+ css-what@6.2.2:
+ resolution: {integrity: sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==}
+ engines: {node: '>= 6'}
+
+ cssbeautify@0.3.1:
+ resolution: {integrity: sha512-ljnSOCOiMbklF+dwPbpooyB78foId02vUrTDogWzu6ca2DCNB7Kc/BHEGBnYOlUYtwXvSW0mWTwaiO2pwFIoRg==}
+ hasBin: true
+
+ cssesc@3.0.0:
+ resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==}
+ engines: {node: '>=4'}
+ hasBin: true
+
+ csstype@3.1.3:
+ resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==}
+
+ cycle@1.0.3:
+ resolution: {integrity: sha512-TVF6svNzeQCOpjCqsy0/CSy8VgObG3wXusJ73xW2GbG5rGx7lC8zxDSURicsXI2UsGdi2L0QNRCi745/wUDvsA==}
+ engines: {node: '>=0.4.0'}
+
+ d3-color@3.1.0:
+ resolution: {integrity: sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==}
+ engines: {node: '>=12'}
+
+ d3-dispatch@3.0.1:
+ resolution: {integrity: sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==}
+ engines: {node: '>=12'}
+
+ d3-drag@3.0.0:
+ resolution: {integrity: sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==}
+ engines: {node: '>=12'}
+
+ d3-ease@3.0.1:
+ resolution: {integrity: sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==}
+ engines: {node: '>=12'}
+
+ d3-hierarchy@1.1.9:
+ resolution: {integrity: sha512-j8tPxlqh1srJHAtxfvOUwKNYJkQuBFdM1+JAUfq6xqH5eAqf93L7oG1NVqDa4CpFZNvnNKtCYEUC8KY9yEn9lQ==}
+
+ d3-interpolate@3.0.1:
+ resolution: {integrity: sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==}
+ engines: {node: '>=12'}
+
+ d3-path@1.0.9:
+ resolution: {integrity: sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg==}
+
+ d3-selection@3.0.0:
+ resolution: {integrity: sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==}
+ engines: {node: '>=12'}
+
+ d3-shape@1.3.7:
+ resolution: {integrity: sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw==}
+
+ d3-timer@3.0.1:
+ resolution: {integrity: sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==}
+ engines: {node: '>=12'}
+
+ d3-transition@3.0.1:
+ resolution: {integrity: sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==}
+ engines: {node: '>=12'}
+ peerDependencies:
+ d3-selection: 2 - 3
+
+ d3-zoom@3.0.0:
+ resolution: {integrity: sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==}
+ engines: {node: '>=12'}
+
+ date-fns@4.1.0:
+ resolution: {integrity: sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==}
+
+ 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.2.0:
+ resolution: {integrity: sha512-c6fcElNV6ShtZXmsgNgFFV5tVX2PaV4g+MOAkb8eXHvn6sryJBrZa9r0zV6+dtTyoCKxtDy5tyQ5ZwQuidtd+Q==}
+
+ dedent@1.7.0:
+ resolution: {integrity: sha512-HGFtf8yhuhGhqO07SV79tRp+br4MnbdjeVxotpn1QBl30pcLLCQjX5b2295ll0fv8RKDKsmWYrl05usHM9CewQ==}
+ peerDependencies:
+ babel-plugin-macros: ^3.1.0
+ peerDependenciesMeta:
+ babel-plugin-macros:
+ optional: true
+
+ deep-eql@5.0.2:
+ resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==}
+ engines: {node: '>=6'}
+
+ defaults@1.0.4:
+ resolution: {integrity: sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==}
+
+ defu@6.1.4:
+ resolution: {integrity: sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==}
+
+ dequal@2.0.3:
+ resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==}
+ engines: {node: '>=6'}
+
+ detect-libc@2.1.2:
+ resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==}
+ engines: {node: '>=8'}
+
+ detect-node-es@1.1.0:
+ resolution: {integrity: sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==}
+
+ devlop@1.1.0:
+ resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==}
+
+ diff@5.2.0:
+ resolution: {integrity: sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==}
+ engines: {node: '>=0.3.1'}
+
+ dom-accessibility-api@0.5.16:
+ resolution: {integrity: sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==}
+
+ dom-helpers@3.4.0:
+ resolution: {integrity: sha512-LnuPJ+dwqKDIyotW1VzmOZ5TONUN7CwkCR5hrgawTUbkBGYdeoNLZo6nNfGkCrjtE1nXXaj7iMMpDa8/d9WoIA==}
+
+ 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==}
+
+ dotenv@16.6.1:
+ resolution: {integrity: sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==}
+ engines: {node: '>=12'}
+
+ duplexify@3.7.1:
+ resolution: {integrity: sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g==}
+
+ eastasianwidth@0.2.0:
+ resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==}
+
+ easy-table@1.2.0:
+ resolution: {integrity: sha512-OFzVOv03YpvtcWGe5AayU5G2hgybsg3iqA6drU8UaoZyB9jLGMTrz9+asnLp/E+6qPh88yEI1gvyZFZ41dmgww==}
+
+ eciesjs@0.4.15:
+ resolution: {integrity: sha512-r6kEJXDKecVOCj2nLMuXK/FCPeurW33+3JRpfXVbjLja3XUYFfD9I/JBreH6sUyzcm3G/YQboBjMla6poKeSdA==}
+ engines: {bun: '>=1', deno: '>=2', node: '>=16'}
+
+ editorconfig@1.0.4:
+ resolution: {integrity: sha512-L9Qe08KWTlqYMVvMcTIvMAdl1cDUubzRNYL+WfA4bLDMHe4nemKkpmYzkznE1FwLKu0EEmy6obgQKzMJrg4x9Q==}
+ engines: {node: '>=14'}
+ hasBin: true
+
+ electron-to-chromium@1.5.230:
+ resolution: {integrity: sha512-A6A6Fd3+gMdaed9wX83CvHYJb4UuapPD5X5SLq72VZJzxHSY0/LUweGXRWmQlh2ln7KV7iw7jnwXK7dlPoOnHQ==}
+
+ emoji-regex@10.5.0:
+ resolution: {integrity: sha512-lb49vf1Xzfx080OKA0o6l8DQQpV+6Vg95zyCJX9VB/BqKYlhG7N4wgROUUHRA+ZPUefLnteQOad7z1kT2bV7bg==}
+
+ emoji-regex@8.0.0:
+ resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==}
+
+ emoji-regex@9.2.2:
+ resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==}
+
+ end-of-stream@1.4.5:
+ resolution: {integrity: sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==}
+
+ enhanced-resolve@5.18.3:
+ resolution: {integrity: sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww==}
+ engines: {node: '>=10.13.0'}
+
+ entities@4.5.0:
+ resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==}
+ engines: {node: '>=0.12'}
+
+ err-code@2.0.3:
+ resolution: {integrity: sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==}
+
+ error-ex@1.3.4:
+ resolution: {integrity: sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==}
+
+ es-module-lexer@1.7.0:
+ resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==}
+
+ 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.23.1:
+ resolution: {integrity: sha512-VVNz/9Sa0bs5SELtn3f7qhJCDPCF5oMEl5cO9/SSinpE9hbPVvxbd572HH5AKiP7WD8INO53GgfDDhRjkylHEg==}
+ engines: {node: '>=18'}
+ hasBin: true
+
+ esbuild@0.25.10:
+ resolution: {integrity: sha512-9RiGKvCwaqxO2owP61uQ4BgNborAQskMR6QusfWzQqv7AZOg5oGehdY2pRJMTKuwxd1IDBP4rSbI5lHzU7SMsQ==}
+ engines: {node: '>=18'}
+ hasBin: true
+
+ escalade@3.2.0:
+ resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==}
+ engines: {node: '>=6'}
+
+ escape-string-regexp@4.0.0:
+ resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==}
+ engines: {node: '>=10'}
+
+ escape-string-regexp@5.0.0:
+ resolution: {integrity: sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==}
+ engines: {node: '>=12'}
+
+ esprima@4.0.1:
+ resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==}
+ engines: {node: '>=4'}
+ hasBin: true
+
+ 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-value-to-estree@3.4.0:
+ resolution: {integrity: sha512-Zlp+gxis+gCfK12d3Srl2PdX2ybsEA8ZYy6vQGVQTNNYLEGRQQ56XB64bjemN8kxIKXP1nC9ip4Z+ILy9LGzvQ==}
+
+ estree-util-visit@2.0.0:
+ resolution: {integrity: sha512-m5KgiH85xAhhW8Wta0vShLcUvOsh3LLPI2YVwcbio1l7E09NTLL1EyMZFM1OyWowoH0skScNbhOPl4kcBgzTww==}
+
+ estree-walker@3.0.3:
+ resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==}
+
+ execa@5.1.1:
+ resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==}
+ engines: {node: '>=10'}
+
+ exit-hook@2.2.1:
+ resolution: {integrity: sha512-eNTPlAD67BmP31LDINZ3U7HSF8l57TxOY2PmBJ1shpCvpnxBF93mWCE8YHBnXs8qiUZJc9WDcWIeC3a2HIAMfw==}
+ engines: {node: '>=6'}
+
+ expect-type@1.2.2:
+ resolution: {integrity: sha512-JhFGDVJ7tmDJItKhYgJCGLOWjuK9vPxiXoUFLwLDc99NlmklilbiQJwoctZtt13+xMw91MCk/REan6MWHqDjyA==}
+ engines: {node: '>=12.0.0'}
+
+ extend-shallow@2.0.1:
+ resolution: {integrity: sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==}
+ engines: {node: '>=0.10.0'}
+
+ extend@3.0.2:
+ resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==}
+
+ eyes@0.1.8:
+ resolution: {integrity: sha512-GipyPsXO1anza0AOZdy69Im7hGFCNB7Y/NGjDlZGJ3GJJLtwNSb2vrzYrTYJRrRloVx7pl+bhUaTB8yiccPvFQ==}
+ engines: {node: '> 0.1.90'}
+
+ fast-glob@3.3.3:
+ resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==}
+ engines: {node: '>=8.6.0'}
+
+ fastq@1.19.1:
+ resolution: {integrity: sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==}
+
+ fault@2.0.1:
+ resolution: {integrity: sha512-WtySTkS4OKev5JtpHXnib4Gxiurzh5NCGvWrFaZ34m6JehfTUhKZvn9njTfw48t6JumVQOmrKqpmGcdwxnhqBQ==}
+
+ fdir@6.5.0:
+ resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==}
+ engines: {node: '>=12.0.0'}
+ peerDependencies:
+ picomatch: ^3 || ^4
+ peerDependenciesMeta:
+ picomatch:
+ optional: true
+
+ fflate@0.8.2:
+ resolution: {integrity: sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==}
+
+ fill-range@7.1.1:
+ resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==}
+ engines: {node: '>=8'}
+
+ find-root@1.1.0:
+ resolution: {integrity: sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==}
+
+ flatted@3.3.3:
+ resolution: {integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==}
+
+ foreground-child@3.3.1:
+ resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==}
+ engines: {node: '>=14'}
+
+ format@0.2.2:
+ resolution: {integrity: sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww==}
+ engines: {node: '>=0.4.x'}
+
+ framer-motion@12.23.22:
+ resolution: {integrity: sha512-ZgGvdxXCw55ZYvhoZChTlG6pUuehecgvEAJz0BHoC5pQKW1EC5xf1Mul1ej5+ai+pVY0pylyFfdl45qnM1/GsA==}
+ peerDependencies:
+ '@emotion/is-prop-valid': '*'
+ react: ^18.0.0 || ^19.0.0
+ react-dom: ^18.0.0 || ^19.0.0
+ peerDependenciesMeta:
+ '@emotion/is-prop-valid':
+ optional: true
+ react:
+ optional: true
+ react-dom:
+ optional: true
+
+ fs-extra@10.1.0:
+ resolution: {integrity: sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==}
+ engines: {node: '>=12'}
+
+ fsevents@2.3.2:
+ resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==}
+ engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
+ os: [darwin]
+
+ fsevents@2.3.3:
+ resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
+ engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
+ os: [darwin]
+
+ function-bind@1.1.2:
+ resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==}
+
+ gensync@1.0.0-beta.2:
+ resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==}
+ engines: {node: '>=6.9.0'}
+
+ get-caller-file@2.0.5:
+ resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==}
+ engines: {node: 6.* || 8.* || >= 10.*}
+
+ get-nonce@1.0.1:
+ resolution: {integrity: sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==}
+ engines: {node: '>=6'}
+
+ get-stream@6.0.1:
+ resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==}
+ engines: {node: '>=10'}
+
+ get-tsconfig@4.10.1:
+ resolution: {integrity: sha512-auHyJ4AgMz7vgS8Hp3N6HXSmlMdUyhSUrfBF16w153rxtLIEOE+HGqaBppczZvnHLqQJfiHotCYpNhl0lUROFQ==}
+
+ github-slugger@2.0.0:
+ resolution: {integrity: sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw==}
+
+ glob-parent@5.1.2:
+ resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==}
+ engines: {node: '>= 6'}
+
+ glob@10.4.5:
+ resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==}
+ hasBin: true
+
+ glob@11.0.3:
+ resolution: {integrity: sha512-2Nim7dha1KVkaiF4q6Dj+ngPPMdfvLJEOpZk/jKiUAkqKebpGAWQXAq9z1xu9HKu5lWfqw/FASuccEjyznjPaA==}
+ engines: {node: 20 || >=22}
+ hasBin: true
+
+ globrex@0.1.2:
+ resolution: {integrity: sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==}
+
+ graceful-fs@4.2.11:
+ resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==}
+
+ graphql@16.11.0:
+ resolution: {integrity: sha512-mS1lbMsxgQj6hge1XZ6p7GPhbrtFwUFYi3wRzXAC/FmYnyXMTvvI3td3rjmQ2u8ewXueaSvRPWaEcgVVOT9Jnw==}
+ engines: {node: ^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0}
+
+ gray-matter@4.0.3:
+ resolution: {integrity: sha512-5v6yZd4JK3eMI3FqqCouswVqwugaA9r4dNZB1wwcmrD02QkV5H0y7XBQW8QwQqEaZY1pM9aqORSORhJRdNK44Q==}
+ engines: {node: '>=6.0'}
+
+ gunzip-maybe@1.4.2:
+ resolution: {integrity: sha512-4haO1M4mLO91PW57BMsDFf75UmwoRX0GkdD+Faw+Lr+r/OZrOCS0pIBwOL1xCKQqnQzbNFGgK2V2CpBUPeFNTw==}
+ hasBin: true
+
+ happy-dom@16.8.1:
+ resolution: {integrity: sha512-n0QrmT9lD81rbpKsyhnlz3DgnMZlaOkJPpgi746doA+HvaMC79bdWkwjrNnGJRvDrWTI8iOcJiVTJ5CdT/AZRw==}
+ engines: {node: '>=18.0.0'}
+
+ has-flag@4.0.0:
+ resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==}
+ engines: {node: '>=8'}
+
+ hasown@2.0.2:
+ resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==}
+ engines: {node: '>= 0.4'}
+
+ hast-util-heading-rank@3.0.0:
+ resolution: {integrity: sha512-EJKb8oMUXVHcWZTDepnr+WNbfnXKFNf9duMesmr4S8SXTJBJ9M4Yok08pu9vxdJwdlGRhVumk9mEhkEvKGifwA==}
+
+ hast-util-to-estree@3.1.3:
+ resolution: {integrity: sha512-48+B/rJWAp0jamNbAAf9M7Uf//UVqAoMmgXhBdxTDJLGKY+LRnZ99qcG+Qjl5HfMpYNzS5v4EAwVEF34LeAj7w==}
+
+ hast-util-to-jsx-runtime@2.3.6:
+ resolution: {integrity: sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg==}
+
+ hast-util-to-string@3.0.1:
+ resolution: {integrity: sha512-XelQVTDWvqcl3axRfI0xSeoVKzyIFPwsAGSLIsKdJKQMXDYJS4WYrBNF/8J7RdhIcFI2BOHgAifggsvsxp/3+A==}
+
+ hast-util-whitespace@3.0.0:
+ resolution: {integrity: sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==}
+
+ he@1.2.0:
+ resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==}
+ hasBin: true
+
+ headers-polyfill@4.0.3:
+ resolution: {integrity: sha512-IScLbePpkvO846sIwOtOTDjutRMWdXdJmXdMvk6gCBHxFO8d+QKOQedyZSxFTTFYRSmlgSTDtXqqq4pcenBXLQ==}
+
+ hoist-non-react-statics@3.3.2:
+ resolution: {integrity: sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==}
+
+ hono@4.6.20:
+ resolution: {integrity: sha512-5qfNQeaIptMaJKyoJ6N/q4gIq0DBp2FCRaLNuUI3LlJKL4S37DY/rLL1uAxA4wrPB39tJ3s+f7kgI79O4ScSug==}
+ engines: {node: '>=16.9.0'}
+
+ hosted-git-info@6.1.3:
+ resolution: {integrity: sha512-HVJyzUrLIL1c0QmviVh5E8VGyUS7xCFPS6yydaVd1UegW+ibV/CohqTH9MkOLDp5o+rb82DMo77PTuc9F/8GKw==}
+ engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}
+
+ html-escaper@2.0.2:
+ resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==}
+
+ html-parse-stringify@3.0.1:
+ resolution: {integrity: sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg==}
+
+ html@1.0.0:
+ resolution: {integrity: sha512-lw/7YsdKiP3kk5PnR1INY17iJuzdAtJewxr14ozKJWbbR97znovZ0mh+WEMZ8rjc3lgTK+ID/htTjuyGKB52Kw==}
+ hasBin: true
+
+ human-signals@2.1.0:
+ resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==}
+ engines: {node: '>=10.17.0'}
+
+ i18next-browser-languagedetector@8.0.2:
+ resolution: {integrity: sha512-shBvPmnIyZeD2VU5jVGIOWP7u9qNG3Lj7mpaiPFpbJ3LVfHZJvVzKR4v1Cb91wAOFpNw442N+LGPzHOHsten2g==}
+
+ i18next-http-backend@3.0.2:
+ resolution: {integrity: sha512-PdlvPnvIp4E1sYi46Ik4tBYh/v/NbYfFFgTjkwFl0is8A18s7/bx9aXqsrOax9WUbeNS6mD2oix7Z0yGGf6m5g==}
+
+ i18next@24.2.2:
+ resolution: {integrity: sha512-NE6i86lBCKRYZa5TaUDkU5S4HFgLIEJRLr3Whf2psgaxBleQ2LC1YW1Vc+SCgkAW7VEzndT6al6+CzegSUHcTQ==}
+ peerDependencies:
+ typescript: ^5
+ peerDependenciesMeta:
+ typescript:
+ optional: true
+
+ ignore@5.3.2:
+ resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==}
+ engines: {node: '>= 4'}
+
+ import-fresh@3.3.1:
+ resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==}
+ engines: {node: '>=6'}
+
+ indent-string@4.0.0:
+ resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==}
+ engines: {node: '>=8'}
+
+ inherits@2.0.4:
+ resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==}
+
+ ini@1.3.8:
+ resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==}
+
+ inline-style-parser@0.2.4:
+ resolution: {integrity: sha512-0aO8FkhNZlj/ZIbNi7Lxxr12obT7cL1moPfE4tg1LkX7LlLfC6DeX4l2ZEud1ukP9jNQyNnfzQVqwbwmAATY4Q==}
+
+ 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-arrayish@0.2.1:
+ resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==}
+
+ is-core-module@2.16.1:
+ resolution: {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==}
+ engines: {node: '>= 0.4'}
+
+ is-decimal@2.0.1:
+ resolution: {integrity: sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==}
+
+ is-deflate@1.0.0:
+ resolution: {integrity: sha512-YDoFpuZWu1VRXlsnlYMzKyVRITXj7Ej/V9gXQ2/pAe7X1J7M/RNOqaIYi6qUn+B7nGyB9pDXrv02dsB58d2ZAQ==}
+
+ is-extendable@0.1.1:
+ resolution: {integrity: sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==}
+ engines: {node: '>=0.10.0'}
+
+ is-extglob@2.1.1:
+ resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==}
+ engines: {node: '>=0.10.0'}
+
+ is-fullwidth-code-point@3.0.0:
+ resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==}
+ engines: {node: '>=8'}
+
+ is-glob@4.0.3:
+ resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==}
+ engines: {node: '>=0.10.0'}
+
+ is-gzip@1.0.0:
+ resolution: {integrity: sha512-rcfALRIb1YewtnksfRIHGcIY93QnK8BIQ/2c9yDYcG/Y6+vRoJuTWBmmSEbyLLYtXm7q35pHOHbZFQBaLrhlWQ==}
+ engines: {node: '>=0.10.0'}
+
+ is-hexadecimal@2.0.1:
+ resolution: {integrity: sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==}
+
+ is-node-process@1.2.0:
+ resolution: {integrity: sha512-Vg4o6/fqPxIjtxgUH5QLJhwZ7gW5diGCVlXpuUfELC62CuxM1iHcRe51f2W1FDy04Ai4KJkagKjx3XaqyfRKXw==}
+
+ is-number@7.0.0:
+ resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==}
+ engines: {node: '>=0.12.0'}
+
+ is-plain-obj@4.1.0:
+ resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==}
+ engines: {node: '>=12'}
+
+ is-platform@1.0.0:
+ resolution: {integrity: sha512-AKxe6+dvzAQsDXhhhxGRL9G67q5rKiyTL0BUl5mCyQz2NdvmqWNmMsjoCOIVdyXOYpP6MhkmZ1DPYGkfgv0MpA==}
+
+ is-stream@2.0.1:
+ resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==}
+ engines: {node: '>=8'}
+
+ isarray@1.0.0:
+ resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==}
+
+ isbot@5.1.22:
+ resolution: {integrity: sha512-RqCFY3cJy3c2y1I+rMn81cfzAR4XJwfPBC+M8kffUjbPzxApzyyv7Tbm1C/gXXq2dSCuD238pKFEWlQMTWsTFw==}
+ engines: {node: '>=18'}
+
+ isexe@2.0.0:
+ resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==}
+
+ isexe@3.1.1:
+ resolution: {integrity: sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==}
+ engines: {node: '>=16'}
+
+ isstream@0.1.2:
+ resolution: {integrity: sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==}
+
+ istanbul-lib-coverage@3.2.2:
+ resolution: {integrity: sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==}
+ engines: {node: '>=8'}
+
+ istanbul-lib-report@3.0.1:
+ resolution: {integrity: sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==}
+ engines: {node: '>=10'}
+
+ istanbul-lib-source-maps@5.0.6:
+ resolution: {integrity: sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==}
+ engines: {node: '>=10'}
+
+ istanbul-reports@3.2.0:
+ resolution: {integrity: sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==}
+ engines: {node: '>=8'}
+
+ jackspeak@3.4.3:
+ resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==}
+
+ jackspeak@4.1.1:
+ resolution: {integrity: sha512-zptv57P3GpL+O0I7VdMJNBZCu+BPHVQUk55Ft8/QCJjTVxrnJHuVuX/0Bl2A6/+2oyR/ZMEuFKwmzqqZ/U5nPQ==}
+ engines: {node: 20 || >=22}
+
+ jiti@2.6.1:
+ resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==}
+ hasBin: true
+
+ js-beautify@1.15.4:
+ resolution: {integrity: sha512-9/KXeZUKKJwqCXUdBxFJ3vPh467OCckSBmYDwSK/EtV090K+iMJ7zx2S3HLVDIWFQdqMIsZWbnaGiba18aWhaA==}
+ engines: {node: '>=14'}
+ hasBin: true
+
+ js-cookie@3.0.5:
+ resolution: {integrity: sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==}
+ engines: {node: '>=14'}
+
+ js-tokens@4.0.0:
+ resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
+
+ js-tokens@9.0.1:
+ resolution: {integrity: sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==}
+
+ js-yaml@3.14.1:
+ resolution: {integrity: sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==}
+ hasBin: true
+
+ js-yaml@4.1.0:
+ resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==}
+ hasBin: true
+
+ jsesc@3.0.2:
+ resolution: {integrity: sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==}
+ engines: {node: '>=6'}
+ hasBin: true
+
+ json-parse-even-better-errors@2.3.1:
+ resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==}
+
+ json-parse-even-better-errors@3.0.2:
+ resolution: {integrity: sha512-fi0NG4bPjCHunUJffmLd0gxssIgkNmArMvis4iNah6Owg1MCJjWhEcDLmsK6iGkJq3tHwbDkTlce70/tmXN4cQ==}
+ engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}
+
+ json5@2.2.3:
+ resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==}
+ engines: {node: '>=6'}
+ hasBin: true
+
+ jsonfile@6.2.0:
+ resolution: {integrity: sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==}
+
+ kind-of@6.0.3:
+ resolution: {integrity: sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==}
+ engines: {node: '>=0.10.0'}
+
+ knip@5.43.6:
+ resolution: {integrity: sha512-bUCFlg44imdV5vayYxu0pIAB373S8Ufjda0qaI9oRZDH6ltJFwUoAO2j7nafxDmo5G0ZeP4IiLAHqlc3wYIONQ==}
+ engines: {node: '>=18.18.0'}
+ hasBin: true
+ peerDependencies:
+ '@types/node': '>=18'
+ typescript: '>=5.0.4'
+
+ lefthook-darwin-arm64@1.10.10:
+ resolution: {integrity: sha512-hEypKdwWpmNSl4Q8eJxgmlGb2ybJj1+W5/v13Mxc+ApEmjbpNiJzPcdjC9zyaMEpPK4EybiHy8g5ZC0dLOwkpA==}
+ cpu: [arm64]
+ os: [darwin]
+
+ lefthook-darwin-x64@1.10.10:
+ resolution: {integrity: sha512-9xNbeE78i4Amz+uOheg9dcy7X/6X12h98SUMrYWk7fONvjW/Bp9h6nPGIGxI5krHp9iRB8rhmo33ljVDVtTlyg==}
+ cpu: [x64]
+ os: [darwin]
+
+ lefthook-freebsd-arm64@1.10.10:
+ resolution: {integrity: sha512-GT9wYxPxkvO1rtIAmctayT9xQIVII5xUIG3Pv6gZo+r6yEyle0EFTLFDbmVje7p7rQNCsvJ8XzCNdnyDrva90g==}
+ cpu: [arm64]
+ os: [freebsd]
+
+ lefthook-freebsd-x64@1.10.10:
+ resolution: {integrity: sha512-2BB/HRhEb9wGpk5K38iNkHtMPnn+TjXDtFG6C/AmUPLXLNhGnNiYp+v2uhUE8quWzxJx7QzfnU7Ga+/gzJcIcw==}
+ cpu: [x64]
+ os: [freebsd]
+
+ lefthook-linux-arm64@1.10.10:
+ resolution: {integrity: sha512-GJ7GALKJ1NcMnNZG9uY+zJR3yS8q7/MgcHFWSJhBl+w4KTiiD/RAdSl5ALwEK2+UX36Eo+7iQA7AXzaRdAii4w==}
+ cpu: [arm64]
+ os: [linux]
+
+ lefthook-linux-x64@1.10.10:
+ resolution: {integrity: sha512-dWUvPM9YTIJ3+X9dB+8iOnzoVHbnNmpscmUqEOKSeizgBrvuuIYKZJGDyjEtw65Qnmn1SJ7ouSaKK93p5c7SkQ==}
+ cpu: [x64]
+ os: [linux]
+
+ lefthook-openbsd-arm64@1.10.10:
+ resolution: {integrity: sha512-KnwDyxOvbvGSBTbEF/OxkynZRPLowd3mIXUKHtkg3ABcQ4UREalX+Sh0nWU2dNjQbINx7Eh6B42TxNC7h+qXEg==}
+ cpu: [arm64]
+ os: [openbsd]
+
+ lefthook-openbsd-x64@1.10.10:
+ resolution: {integrity: sha512-49nnG886CI3WkrzVJ71D1M2KWpUYN1BP9LMKNzN11cmZ0j6dUK4hj3nbW+NcrKXxgYzzyLU3FFwrc51OVy2eKA==}
+ cpu: [x64]
+ os: [openbsd]
+
+ lefthook-windows-arm64@1.10.10:
+ resolution: {integrity: sha512-9ni0Tsnk+O5oL7EBfKj9C5ZctD1mrTyHCtiu1zQJBbREReJtPjIM9DwWzecfbuVfrIlpbviVQvx5mjZ44bqlWw==}
+ cpu: [arm64]
+ os: [win32]
+
+ lefthook-windows-x64@1.10.10:
+ resolution: {integrity: sha512-gkKWYrlay4iecFfY1Ris5VcRYa0BaNJKMk0qE/wZmIpMgu4GvNg+f9BEwTMflkQIanABduT9lrECaL1lX5ClKw==}
+ cpu: [x64]
+ os: [win32]
+
+ lefthook@1.10.10:
+ resolution: {integrity: sha512-YW0fTONgOXsephvXq2gIFbegCW19MHCyKYX7JDWmzVF1ZiVMnDBYUL/SP3i0RtFvlCmqENl4SgKwYYQGUMnvig==}
+ hasBin: true
+
+ lightningcss-android-arm64@1.30.2:
+ resolution: {integrity: sha512-BH9sEdOCahSgmkVhBLeU7Hc9DWeZ1Eb6wNS6Da8igvUwAe0sqROHddIlvU06q3WyXVEOYDZ6ykBZQnjTbmo4+A==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [arm64]
+ os: [android]
+
+ lightningcss-darwin-arm64@1.30.2:
+ resolution: {integrity: sha512-ylTcDJBN3Hp21TdhRT5zBOIi73P6/W0qwvlFEk22fkdXchtNTOU4Qc37SkzV+EKYxLouZ6M4LG9NfZ1qkhhBWA==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [arm64]
+ os: [darwin]
+
+ lightningcss-darwin-x64@1.30.2:
+ resolution: {integrity: sha512-oBZgKchomuDYxr7ilwLcyms6BCyLn0z8J0+ZZmfpjwg9fRVZIR5/GMXd7r9RH94iDhld3UmSjBM6nXWM2TfZTQ==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [x64]
+ os: [darwin]
+
+ lightningcss-freebsd-x64@1.30.2:
+ resolution: {integrity: sha512-c2bH6xTrf4BDpK8MoGG4Bd6zAMZDAXS569UxCAGcA7IKbHNMlhGQ89eRmvpIUGfKWNVdbhSbkQaWhEoMGmGslA==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [x64]
+ os: [freebsd]
+
+ lightningcss-linux-arm-gnueabihf@1.30.2:
+ resolution: {integrity: sha512-eVdpxh4wYcm0PofJIZVuYuLiqBIakQ9uFZmipf6LF/HRj5Bgm0eb3qL/mr1smyXIS1twwOxNWndd8z0E374hiA==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [arm]
+ os: [linux]
+
+ lightningcss-linux-arm64-gnu@1.30.2:
+ resolution: {integrity: sha512-UK65WJAbwIJbiBFXpxrbTNArtfuznvxAJw4Q2ZGlU8kPeDIWEX1dg3rn2veBVUylA2Ezg89ktszWbaQnxD/e3A==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [arm64]
+ os: [linux]
+
+ lightningcss-linux-arm64-musl@1.30.2:
+ resolution: {integrity: sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [arm64]
+ os: [linux]
+
+ lightningcss-linux-x64-gnu@1.30.2:
+ resolution: {integrity: sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [x64]
+ os: [linux]
+
+ lightningcss-linux-x64-musl@1.30.2:
+ resolution: {integrity: sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [x64]
+ os: [linux]
+
+ lightningcss-win32-arm64-msvc@1.30.2:
+ resolution: {integrity: sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [arm64]
+ os: [win32]
+
+ lightningcss-win32-x64-msvc@1.30.2:
+ resolution: {integrity: sha512-5g1yc73p+iAkid5phb4oVFMB45417DkRevRbt/El/gKXJk4jid+vPFF/AXbxn05Aky8PapwzZrdJShv5C0avjw==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [x64]
+ os: [win32]
+
+ lightningcss@1.30.2:
+ resolution: {integrity: sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ==}
+ engines: {node: '>= 12.0.0'}
+
+ lines-and-columns@1.2.4:
+ resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==}
+
+ lite-emit@2.3.0:
+ resolution: {integrity: sha512-QMPrnwPho7lfkzZUN3a0RJ/oiwpt464eXf6aVh1HGOYh+s7Utu78q3FcFbW59c8TNWWQaz9flKN1cEb8dmxD+g==}
+
+ lodash.castarray@4.4.0:
+ resolution: {integrity: sha512-aVx8ztPv7/2ULbArGJ2Y42bG1mEQ5mGjpdvrbJcJFU3TbYybe+QlLS4pst9zV52ymy2in1KpFPiZnAOATxD4+Q==}
+
+ lodash.isplainobject@4.0.6:
+ resolution: {integrity: sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==}
+
+ lodash.merge@4.6.2:
+ resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==}
+
+ lodash@4.17.21:
+ resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==}
+
+ longest-streak@3.1.0:
+ resolution: {integrity: sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==}
+
+ loose-envify@1.4.0:
+ resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==}
+ hasBin: true
+
+ loupe@3.2.1:
+ resolution: {integrity: sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==}
+
+ lru-cache@10.4.3:
+ resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==}
+
+ lru-cache@11.2.2:
+ resolution: {integrity: sha512-F9ODfyqML2coTIsQpSkRHnLSZMtkU8Q+mSfcaIyKwy58u+8k5nvAYeiNhsyMARvzNcXJ9QfWVrcPsC9e9rAxtg==}
+ engines: {node: 20 || >=22}
+
+ lru-cache@5.1.1:
+ resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==}
+
+ lru-cache@7.18.3:
+ resolution: {integrity: sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==}
+ engines: {node: '>=12'}
+
+ lz-string@1.5.0:
+ resolution: {integrity: sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==}
+ hasBin: true
+
+ magic-string@0.30.19:
+ resolution: {integrity: sha512-2N21sPY9Ws53PZvsEpVtNuSW+ScYbQdp4b9qUaL+9QkHUrGFKo56Lg9Emg5s9V/qrtNBmiR01sYhUOwu3H+VOw==}
+
+ magicast@0.3.5:
+ resolution: {integrity: sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ==}
+
+ make-dir@4.0.0:
+ resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==}
+ engines: {node: '>=10'}
+
+ markdown-extensions@2.0.0:
+ resolution: {integrity: sha512-o5vL7aDWatOTX8LzaS1WMoaoxIiLRQJuIKKe2wAw6IeULDHaqbiqiggmx+pKvZDb1Sj+pE46Sn1T7lCqfFtg1Q==}
+ engines: {node: '>=16'}
+
+ mdast-util-from-markdown@2.0.2:
+ resolution: {integrity: sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA==}
+
+ mdast-util-frontmatter@2.0.1:
+ resolution: {integrity: sha512-LRqI9+wdgC25P0URIJY9vwocIzCcksduHQ9OF2joxQoyTNVduwLAFUzjoopuRJbJAReaKrNQKAZKL3uCMugWJA==}
+
+ 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.0:
+ resolution: {integrity: sha512-QGYKEuUsYT9ykKBCMOEDLsU5JRObWQusAolFMeko/tYPufNkRffBAQjIE+99jbA87xv6FgmjLtwjh9wBWajwAA==}
+
+ 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==}
+
+ mdx-bundler@10.1.1:
+ resolution: {integrity: sha512-87FtxC7miUPznwqEaAlJARinHJ6Qin9kDuG2E2BCCNEOszr62kHpqivI/IF/CmwObVSpvApVFFxN1ftM/Gykvw==}
+ engines: {node: '>=18', npm: '>=6'}
+ peerDependencies:
+ esbuild: 0.*
+
+ memoize-one@6.0.0:
+ resolution: {integrity: sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==}
+
+ merge-stream@2.0.0:
+ resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==}
+
+ merge2@1.4.1:
+ resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==}
+ engines: {node: '>= 8'}
+
+ micromark-core-commonmark@2.0.3:
+ resolution: {integrity: sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==}
+
+ micromark-extension-frontmatter@2.0.0:
+ resolution: {integrity: sha512-C4AkuM3dA58cgZha7zVnuVxBhDsbttIMiytjgsM2XbHAB2faRVaHRle40558FBN+DJcrLNCoqG5mlrpdU4cRtg==}
+
+ 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==}
+
+ micromatch@4.0.8:
+ resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==}
+ engines: {node: '>=8.6'}
+
+ mimic-fn@2.1.0:
+ resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==}
+ engines: {node: '>=6'}
+
+ minimatch@10.0.3:
+ resolution: {integrity: sha512-IPZ167aShDZZUMdRk66cyQAW3qr0WzbHkPdMYa8bzZhlHhO3jALbKdxcaak7W9FfT2rZNpQuUu4Od7ILEpXSaw==}
+ engines: {node: 20 || >=22}
+
+ minimatch@9.0.1:
+ resolution: {integrity: sha512-0jWhJpD/MdhPXwPuiRkCbfYfSKp2qnn2eOc279qI7f+osl/l+prKSrvhg157zSYvx/1nmgn2NqdT6k2Z7zSH9w==}
+ engines: {node: '>=16 || 14 >=14.17'}
+
+ minimatch@9.0.5:
+ resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==}
+ engines: {node: '>=16 || 14 >=14.17'}
+
+ minimist@1.2.8:
+ resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==}
+
+ minipass@7.1.2:
+ resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==}
+ engines: {node: '>=16 || 14 >=14.17'}
+
+ motion-dom@12.23.21:
+ resolution: {integrity: sha512-5xDXx/AbhrfgsQmSE7YESMn4Dpo6x5/DTZ4Iyy4xqDvVHWvFVoV+V2Ri2S/ksx+D40wrZ7gPYiMWshkdoqNgNQ==}
+
+ motion-utils@12.23.6:
+ resolution: {integrity: sha512-eAWoPgr4eFEOFfg2WjIsMoqJTW6Z8MTUCgn/GZ3VRpClWBdnbjryiA3ZSNLyxCTmCQx4RmYX6jX1iWHbenUPNQ==}
+
+ mrmime@2.0.1:
+ resolution: {integrity: sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==}
+ engines: {node: '>=10'}
+
+ ms@2.1.3:
+ resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
+
+ msw@2.11.3:
+ resolution: {integrity: sha512-878imp8jxIpfzuzxYfX0qqTq1IFQz/1/RBHs/PyirSjzi+xKM/RRfIpIqHSCWjH0GxidrjhgiiXC+DWXNDvT9w==}
+ engines: {node: '>=18'}
+ hasBin: true
+ peerDependencies:
+ typescript: '>= 4.8.x'
+ peerDependenciesMeta:
+ typescript:
+ optional: true
+
+ mute-stream@0.0.8:
+ resolution: {integrity: sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==}
+
+ mute-stream@2.0.0:
+ resolution: {integrity: sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA==}
+ engines: {node: ^18.17.0 || >=20.5.0}
+
+ nanoid@3.3.11:
+ resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==}
+ engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
+ hasBin: true
+
+ node-fetch@2.7.0:
+ resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==}
+ engines: {node: 4.x || >=6.0.0}
+ peerDependencies:
+ encoding: ^0.1.0
+ peerDependenciesMeta:
+ encoding:
+ optional: true
+
+ node-html-parser@7.0.1:
+ resolution: {integrity: sha512-KGtmPY2kS0thCWGK0VuPyOS+pBKhhe8gXztzA2ilAOhbUbxa9homF1bOyKvhGzMLXUoRds9IOmr/v5lr/lqNmA==}
+
+ node-releases@2.0.23:
+ resolution: {integrity: sha512-cCmFDMSm26S6tQSDpBCg/NR8NENrVPhAJSf+XbxBG4rPFaaonlEoE9wHQmun+cls499TQGSb7ZyPBRlzgKfpeg==}
+
+ nopt@7.2.1:
+ resolution: {integrity: sha512-taM24ViiimT/XntxbPyJQzCG+p4EKOpgD3mxFwW38mGjVUrfERQOeY4EDHjdnptttfHuHQXFx+lTP08Q+mLa/w==}
+ engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}
+ hasBin: true
+
+ normalize-package-data@5.0.0:
+ resolution: {integrity: sha512-h9iPVIfrVZ9wVYQnxFgtw1ugSvGEMOlyPWWtm8BMJhnwyEL/FLbYbTY3V3PpjI/BUK67n9PEWDu6eHzu1fB15Q==}
+ engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}
+
+ npm-install-checks@6.3.0:
+ resolution: {integrity: sha512-W29RiK/xtpCGqn6f3ixfRYGk+zRyr+Ew9F2E20BfXxT5/euLdA/Nm7fO7OeTGuAmTs30cpgInyJ0cYe708YTZw==}
+ engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}
+
+ npm-normalize-package-bin@3.0.1:
+ resolution: {integrity: sha512-dMxCf+zZ+3zeQZXKxmyuCKlIDPGuv8EF940xbkC4kQVDTtqoh6rJFO+JTKSA6/Rwi0getWmtuy4Itup0AMcaDQ==}
+ engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}
+
+ npm-package-arg@10.1.0:
+ resolution: {integrity: sha512-uFyyCEmgBfZTtrKk/5xDfHp6+MdrqGotX/VoOyEEl3mBwiEE5FlBaePanazJSVMPT7vKepcjYBY2ztg9A3yPIA==}
+ engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}
+
+ npm-pick-manifest@8.0.2:
+ resolution: {integrity: sha512-1dKY+86/AIiq1tkKVD3l0WI+Gd3vkknVGAggsFeBkTvbhMQ1OND/LKkYv4JtXPKUJ8bOTCyLiqEg2P6QNdK+Gg==}
+ engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}
+
+ npm-run-path@4.0.1:
+ resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==}
+ engines: {node: '>=8'}
+
+ nth-check@2.1.1:
+ resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==}
+
+ object-assign@4.1.1:
+ resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==}
+ engines: {node: '>=0.10.0'}
+
+ object-treeify@1.1.33:
+ resolution: {integrity: sha512-EFVjAYfzWqWsBMRHPMAXLCDIJnpMhdWAqR7xG6M6a2cs6PMFpl/+Z20w9zDW4vkxOFfddegBKq9Rehd0bxWE7A==}
+ engines: {node: '>= 10'}
+
+ once@1.4.0:
+ resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==}
+
+ onetime@5.1.2:
+ resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==}
+ engines: {node: '>=6'}
+
+ outvariant@1.4.3:
+ resolution: {integrity: sha512-+Sl2UErvtsoajRDKCE5/dBz4DIvHXQQnAxtQTF04OJxY0+DyZXSo5P5Bb7XYWOh81syohlYL24hbDwxedPUJCA==}
+
+ p-limit@6.2.0:
+ resolution: {integrity: sha512-kuUqqHNUqoIWp/c467RI4X6mmyuojY5jGutNU0wVTmEOOfcuwLqyMVoAi9MKi2Ak+5i9+nhmrK4ufZE8069kHA==}
+ engines: {node: '>=18'}
+
+ p-map@4.0.0:
+ resolution: {integrity: sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==}
+ engines: {node: '>=10'}
+
+ package-json-from-dist@1.0.1:
+ resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==}
+
+ pako@0.2.9:
+ resolution: {integrity: sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA==}
+
+ parent-module@1.0.1:
+ resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==}
+ engines: {node: '>=6'}
+
+ parse-entities@4.0.2:
+ resolution: {integrity: sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==}
+
+ parse-json@5.2.0:
+ resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==}
+ engines: {node: '>=8'}
+
+ parse-ms@4.0.0:
+ resolution: {integrity: sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw==}
+ engines: {node: '>=18'}
+
+ path-key@3.1.1:
+ resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==}
+ engines: {node: '>=8'}
+
+ path-parse@1.0.7:
+ resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==}
+
+ path-scurry@1.11.1:
+ resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==}
+ engines: {node: '>=16 || 14 >=14.18'}
+
+ path-scurry@2.0.0:
+ resolution: {integrity: sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg==}
+ engines: {node: 20 || >=22}
+
+ path-to-regexp@6.3.0:
+ resolution: {integrity: sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==}
+
+ path-type@4.0.0:
+ resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==}
+ engines: {node: '>=8'}
+
+ pathe@1.1.2:
+ resolution: {integrity: sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==}
+
+ pathe@2.0.3:
+ resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==}
+
+ pathval@2.0.1:
+ resolution: {integrity: sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==}
+ engines: {node: '>= 14.16'}
+
+ peek-stream@1.1.3:
+ resolution: {integrity: sha512-FhJ+YbOSBb9/rIl2ZeE/QHEsWn7PqNYt8ARAY3kIgNGOk13g9FGyIY6JIl/xB/3TFRVoTv5as0l11weORrTekA==}
+
+ picocolors@1.1.1:
+ resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
+
+ picomatch@2.3.1:
+ resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==}
+ engines: {node: '>=8.6'}
+
+ picomatch@4.0.3:
+ resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==}
+ engines: {node: '>=12'}
+
+ playwright-core@1.50.1:
+ resolution: {integrity: sha512-ra9fsNWayuYumt+NiM069M6OkcRb1FZSK8bgi66AtpFoWkg2+y0bJSNmkFrWhMbEBbVKC/EruAHH3g0zmtwGmQ==}
+ engines: {node: '>=18'}
+ hasBin: true
+
+ playwright@1.50.1:
+ resolution: {integrity: sha512-G8rwsOQJ63XG6BbKj2w5rHeavFjy5zynBA9zsJMMtBoe/Uf757oG12NXz6e6OirF7RCrTVAKFXbLmn1RbL7Qaw==}
+ engines: {node: '>=18'}
+ hasBin: true
+
+ pluralize@8.0.0:
+ resolution: {integrity: sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==}
+ engines: {node: '>=4'}
+
+ postcss-selector-parser@6.0.10:
+ resolution: {integrity: sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==}
+ engines: {node: '>=4'}
+
+ postcss@8.5.6:
+ resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==}
+ engines: {node: ^10 || ^12 || >=14}
+
+ prettier@2.8.8:
+ resolution: {integrity: sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==}
+ engines: {node: '>=10.13.0'}
+ hasBin: true
+
+ pretty-cache-header@1.0.0:
+ resolution: {integrity: sha512-xtXazslu25CdnGnUkByU1RoOjK55TqwatJkjjJLg5ZAdz2Lngko/mmaUgeET36P2GMlNwh3fdM7FWBO717pNcw==}
+ engines: {node: '>=12.13'}
+
+ pretty-format@27.5.1:
+ resolution: {integrity: sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==}
+ engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0}
+
+ pretty-ms@9.3.0:
+ resolution: {integrity: sha512-gjVS5hOP+M3wMm5nmNOucbIrqudzs9v/57bWRHQWLYklXqoXKrVfYW2W9+glfGsqtPgpiz5WwyEEB+ksXIx3gQ==}
+ engines: {node: '>=18'}
+
+ proc-log@3.0.0:
+ resolution: {integrity: sha512-++Vn7NS4Xf9NacaU9Xq3URUuqZETPsf8L4j5/ckhaRYsfPeRyzGw+iDjFhV/Jr3uNmTvvddEJFWh5R1gRgUH8A==}
+ engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}
+
+ process-nextick-args@2.0.1:
+ resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==}
+
+ promise-inflight@1.0.1:
+ resolution: {integrity: sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g==}
+ peerDependencies:
+ bluebird: '*'
+ peerDependenciesMeta:
+ bluebird:
+ optional: true
+
+ promise-retry@2.0.1:
+ resolution: {integrity: sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==}
+ engines: {node: '>=10'}
+
+ prompt@1.3.0:
+ resolution: {integrity: sha512-ZkaRWtaLBZl7KKAKndKYUL8WqNT+cQHKRZnT4RYYms48jQkFw3rrBL+/N5K/KtdEveHkxs982MX2BkDKub2ZMg==}
+ engines: {node: '>= 6.0.0'}
+
+ prop-types@15.8.1:
+ resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==}
+
+ property-information@7.1.0:
+ resolution: {integrity: sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==}
+
+ proto-list@1.2.4:
+ resolution: {integrity: sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==}
+
+ pump@2.0.1:
+ resolution: {integrity: sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==}
+
+ pumpify@1.5.1:
+ resolution: {integrity: sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ==}
+
+ queue-microtask@1.2.3:
+ resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
+
+ randombytes@2.1.0:
+ resolution: {integrity: sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==}
+
+ react-d3-tree@3.6.6:
+ resolution: {integrity: sha512-E9ByUdeqvlxLlF9BSL7KWQH3ikYHtHO+g1rAPcVgj6mu92tjRUCan2AWxoD4eTSzzAATf8BZtf+CXGSoSd6ioQ==}
+ peerDependencies:
+ react: 16.x || 17.x || 18.x || 19.x
+ react-dom: 16.x || 17.x || 18.x || 19.x
+
+ react-diff-viewer-continued@4.0.6:
+ resolution: {integrity: sha512-QtJuaAlAu9w7vLrEvjkUD4XFY/uvA4k4kfRI0SP0xQXLjOXw2QuE/Cg6VSE6qdJCwIWw8FZl5p7NJjW4yVuEUA==}
+ engines: {node: '>= 16'}
+ peerDependencies:
+ react: ^15.3.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
+ react-dom: ^15.3.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
+
+ react-dom@19.0.0:
+ resolution: {integrity: sha512-4GV5sHFG0e/0AD4X+ySy6UJd3jVl1iNsNHdpad0qhABJ11twS3TTBnseqsKurKcsNqCEFeGL3uLpVChpIO3QfQ==}
+ peerDependencies:
+ react: ^19.0.0
+
+ react-hotkeys-hook@4.6.2:
+ resolution: {integrity: sha512-FmP+ZriY3EG59Ug/lxNfrObCnW9xQShgk7Nb83+CkpfkcCpfS95ydv+E9JuXA5cp8KtskU7LGlIARpkc92X22Q==}
+ peerDependencies:
+ react: '>=16.8.1'
+ react-dom: '>=16.8.1'
+
+ react-i18next@15.4.0:
+ resolution: {integrity: sha512-Py6UkX3zV08RTvL6ZANRoBh9sL/ne6rQq79XlkHEdd82cZr2H9usbWpUNVadJntIZP2pu3M2rL1CN+5rQYfYFw==}
+ peerDependencies:
+ i18next: '>= 23.2.3'
+ react: '>= 16.8.0'
+ react-dom: '*'
+ react-native: '*'
+ peerDependenciesMeta:
+ react-dom:
+ optional: true
+ react-native:
+ optional: true
+
+ react-is@16.13.1:
+ resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==}
+
+ react-is@17.0.2:
+ resolution: {integrity: sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==}
+
+ react-lifecycles-compat@3.0.4:
+ resolution: {integrity: sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==}
+
+ react-refresh@0.14.2:
+ resolution: {integrity: sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==}
+ engines: {node: '>=0.10.0'}
+
+ react-remove-scroll-bar@2.3.8:
+ resolution: {integrity: sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==}
+ engines: {node: '>=10'}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
+ react-remove-scroll@2.7.1:
+ resolution: {integrity: sha512-HpMh8+oahmIdOuS5aFKKY6Pyog+FNaZV/XyJOq7b4YFwsFHe5yYfdbIalI4k3vU2nSDql7YskmUseHsRrJqIPA==}
+ engines: {node: '>=10'}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
+ react-router-devtools@5.0.4:
+ resolution: {integrity: sha512-iTWKNOOPZYMH6pYCoPc0aOFu0F5uVHc//Ugo90SRFqajPf4GRM5jFvrlmvNvxC98yp3ceJDit6Hargml9JpLtA==}
+ peerDependencies:
+ '@types/react': '>=17'
+ '@types/react-dom': '>=17'
+ react: '>=17'
+ react-dom: '>=17'
+ react-router: '>=7.0.0'
+ vite: '>=5.0.0 || >=6.0.0'
+
+ react-router-hono-server@2.10.0:
+ resolution: {integrity: sha512-IC0YKpza5BXZjXME2vuKhAflt5mWuZ7RwNDNaMytSpW2K9HhcLB3yxEkFg5rowkJdW1GYI945W34fY/qNs4Q4g==}
+ engines: {node: '>=22.12.0'}
+ hasBin: true
+ peerDependencies:
+ '@cloudflare/workers-types': ^4.20241112.0
+ '@react-router/dev': ^7.2.0
+ '@types/react': ^18.3.10 || ^19.0.0
+ miniflare: ^3.20241205.0
+ react-router: ^7.2.0
+ vite: ^5.1.0 || ^6.0.0
+ wrangler: ^3.91.0
+ peerDependenciesMeta:
+ '@cloudflare/workers-types':
+ optional: true
+ miniflare:
+ optional: true
+ wrangler:
+ optional: true
+
+ react-router@7.2.0:
+ resolution: {integrity: sha512-fXyqzPgCPZbqhrk7k3hPcCpYIlQ2ugIXDboHUzhJISFVy2DEPsmHgN588MyGmkIOv3jDgNfUE3kJi83L28s/LQ==}
+ engines: {node: '>=20.0.0'}
+ peerDependencies:
+ react: '>=18'
+ react-dom: '>=18'
+ peerDependenciesMeta:
+ react-dom:
+ optional: true
+
+ react-style-singleton@2.2.3:
+ resolution: {integrity: sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==}
+ engines: {node: '>=10'}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
+ react-tooltip@5.29.1:
+ resolution: {integrity: sha512-rmJmEb/p99xWhwmVT7F7riLG08wwKykjHiMGbDPloNJk3tdI73oHsVOwzZ4SRjqMdd5/xwb/4nmz0RcoMfY7Bw==}
+ peerDependencies:
+ react: '>=16.14.0'
+ react-dom: '>=16.14.0'
+
+ react@19.0.0:
+ resolution: {integrity: sha512-V8AVnmPIICiWpGfm6GLzCR/W5FXLchHop40W4nXBmdlEceh16rCN8O8LNWm5bh5XUX91fh7KpA+W0TgMKmgTpQ==}
+ engines: {node: '>=0.10.0'}
+
+ read@1.0.7:
+ resolution: {integrity: sha512-rSOKNYUmaxy0om1BNjMN4ezNT6VKK+2xF4GBhc81mkH7L60i6dp8qPYrkndNLT3QPphoII3maL9PVC9XmhHwVQ==}
+ engines: {node: '>=0.8'}
+
+ readable-stream@2.3.8:
+ resolution: {integrity: sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==}
+
+ readdirp@4.1.2:
+ resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==}
+ engines: {node: '>= 14.18.0'}
+
+ 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==}
+
+ rehype-recma@1.0.0:
+ resolution: {integrity: sha512-lqA4rGUf1JmacCNWWZx0Wv1dHqMwxzsDWYMTowuplHF3xH0N/MmrZ/G3BDZnzAkRmxDadujCjaKM2hqYdCBOGw==}
+
+ rehype-slug@6.0.0:
+ resolution: {integrity: sha512-lWyvf/jwu+oS5+hL5eClVd3hNdmwM1kAC0BUvEGD19pajQMIzcNUd/k9GsfQ+FfECvX+JE+e9/btsKH0EjJT6A==}
+
+ remark-frontmatter@5.0.0:
+ resolution: {integrity: sha512-XTFYvNASMe5iPN0719nPrdItC9aU0ssC4v14mH1BCi1u0n1gAocqcujWUrByftZTbLhRtiKRyjYTSIOcr69UVQ==}
+
+ remark-mdx-frontmatter@4.0.0:
+ resolution: {integrity: sha512-PZzAiDGOEfv1Ua7exQ8S5kKxkD8CDaSb4nM+1Mprs6u8dyvQifakh+kCj6NovfGXW+bTvrhjaR3srzjS2qJHKg==}
+
+ 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==}
+
+ remix-hono@0.0.18:
+ resolution: {integrity: sha512-pYRFCRjCSDxjIco+qUkGQNIOZwKC/3NaDssLE2gBsLVHuNymUbhwMZeQDu1ERxdrYQuBE19Zn3vS8jL8AXcoxA==}
+ peerDependencies:
+ '@react-router/cloudflare': ^7.0.1
+ hono: ^4.6.12
+ i18next: ^24.0.5
+ pretty-cache-header: ^1.0.0
+ react-router: ^7.0.1
+ remix-i18next: ^7.0.0
+ zod: ^3.0.0
+ peerDependenciesMeta:
+ '@react-router/cloudflare':
+ optional: true
+ i18next:
+ optional: true
+ react-router:
+ optional: true
+ remix-i18next:
+ optional: true
+ zod:
+ optional: true
+
+ remix-i18next@7.0.2:
+ resolution: {integrity: sha512-mfqbEdB76KgJo3f1+2FJBtoI5VMrq6zLKt+aYpCBeycsj7njafkDYRQ6SVzUYAES8EyT6gNd45agmhr8H10VmQ==}
+ engines: {node: '>=20.0.0'}
+ peerDependencies:
+ i18next: ^24.0.0
+ react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
+ react-i18next: ^13.0.0 || ^14.0.0 || ^15.0.0
+ react-router: ^7.0.0
+
+ require-directory@2.1.1:
+ resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==}
+ engines: {node: '>=0.10.0'}
+
+ resolve-from@4.0.0:
+ resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==}
+ engines: {node: '>=4'}
+
+ resolve-pkg-maps@1.0.0:
+ resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==}
+
+ resolve@1.22.10:
+ resolution: {integrity: sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==}
+ engines: {node: '>= 0.4'}
+ hasBin: true
+
+ retry@0.12.0:
+ resolution: {integrity: sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==}
+ engines: {node: '>= 4'}
+
+ rettime@0.7.0:
+ resolution: {integrity: sha512-LPRKoHnLKd/r3dVxcwO7vhCW+orkOGj9ViueosEBK6ie89CijnfRlhaDhHq/3Hxu4CkWQtxwlBG0mzTQY6uQjw==}
+
+ reusify@1.1.0:
+ resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==}
+ engines: {iojs: '>=1.0.0', node: '>=0.10.0'}
+
+ revalidator@0.1.8:
+ resolution: {integrity: sha512-xcBILK2pA9oh4SiinPEZfhP8HfrB/ha+a2fTMyl7Om2WjlDVrOQy99N2MXXlUHqGJz4qEu2duXxHJjDWuK/0xg==}
+ engines: {node: '>= 0.4.0'}
+
+ rollup@4.52.4:
+ resolution: {integrity: sha512-CLEVl+MnPAiKh5pl4dEWSyMTpuflgNQiLGhMv8ezD5W/qP8AKvmYpCOKRRNOh7oRKnauBZ4SyeYkMS+1VSyKwQ==}
+ engines: {node: '>=18.0.0', npm: '>=8.0.0'}
+ hasBin: true
+
+ run-parallel@1.2.0:
+ resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==}
+
+ safe-buffer@5.1.2:
+ resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==}
+
+ safe-buffer@5.2.1:
+ resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==}
+
+ scheduler@0.25.0:
+ resolution: {integrity: sha512-xFVuu11jh+xcO7JOAGJNOXld8/TcEHK/4CituBUeUb5hqxJLj9YuemAEuvm9gQ/+pgXYfbQuqAkiYu+u7YEsNA==}
+
+ section-matter@1.0.0:
+ resolution: {integrity: sha512-vfD3pmTzGpufjScBh50YHKzEu2lxBWhVEHsNGoEXmCmn2hKGfeNLYMzCJpe8cD7gqX7TJluOVpBkAequ6dgMmA==}
+ engines: {node: '>=4'}
+
+ semver@6.3.1:
+ resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==}
+ hasBin: true
+
+ semver@7.7.2:
+ resolution: {integrity: sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==}
+ engines: {node: '>=10'}
+ hasBin: true
+
+ serialize-javascript@6.0.2:
+ resolution: {integrity: sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==}
+
+ set-cookie-parser@2.7.1:
+ resolution: {integrity: sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==}
+
+ shebang-command@2.0.0:
+ resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==}
+ engines: {node: '>=8'}
+
+ shebang-regex@3.0.0:
+ resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==}
+ engines: {node: '>=8'}
+
+ siginfo@2.0.0:
+ resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==}
+
+ signal-exit@3.0.7:
+ resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==}
+
+ 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'}
+
+ slug@11.0.0:
+ resolution: {integrity: sha512-71pb27F9TII2dIweGr2ybS220IUZo1A9GKZ+e2q8rpUr24mejBb6fTaSStM0SE1ITUUOshilqZze8Yt1BKj+ew==}
+ hasBin: true
+
+ smol-toml@1.4.2:
+ resolution: {integrity: sha512-rInDH6lCNiEyn3+hH8KVGFdbjc099j47+OSgbMrfDYX1CmXLfdKd7qi6IfcWj2wFxvSVkuI46M+wPGYfEOEj6g==}
+ engines: {node: '>= 18'}
+
+ source-map-js@1.2.1:
+ resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
+ engines: {node: '>=0.10.0'}
+
+ source-map-support@0.5.21:
+ resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==}
+
+ source-map@0.5.7:
+ resolution: {integrity: sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==}
+ engines: {node: '>=0.10.0'}
+
+ source-map@0.6.1:
+ resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==}
+ 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==}
+
+ spdx-correct@3.2.0:
+ resolution: {integrity: sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==}
+
+ spdx-exceptions@2.5.0:
+ resolution: {integrity: sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==}
+
+ spdx-expression-parse@3.0.1:
+ resolution: {integrity: sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==}
+
+ spdx-license-ids@3.0.22:
+ resolution: {integrity: sha512-4PRT4nh1EImPbt2jASOKHX7PB7I+e4IWNLvkKFDxNhJlfjbYlleYQh285Z/3mPTHSAK/AvdMmw5BNNuYH8ShgQ==}
+
+ sprintf-js@1.0.3:
+ resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==}
+
+ stack-trace@0.0.10:
+ resolution: {integrity: sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg==}
+
+ stackback@0.0.2:
+ resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==}
+
+ statuses@2.0.2:
+ resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==}
+ engines: {node: '>= 0.8'}
+
+ std-env@3.9.0:
+ resolution: {integrity: sha512-UGvjygr6F6tpH7o2qyqR6QYpwraIjKSdtzyBdyytFOHmPZY917kwdwLG0RbOjWOnKmnm3PeHjaoLLMie7kPLQw==}
+
+ stream-shift@1.0.3:
+ resolution: {integrity: sha512-76ORR0DO1o1hlKwTbi/DM3EXWGf3ZJYO8cXX5RJwnul2DEg2oyoZyjLNoQM8WsvZiFKCRfC1O0J7iCvie3RZmQ==}
+
+ stream-slice@0.1.2:
+ resolution: {integrity: sha512-QzQxpoacatkreL6jsxnVb7X5R/pGw9OUv2qWTYWnmLpg4NdN31snPy/f3TdQE1ZUXaThRvj1Zw4/OGg0ZkaLMA==}
+
+ strict-event-emitter@0.5.1:
+ resolution: {integrity: sha512-vMgjE/GGEPEFnhFub6pa4FmJBRBVOLpIII2hvCZ8Kzb7K0hlHo7mQv6xYrBvCL2LtAIBwFUK8wvuJgTVSQ5MFQ==}
+
+ string-width@4.2.3:
+ resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==}
+ engines: {node: '>=8'}
+
+ string-width@5.1.2:
+ resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==}
+ engines: {node: '>=12'}
+
+ string-width@6.1.0:
+ resolution: {integrity: sha512-k01swCJAgQmuADB0YIc+7TuatfNvTBVOoaUWJjTB9R4VJzR5vNWzf5t42ESVZFPS8xTySF7CAdV4t/aaIm3UnQ==}
+ engines: {node: '>=16'}
+
+ string_decoder@1.1.1:
+ resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==}
+
+ stringify-entities@4.0.4:
+ resolution: {integrity: sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==}
+
+ strip-ansi@6.0.1:
+ resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==}
+ engines: {node: '>=8'}
+
+ strip-ansi@7.1.2:
+ resolution: {integrity: sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==}
+ engines: {node: '>=12'}
+
+ strip-bom-string@1.0.0:
+ resolution: {integrity: sha512-uCC2VHvQRYu+lMh4My/sFNmF2klFymLX1wHJeXnbEJERpV/ZsVuonzerjfrGpIGF7LBVa1O7i9kjiWvJiFck8g==}
+ engines: {node: '>=0.10.0'}
+
+ strip-final-newline@2.0.0:
+ resolution: {integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==}
+ engines: {node: '>=6'}
+
+ strip-json-comments@5.0.1:
+ resolution: {integrity: sha512-0fk9zBqO67Nq5M/m45qHCJxylV/DhBlIOVExqgOMiCCrzrhU6tCibRXNqE3jwJLftzE9SNuZtYbpzcO+i9FiKw==}
+ engines: {node: '>=14.16'}
+
+ strip-literal@3.1.0:
+ resolution: {integrity: sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg==}
+
+ style-to-js@1.1.17:
+ resolution: {integrity: sha512-xQcBGDxJb6jjFCTzvQtfiPn6YvvP2O8U1MDIPNfJQlWMYfktPy+iGsHE7cssjs7y84d9fQaK4UF3RIJaAHSoYA==}
+
+ style-to-object@1.0.9:
+ resolution: {integrity: sha512-G4qppLgKu/k6FwRpHiGiKPaPTFcG3g4wNVX/Qsfu+RqQM30E7Tyu/TEgxcL9PNLF5pdRLwQdE3YKKf+KF2Dzlw==}
+
+ stylis@4.2.0:
+ resolution: {integrity: sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==}
+
+ summary@2.1.0:
+ resolution: {integrity: sha512-nMIjMrd5Z2nuB2RZCKJfFMjgS3fygbeyGk9PxPPaJR1RIcyN9yn4A63Isovzm3ZtQuEkLBVgMdPup8UeLH7aQw==}
+
+ supports-color@7.2.0:
+ resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==}
+ engines: {node: '>=8'}
+
+ supports-preserve-symlinks-flag@1.0.0:
+ resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==}
+ engines: {node: '>= 0.4'}
+
+ tailwind-merge@3.0.1:
+ resolution: {integrity: sha512-AvzE8FmSoXC7nC+oU5GlQJbip2UO7tmOhOfQyOmPhrStOGXHU08j8mZEHZ4BmCqY5dWTCo4ClWkNyRNx1wpT0g==}
+
+ tailwindcss@4.0.9:
+ resolution: {integrity: sha512-12laZu+fv1ONDRoNR9ipTOpUD7RN9essRVkX36sjxuRUInpN7hIiHN4lBd/SIFjbISvnXzp8h/hXzmU8SQQYhw==}
+
+ tapable@2.3.0:
+ resolution: {integrity: sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==}
+ engines: {node: '>=6'}
+
+ test-exclude@7.0.1:
+ resolution: {integrity: sha512-pFYqmTw68LXVjeWJMST4+borgQP2AyMNbg1BpZh9LbyhUeNkeaPF9gzfPGUAnSMV3qPYdWUwDIjjCLiSDOl7vg==}
+ engines: {node: '>=18'}
+
+ text-table@0.2.0:
+ resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==}
+
+ through2@2.0.5:
+ resolution: {integrity: sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==}
+
+ timestring@6.0.0:
+ resolution: {integrity: sha512-wMctrWD2HZZLuIlchlkE2dfXJh7J2KDI9Dwl+2abPYg0mswQHfOAyQW3jJg1pY5VfttSINZuKcXoB3FGypVklA==}
+ engines: {node: '>=8'}
+
+ tinybench@2.9.0:
+ resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==}
+
+ tinyexec@0.3.2:
+ resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==}
+
+ tinyglobby@0.2.15:
+ resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==}
+ engines: {node: '>=12.0.0'}
+
+ tinypool@1.1.1:
+ resolution: {integrity: sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==}
+ engines: {node: ^18.0.0 || >=20.0.0}
+
+ tinyrainbow@2.0.0:
+ resolution: {integrity: sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==}
+ engines: {node: '>=14.0.0'}
+
+ tinyspy@4.0.4:
+ resolution: {integrity: sha512-azl+t0z7pw/z958Gy9svOTuzqIk6xq+NSheJzn5MMWtWTFywIacg2wUlzKFGtt3cthx0r2SxMK0yzJOR0IES7Q==}
+ engines: {node: '>=14.0.0'}
+
+ tldts-core@7.0.16:
+ resolution: {integrity: sha512-XHhPmHxphLi+LGbH0G/O7dmUH9V65OY20R7vH8gETHsp5AZCjBk9l8sqmRKLaGOxnETU7XNSDUPtewAy/K6jbA==}
+
+ tldts@7.0.16:
+ resolution: {integrity: sha512-5bdPHSwbKTeHmXrgecID4Ljff8rQjv7g8zKQPkCozRo2HWWni+p310FSn5ImI+9kWw9kK4lzOB5q/a6iv0IJsw==}
+ hasBin: true
+
+ to-regex-range@5.0.1:
+ resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==}
+ engines: {node: '>=8.0'}
+
+ toml@3.0.0:
+ resolution: {integrity: sha512-y/mWCZinnvxjTKYhJ+pYxwD0mRLVvOtdS2Awbgxln6iEnt4rk0yBxeSBHkGJcPucRiG0e55mwWp+g/05rsrd6w==}
+
+ totalist@3.0.1:
+ resolution: {integrity: sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==}
+ engines: {node: '>=6'}
+
+ tough-cookie@6.0.0:
+ resolution: {integrity: sha512-kXuRi1mtaKMrsLUxz3sQYvVl37B0Ns6MzfrtV5DvJceE9bPyspOqk9xxv7XbZWcfLWbFmm997vl83qUWVJA64w==}
+ engines: {node: '>=16'}
+
+ tr46@0.0.3:
+ resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==}
+
+ trim-lines@3.0.1:
+ resolution: {integrity: sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==}
+
+ trough@2.2.0:
+ resolution: {integrity: sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==}
+
+ tsconfck@3.1.6:
+ resolution: {integrity: sha512-ks6Vjr/jEw0P1gmOVwutM3B7fWxoWBL2KRDb1JfqGVawBmO5UsvmWOQFGHBPl5yxYz4eERr19E6L7NMv+Fej4w==}
+ engines: {node: ^18 || >=20}
+ hasBin: true
+ peerDependencies:
+ typescript: ^5.0.0
+ peerDependenciesMeta:
+ typescript:
+ optional: true
+
+ tslib@2.8.1:
+ resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==}
+
+ tsx@4.19.2:
+ resolution: {integrity: sha512-pOUl6Vo2LUq/bSa8S5q7b91cgNSjctn9ugq/+Mvow99qW6x/UZYwzxy/3NmqoT66eHYfCVvFvACC58UBPFf28g==}
+ engines: {node: '>=18.0.0'}
+ hasBin: true
+
+ turbo-stream@2.4.0:
+ resolution: {integrity: sha512-FHncC10WpBd2eOmGwpmQsWLDoK4cqsA/UT/GqNoaKOQnT8uzhtCbg3EoUDMvqpOSAI0S26mr0rkjzbOO6S3v1g==}
+
+ turbo-stream@2.4.1:
+ resolution: {integrity: sha512-v8kOJXpG3WoTN/+at8vK7erSzo6nW6CIaeOvNOkHQVDajfz1ZVeSxCbc6tOH4hrGZW7VUCV0TOXd8CPzYnYkrw==}
+
+ type-fest@4.41.0:
+ resolution: {integrity: sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==}
+ engines: {node: '>=16'}
+
+ type-flag@3.0.0:
+ resolution: {integrity: sha512-3YaYwMseXCAhBB14RXW5cRQfJQlEknS6i4C8fCfeUdS3ihG9EdccdR9kt3vP73ZdeTGmPb4bZtkDn5XMIn1DLA==}
+
+ typedarray@0.0.6:
+ resolution: {integrity: sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==}
+
+ typescript@5.7.3:
+ resolution: {integrity: sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==}
+ engines: {node: '>=14.17'}
+ hasBin: true
+
+ undici-types@6.20.0:
+ resolution: {integrity: sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==}
+
+ undici@6.22.0:
+ resolution: {integrity: sha512-hU/10obOIu62MGYjdskASR3CUAiYaFTtC9Pa6vHyf//mAipSvSQg6od2CnJswq7fvzNS3zJhxoRkgNVaHurWKw==}
+ engines: {node: '>=18.17'}
+
+ unified@11.0.5:
+ resolution: {integrity: sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==}
+
+ unist-util-is@6.0.0:
+ resolution: {integrity: sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw==}
+
+ 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-stringify-position@4.0.0:
+ resolution: {integrity: sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==}
+
+ unist-util-visit-parents@6.0.1:
+ resolution: {integrity: sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw==}
+
+ unist-util-visit@5.0.0:
+ resolution: {integrity: sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==}
+
+ universalify@2.0.1:
+ resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==}
+ engines: {node: '>= 10.0.0'}
+
+ until-async@3.0.2:
+ resolution: {integrity: sha512-IiSk4HlzAMqTUseHHe3VhIGyuFmN90zMTpD3Z3y8jeQbzLIq500MVM7Jq2vUAnTKAFPJrqwkzr6PoTcPhGcOiw==}
+
+ update-browserslist-db@1.1.3:
+ resolution: {integrity: sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==}
+ hasBin: true
+ peerDependencies:
+ browserslist: '>= 4.21.0'
+
+ url-pattern@1.0.3:
+ resolution: {integrity: sha512-uQcEj/2puA4aq1R3A2+VNVBgaWYR24FdWjl7VNW83rnWftlhyzOZ/tBjezRiC2UkIzuxC8Top3IekN3vUf1WxA==}
+ engines: {node: '>=0.12.0'}
+
+ use-callback-ref@1.3.3:
+ resolution: {integrity: sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==}
+ engines: {node: '>=10'}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
+ use-sidecar@1.1.3:
+ resolution: {integrity: sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==}
+ engines: {node: '>=10'}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
+ util-deprecate@1.0.2:
+ resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
+
+ uuid@8.3.2:
+ resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==}
+ hasBin: true
+
+ uuid@9.0.1:
+ resolution: {integrity: sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==}
+ hasBin: true
+
+ valibot@0.41.0:
+ resolution: {integrity: sha512-igDBb8CTYr8YTQlOKgaN9nSS0Be7z+WRuaeYqGf3Cjz3aKmSnqEmYnkfVjzIuumGqfHpa3fLIvMEAfhrpqN8ng==}
+ peerDependencies:
+ typescript: '>=5'
+ peerDependenciesMeta:
+ typescript:
+ optional: true
+
+ validate-npm-package-license@3.0.4:
+ resolution: {integrity: sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==}
+
+ validate-npm-package-name@5.0.1:
+ resolution: {integrity: sha512-OljLrQ9SQdOUqTaQxqL5dEfZWrXExyyWsozYlAWFawPVNuD83igl7uJD2RTkNMbniIYgt8l81eCJGIdQF7avLQ==}
+ engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}
+
+ vfile-message@4.0.3:
+ resolution: {integrity: sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==}
+
+ vfile@6.0.3:
+ resolution: {integrity: sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==}
+
+ vite-node@3.0.0-beta.2:
+ resolution: {integrity: sha512-ofTf6cfRdL30Wbl9n/BX81EyIR5s4PReLmSurrxQ+koLaWUNOEo8E0lCM53OJkb8vpa2URM2nSrxZsIFyvY1rg==}
+ engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0}
+ hasBin: true
+
+ vite-node@3.2.4:
+ resolution: {integrity: sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==}
+ engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0}
+ hasBin: true
+
+ vite-plugin-babel@1.3.0:
+ resolution: {integrity: sha512-C5WKX0UwvQKH8WD2GiyWUjI62UBfLbfUhiLexnIm4asLdENX5ymrRipFlBnGeVxoOaYgTL5dh5KW6YDGpWsR8A==}
+ peerDependencies:
+ '@babel/core': ^7.0.0
+ vite: ^2.7.0 || ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0
+
+ vite-plugin-icons-spritesheet@3.0.1:
+ resolution: {integrity: sha512-Cr0+Z6wRMwSwKisWW9PHeTjqmQFv0jwRQQMc3YgAhAgZEe03j21el0P/CA31KN/L5eiL1LhR14VTXl96LetonA==}
+ peerDependencies:
+ vite: '>=5.2.0'
+
+ vite-tsconfig-paths@5.1.4:
+ resolution: {integrity: sha512-cYj0LRuLV2c2sMqhqhGpaO3LretdtMn/BVX4cPLanIZuwwrkVl+lK84E/miEXkCHWXuq65rhNN4rXsBcOB3S4w==}
+ peerDependencies:
+ vite: '*'
+ peerDependenciesMeta:
+ vite:
+ optional: true
+
+ vite@6.2.0:
+ resolution: {integrity: sha512-7dPxoo+WsT/64rDcwoOjk76XHj+TqNTIvHKcuMQ1k4/SeHDaQt5GFAeLYzrimZrMpn/O6DtdI03WUjdxuPM0oQ==}
+ engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0}
+ hasBin: true
+ peerDependencies:
+ '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0
+ jiti: '>=1.21.0'
+ less: '*'
+ lightningcss: ^1.21.0
+ sass: '*'
+ sass-embedded: '*'
+ stylus: '*'
+ sugarss: '*'
+ 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
+
+ vitest-browser-react@1.0.1:
+ resolution: {integrity: sha512-LqiGFCdknrbMoSDWXTCTrPsED3SvdIXIgYOOZyYUNj2dkJusW2eF6NENOlBlxwq+FBQqzNK1X59b+b03pXFpAQ==}
+ engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0}
+ peerDependencies:
+ '@types/react': ^18.0.0 || ^19.0.0
+ '@types/react-dom': ^18.0.0 || ^19.0.0
+ '@vitest/browser': ^2.1.0 || ^3.0.0 || ^4.0.0-0
+ react: ^18.0.0 || ^19.0.0
+ react-dom: ^18.0.0 || ^19.0.0
+ vitest: ^2.1.0 || ^3.0.0 || ^4.0.0-0
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+
+ vitest@3.2.4:
+ resolution: {integrity: sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==}
+ engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0}
+ hasBin: true
+ peerDependencies:
+ '@edge-runtime/vm': '*'
+ '@types/debug': ^4.1.12
+ '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0
+ '@vitest/browser': 3.2.4
+ '@vitest/ui': 3.2.4
+ happy-dom: '*'
+ jsdom: '*'
+ peerDependenciesMeta:
+ '@edge-runtime/vm':
+ optional: true
+ '@types/debug':
+ optional: true
+ '@types/node':
+ optional: true
+ '@vitest/browser':
+ optional: true
+ '@vitest/ui':
+ optional: true
+ happy-dom:
+ optional: true
+ jsdom:
+ optional: true
+
+ void-elements@3.1.0:
+ resolution: {integrity: sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==}
+ engines: {node: '>=0.10.0'}
+
+ warning@3.0.0:
+ resolution: {integrity: sha512-jMBt6pUrKn5I+OGgtQ4YZLdhIeJmObddh6CsibPxyQ5yPZm1XExSyzC1LCNX7BzhxWgiHmizBWJTHJIjMjTQYQ==}
+
+ wcwidth@1.0.1:
+ resolution: {integrity: sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==}
+
+ webidl-conversions@3.0.1:
+ resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==}
+
+ webidl-conversions@7.0.0:
+ resolution: {integrity: sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==}
+ engines: {node: '>=12'}
+
+ whatwg-mimetype@3.0.0:
+ resolution: {integrity: sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==}
+ engines: {node: '>=12'}
+
+ whatwg-url@5.0.0:
+ resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==}
+
+ which@2.0.2:
+ resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==}
+ engines: {node: '>= 8'}
+ hasBin: true
+
+ which@3.0.1:
+ resolution: {integrity: sha512-XA1b62dzQzLfaEOSQFTCOd5KFf/1VSzZo7/7TUjnya6u0vGGKzU96UQBZTAThCb2j4/xjBAyii1OhRLJEivHvg==}
+ engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}
+ hasBin: true
+
+ which@4.0.0:
+ resolution: {integrity: sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==}
+ engines: {node: ^16.13.0 || >=18.0.0}
+ hasBin: true
+
+ why-is-node-running@2.3.0:
+ resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==}
+ engines: {node: '>=8'}
+ hasBin: true
+
+ winston@2.4.7:
+ resolution: {integrity: sha512-vLB4BqzCKDnnZH9PHGoS2ycawueX4HLqENXQitvFHczhgW2vFpSOn31LZtVr1KU8YTw7DS4tM+cqyovxo8taVg==}
+ engines: {node: '>= 0.10.0'}
+
+ wrap-ansi@6.2.0:
+ resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==}
+ engines: {node: '>=8'}
+
+ wrap-ansi@7.0.0:
+ resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==}
+ engines: {node: '>=10'}
+
+ wrap-ansi@8.1.0:
+ resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==}
+ engines: {node: '>=12'}
+
+ wrappy@1.0.2:
+ resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==}
+
+ ws@8.18.3:
+ resolution: {integrity: sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==}
+ engines: {node: '>=10.0.0'}
+ peerDependencies:
+ bufferutil: ^4.0.1
+ utf-8-validate: '>=5.0.2'
+ peerDependenciesMeta:
+ bufferutil:
+ optional: true
+ utf-8-validate:
+ optional: true
+
+ xtend@4.0.2:
+ resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==}
+ engines: {node: '>=0.4'}
+
+ y18n@5.0.8:
+ resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==}
+ engines: {node: '>=10'}
+
+ yallist@3.1.1:
+ resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==}
+
+ yaml@1.10.2:
+ resolution: {integrity: sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==}
+ engines: {node: '>= 6'}
+
+ yaml@2.8.1:
+ resolution: {integrity: sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw==}
+ engines: {node: '>= 14.6'}
+ hasBin: true
+
+ yargs-parser@21.1.1:
+ resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==}
+ engines: {node: '>=12'}
+
+ yargs@17.7.2:
+ resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==}
+ engines: {node: '>=12'}
+
+ yocto-queue@1.2.1:
+ resolution: {integrity: sha512-AyeEbWOu/TAXdxlV9wmGcR0+yh2j3vYPGOECcIj2S7MkrLyC7ne+oye2BKTItt0ii2PHk4cDy+95+LshzbXnGg==}
+ engines: {node: '>=12.20'}
+
+ yoctocolors-cjs@2.1.3:
+ resolution: {integrity: sha512-U/PBtDf35ff0D8X8D0jfdzHYEPFxAI7jJlxZXwCSez5M3190m+QobIfh+sWDWSHMCWWJN2AWamkegn6vr6YBTw==}
+ engines: {node: '>=18'}
+
+ yoctocolors@1.0.0:
+ resolution: {integrity: sha512-qJNAmSF77lWjfRVwCZK3PcKYWrr+55RUQTiXDxXHGbxzf8WuuRgftIB3hqZ5fykjOF/MC62cazsG/2ZDBedOnQ==}
+ engines: {node: '>=14.16'}
+
+ zod-validation-error@3.5.3:
+ resolution: {integrity: sha512-OT5Y8lbUadqVZCsnyFaTQ4/O2mys4tj7PqhdbBCp7McPwvIEKfPtdA6QfPeFQK2/Rz5LgwmAXRJTugBNBi0btw==}
+ engines: {node: '>=18.0.0'}
+ peerDependencies:
+ zod: ^3.25.0 || ^4.0.0
+
+ zod@3.25.76:
+ resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==}
+
+ zod@4.0.17:
+ resolution: {integrity: sha512-1PHjlYRevNxxdy2JZ8JcNAw7rX8V9P1AKkP+x/xZfxB0K5FYfuV+Ug6P/6NVSR2jHQ+FzDDoDHS04nYUsOIyLQ==}
+
+ zwitch@2.0.4:
+ resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==}
+
+snapshots:
+
+ '@ampproject/remapping@2.3.0':
+ dependencies:
+ '@jridgewell/gen-mapping': 0.3.13
+ '@jridgewell/trace-mapping': 0.3.31
+
+ '@babel/code-frame@7.27.1':
+ dependencies:
+ '@babel/helper-validator-identifier': 7.27.1
+ js-tokens: 4.0.0
+ picocolors: 1.1.1
+
+ '@babel/compat-data@7.28.4': {}
+
+ '@babel/core@7.28.4':
+ dependencies:
+ '@babel/code-frame': 7.27.1
+ '@babel/generator': 7.28.3
+ '@babel/helper-compilation-targets': 7.27.2
+ '@babel/helper-module-transforms': 7.28.3(@babel/core@7.28.4)
+ '@babel/helpers': 7.28.4
+ '@babel/parser': 7.28.4
+ '@babel/template': 7.27.2
+ '@babel/traverse': 7.28.4
+ '@babel/types': 7.28.4
+ '@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.28.3':
+ dependencies:
+ '@babel/parser': 7.28.4
+ '@babel/types': 7.28.4
+ '@jridgewell/gen-mapping': 0.3.13
+ '@jridgewell/trace-mapping': 0.3.31
+ jsesc: 3.0.2
+
+ '@babel/helper-annotate-as-pure@7.27.3':
+ dependencies:
+ '@babel/types': 7.28.4
+
+ '@babel/helper-compilation-targets@7.27.2':
+ dependencies:
+ '@babel/compat-data': 7.28.4
+ '@babel/helper-validator-option': 7.27.1
+ browserslist: 4.26.3
+ lru-cache: 5.1.1
+ semver: 6.3.1
+
+ '@babel/helper-create-class-features-plugin@7.28.3(@babel/core@7.28.4)':
+ dependencies:
+ '@babel/core': 7.28.4
+ '@babel/helper-annotate-as-pure': 7.27.3
+ '@babel/helper-member-expression-to-functions': 7.27.1
+ '@babel/helper-optimise-call-expression': 7.27.1
+ '@babel/helper-replace-supers': 7.27.1(@babel/core@7.28.4)
+ '@babel/helper-skip-transparent-expression-wrappers': 7.27.1
+ '@babel/traverse': 7.28.4
+ semver: 6.3.1
+ transitivePeerDependencies:
+ - supports-color
+
+ '@babel/helper-globals@7.28.0': {}
+
+ '@babel/helper-member-expression-to-functions@7.27.1':
+ dependencies:
+ '@babel/traverse': 7.28.4
+ '@babel/types': 7.28.4
+ transitivePeerDependencies:
+ - supports-color
+
+ '@babel/helper-module-imports@7.27.1':
+ dependencies:
+ '@babel/traverse': 7.28.4
+ '@babel/types': 7.28.4
+ transitivePeerDependencies:
+ - supports-color
+
+ '@babel/helper-module-transforms@7.28.3(@babel/core@7.28.4)':
+ dependencies:
+ '@babel/core': 7.28.4
+ '@babel/helper-module-imports': 7.27.1
+ '@babel/helper-validator-identifier': 7.27.1
+ '@babel/traverse': 7.28.4
+ transitivePeerDependencies:
+ - supports-color
+
+ '@babel/helper-optimise-call-expression@7.27.1':
+ dependencies:
+ '@babel/types': 7.28.4
+
+ '@babel/helper-plugin-utils@7.27.1': {}
+
+ '@babel/helper-replace-supers@7.27.1(@babel/core@7.28.4)':
+ dependencies:
+ '@babel/core': 7.28.4
+ '@babel/helper-member-expression-to-functions': 7.27.1
+ '@babel/helper-optimise-call-expression': 7.27.1
+ '@babel/traverse': 7.28.4
+ transitivePeerDependencies:
+ - supports-color
+
+ '@babel/helper-skip-transparent-expression-wrappers@7.27.1':
+ dependencies:
+ '@babel/traverse': 7.28.4
+ '@babel/types': 7.28.4
+ transitivePeerDependencies:
+ - supports-color
+
+ '@babel/helper-string-parser@7.27.1': {}
+
+ '@babel/helper-validator-identifier@7.27.1': {}
+
+ '@babel/helper-validator-option@7.27.1': {}
+
+ '@babel/helpers@7.28.4':
+ dependencies:
+ '@babel/template': 7.27.2
+ '@babel/types': 7.28.4
+
+ '@babel/parser@7.28.4':
+ dependencies:
+ '@babel/types': 7.28.4
+
+ '@babel/plugin-syntax-decorators@7.27.1(@babel/core@7.28.4)':
+ dependencies:
+ '@babel/core': 7.28.4
+ '@babel/helper-plugin-utils': 7.27.1
+
+ '@babel/plugin-syntax-jsx@7.27.1(@babel/core@7.28.4)':
+ dependencies:
+ '@babel/core': 7.28.4
+ '@babel/helper-plugin-utils': 7.27.1
+
+ '@babel/plugin-syntax-typescript@7.27.1(@babel/core@7.28.4)':
+ dependencies:
+ '@babel/core': 7.28.4
+ '@babel/helper-plugin-utils': 7.27.1
+
+ '@babel/plugin-transform-modules-commonjs@7.27.1(@babel/core@7.28.4)':
+ dependencies:
+ '@babel/core': 7.28.4
+ '@babel/helper-module-transforms': 7.28.3(@babel/core@7.28.4)
+ '@babel/helper-plugin-utils': 7.27.1
+ transitivePeerDependencies:
+ - supports-color
+
+ '@babel/plugin-transform-typescript@7.28.0(@babel/core@7.28.4)':
+ dependencies:
+ '@babel/core': 7.28.4
+ '@babel/helper-annotate-as-pure': 7.27.3
+ '@babel/helper-create-class-features-plugin': 7.28.3(@babel/core@7.28.4)
+ '@babel/helper-plugin-utils': 7.27.1
+ '@babel/helper-skip-transparent-expression-wrappers': 7.27.1
+ '@babel/plugin-syntax-typescript': 7.27.1(@babel/core@7.28.4)
+ transitivePeerDependencies:
+ - supports-color
+
+ '@babel/preset-typescript@7.26.0(@babel/core@7.28.4)':
+ dependencies:
+ '@babel/core': 7.28.4
+ '@babel/helper-plugin-utils': 7.27.1
+ '@babel/helper-validator-option': 7.27.1
+ '@babel/plugin-syntax-jsx': 7.27.1(@babel/core@7.28.4)
+ '@babel/plugin-transform-modules-commonjs': 7.27.1(@babel/core@7.28.4)
+ '@babel/plugin-transform-typescript': 7.28.0(@babel/core@7.28.4)
+ transitivePeerDependencies:
+ - supports-color
+
+ '@babel/runtime@7.28.4': {}
+
+ '@babel/template@7.27.2':
+ dependencies:
+ '@babel/code-frame': 7.27.1
+ '@babel/parser': 7.28.4
+ '@babel/types': 7.28.4
+
+ '@babel/traverse@7.28.4':
+ dependencies:
+ '@babel/code-frame': 7.27.1
+ '@babel/generator': 7.28.3
+ '@babel/helper-globals': 7.28.0
+ '@babel/parser': 7.28.4
+ '@babel/template': 7.27.2
+ '@babel/types': 7.28.4
+ debug: 4.4.3
+ transitivePeerDependencies:
+ - supports-color
+
+ '@babel/types@7.28.4':
+ dependencies:
+ '@babel/helper-string-parser': 7.27.1
+ '@babel/helper-validator-identifier': 7.27.1
+
+ '@bcoe/v8-coverage@1.0.2': {}
+
+ '@biomejs/biome@1.9.4':
+ optionalDependencies:
+ '@biomejs/cli-darwin-arm64': 1.9.4
+ '@biomejs/cli-darwin-x64': 1.9.4
+ '@biomejs/cli-linux-arm64': 1.9.4
+ '@biomejs/cli-linux-arm64-musl': 1.9.4
+ '@biomejs/cli-linux-x64': 1.9.4
+ '@biomejs/cli-linux-x64-musl': 1.9.4
+ '@biomejs/cli-win32-arm64': 1.9.4
+ '@biomejs/cli-win32-x64': 1.9.4
+
+ '@biomejs/cli-darwin-arm64@1.9.4':
+ optional: true
+
+ '@biomejs/cli-darwin-x64@1.9.4':
+ optional: true
+
+ '@biomejs/cli-linux-arm64-musl@1.9.4':
+ optional: true
+
+ '@biomejs/cli-linux-arm64@1.9.4':
+ optional: true
+
+ '@biomejs/cli-linux-x64-musl@1.9.4':
+ optional: true
+
+ '@biomejs/cli-linux-x64@1.9.4':
+ optional: true
+
+ '@biomejs/cli-win32-arm64@1.9.4':
+ optional: true
+
+ '@biomejs/cli-win32-x64@1.9.4':
+ optional: true
+
+ '@bkrem/react-transition-group@1.3.5(react-dom@19.0.0(react@19.0.0))(react@19.0.0)':
+ dependencies:
+ chain-function: 1.0.1
+ dom-helpers: 3.4.0
+ loose-envify: 1.4.0
+ prop-types: 15.8.1
+ react: 19.0.0
+ react-dom: 19.0.0(react@19.0.0)
+ react-lifecycles-compat: 3.0.4
+ warning: 3.0.0
+
+ '@bundled-es-modules/cookie@2.0.1':
+ dependencies:
+ cookie: 0.7.2
+ optional: true
+
+ '@bundled-es-modules/statuses@1.0.1':
+ dependencies:
+ statuses: 2.0.2
+ optional: true
+
+ '@clerc/core@0.44.0':
+ dependencies:
+ '@clerc/utils': 0.44.0(@clerc/core@0.44.0)
+ defu: 6.1.4
+ is-platform: 1.0.0
+ lite-emit: 2.3.0
+ type-fest: 4.41.0
+ type-flag: 3.0.0
+
+ '@clerc/plugin-completions@0.44.0(@clerc/core@0.44.0)':
+ dependencies:
+ '@clerc/core': 0.44.0
+ '@clerc/utils': 0.44.0(@clerc/core@0.44.0)
+
+ '@clerc/plugin-help@0.44.0(@clerc/core@0.44.0)':
+ dependencies:
+ '@clerc/core': 0.44.0
+ '@clerc/utils': 0.44.0(@clerc/core@0.44.0)
+ '@types/text-table': 0.2.5
+ string-width: 6.1.0
+ text-table: 0.2.0
+ yoctocolors: 1.0.0
+
+ '@clerc/plugin-version@0.44.0(@clerc/core@0.44.0)':
+ dependencies:
+ '@clerc/core': 0.44.0
+ '@clerc/utils': 0.44.0(@clerc/core@0.44.0)
+
+ '@clerc/utils@0.44.0(@clerc/core@0.44.0)':
+ dependencies:
+ '@clerc/core': 0.44.0
+
+ '@colors/colors@1.5.0': {}
+
+ '@content-collections/cli@0.1.7(@content-collections/core@0.10.0(typescript@5.7.3))':
+ dependencies:
+ '@clerc/core': 0.44.0
+ '@clerc/plugin-completions': 0.44.0(@clerc/core@0.44.0)
+ '@clerc/plugin-help': 0.44.0(@clerc/core@0.44.0)
+ '@clerc/plugin-version': 0.44.0(@clerc/core@0.44.0)
+ '@content-collections/core': 0.10.0(typescript@5.7.3)
+ '@content-collections/integrations': 0.3.0(@content-collections/core@0.10.0(typescript@5.7.3))
+
+ '@content-collections/core@0.10.0(typescript@5.7.3)':
+ dependencies:
+ '@standard-schema/spec': 1.0.0
+ camelcase: 8.0.0
+ chokidar: 4.0.3
+ esbuild: 0.25.10
+ gray-matter: 4.0.3
+ p-limit: 6.2.0
+ picomatch: 4.0.3
+ pluralize: 8.0.0
+ serialize-javascript: 6.0.2
+ tinyglobby: 0.2.15
+ typescript: 5.7.3
+ yaml: 2.8.1
+ zod: 3.25.76
+
+ '@content-collections/integrations@0.3.0(@content-collections/core@0.10.0(typescript@5.7.3))':
+ dependencies:
+ '@content-collections/core': 0.10.0(typescript@5.7.3)
+
+ '@content-collections/mdx@0.2.2(@content-collections/core@0.10.0(typescript@5.7.3))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)':
+ dependencies:
+ '@content-collections/core': 0.10.0(typescript@5.7.3)
+ esbuild: 0.25.10
+ mdx-bundler: 10.1.1(esbuild@0.25.10)
+ react: 19.0.0
+ react-dom: 19.0.0(react@19.0.0)
+ unified: 11.0.5
+ transitivePeerDependencies:
+ - supports-color
+
+ '@content-collections/remix-vite@0.2.2(@content-collections/core@0.10.0(typescript@5.7.3))(vite@6.2.0(@types/node@22.13.1)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.19.2)(yaml@2.8.1))':
+ dependencies:
+ '@content-collections/core': 0.10.0(typescript@5.7.3)
+ vite: 6.2.0(@types/node@22.13.1)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.19.2)(yaml@2.8.1)
+
+ '@dotenvx/dotenvx@1.34.0':
+ dependencies:
+ commander: 11.1.0
+ dotenv: 16.6.1
+ eciesjs: 0.4.15
+ execa: 5.1.1
+ fdir: 6.5.0(picomatch@4.0.3)
+ ignore: 5.3.2
+ object-treeify: 1.1.33
+ picomatch: 4.0.3
+ which: 4.0.0
+
+ '@drizzle-team/brocli@0.11.0': {}
+
+ '@ecies/ciphers@0.2.4(@noble/ciphers@1.3.0)':
+ dependencies:
+ '@noble/ciphers': 1.3.0
+
+ '@emotion/babel-plugin@11.13.5':
+ dependencies:
+ '@babel/helper-module-imports': 7.27.1
+ '@babel/runtime': 7.28.4
+ '@emotion/hash': 0.9.2
+ '@emotion/memoize': 0.9.0
+ '@emotion/serialize': 1.3.3
+ babel-plugin-macros: 3.1.0
+ convert-source-map: 1.9.0
+ escape-string-regexp: 4.0.0
+ find-root: 1.1.0
+ source-map: 0.5.7
+ stylis: 4.2.0
+ transitivePeerDependencies:
+ - supports-color
+
+ '@emotion/cache@11.14.0':
+ dependencies:
+ '@emotion/memoize': 0.9.0
+ '@emotion/sheet': 1.4.0
+ '@emotion/utils': 1.4.2
+ '@emotion/weak-memoize': 0.4.0
+ stylis: 4.2.0
+
+ '@emotion/css@11.13.5':
+ dependencies:
+ '@emotion/babel-plugin': 11.13.5
+ '@emotion/cache': 11.14.0
+ '@emotion/serialize': 1.3.3
+ '@emotion/sheet': 1.4.0
+ '@emotion/utils': 1.4.2
+ transitivePeerDependencies:
+ - supports-color
+
+ '@emotion/hash@0.9.2': {}
+
+ '@emotion/memoize@0.9.0': {}
+
+ '@emotion/react@11.14.0(@types/react@19.0.8)(react@19.0.0)':
+ dependencies:
+ '@babel/runtime': 7.28.4
+ '@emotion/babel-plugin': 11.13.5
+ '@emotion/cache': 11.14.0
+ '@emotion/serialize': 1.3.3
+ '@emotion/use-insertion-effect-with-fallbacks': 1.2.0(react@19.0.0)
+ '@emotion/utils': 1.4.2
+ '@emotion/weak-memoize': 0.4.0
+ hoist-non-react-statics: 3.3.2
+ react: 19.0.0
+ optionalDependencies:
+ '@types/react': 19.0.8
+ transitivePeerDependencies:
+ - supports-color
+
+ '@emotion/serialize@1.3.3':
+ dependencies:
+ '@emotion/hash': 0.9.2
+ '@emotion/memoize': 0.9.0
+ '@emotion/unitless': 0.10.0
+ '@emotion/utils': 1.4.2
+ csstype: 3.1.3
+
+ '@emotion/sheet@1.4.0': {}
+
+ '@emotion/unitless@0.10.0': {}
+
+ '@emotion/use-insertion-effect-with-fallbacks@1.2.0(react@19.0.0)':
+ dependencies:
+ react: 19.0.0
+
+ '@emotion/utils@1.4.2': {}
+
+ '@emotion/weak-memoize@0.4.0': {}
+
+ '@epic-web/client-hints@1.3.5': {}
+
+ '@esbuild-plugins/node-resolve@0.2.2(esbuild@0.25.10)':
+ dependencies:
+ '@types/resolve': 1.20.6
+ debug: 4.4.3
+ esbuild: 0.25.10
+ escape-string-regexp: 4.0.0
+ resolve: 1.22.10
+ transitivePeerDependencies:
+ - supports-color
+
+ '@esbuild/aix-ppc64@0.23.1':
+ optional: true
+
+ '@esbuild/aix-ppc64@0.25.10':
+ optional: true
+
+ '@esbuild/android-arm64@0.23.1':
+ optional: true
+
+ '@esbuild/android-arm64@0.25.10':
+ optional: true
+
+ '@esbuild/android-arm@0.23.1':
+ optional: true
+
+ '@esbuild/android-arm@0.25.10':
+ optional: true
+
+ '@esbuild/android-x64@0.23.1':
+ optional: true
+
+ '@esbuild/android-x64@0.25.10':
+ optional: true
+
+ '@esbuild/darwin-arm64@0.23.1':
+ optional: true
+
+ '@esbuild/darwin-arm64@0.25.10':
+ optional: true
+
+ '@esbuild/darwin-x64@0.23.1':
+ optional: true
+
+ '@esbuild/darwin-x64@0.25.10':
+ optional: true
+
+ '@esbuild/freebsd-arm64@0.23.1':
+ optional: true
+
+ '@esbuild/freebsd-arm64@0.25.10':
+ optional: true
+
+ '@esbuild/freebsd-x64@0.23.1':
+ optional: true
+
+ '@esbuild/freebsd-x64@0.25.10':
+ optional: true
+
+ '@esbuild/linux-arm64@0.23.1':
+ optional: true
+
+ '@esbuild/linux-arm64@0.25.10':
+ optional: true
+
+ '@esbuild/linux-arm@0.23.1':
+ optional: true
+
+ '@esbuild/linux-arm@0.25.10':
+ optional: true
+
+ '@esbuild/linux-ia32@0.23.1':
+ optional: true
+
+ '@esbuild/linux-ia32@0.25.10':
+ optional: true
+
+ '@esbuild/linux-loong64@0.23.1':
+ optional: true
+
+ '@esbuild/linux-loong64@0.25.10':
+ optional: true
+
+ '@esbuild/linux-mips64el@0.23.1':
+ optional: true
+
+ '@esbuild/linux-mips64el@0.25.10':
+ optional: true
+
+ '@esbuild/linux-ppc64@0.23.1':
+ optional: true
+
+ '@esbuild/linux-ppc64@0.25.10':
+ optional: true
+
+ '@esbuild/linux-riscv64@0.23.1':
+ optional: true
+
+ '@esbuild/linux-riscv64@0.25.10':
+ optional: true
+
+ '@esbuild/linux-s390x@0.23.1':
+ optional: true
+
+ '@esbuild/linux-s390x@0.25.10':
+ optional: true
+
+ '@esbuild/linux-x64@0.23.1':
+ optional: true
+
+ '@esbuild/linux-x64@0.25.10':
+ optional: true
+
+ '@esbuild/netbsd-arm64@0.25.10':
+ optional: true
+
+ '@esbuild/netbsd-x64@0.23.1':
+ optional: true
+
+ '@esbuild/netbsd-x64@0.25.10':
+ optional: true
+
+ '@esbuild/openbsd-arm64@0.23.1':
+ optional: true
+
+ '@esbuild/openbsd-arm64@0.25.10':
+ optional: true
+
+ '@esbuild/openbsd-x64@0.23.1':
+ optional: true
+
+ '@esbuild/openbsd-x64@0.25.10':
+ optional: true
+
+ '@esbuild/openharmony-arm64@0.25.10':
+ optional: true
+
+ '@esbuild/sunos-x64@0.23.1':
+ optional: true
+
+ '@esbuild/sunos-x64@0.25.10':
+ optional: true
+
+ '@esbuild/win32-arm64@0.23.1':
+ optional: true
+
+ '@esbuild/win32-arm64@0.25.10':
+ optional: true
+
+ '@esbuild/win32-ia32@0.23.1':
+ optional: true
+
+ '@esbuild/win32-ia32@0.25.10':
+ optional: true
+
+ '@esbuild/win32-x64@0.23.1':
+ optional: true
+
+ '@esbuild/win32-x64@0.25.10':
+ optional: true
+
+ '@fal-works/esbuild-plugin-global-externals@2.1.2': {}
+
+ '@floating-ui/core@1.7.3':
+ dependencies:
+ '@floating-ui/utils': 0.2.10
+
+ '@floating-ui/dom@1.7.4':
+ dependencies:
+ '@floating-ui/core': 1.7.3
+ '@floating-ui/utils': 0.2.10
+
+ '@floating-ui/react-dom@2.1.6(react-dom@19.0.0(react@19.0.0))(react@19.0.0)':
+ dependencies:
+ '@floating-ui/dom': 1.7.4
+ react: 19.0.0
+ react-dom: 19.0.0(react@19.0.0)
+
+ '@floating-ui/utils@0.2.10': {}
+
+ '@forge42/seo-tools@1.3.0(typescript@5.7.3)':
+ dependencies:
+ url-pattern: 1.0.3
+ optionalDependencies:
+ '@remix-run/server-runtime': 2.17.1(typescript@5.7.3)
+ '@rollup/rollup-linux-arm64-gnu': 4.18.1
+ '@rollup/rollup-linux-x64-gnu': 4.52.4
+ '@rollup/rollup-linux-x64-musl': 4.52.4
+ '@rollup/rollup-win32-arm64-msvc': 4.18.1
+ '@rollup/rollup-win32-x64-msvc': 4.18.1
+ transitivePeerDependencies:
+ - typescript
+
+ '@hono/node-server@1.19.5(hono@4.6.20)':
+ dependencies:
+ hono: 4.6.20
+
+ '@hono/node-ws@1.2.0(@hono/node-server@1.19.5(hono@4.6.20))(hono@4.6.20)':
+ dependencies:
+ '@hono/node-server': 1.19.5(hono@4.6.20)
+ hono: 4.6.20
+ ws: 8.18.3
+ transitivePeerDependencies:
+ - bufferutil
+ - utf-8-validate
+
+ '@hono/vite-dev-server@0.17.0(hono@4.6.20)':
+ dependencies:
+ '@hono/node-server': 1.19.5(hono@4.6.20)
+ hono: 4.6.20
+ minimatch: 9.0.5
+
+ '@inquirer/ansi@1.0.0':
+ optional: true
+
+ '@inquirer/confirm@5.1.18(@types/node@22.13.1)':
+ dependencies:
+ '@inquirer/core': 10.2.2(@types/node@22.13.1)
+ '@inquirer/type': 3.0.8(@types/node@22.13.1)
+ optionalDependencies:
+ '@types/node': 22.13.1
+ optional: true
+
+ '@inquirer/core@10.2.2(@types/node@22.13.1)':
+ dependencies:
+ '@inquirer/ansi': 1.0.0
+ '@inquirer/figures': 1.0.13
+ '@inquirer/type': 3.0.8(@types/node@22.13.1)
+ cli-width: 4.1.0
+ mute-stream: 2.0.0
+ signal-exit: 4.1.0
+ wrap-ansi: 6.2.0
+ yoctocolors-cjs: 2.1.3
+ optionalDependencies:
+ '@types/node': 22.13.1
+ optional: true
+
+ '@inquirer/figures@1.0.13':
+ optional: true
+
+ '@inquirer/type@3.0.8(@types/node@22.13.1)':
+ optionalDependencies:
+ '@types/node': 22.13.1
+ optional: true
+
+ '@isaacs/balanced-match@4.0.1': {}
+
+ '@isaacs/brace-expansion@5.0.0':
+ dependencies:
+ '@isaacs/balanced-match': 4.0.1
+
+ '@isaacs/cliui@8.0.2':
+ dependencies:
+ string-width: 5.1.2
+ string-width-cjs: string-width@4.2.3
+ strip-ansi: 7.1.2
+ strip-ansi-cjs: strip-ansi@6.0.1
+ wrap-ansi: 8.1.0
+ wrap-ansi-cjs: wrap-ansi@7.0.0
+
+ '@istanbuljs/schema@0.1.3': {}
+
+ '@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/esbuild@3.1.1(esbuild@0.25.10)':
+ dependencies:
+ '@mdx-js/mdx': 3.1.1
+ '@types/unist': 3.0.3
+ esbuild: 0.25.10
+ source-map: 0.7.6
+ vfile: 6.0.3
+ vfile-message: 4.0.3
+ transitivePeerDependencies:
+ - supports-color
+
+ '@mdx-js/mdx@3.1.1':
+ dependencies:
+ '@types/estree': 1.0.8
+ '@types/estree-jsx': 1.0.5
+ '@types/hast': 3.0.4
+ '@types/mdx': 2.0.13
+ acorn: 8.15.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.15.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.0.0
+ vfile: 6.0.3
+ transitivePeerDependencies:
+ - supports-color
+
+ '@mjackson/node-fetch-server@0.2.0': {}
+
+ '@mswjs/interceptors@0.39.7':
+ dependencies:
+ '@open-draft/deferred-promise': 2.2.0
+ '@open-draft/logger': 0.3.0
+ '@open-draft/until': 2.1.0
+ is-node-process: 1.2.0
+ outvariant: 1.4.3
+ strict-event-emitter: 0.5.1
+ optional: true
+
+ '@noble/ciphers@1.3.0': {}
+
+ '@noble/curves@1.9.7':
+ dependencies:
+ '@noble/hashes': 1.8.0
+
+ '@noble/hashes@1.8.0': {}
+
+ '@nodelib/fs.scandir@2.1.5':
+ dependencies:
+ '@nodelib/fs.stat': 2.0.5
+ run-parallel: 1.2.0
+
+ '@nodelib/fs.scandir@4.0.1':
+ dependencies:
+ '@nodelib/fs.stat': 4.0.0
+ run-parallel: 1.2.0
+
+ '@nodelib/fs.stat@2.0.5': {}
+
+ '@nodelib/fs.stat@4.0.0': {}
+
+ '@nodelib/fs.walk@1.2.8':
+ dependencies:
+ '@nodelib/fs.scandir': 2.1.5
+ fastq: 1.19.1
+
+ '@nodelib/fs.walk@3.0.1':
+ dependencies:
+ '@nodelib/fs.scandir': 4.0.1
+ fastq: 1.19.1
+
+ '@npmcli/git@4.1.0':
+ dependencies:
+ '@npmcli/promise-spawn': 6.0.2
+ lru-cache: 7.18.3
+ npm-pick-manifest: 8.0.2
+ proc-log: 3.0.0
+ promise-inflight: 1.0.1
+ promise-retry: 2.0.1
+ semver: 7.7.2
+ which: 3.0.1
+ transitivePeerDependencies:
+ - bluebird
+
+ '@npmcli/package-json@4.0.1':
+ dependencies:
+ '@npmcli/git': 4.1.0
+ glob: 10.4.5
+ hosted-git-info: 6.1.3
+ json-parse-even-better-errors: 3.0.2
+ normalize-package-data: 5.0.0
+ proc-log: 3.0.0
+ semver: 7.7.2
+ transitivePeerDependencies:
+ - bluebird
+
+ '@npmcli/promise-spawn@6.0.2':
+ dependencies:
+ which: 3.0.1
+
+ '@one-ini/wasm@0.1.1': {}
+
+ '@open-draft/deferred-promise@2.2.0':
+ optional: true
+
+ '@open-draft/logger@0.3.0':
+ dependencies:
+ is-node-process: 1.2.0
+ outvariant: 1.4.3
+ optional: true
+
+ '@open-draft/until@2.1.0':
+ optional: true
+
+ '@pkgjs/parseargs@0.11.0':
+ optional: true
+
+ '@polka/url@1.0.0-next.29': {}
+
+ '@radix-ui/number@1.1.1': {}
+
+ '@radix-ui/primitive@1.1.3': {}
+
+ '@radix-ui/react-accordion@1.2.12(@types/react-dom@19.0.3(@types/react@19.0.8))(@types/react@19.0.8)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)':
+ dependencies:
+ '@radix-ui/primitive': 1.1.3
+ '@radix-ui/react-collapsible': 1.1.12(@types/react-dom@19.0.3(@types/react@19.0.8))(@types/react@19.0.8)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
+ '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.0.3(@types/react@19.0.8))(@types/react@19.0.8)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
+ '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.0.8)(react@19.0.0)
+ '@radix-ui/react-context': 1.1.2(@types/react@19.0.8)(react@19.0.0)
+ '@radix-ui/react-direction': 1.1.1(@types/react@19.0.8)(react@19.0.0)
+ '@radix-ui/react-id': 1.1.1(@types/react@19.0.8)(react@19.0.0)
+ '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.0.3(@types/react@19.0.8))(@types/react@19.0.8)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
+ '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.0.8)(react@19.0.0)
+ react: 19.0.0
+ react-dom: 19.0.0(react@19.0.0)
+ optionalDependencies:
+ '@types/react': 19.0.8
+ '@types/react-dom': 19.0.3(@types/react@19.0.8)
+
+ '@radix-ui/react-arrow@1.1.7(@types/react-dom@19.0.3(@types/react@19.0.8))(@types/react@19.0.8)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)':
+ dependencies:
+ '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.0.3(@types/react@19.0.8))(@types/react@19.0.8)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
+ react: 19.0.0
+ react-dom: 19.0.0(react@19.0.0)
+ optionalDependencies:
+ '@types/react': 19.0.8
+ '@types/react-dom': 19.0.3(@types/react@19.0.8)
+
+ '@radix-ui/react-collapsible@1.1.12(@types/react-dom@19.0.3(@types/react@19.0.8))(@types/react@19.0.8)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)':
+ dependencies:
+ '@radix-ui/primitive': 1.1.3
+ '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.0.8)(react@19.0.0)
+ '@radix-ui/react-context': 1.1.2(@types/react@19.0.8)(react@19.0.0)
+ '@radix-ui/react-id': 1.1.1(@types/react@19.0.8)(react@19.0.0)
+ '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.0.3(@types/react@19.0.8))(@types/react@19.0.8)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
+ '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.0.3(@types/react@19.0.8))(@types/react@19.0.8)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
+ '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.0.8)(react@19.0.0)
+ '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.0.8)(react@19.0.0)
+ react: 19.0.0
+ react-dom: 19.0.0(react@19.0.0)
+ optionalDependencies:
+ '@types/react': 19.0.8
+ '@types/react-dom': 19.0.3(@types/react@19.0.8)
+
+ '@radix-ui/react-collection@1.1.7(@types/react-dom@19.0.3(@types/react@19.0.8))(@types/react@19.0.8)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)':
+ dependencies:
+ '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.0.8)(react@19.0.0)
+ '@radix-ui/react-context': 1.1.2(@types/react@19.0.8)(react@19.0.0)
+ '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.0.3(@types/react@19.0.8))(@types/react@19.0.8)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
+ '@radix-ui/react-slot': 1.2.3(@types/react@19.0.8)(react@19.0.0)
+ react: 19.0.0
+ react-dom: 19.0.0(react@19.0.0)
+ optionalDependencies:
+ '@types/react': 19.0.8
+ '@types/react-dom': 19.0.3(@types/react@19.0.8)
+
+ '@radix-ui/react-compose-refs@1.1.2(@types/react@19.0.8)(react@19.0.0)':
+ dependencies:
+ react: 19.0.0
+ optionalDependencies:
+ '@types/react': 19.0.8
+
+ '@radix-ui/react-context@1.1.2(@types/react@19.0.8)(react@19.0.0)':
+ dependencies:
+ react: 19.0.0
+ optionalDependencies:
+ '@types/react': 19.0.8
+
+ '@radix-ui/react-direction@1.1.1(@types/react@19.0.8)(react@19.0.0)':
+ dependencies:
+ react: 19.0.0
+ optionalDependencies:
+ '@types/react': 19.0.8
+
+ '@radix-ui/react-dismissable-layer@1.1.11(@types/react-dom@19.0.3(@types/react@19.0.8))(@types/react@19.0.8)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)':
+ dependencies:
+ '@radix-ui/primitive': 1.1.3
+ '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.0.8)(react@19.0.0)
+ '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.0.3(@types/react@19.0.8))(@types/react@19.0.8)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
+ '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.0.8)(react@19.0.0)
+ '@radix-ui/react-use-escape-keydown': 1.1.1(@types/react@19.0.8)(react@19.0.0)
+ react: 19.0.0
+ react-dom: 19.0.0(react@19.0.0)
+ optionalDependencies:
+ '@types/react': 19.0.8
+ '@types/react-dom': 19.0.3(@types/react@19.0.8)
+
+ '@radix-ui/react-focus-guards@1.1.3(@types/react@19.0.8)(react@19.0.0)':
+ dependencies:
+ react: 19.0.0
+ optionalDependencies:
+ '@types/react': 19.0.8
+
+ '@radix-ui/react-focus-scope@1.1.7(@types/react-dom@19.0.3(@types/react@19.0.8))(@types/react@19.0.8)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)':
+ dependencies:
+ '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.0.8)(react@19.0.0)
+ '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.0.3(@types/react@19.0.8))(@types/react@19.0.8)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
+ '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.0.8)(react@19.0.0)
+ react: 19.0.0
+ react-dom: 19.0.0(react@19.0.0)
+ optionalDependencies:
+ '@types/react': 19.0.8
+ '@types/react-dom': 19.0.3(@types/react@19.0.8)
+
+ '@radix-ui/react-id@1.1.1(@types/react@19.0.8)(react@19.0.0)':
+ dependencies:
+ '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.0.8)(react@19.0.0)
+ react: 19.0.0
+ optionalDependencies:
+ '@types/react': 19.0.8
+
+ '@radix-ui/react-popper@1.2.8(@types/react-dom@19.0.3(@types/react@19.0.8))(@types/react@19.0.8)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)':
+ dependencies:
+ '@floating-ui/react-dom': 2.1.6(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
+ '@radix-ui/react-arrow': 1.1.7(@types/react-dom@19.0.3(@types/react@19.0.8))(@types/react@19.0.8)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
+ '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.0.8)(react@19.0.0)
+ '@radix-ui/react-context': 1.1.2(@types/react@19.0.8)(react@19.0.0)
+ '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.0.3(@types/react@19.0.8))(@types/react@19.0.8)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
+ '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.0.8)(react@19.0.0)
+ '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.0.8)(react@19.0.0)
+ '@radix-ui/react-use-rect': 1.1.1(@types/react@19.0.8)(react@19.0.0)
+ '@radix-ui/react-use-size': 1.1.1(@types/react@19.0.8)(react@19.0.0)
+ '@radix-ui/rect': 1.1.1
+ react: 19.0.0
+ react-dom: 19.0.0(react@19.0.0)
+ optionalDependencies:
+ '@types/react': 19.0.8
+ '@types/react-dom': 19.0.3(@types/react@19.0.8)
+
+ '@radix-ui/react-portal@1.1.9(@types/react-dom@19.0.3(@types/react@19.0.8))(@types/react@19.0.8)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)':
+ dependencies:
+ '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.0.3(@types/react@19.0.8))(@types/react@19.0.8)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
+ '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.0.8)(react@19.0.0)
+ react: 19.0.0
+ react-dom: 19.0.0(react@19.0.0)
+ optionalDependencies:
+ '@types/react': 19.0.8
+ '@types/react-dom': 19.0.3(@types/react@19.0.8)
+
+ '@radix-ui/react-presence@1.1.5(@types/react-dom@19.0.3(@types/react@19.0.8))(@types/react@19.0.8)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)':
+ dependencies:
+ '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.0.8)(react@19.0.0)
+ '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.0.8)(react@19.0.0)
+ react: 19.0.0
+ react-dom: 19.0.0(react@19.0.0)
+ optionalDependencies:
+ '@types/react': 19.0.8
+ '@types/react-dom': 19.0.3(@types/react@19.0.8)
+
+ '@radix-ui/react-primitive@2.1.3(@types/react-dom@19.0.3(@types/react@19.0.8))(@types/react@19.0.8)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)':
+ dependencies:
+ '@radix-ui/react-slot': 1.2.3(@types/react@19.0.8)(react@19.0.0)
+ react: 19.0.0
+ react-dom: 19.0.0(react@19.0.0)
+ optionalDependencies:
+ '@types/react': 19.0.8
+ '@types/react-dom': 19.0.3(@types/react@19.0.8)
+
+ '@radix-ui/react-select@2.2.6(@types/react-dom@19.0.3(@types/react@19.0.8))(@types/react@19.0.8)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)':
+ dependencies:
+ '@radix-ui/number': 1.1.1
+ '@radix-ui/primitive': 1.1.3
+ '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.0.3(@types/react@19.0.8))(@types/react@19.0.8)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
+ '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.0.8)(react@19.0.0)
+ '@radix-ui/react-context': 1.1.2(@types/react@19.0.8)(react@19.0.0)
+ '@radix-ui/react-direction': 1.1.1(@types/react@19.0.8)(react@19.0.0)
+ '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.0.3(@types/react@19.0.8))(@types/react@19.0.8)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
+ '@radix-ui/react-focus-guards': 1.1.3(@types/react@19.0.8)(react@19.0.0)
+ '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@19.0.3(@types/react@19.0.8))(@types/react@19.0.8)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
+ '@radix-ui/react-id': 1.1.1(@types/react@19.0.8)(react@19.0.0)
+ '@radix-ui/react-popper': 1.2.8(@types/react-dom@19.0.3(@types/react@19.0.8))(@types/react@19.0.8)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
+ '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.0.3(@types/react@19.0.8))(@types/react@19.0.8)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
+ '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.0.3(@types/react@19.0.8))(@types/react@19.0.8)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
+ '@radix-ui/react-slot': 1.2.3(@types/react@19.0.8)(react@19.0.0)
+ '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.0.8)(react@19.0.0)
+ '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.0.8)(react@19.0.0)
+ '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.0.8)(react@19.0.0)
+ '@radix-ui/react-use-previous': 1.1.1(@types/react@19.0.8)(react@19.0.0)
+ '@radix-ui/react-visually-hidden': 1.2.3(@types/react-dom@19.0.3(@types/react@19.0.8))(@types/react@19.0.8)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
+ aria-hidden: 1.2.6
+ react: 19.0.0
+ react-dom: 19.0.0(react@19.0.0)
+ react-remove-scroll: 2.7.1(@types/react@19.0.8)(react@19.0.0)
+ optionalDependencies:
+ '@types/react': 19.0.8
+ '@types/react-dom': 19.0.3(@types/react@19.0.8)
+
+ '@radix-ui/react-slot@1.2.3(@types/react@19.0.8)(react@19.0.0)':
+ dependencies:
+ '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.0.8)(react@19.0.0)
+ react: 19.0.0
+ optionalDependencies:
+ '@types/react': 19.0.8
+
+ '@radix-ui/react-use-callback-ref@1.1.1(@types/react@19.0.8)(react@19.0.0)':
+ dependencies:
+ react: 19.0.0
+ optionalDependencies:
+ '@types/react': 19.0.8
+
+ '@radix-ui/react-use-controllable-state@1.2.2(@types/react@19.0.8)(react@19.0.0)':
+ dependencies:
+ '@radix-ui/react-use-effect-event': 0.0.2(@types/react@19.0.8)(react@19.0.0)
+ '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.0.8)(react@19.0.0)
+ react: 19.0.0
+ optionalDependencies:
+ '@types/react': 19.0.8
+
+ '@radix-ui/react-use-effect-event@0.0.2(@types/react@19.0.8)(react@19.0.0)':
+ dependencies:
+ '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.0.8)(react@19.0.0)
+ react: 19.0.0
+ optionalDependencies:
+ '@types/react': 19.0.8
+
+ '@radix-ui/react-use-escape-keydown@1.1.1(@types/react@19.0.8)(react@19.0.0)':
+ dependencies:
+ '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.0.8)(react@19.0.0)
+ react: 19.0.0
+ optionalDependencies:
+ '@types/react': 19.0.8
+
+ '@radix-ui/react-use-layout-effect@1.1.1(@types/react@19.0.8)(react@19.0.0)':
+ dependencies:
+ react: 19.0.0
+ optionalDependencies:
+ '@types/react': 19.0.8
+
+ '@radix-ui/react-use-previous@1.1.1(@types/react@19.0.8)(react@19.0.0)':
+ dependencies:
+ react: 19.0.0
+ optionalDependencies:
+ '@types/react': 19.0.8
+
+ '@radix-ui/react-use-rect@1.1.1(@types/react@19.0.8)(react@19.0.0)':
+ dependencies:
+ '@radix-ui/rect': 1.1.1
+ react: 19.0.0
+ optionalDependencies:
+ '@types/react': 19.0.8
+
+ '@radix-ui/react-use-size@1.1.1(@types/react@19.0.8)(react@19.0.0)':
+ dependencies:
+ '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.0.8)(react@19.0.0)
+ react: 19.0.0
+ optionalDependencies:
+ '@types/react': 19.0.8
+
+ '@radix-ui/react-visually-hidden@1.2.3(@types/react-dom@19.0.3(@types/react@19.0.8))(@types/react@19.0.8)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)':
+ dependencies:
+ '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.0.3(@types/react@19.0.8))(@types/react@19.0.8)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
+ react: 19.0.0
+ react-dom: 19.0.0(react@19.0.0)
+ optionalDependencies:
+ '@types/react': 19.0.8
+ '@types/react-dom': 19.0.3(@types/react@19.0.8)
+
+ '@radix-ui/rect@1.1.1': {}
+
+ '@react-router/dev@7.2.0(@types/node@22.13.1)(babel-plugin-macros@3.1.0)(jiti@2.6.1)(lightningcss@1.30.2)(react-router@7.2.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(tsx@4.19.2)(typescript@5.7.3)(vite@6.2.0(@types/node@22.13.1)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.19.2)(yaml@2.8.1))(yaml@2.8.1)':
+ dependencies:
+ '@babel/core': 7.28.4
+ '@babel/generator': 7.28.3
+ '@babel/parser': 7.28.4
+ '@babel/plugin-syntax-decorators': 7.27.1(@babel/core@7.28.4)
+ '@babel/plugin-syntax-jsx': 7.27.1(@babel/core@7.28.4)
+ '@babel/preset-typescript': 7.26.0(@babel/core@7.28.4)
+ '@babel/traverse': 7.28.4
+ '@babel/types': 7.28.4
+ '@npmcli/package-json': 4.0.1
+ '@react-router/node': 7.2.0(react-router@7.2.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(typescript@5.7.3)
+ arg: 5.0.2
+ babel-dead-code-elimination: 1.0.10
+ chokidar: 4.0.3
+ dedent: 1.7.0(babel-plugin-macros@3.1.0)
+ es-module-lexer: 1.7.0
+ exit-hook: 2.2.1
+ fs-extra: 10.1.0
+ gunzip-maybe: 1.4.2
+ jsesc: 3.0.2
+ lodash: 4.17.21
+ pathe: 1.1.2
+ picocolors: 1.1.1
+ picomatch: 2.3.1
+ prettier: 2.8.8
+ react-refresh: 0.14.2
+ react-router: 7.2.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
+ semver: 7.7.2
+ set-cookie-parser: 2.7.1
+ valibot: 0.41.0(typescript@5.7.3)
+ vite: 6.2.0(@types/node@22.13.1)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.19.2)(yaml@2.8.1)
+ vite-node: 3.0.0-beta.2(@types/node@22.13.1)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.19.2)(yaml@2.8.1)
+ optionalDependencies:
+ typescript: 5.7.3
+ transitivePeerDependencies:
+ - '@types/node'
+ - babel-plugin-macros
+ - bluebird
+ - jiti
+ - less
+ - lightningcss
+ - sass
+ - sass-embedded
+ - stylus
+ - sugarss
+ - supports-color
+ - terser
+ - tsx
+ - yaml
+
+ '@react-router/node@7.2.0(react-router@7.2.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(typescript@5.7.3)':
+ dependencies:
+ '@mjackson/node-fetch-server': 0.2.0
+ react-router: 7.2.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
+ source-map-support: 0.5.21
+ stream-slice: 0.1.2
+ undici: 6.22.0
+ optionalDependencies:
+ typescript: 5.7.3
+
+ '@remix-run/router@1.23.0':
+ optional: true
+
+ '@remix-run/server-runtime@2.17.1(typescript@5.7.3)':
+ dependencies:
+ '@remix-run/router': 1.23.0
+ '@types/cookie': 0.6.0
+ '@web3-storage/multipart-parser': 1.0.0
+ cookie: 0.7.2
+ set-cookie-parser: 2.7.1
+ source-map: 0.7.6
+ turbo-stream: 2.4.1
+ optionalDependencies:
+ typescript: 5.7.3
+ optional: true
+
+ '@rollup/rollup-android-arm-eabi@4.52.4':
+ optional: true
+
+ '@rollup/rollup-android-arm64@4.52.4':
+ optional: true
+
+ '@rollup/rollup-darwin-arm64@4.52.4':
+ optional: true
+
+ '@rollup/rollup-darwin-x64@4.52.4':
+ optional: true
+
+ '@rollup/rollup-freebsd-arm64@4.52.4':
+ optional: true
+
+ '@rollup/rollup-freebsd-x64@4.52.4':
+ optional: true
+
+ '@rollup/rollup-linux-arm-gnueabihf@4.52.4':
+ optional: true
+
+ '@rollup/rollup-linux-arm-musleabihf@4.52.4':
+ optional: true
+
+ '@rollup/rollup-linux-arm64-gnu@4.18.1':
+ optional: true
+
+ '@rollup/rollup-linux-arm64-gnu@4.52.4':
+ optional: true
+
+ '@rollup/rollup-linux-arm64-musl@4.52.4':
+ optional: true
+
+ '@rollup/rollup-linux-loong64-gnu@4.52.4':
+ optional: true
+
+ '@rollup/rollup-linux-ppc64-gnu@4.52.4':
+ optional: true
+
+ '@rollup/rollup-linux-riscv64-gnu@4.52.4':
+ optional: true
+
+ '@rollup/rollup-linux-riscv64-musl@4.52.4':
+ optional: true
+
+ '@rollup/rollup-linux-s390x-gnu@4.52.4':
+ optional: true
+
+ '@rollup/rollup-linux-x64-gnu@4.52.4':
+ optional: true
+
+ '@rollup/rollup-linux-x64-musl@4.52.4':
+ optional: true
+
+ '@rollup/rollup-openharmony-arm64@4.52.4':
+ optional: true
+
+ '@rollup/rollup-win32-arm64-msvc@4.18.1':
+ optional: true
+
+ '@rollup/rollup-win32-arm64-msvc@4.52.4':
+ optional: true
+
+ '@rollup/rollup-win32-ia32-msvc@4.52.4':
+ optional: true
+
+ '@rollup/rollup-win32-x64-gnu@4.52.4':
+ optional: true
+
+ '@rollup/rollup-win32-x64-msvc@4.18.1':
+ optional: true
+
+ '@rollup/rollup-win32-x64-msvc@4.52.4':
+ optional: true
+
+ '@snyk/github-codeowners@1.1.0':
+ dependencies:
+ commander: 4.1.1
+ ignore: 5.3.2
+ p-map: 4.0.0
+
+ '@standard-schema/spec@1.0.0': {}
+
+ '@tailwindcss/node@4.0.9':
+ dependencies:
+ enhanced-resolve: 5.18.3
+ jiti: 2.6.1
+ tailwindcss: 4.0.9
+
+ '@tailwindcss/oxide-android-arm64@4.0.9':
+ optional: true
+
+ '@tailwindcss/oxide-darwin-arm64@4.0.9':
+ optional: true
+
+ '@tailwindcss/oxide-darwin-x64@4.0.9':
+ optional: true
+
+ '@tailwindcss/oxide-freebsd-x64@4.0.9':
+ optional: true
+
+ '@tailwindcss/oxide-linux-arm-gnueabihf@4.0.9':
+ optional: true
+
+ '@tailwindcss/oxide-linux-arm64-gnu@4.0.9':
+ optional: true
+
+ '@tailwindcss/oxide-linux-arm64-musl@4.0.9':
+ optional: true
+
+ '@tailwindcss/oxide-linux-x64-gnu@4.0.9':
+ optional: true
+
+ '@tailwindcss/oxide-linux-x64-musl@4.0.9':
+ optional: true
+
+ '@tailwindcss/oxide-win32-arm64-msvc@4.0.9':
+ optional: true
+
+ '@tailwindcss/oxide-win32-x64-msvc@4.0.9':
+ optional: true
+
+ '@tailwindcss/oxide@4.0.9':
+ optionalDependencies:
+ '@tailwindcss/oxide-android-arm64': 4.0.9
+ '@tailwindcss/oxide-darwin-arm64': 4.0.9
+ '@tailwindcss/oxide-darwin-x64': 4.0.9
+ '@tailwindcss/oxide-freebsd-x64': 4.0.9
+ '@tailwindcss/oxide-linux-arm-gnueabihf': 4.0.9
+ '@tailwindcss/oxide-linux-arm64-gnu': 4.0.9
+ '@tailwindcss/oxide-linux-arm64-musl': 4.0.9
+ '@tailwindcss/oxide-linux-x64-gnu': 4.0.9
+ '@tailwindcss/oxide-linux-x64-musl': 4.0.9
+ '@tailwindcss/oxide-win32-arm64-msvc': 4.0.9
+ '@tailwindcss/oxide-win32-x64-msvc': 4.0.9
+
+ '@tailwindcss/typography@0.5.16(tailwindcss@4.0.9)':
+ dependencies:
+ lodash.castarray: 4.4.0
+ lodash.isplainobject: 4.0.6
+ lodash.merge: 4.6.2
+ postcss-selector-parser: 6.0.10
+ tailwindcss: 4.0.9
+
+ '@tailwindcss/vite@4.0.9(vite@6.2.0(@types/node@22.13.1)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.19.2)(yaml@2.8.1))':
+ dependencies:
+ '@tailwindcss/node': 4.0.9
+ '@tailwindcss/oxide': 4.0.9
+ lightningcss: 1.30.2
+ tailwindcss: 4.0.9
+ vite: 6.2.0(@types/node@22.13.1)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.19.2)(yaml@2.8.1)
+
+ '@testing-library/dom@10.4.1':
+ dependencies:
+ '@babel/code-frame': 7.27.1
+ '@babel/runtime': 7.28.4
+ '@types/aria-query': 5.0.4
+ aria-query: 5.3.0
+ dom-accessibility-api: 0.5.16
+ lz-string: 1.5.0
+ picocolors: 1.1.1
+ pretty-format: 27.5.1
+
+ '@testing-library/react@16.2.0(@testing-library/dom@10.4.1)(@types/react-dom@19.0.3(@types/react@19.0.8))(@types/react@19.0.8)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)':
+ dependencies:
+ '@babel/runtime': 7.28.4
+ '@testing-library/dom': 10.4.1
+ react: 19.0.0
+ react-dom: 19.0.0(react@19.0.0)
+ optionalDependencies:
+ '@types/react': 19.0.8
+ '@types/react-dom': 19.0.3(@types/react@19.0.8)
+
+ '@testing-library/user-event@14.6.1(@testing-library/dom@10.4.1)':
+ dependencies:
+ '@testing-library/dom': 10.4.1
+
+ '@types/aria-query@5.0.4': {}
+
+ '@types/chai@5.2.2':
+ dependencies:
+ '@types/deep-eql': 4.0.2
+
+ '@types/cookie@0.6.0': {}
+
+ '@types/d3-hierarchy@1.1.11': {}
+
+ '@types/debug@4.1.12':
+ dependencies:
+ '@types/ms': 2.1.0
+
+ '@types/deep-eql@4.0.2': {}
+
+ '@types/estree-jsx@1.0.5':
+ dependencies:
+ '@types/estree': 1.0.8
+
+ '@types/estree@1.0.8': {}
+
+ '@types/hast@3.0.4':
+ dependencies:
+ '@types/unist': 3.0.3
+
+ '@types/mdast@4.0.4':
+ dependencies:
+ '@types/unist': 3.0.3
+
+ '@types/mdx@2.0.13': {}
+
+ '@types/ms@2.1.0': {}
+
+ '@types/node@22.13.1':
+ dependencies:
+ undici-types: 6.20.0
+
+ '@types/parse-json@4.0.2': {}
+
+ '@types/prompt@1.1.9':
+ dependencies:
+ '@types/node': 22.13.1
+ '@types/revalidator': 0.3.12
+
+ '@types/react-dom@19.0.3(@types/react@19.0.8)':
+ dependencies:
+ '@types/react': 19.0.8
+
+ '@types/react-reconciler@0.28.9(@types/react@19.0.8)':
+ dependencies:
+ '@types/react': 19.0.8
+
+ '@types/react@19.0.8':
+ dependencies:
+ csstype: 3.1.3
+
+ '@types/resolve@1.20.6': {}
+
+ '@types/revalidator@0.3.12': {}
+
+ '@types/semver@7.7.0': {}
+
+ '@types/slug@5.0.9': {}
+
+ '@types/statuses@2.0.6':
+ optional: true
+
+ '@types/text-table@0.2.5': {}
+
+ '@types/unist@2.0.11': {}
+
+ '@types/unist@3.0.3': {}
+
+ '@ungap/structured-clone@1.3.0': {}
+
+ '@vitest/browser@3.2.4(msw@2.11.3(@types/node@22.13.1)(typescript@5.7.3))(playwright@1.50.1)(vite@6.2.0(@types/node@22.13.1)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.19.2)(yaml@2.8.1))(vitest@3.2.4)':
+ dependencies:
+ '@testing-library/dom': 10.4.1
+ '@testing-library/user-event': 14.6.1(@testing-library/dom@10.4.1)
+ '@vitest/mocker': 3.2.4(msw@2.11.3(@types/node@22.13.1)(typescript@5.7.3))(vite@6.2.0(@types/node@22.13.1)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.19.2)(yaml@2.8.1))
+ '@vitest/utils': 3.2.4
+ magic-string: 0.30.19
+ sirv: 3.0.2
+ tinyrainbow: 2.0.0
+ vitest: 3.2.4(@types/debug@4.1.12)(@types/node@22.13.1)(@vitest/browser@3.2.4)(@vitest/ui@3.2.4)(happy-dom@16.8.1)(jiti@2.6.1)(lightningcss@1.30.2)(msw@2.11.3(@types/node@22.13.1)(typescript@5.7.3))(tsx@4.19.2)(yaml@2.8.1)
+ ws: 8.18.3
+ optionalDependencies:
+ playwright: 1.50.1
+ transitivePeerDependencies:
+ - bufferutil
+ - msw
+ - utf-8-validate
+ - vite
+
+ '@vitest/coverage-v8@3.2.4(@vitest/browser@3.2.4)(vitest@3.2.4)':
+ dependencies:
+ '@ampproject/remapping': 2.3.0
+ '@bcoe/v8-coverage': 1.0.2
+ ast-v8-to-istanbul: 0.3.7
+ debug: 4.4.3
+ istanbul-lib-coverage: 3.2.2
+ istanbul-lib-report: 3.0.1
+ istanbul-lib-source-maps: 5.0.6
+ istanbul-reports: 3.2.0
+ magic-string: 0.30.19
+ magicast: 0.3.5
+ std-env: 3.9.0
+ test-exclude: 7.0.1
+ tinyrainbow: 2.0.0
+ vitest: 3.2.4(@types/debug@4.1.12)(@types/node@22.13.1)(@vitest/browser@3.2.4)(@vitest/ui@3.2.4)(happy-dom@16.8.1)(jiti@2.6.1)(lightningcss@1.30.2)(msw@2.11.3(@types/node@22.13.1)(typescript@5.7.3))(tsx@4.19.2)(yaml@2.8.1)
+ optionalDependencies:
+ '@vitest/browser': 3.2.4(msw@2.11.3(@types/node@22.13.1)(typescript@5.7.3))(playwright@1.50.1)(vite@6.2.0(@types/node@22.13.1)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.19.2)(yaml@2.8.1))(vitest@3.2.4)
+ transitivePeerDependencies:
+ - supports-color
+
+ '@vitest/expect@3.2.4':
+ dependencies:
+ '@types/chai': 5.2.2
+ '@vitest/spy': 3.2.4
+ '@vitest/utils': 3.2.4
+ chai: 5.3.3
+ tinyrainbow: 2.0.0
+
+ '@vitest/mocker@3.2.4(msw@2.11.3(@types/node@22.13.1)(typescript@5.7.3))(vite@6.2.0(@types/node@22.13.1)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.19.2)(yaml@2.8.1))':
+ dependencies:
+ '@vitest/spy': 3.2.4
+ estree-walker: 3.0.3
+ magic-string: 0.30.19
+ optionalDependencies:
+ msw: 2.11.3(@types/node@22.13.1)(typescript@5.7.3)
+ vite: 6.2.0(@types/node@22.13.1)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.19.2)(yaml@2.8.1)
+
+ '@vitest/pretty-format@3.2.4':
+ dependencies:
+ tinyrainbow: 2.0.0
+
+ '@vitest/runner@3.2.4':
+ dependencies:
+ '@vitest/utils': 3.2.4
+ pathe: 2.0.3
+ strip-literal: 3.1.0
+
+ '@vitest/snapshot@3.2.4':
+ dependencies:
+ '@vitest/pretty-format': 3.2.4
+ magic-string: 0.30.19
+ pathe: 2.0.3
+
+ '@vitest/spy@3.2.4':
+ dependencies:
+ tinyspy: 4.0.4
+
+ '@vitest/ui@3.2.4(vitest@3.2.4)':
+ dependencies:
+ '@vitest/utils': 3.2.4
+ fflate: 0.8.2
+ flatted: 3.3.3
+ pathe: 2.0.3
+ sirv: 3.0.2
+ tinyglobby: 0.2.15
+ tinyrainbow: 2.0.0
+ vitest: 3.2.4(@types/debug@4.1.12)(@types/node@22.13.1)(@vitest/browser@3.2.4)(@vitest/ui@3.2.4)(happy-dom@16.8.1)(jiti@2.6.1)(lightningcss@1.30.2)(msw@2.11.3(@types/node@22.13.1)(typescript@5.7.3))(tsx@4.19.2)(yaml@2.8.1)
+
+ '@vitest/utils@3.2.4':
+ dependencies:
+ '@vitest/pretty-format': 3.2.4
+ loupe: 3.2.1
+ tinyrainbow: 2.0.0
+
+ '@web3-storage/multipart-parser@1.0.0':
+ optional: true
+
+ abbrev@2.0.0: {}
+
+ acorn-jsx@5.3.2(acorn@8.15.0):
+ dependencies:
+ acorn: 8.15.0
+
+ acorn@8.15.0: {}
+
+ aggregate-error@3.1.0:
+ dependencies:
+ clean-stack: 2.2.0
+ indent-string: 4.0.0
+
+ ansi-regex@5.0.1: {}
+
+ ansi-regex@6.2.2: {}
+
+ ansi-styles@4.3.0:
+ dependencies:
+ color-convert: 2.0.1
+
+ ansi-styles@5.2.0: {}
+
+ ansi-styles@6.2.3: {}
+
+ arg@5.0.2: {}
+
+ argparse@1.0.10:
+ dependencies:
+ sprintf-js: 1.0.3
+
+ argparse@2.0.1: {}
+
+ aria-hidden@1.2.6:
+ dependencies:
+ tslib: 2.8.1
+
+ aria-query@5.3.0:
+ dependencies:
+ dequal: 2.0.3
+
+ assertion-error@2.0.1: {}
+
+ ast-v8-to-istanbul@0.3.7:
+ dependencies:
+ '@jridgewell/trace-mapping': 0.3.31
+ estree-walker: 3.0.3
+ js-tokens: 9.0.1
+
+ astring@1.9.0: {}
+
+ async@2.6.4:
+ dependencies:
+ lodash: 4.17.21
+
+ async@3.2.3: {}
+
+ babel-dead-code-elimination@1.0.10:
+ dependencies:
+ '@babel/core': 7.28.4
+ '@babel/parser': 7.28.4
+ '@babel/traverse': 7.28.4
+ '@babel/types': 7.28.4
+ transitivePeerDependencies:
+ - supports-color
+
+ babel-plugin-macros@3.1.0:
+ dependencies:
+ '@babel/runtime': 7.28.4
+ cosmiconfig: 7.1.0
+ resolve: 1.22.10
+
+ babel-plugin-react-compiler@19.0.0-beta-df7b47d-20241124:
+ dependencies:
+ '@babel/types': 7.28.4
+
+ bail@2.0.2: {}
+
+ balanced-match@1.0.2: {}
+
+ baseline-browser-mapping@2.8.12: {}
+
+ beautify@0.0.8:
+ dependencies:
+ cssbeautify: 0.3.1
+ html: 1.0.0
+ js-beautify: 1.15.4
+
+ bippy@0.3.27(@types/react@19.0.8)(react@19.0.0):
+ dependencies:
+ '@types/react-reconciler': 0.28.9(@types/react@19.0.8)
+ react: 19.0.0
+ transitivePeerDependencies:
+ - '@types/react'
+
+ boolbase@1.0.0: {}
+
+ brace-expansion@2.0.2:
+ dependencies:
+ balanced-match: 1.0.2
+
+ braces@3.0.3:
+ dependencies:
+ fill-range: 7.1.1
+
+ browserify-zlib@0.1.4:
+ dependencies:
+ pako: 0.2.9
+
+ browserslist@4.26.3:
+ dependencies:
+ baseline-browser-mapping: 2.8.12
+ caniuse-lite: 1.0.30001748
+ electron-to-chromium: 1.5.230
+ node-releases: 2.0.23
+ update-browserslist-db: 1.1.3(browserslist@4.26.3)
+
+ buffer-from@1.1.2: {}
+
+ cac@6.7.14: {}
+
+ callsites@3.1.0: {}
+
+ camelcase@8.0.0: {}
+
+ caniuse-lite@1.0.30001748: {}
+
+ ccount@2.0.1: {}
+
+ chai@5.3.3:
+ dependencies:
+ assertion-error: 2.0.1
+ check-error: 2.1.1
+ deep-eql: 5.0.2
+ loupe: 3.2.1
+ pathval: 2.0.1
+
+ chain-function@1.0.1: {}
+
+ chalk@5.4.1: {}
+
+ character-entities-html4@2.1.0: {}
+
+ character-entities-legacy@3.0.0: {}
+
+ character-entities@2.0.2: {}
+
+ character-reference-invalid@2.0.1: {}
+
+ check-error@2.1.1: {}
+
+ chokidar@4.0.3:
+ dependencies:
+ readdirp: 4.1.2
+
+ classnames@2.5.1: {}
+
+ clean-stack@2.2.0: {}
+
+ cli-width@4.1.0:
+ optional: true
+
+ cliui@8.0.1:
+ dependencies:
+ string-width: 4.2.3
+ strip-ansi: 6.0.1
+ wrap-ansi: 7.0.0
+ optional: true
+
+ clone@1.0.4:
+ optional: true
+
+ clone@2.1.2: {}
+
+ clsx@2.1.1: {}
+
+ collapse-white-space@2.1.0: {}
+
+ color-convert@2.0.1:
+ dependencies:
+ color-name: 1.1.4
+
+ color-name@1.1.4: {}
+
+ colors@1.0.3: {}
+
+ comma-separated-tokens@2.0.3: {}
+
+ commander@10.0.1: {}
+
+ commander@11.1.0: {}
+
+ commander@4.1.1: {}
+
+ concat-stream@1.6.2:
+ dependencies:
+ buffer-from: 1.1.2
+ inherits: 2.0.4
+ readable-stream: 2.3.8
+ typedarray: 0.0.6
+
+ config-chain@1.1.13:
+ dependencies:
+ ini: 1.3.8
+ proto-list: 1.2.4
+
+ convert-source-map@1.9.0: {}
+
+ convert-source-map@2.0.0: {}
+
+ cookie@0.7.2:
+ optional: true
+
+ cookie@1.0.2: {}
+
+ core-util-is@1.0.3: {}
+
+ cosmiconfig@7.1.0:
+ dependencies:
+ '@types/parse-json': 4.0.2
+ import-fresh: 3.3.1
+ parse-json: 5.2.0
+ path-type: 4.0.0
+ yaml: 1.10.2
+
+ cross-fetch@4.0.0:
+ dependencies:
+ node-fetch: 2.7.0
+ transitivePeerDependencies:
+ - encoding
+
+ cross-spawn@7.0.6:
+ dependencies:
+ path-key: 3.1.1
+ shebang-command: 2.0.0
+ which: 2.0.2
+
+ 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-what@6.2.2: {}
+
+ cssbeautify@0.3.1: {}
+
+ cssesc@3.0.0: {}
+
+ csstype@3.1.3: {}
+
+ cycle@1.0.3: {}
+
+ d3-color@3.1.0: {}
+
+ d3-dispatch@3.0.1: {}
+
+ d3-drag@3.0.0:
+ dependencies:
+ d3-dispatch: 3.0.1
+ d3-selection: 3.0.0
+
+ d3-ease@3.0.1: {}
+
+ d3-hierarchy@1.1.9: {}
+
+ d3-interpolate@3.0.1:
+ dependencies:
+ d3-color: 3.1.0
+
+ d3-path@1.0.9: {}
+
+ d3-selection@3.0.0: {}
+
+ d3-shape@1.3.7:
+ dependencies:
+ d3-path: 1.0.9
+
+ d3-timer@3.0.1: {}
+
+ d3-transition@3.0.1(d3-selection@3.0.0):
+ dependencies:
+ d3-color: 3.1.0
+ d3-dispatch: 3.0.1
+ d3-ease: 3.0.1
+ d3-interpolate: 3.0.1
+ d3-selection: 3.0.0
+ d3-timer: 3.0.1
+
+ d3-zoom@3.0.0:
+ dependencies:
+ d3-dispatch: 3.0.1
+ d3-drag: 3.0.0
+ d3-interpolate: 3.0.1
+ d3-selection: 3.0.0
+ d3-transition: 3.0.1(d3-selection@3.0.0)
+
+ date-fns@4.1.0: {}
+
+ debug@4.4.3:
+ dependencies:
+ ms: 2.1.3
+
+ decode-named-character-reference@1.2.0:
+ dependencies:
+ character-entities: 2.0.2
+
+ dedent@1.7.0(babel-plugin-macros@3.1.0):
+ optionalDependencies:
+ babel-plugin-macros: 3.1.0
+
+ deep-eql@5.0.2: {}
+
+ defaults@1.0.4:
+ dependencies:
+ clone: 1.0.4
+ optional: true
+
+ defu@6.1.4: {}
+
+ dequal@2.0.3: {}
+
+ detect-libc@2.1.2: {}
+
+ detect-node-es@1.1.0: {}
+
+ devlop@1.1.0:
+ dependencies:
+ dequal: 2.0.3
+
+ diff@5.2.0: {}
+
+ dom-accessibility-api@0.5.16: {}
+
+ dom-helpers@3.4.0:
+ dependencies:
+ '@babel/runtime': 7.28.4
+
+ 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
+
+ dotenv@16.6.1: {}
+
+ duplexify@3.7.1:
+ dependencies:
+ end-of-stream: 1.4.5
+ inherits: 2.0.4
+ readable-stream: 2.3.8
+ stream-shift: 1.0.3
+
+ eastasianwidth@0.2.0: {}
+
+ easy-table@1.2.0:
+ dependencies:
+ ansi-regex: 5.0.1
+ optionalDependencies:
+ wcwidth: 1.0.1
+
+ eciesjs@0.4.15:
+ dependencies:
+ '@ecies/ciphers': 0.2.4(@noble/ciphers@1.3.0)
+ '@noble/ciphers': 1.3.0
+ '@noble/curves': 1.9.7
+ '@noble/hashes': 1.8.0
+
+ editorconfig@1.0.4:
+ dependencies:
+ '@one-ini/wasm': 0.1.1
+ commander: 10.0.1
+ minimatch: 9.0.1
+ semver: 7.7.2
+
+ electron-to-chromium@1.5.230: {}
+
+ emoji-regex@10.5.0: {}
+
+ emoji-regex@8.0.0: {}
+
+ emoji-regex@9.2.2: {}
+
+ end-of-stream@1.4.5:
+ dependencies:
+ once: 1.4.0
+
+ enhanced-resolve@5.18.3:
+ dependencies:
+ graceful-fs: 4.2.11
+ tapable: 2.3.0
+
+ entities@4.5.0: {}
+
+ err-code@2.0.3: {}
+
+ error-ex@1.3.4:
+ dependencies:
+ is-arrayish: 0.2.1
+
+ es-module-lexer@1.7.0: {}
+
+ 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.15.0
+ esast-util-from-estree: 2.0.0
+ vfile-message: 4.0.3
+
+ esbuild@0.23.1:
+ optionalDependencies:
+ '@esbuild/aix-ppc64': 0.23.1
+ '@esbuild/android-arm': 0.23.1
+ '@esbuild/android-arm64': 0.23.1
+ '@esbuild/android-x64': 0.23.1
+ '@esbuild/darwin-arm64': 0.23.1
+ '@esbuild/darwin-x64': 0.23.1
+ '@esbuild/freebsd-arm64': 0.23.1
+ '@esbuild/freebsd-x64': 0.23.1
+ '@esbuild/linux-arm': 0.23.1
+ '@esbuild/linux-arm64': 0.23.1
+ '@esbuild/linux-ia32': 0.23.1
+ '@esbuild/linux-loong64': 0.23.1
+ '@esbuild/linux-mips64el': 0.23.1
+ '@esbuild/linux-ppc64': 0.23.1
+ '@esbuild/linux-riscv64': 0.23.1
+ '@esbuild/linux-s390x': 0.23.1
+ '@esbuild/linux-x64': 0.23.1
+ '@esbuild/netbsd-x64': 0.23.1
+ '@esbuild/openbsd-arm64': 0.23.1
+ '@esbuild/openbsd-x64': 0.23.1
+ '@esbuild/sunos-x64': 0.23.1
+ '@esbuild/win32-arm64': 0.23.1
+ '@esbuild/win32-ia32': 0.23.1
+ '@esbuild/win32-x64': 0.23.1
+
+ esbuild@0.25.10:
+ optionalDependencies:
+ '@esbuild/aix-ppc64': 0.25.10
+ '@esbuild/android-arm': 0.25.10
+ '@esbuild/android-arm64': 0.25.10
+ '@esbuild/android-x64': 0.25.10
+ '@esbuild/darwin-arm64': 0.25.10
+ '@esbuild/darwin-x64': 0.25.10
+ '@esbuild/freebsd-arm64': 0.25.10
+ '@esbuild/freebsd-x64': 0.25.10
+ '@esbuild/linux-arm': 0.25.10
+ '@esbuild/linux-arm64': 0.25.10
+ '@esbuild/linux-ia32': 0.25.10
+ '@esbuild/linux-loong64': 0.25.10
+ '@esbuild/linux-mips64el': 0.25.10
+ '@esbuild/linux-ppc64': 0.25.10
+ '@esbuild/linux-riscv64': 0.25.10
+ '@esbuild/linux-s390x': 0.25.10
+ '@esbuild/linux-x64': 0.25.10
+ '@esbuild/netbsd-arm64': 0.25.10
+ '@esbuild/netbsd-x64': 0.25.10
+ '@esbuild/openbsd-arm64': 0.25.10
+ '@esbuild/openbsd-x64': 0.25.10
+ '@esbuild/openharmony-arm64': 0.25.10
+ '@esbuild/sunos-x64': 0.25.10
+ '@esbuild/win32-arm64': 0.25.10
+ '@esbuild/win32-ia32': 0.25.10
+ '@esbuild/win32-x64': 0.25.10
+
+ escalade@3.2.0: {}
+
+ escape-string-regexp@4.0.0: {}
+
+ escape-string-regexp@5.0.0: {}
+
+ esprima@4.0.1: {}
+
+ estree-util-attach-comments@3.0.0:
+ dependencies:
+ '@types/estree': 1.0.8
+
+ 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.8
+ 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-value-to-estree@3.4.0:
+ dependencies:
+ '@types/estree': 1.0.8
+
+ estree-util-visit@2.0.0:
+ dependencies:
+ '@types/estree-jsx': 1.0.5
+ '@types/unist': 3.0.3
+
+ estree-walker@3.0.3:
+ dependencies:
+ '@types/estree': 1.0.8
+
+ execa@5.1.1:
+ dependencies:
+ cross-spawn: 7.0.6
+ get-stream: 6.0.1
+ human-signals: 2.1.0
+ is-stream: 2.0.1
+ merge-stream: 2.0.0
+ npm-run-path: 4.0.1
+ onetime: 5.1.2
+ signal-exit: 3.0.7
+ strip-final-newline: 2.0.0
+
+ exit-hook@2.2.1: {}
+
+ expect-type@1.2.2: {}
+
+ extend-shallow@2.0.1:
+ dependencies:
+ is-extendable: 0.1.1
+
+ extend@3.0.2: {}
+
+ eyes@0.1.8: {}
+
+ fast-glob@3.3.3:
+ dependencies:
+ '@nodelib/fs.stat': 2.0.5
+ '@nodelib/fs.walk': 1.2.8
+ glob-parent: 5.1.2
+ merge2: 1.4.1
+ micromatch: 4.0.8
+
+ fastq@1.19.1:
+ dependencies:
+ reusify: 1.1.0
+
+ fault@2.0.1:
+ dependencies:
+ format: 0.2.2
+
+ fdir@6.5.0(picomatch@4.0.3):
+ optionalDependencies:
+ picomatch: 4.0.3
+
+ fflate@0.8.2: {}
+
+ fill-range@7.1.1:
+ dependencies:
+ to-regex-range: 5.0.1
+
+ find-root@1.1.0: {}
+
+ flatted@3.3.3: {}
+
+ foreground-child@3.3.1:
+ dependencies:
+ cross-spawn: 7.0.6
+ signal-exit: 4.1.0
+
+ format@0.2.2: {}
+
+ framer-motion@12.23.22(react-dom@19.0.0(react@19.0.0))(react@19.0.0):
+ dependencies:
+ motion-dom: 12.23.21
+ motion-utils: 12.23.6
+ tslib: 2.8.1
+ optionalDependencies:
+ react: 19.0.0
+ react-dom: 19.0.0(react@19.0.0)
+
+ fs-extra@10.1.0:
+ dependencies:
+ graceful-fs: 4.2.11
+ jsonfile: 6.2.0
+ universalify: 2.0.1
+
+ fsevents@2.3.2:
+ optional: true
+
+ fsevents@2.3.3:
+ optional: true
+
+ function-bind@1.1.2: {}
+
+ gensync@1.0.0-beta.2: {}
+
+ get-caller-file@2.0.5:
+ optional: true
+
+ get-nonce@1.0.1: {}
+
+ get-stream@6.0.1: {}
+
+ get-tsconfig@4.10.1:
+ dependencies:
+ resolve-pkg-maps: 1.0.0
+
+ github-slugger@2.0.0: {}
+
+ glob-parent@5.1.2:
+ dependencies:
+ is-glob: 4.0.3
+
+ glob@10.4.5:
+ dependencies:
+ foreground-child: 3.3.1
+ jackspeak: 3.4.3
+ minimatch: 9.0.5
+ minipass: 7.1.2
+ package-json-from-dist: 1.0.1
+ path-scurry: 1.11.1
+
+ glob@11.0.3:
+ dependencies:
+ foreground-child: 3.3.1
+ jackspeak: 4.1.1
+ minimatch: 10.0.3
+ minipass: 7.1.2
+ package-json-from-dist: 1.0.1
+ path-scurry: 2.0.0
+
+ globrex@0.1.2: {}
+
+ graceful-fs@4.2.11: {}
+
+ graphql@16.11.0:
+ optional: true
+
+ gray-matter@4.0.3:
+ dependencies:
+ js-yaml: 3.14.1
+ kind-of: 6.0.3
+ section-matter: 1.0.0
+ strip-bom-string: 1.0.0
+
+ gunzip-maybe@1.4.2:
+ dependencies:
+ browserify-zlib: 0.1.4
+ is-deflate: 1.0.0
+ is-gzip: 1.0.0
+ peek-stream: 1.1.3
+ pumpify: 1.5.1
+ through2: 2.0.5
+
+ happy-dom@16.8.1:
+ dependencies:
+ webidl-conversions: 7.0.0
+ whatwg-mimetype: 3.0.0
+
+ has-flag@4.0.0: {}
+
+ hasown@2.0.2:
+ dependencies:
+ function-bind: 1.1.2
+
+ hast-util-heading-rank@3.0.0:
+ dependencies:
+ '@types/hast': 3.0.4
+
+ hast-util-to-estree@3.1.3:
+ dependencies:
+ '@types/estree': 1.0.8
+ '@types/estree-jsx': 1.0.5
+ '@types/hast': 3.0.4
+ 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.1.0
+ space-separated-tokens: 2.0.2
+ style-to-js: 1.1.17
+ unist-util-position: 5.0.0
+ zwitch: 2.0.4
+ transitivePeerDependencies:
+ - supports-color
+
+ hast-util-to-jsx-runtime@2.3.6:
+ dependencies:
+ '@types/estree': 1.0.8
+ '@types/hast': 3.0.4
+ '@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.1.0
+ space-separated-tokens: 2.0.2
+ style-to-js: 1.1.17
+ unist-util-position: 5.0.0
+ vfile-message: 4.0.3
+ transitivePeerDependencies:
+ - supports-color
+
+ hast-util-to-string@3.0.1:
+ dependencies:
+ '@types/hast': 3.0.4
+
+ hast-util-whitespace@3.0.0:
+ dependencies:
+ '@types/hast': 3.0.4
+
+ he@1.2.0: {}
+
+ headers-polyfill@4.0.3:
+ optional: true
+
+ hoist-non-react-statics@3.3.2:
+ dependencies:
+ react-is: 16.13.1
+
+ hono@4.6.20: {}
+
+ hosted-git-info@6.1.3:
+ dependencies:
+ lru-cache: 7.18.3
+
+ html-escaper@2.0.2: {}
+
+ html-parse-stringify@3.0.1:
+ dependencies:
+ void-elements: 3.1.0
+
+ html@1.0.0:
+ dependencies:
+ concat-stream: 1.6.2
+
+ human-signals@2.1.0: {}
+
+ i18next-browser-languagedetector@8.0.2:
+ dependencies:
+ '@babel/runtime': 7.28.4
+
+ i18next-http-backend@3.0.2:
+ dependencies:
+ cross-fetch: 4.0.0
+ transitivePeerDependencies:
+ - encoding
+
+ i18next@24.2.2(typescript@5.7.3):
+ dependencies:
+ '@babel/runtime': 7.28.4
+ optionalDependencies:
+ typescript: 5.7.3
+
+ ignore@5.3.2: {}
+
+ import-fresh@3.3.1:
+ dependencies:
+ parent-module: 1.0.1
+ resolve-from: 4.0.0
+
+ indent-string@4.0.0: {}
+
+ inherits@2.0.4: {}
+
+ ini@1.3.8: {}
+
+ inline-style-parser@0.2.4: {}
+
+ is-alphabetical@2.0.1: {}
+
+ is-alphanumerical@2.0.1:
+ dependencies:
+ is-alphabetical: 2.0.1
+ is-decimal: 2.0.1
+
+ is-arrayish@0.2.1: {}
+
+ is-core-module@2.16.1:
+ dependencies:
+ hasown: 2.0.2
+
+ is-decimal@2.0.1: {}
+
+ is-deflate@1.0.0: {}
+
+ is-extendable@0.1.1: {}
+
+ is-extglob@2.1.1: {}
+
+ is-fullwidth-code-point@3.0.0: {}
+
+ is-glob@4.0.3:
+ dependencies:
+ is-extglob: 2.1.1
+
+ is-gzip@1.0.0: {}
+
+ is-hexadecimal@2.0.1: {}
+
+ is-node-process@1.2.0:
+ optional: true
+
+ is-number@7.0.0: {}
+
+ is-plain-obj@4.1.0: {}
+
+ is-platform@1.0.0: {}
+
+ is-stream@2.0.1: {}
+
+ isarray@1.0.0: {}
+
+ isbot@5.1.22: {}
+
+ isexe@2.0.0: {}
+
+ isexe@3.1.1: {}
+
+ isstream@0.1.2: {}
+
+ istanbul-lib-coverage@3.2.2: {}
+
+ istanbul-lib-report@3.0.1:
+ dependencies:
+ istanbul-lib-coverage: 3.2.2
+ make-dir: 4.0.0
+ supports-color: 7.2.0
+
+ istanbul-lib-source-maps@5.0.6:
+ dependencies:
+ '@jridgewell/trace-mapping': 0.3.31
+ debug: 4.4.3
+ istanbul-lib-coverage: 3.2.2
+ transitivePeerDependencies:
+ - supports-color
+
+ istanbul-reports@3.2.0:
+ dependencies:
+ html-escaper: 2.0.2
+ istanbul-lib-report: 3.0.1
+
+ jackspeak@3.4.3:
+ dependencies:
+ '@isaacs/cliui': 8.0.2
+ optionalDependencies:
+ '@pkgjs/parseargs': 0.11.0
+
+ jackspeak@4.1.1:
+ dependencies:
+ '@isaacs/cliui': 8.0.2
+
+ jiti@2.6.1: {}
+
+ js-beautify@1.15.4:
+ dependencies:
+ config-chain: 1.1.13
+ editorconfig: 1.0.4
+ glob: 10.4.5
+ js-cookie: 3.0.5
+ nopt: 7.2.1
+
+ js-cookie@3.0.5: {}
+
+ js-tokens@4.0.0: {}
+
+ js-tokens@9.0.1: {}
+
+ js-yaml@3.14.1:
+ dependencies:
+ argparse: 1.0.10
+ esprima: 4.0.1
+
+ js-yaml@4.1.0:
+ dependencies:
+ argparse: 2.0.1
+
+ jsesc@3.0.2: {}
+
+ json-parse-even-better-errors@2.3.1: {}
+
+ json-parse-even-better-errors@3.0.2: {}
+
+ json5@2.2.3: {}
+
+ jsonfile@6.2.0:
+ dependencies:
+ universalify: 2.0.1
+ optionalDependencies:
+ graceful-fs: 4.2.11
+
+ kind-of@6.0.3: {}
+
+ knip@5.43.6(@types/node@22.13.1)(typescript@5.7.3):
+ dependencies:
+ '@nodelib/fs.walk': 3.0.1
+ '@snyk/github-codeowners': 1.1.0
+ '@types/node': 22.13.1
+ easy-table: 1.2.0
+ enhanced-resolve: 5.18.3
+ fast-glob: 3.3.3
+ jiti: 2.6.1
+ js-yaml: 4.1.0
+ minimist: 1.2.8
+ picocolors: 1.1.1
+ picomatch: 4.0.3
+ pretty-ms: 9.3.0
+ smol-toml: 1.4.2
+ strip-json-comments: 5.0.1
+ summary: 2.1.0
+ typescript: 5.7.3
+ zod: 3.25.76
+ zod-validation-error: 3.5.3(zod@3.25.76)
+
+ lefthook-darwin-arm64@1.10.10:
+ optional: true
+
+ lefthook-darwin-x64@1.10.10:
+ optional: true
+
+ lefthook-freebsd-arm64@1.10.10:
+ optional: true
+
+ lefthook-freebsd-x64@1.10.10:
+ optional: true
+
+ lefthook-linux-arm64@1.10.10:
+ optional: true
+
+ lefthook-linux-x64@1.10.10:
+ optional: true
+
+ lefthook-openbsd-arm64@1.10.10:
+ optional: true
+
+ lefthook-openbsd-x64@1.10.10:
+ optional: true
+
+ lefthook-windows-arm64@1.10.10:
+ optional: true
+
+ lefthook-windows-x64@1.10.10:
+ optional: true
+
+ lefthook@1.10.10:
+ optionalDependencies:
+ lefthook-darwin-arm64: 1.10.10
+ lefthook-darwin-x64: 1.10.10
+ lefthook-freebsd-arm64: 1.10.10
+ lefthook-freebsd-x64: 1.10.10
+ lefthook-linux-arm64: 1.10.10
+ lefthook-linux-x64: 1.10.10
+ lefthook-openbsd-arm64: 1.10.10
+ lefthook-openbsd-x64: 1.10.10
+ lefthook-windows-arm64: 1.10.10
+ lefthook-windows-x64: 1.10.10
+
+ lightningcss-android-arm64@1.30.2:
+ optional: true
+
+ lightningcss-darwin-arm64@1.30.2:
+ optional: true
+
+ lightningcss-darwin-x64@1.30.2:
+ optional: true
+
+ lightningcss-freebsd-x64@1.30.2:
+ optional: true
+
+ lightningcss-linux-arm-gnueabihf@1.30.2:
+ optional: true
+
+ lightningcss-linux-arm64-gnu@1.30.2:
+ optional: true
+
+ lightningcss-linux-arm64-musl@1.30.2:
+ optional: true
+
+ lightningcss-linux-x64-gnu@1.30.2:
+ optional: true
+
+ lightningcss-linux-x64-musl@1.30.2:
+ optional: true
+
+ lightningcss-win32-arm64-msvc@1.30.2:
+ optional: true
+
+ lightningcss-win32-x64-msvc@1.30.2:
+ optional: true
+
+ lightningcss@1.30.2:
+ dependencies:
+ detect-libc: 2.1.2
+ optionalDependencies:
+ lightningcss-android-arm64: 1.30.2
+ lightningcss-darwin-arm64: 1.30.2
+ lightningcss-darwin-x64: 1.30.2
+ lightningcss-freebsd-x64: 1.30.2
+ lightningcss-linux-arm-gnueabihf: 1.30.2
+ lightningcss-linux-arm64-gnu: 1.30.2
+ lightningcss-linux-arm64-musl: 1.30.2
+ lightningcss-linux-x64-gnu: 1.30.2
+ lightningcss-linux-x64-musl: 1.30.2
+ lightningcss-win32-arm64-msvc: 1.30.2
+ lightningcss-win32-x64-msvc: 1.30.2
+
+ lines-and-columns@1.2.4: {}
+
+ lite-emit@2.3.0: {}
+
+ lodash.castarray@4.4.0: {}
+
+ lodash.isplainobject@4.0.6: {}
+
+ lodash.merge@4.6.2: {}
+
+ lodash@4.17.21: {}
+
+ longest-streak@3.1.0: {}
+
+ loose-envify@1.4.0:
+ dependencies:
+ js-tokens: 4.0.0
+
+ loupe@3.2.1: {}
+
+ lru-cache@10.4.3: {}
+
+ lru-cache@11.2.2: {}
+
+ lru-cache@5.1.1:
+ dependencies:
+ yallist: 3.1.1
+
+ lru-cache@7.18.3: {}
+
+ lz-string@1.5.0: {}
+
+ magic-string@0.30.19:
+ dependencies:
+ '@jridgewell/sourcemap-codec': 1.5.5
+
+ magicast@0.3.5:
+ dependencies:
+ '@babel/parser': 7.28.4
+ '@babel/types': 7.28.4
+ source-map-js: 1.2.1
+
+ make-dir@4.0.0:
+ dependencies:
+ semver: 7.7.2
+
+ markdown-extensions@2.0.0: {}
+
+ mdast-util-from-markdown@2.0.2:
+ dependencies:
+ '@types/mdast': 4.0.4
+ '@types/unist': 3.0.3
+ decode-named-character-reference: 1.2.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-frontmatter@2.0.1:
+ dependencies:
+ '@types/mdast': 4.0.4
+ devlop: 1.1.0
+ escape-string-regexp: 5.0.0
+ mdast-util-from-markdown: 2.0.2
+ mdast-util-to-markdown: 2.1.2
+ micromark-extension-frontmatter: 2.0.0
+ transitivePeerDependencies:
+ - supports-color
+
+ mdast-util-mdx-expression@2.0.1:
+ dependencies:
+ '@types/estree-jsx': 1.0.5
+ '@types/hast': 3.0.4
+ '@types/mdast': 4.0.4
+ devlop: 1.1.0
+ mdast-util-from-markdown: 2.0.2
+ 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.4
+ '@types/mdast': 4.0.4
+ '@types/unist': 3.0.3
+ ccount: 2.0.1
+ devlop: 1.1.0
+ mdast-util-from-markdown: 2.0.2
+ 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.2
+ 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.4
+ '@types/mdast': 4.0.4
+ devlop: 1.1.0
+ mdast-util-from-markdown: 2.0.2
+ 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.0
+
+ mdast-util-to-hast@13.2.0:
+ dependencies:
+ '@types/hast': 3.0.4
+ '@types/mdast': 4.0.4
+ '@ungap/structured-clone': 1.3.0
+ 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.0.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.0.0
+ zwitch: 2.0.4
+
+ mdast-util-to-string@4.0.0:
+ dependencies:
+ '@types/mdast': 4.0.4
+
+ mdx-bundler@10.1.1(esbuild@0.25.10):
+ dependencies:
+ '@babel/runtime': 7.28.4
+ '@esbuild-plugins/node-resolve': 0.2.2(esbuild@0.25.10)
+ '@fal-works/esbuild-plugin-global-externals': 2.1.2
+ '@mdx-js/esbuild': 3.1.1(esbuild@0.25.10)
+ esbuild: 0.25.10
+ gray-matter: 4.0.3
+ remark-frontmatter: 5.0.0
+ remark-mdx-frontmatter: 4.0.0
+ uuid: 9.0.1
+ vfile: 6.0.3
+ transitivePeerDependencies:
+ - supports-color
+
+ memoize-one@6.0.0: {}
+
+ merge-stream@2.0.0: {}
+
+ merge2@1.4.1: {}
+
+ micromark-core-commonmark@2.0.3:
+ dependencies:
+ decode-named-character-reference: 1.2.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-frontmatter@2.0.0:
+ dependencies:
+ fault: 2.0.1
+ micromark-util-character: 2.1.1
+ micromark-util-symbol: 2.0.1
+ micromark-util-types: 2.0.2
+
+ micromark-extension-mdx-expression@3.0.1:
+ dependencies:
+ '@types/estree': 1.0.8
+ 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.8
+ 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.8
+ 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.15.0
+ acorn-jsx: 5.3.2(acorn@8.15.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.8
+ 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.2.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.8
+ '@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.12
+ debug: 4.4.3
+ decode-named-character-reference: 1.2.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
+
+ micromatch@4.0.8:
+ dependencies:
+ braces: 3.0.3
+ picomatch: 2.3.1
+
+ mimic-fn@2.1.0: {}
+
+ minimatch@10.0.3:
+ dependencies:
+ '@isaacs/brace-expansion': 5.0.0
+
+ minimatch@9.0.1:
+ dependencies:
+ brace-expansion: 2.0.2
+
+ minimatch@9.0.5:
+ dependencies:
+ brace-expansion: 2.0.2
+
+ minimist@1.2.8: {}
+
+ minipass@7.1.2: {}
+
+ motion-dom@12.23.21:
+ dependencies:
+ motion-utils: 12.23.6
+
+ motion-utils@12.23.6: {}
+
+ mrmime@2.0.1: {}
+
+ ms@2.1.3: {}
+
+ msw@2.11.3(@types/node@22.13.1)(typescript@5.7.3):
+ dependencies:
+ '@bundled-es-modules/cookie': 2.0.1
+ '@bundled-es-modules/statuses': 1.0.1
+ '@inquirer/confirm': 5.1.18(@types/node@22.13.1)
+ '@mswjs/interceptors': 0.39.7
+ '@open-draft/deferred-promise': 2.2.0
+ '@types/cookie': 0.6.0
+ '@types/statuses': 2.0.6
+ graphql: 16.11.0
+ headers-polyfill: 4.0.3
+ is-node-process: 1.2.0
+ outvariant: 1.4.3
+ path-to-regexp: 6.3.0
+ picocolors: 1.1.1
+ rettime: 0.7.0
+ strict-event-emitter: 0.5.1
+ tough-cookie: 6.0.0
+ type-fest: 4.41.0
+ until-async: 3.0.2
+ yargs: 17.7.2
+ optionalDependencies:
+ typescript: 5.7.3
+ transitivePeerDependencies:
+ - '@types/node'
+ optional: true
+
+ mute-stream@0.0.8: {}
+
+ mute-stream@2.0.0:
+ optional: true
+
+ nanoid@3.3.11: {}
+
+ node-fetch@2.7.0:
+ dependencies:
+ whatwg-url: 5.0.0
+
+ node-html-parser@7.0.1:
+ dependencies:
+ css-select: 5.2.2
+ he: 1.2.0
+
+ node-releases@2.0.23: {}
+
+ nopt@7.2.1:
+ dependencies:
+ abbrev: 2.0.0
+
+ normalize-package-data@5.0.0:
+ dependencies:
+ hosted-git-info: 6.1.3
+ is-core-module: 2.16.1
+ semver: 7.7.2
+ validate-npm-package-license: 3.0.4
+
+ npm-install-checks@6.3.0:
+ dependencies:
+ semver: 7.7.2
+
+ npm-normalize-package-bin@3.0.1: {}
+
+ npm-package-arg@10.1.0:
+ dependencies:
+ hosted-git-info: 6.1.3
+ proc-log: 3.0.0
+ semver: 7.7.2
+ validate-npm-package-name: 5.0.1
+
+ npm-pick-manifest@8.0.2:
+ dependencies:
+ npm-install-checks: 6.3.0
+ npm-normalize-package-bin: 3.0.1
+ npm-package-arg: 10.1.0
+ semver: 7.7.2
+
+ npm-run-path@4.0.1:
+ dependencies:
+ path-key: 3.1.1
+
+ nth-check@2.1.1:
+ dependencies:
+ boolbase: 1.0.0
+
+ object-assign@4.1.1: {}
+
+ object-treeify@1.1.33: {}
+
+ once@1.4.0:
+ dependencies:
+ wrappy: 1.0.2
+
+ onetime@5.1.2:
+ dependencies:
+ mimic-fn: 2.1.0
+
+ outvariant@1.4.3:
+ optional: true
+
+ p-limit@6.2.0:
+ dependencies:
+ yocto-queue: 1.2.1
+
+ p-map@4.0.0:
+ dependencies:
+ aggregate-error: 3.1.0
+
+ package-json-from-dist@1.0.1: {}
+
+ pako@0.2.9: {}
+
+ parent-module@1.0.1:
+ dependencies:
+ callsites: 3.1.0
+
+ 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.2.0
+ is-alphanumerical: 2.0.1
+ is-decimal: 2.0.1
+ is-hexadecimal: 2.0.1
+
+ parse-json@5.2.0:
+ dependencies:
+ '@babel/code-frame': 7.27.1
+ error-ex: 1.3.4
+ json-parse-even-better-errors: 2.3.1
+ lines-and-columns: 1.2.4
+
+ parse-ms@4.0.0: {}
+
+ path-key@3.1.1: {}
+
+ path-parse@1.0.7: {}
+
+ path-scurry@1.11.1:
+ dependencies:
+ lru-cache: 10.4.3
+ minipass: 7.1.2
+
+ path-scurry@2.0.0:
+ dependencies:
+ lru-cache: 11.2.2
+ minipass: 7.1.2
+
+ path-to-regexp@6.3.0:
+ optional: true
+
+ path-type@4.0.0: {}
+
+ pathe@1.1.2: {}
+
+ pathe@2.0.3: {}
+
+ pathval@2.0.1: {}
+
+ peek-stream@1.1.3:
+ dependencies:
+ buffer-from: 1.1.2
+ duplexify: 3.7.1
+ through2: 2.0.5
+
+ picocolors@1.1.1: {}
+
+ picomatch@2.3.1: {}
+
+ picomatch@4.0.3: {}
+
+ playwright-core@1.50.1: {}
+
+ playwright@1.50.1:
+ dependencies:
+ playwright-core: 1.50.1
+ optionalDependencies:
+ fsevents: 2.3.2
+
+ pluralize@8.0.0: {}
+
+ postcss-selector-parser@6.0.10:
+ dependencies:
+ cssesc: 3.0.0
+ util-deprecate: 1.0.2
+
+ postcss@8.5.6:
+ dependencies:
+ nanoid: 3.3.11
+ picocolors: 1.1.1
+ source-map-js: 1.2.1
+
+ prettier@2.8.8: {}
+
+ pretty-cache-header@1.0.0:
+ dependencies:
+ timestring: 6.0.0
+
+ pretty-format@27.5.1:
+ dependencies:
+ ansi-regex: 5.0.1
+ ansi-styles: 5.2.0
+ react-is: 17.0.2
+
+ pretty-ms@9.3.0:
+ dependencies:
+ parse-ms: 4.0.0
+
+ proc-log@3.0.0: {}
+
+ process-nextick-args@2.0.1: {}
+
+ promise-inflight@1.0.1: {}
+
+ promise-retry@2.0.1:
+ dependencies:
+ err-code: 2.0.3
+ retry: 0.12.0
+
+ prompt@1.3.0:
+ dependencies:
+ '@colors/colors': 1.5.0
+ async: 3.2.3
+ read: 1.0.7
+ revalidator: 0.1.8
+ winston: 2.4.7
+
+ prop-types@15.8.1:
+ dependencies:
+ loose-envify: 1.4.0
+ object-assign: 4.1.1
+ react-is: 16.13.1
+
+ property-information@7.1.0: {}
+
+ proto-list@1.2.4: {}
+
+ pump@2.0.1:
+ dependencies:
+ end-of-stream: 1.4.5
+ once: 1.4.0
+
+ pumpify@1.5.1:
+ dependencies:
+ duplexify: 3.7.1
+ inherits: 2.0.4
+ pump: 2.0.1
+
+ queue-microtask@1.2.3: {}
+
+ randombytes@2.1.0:
+ dependencies:
+ safe-buffer: 5.2.1
+
+ react-d3-tree@3.6.6(react-dom@19.0.0(react@19.0.0))(react@19.0.0):
+ dependencies:
+ '@bkrem/react-transition-group': 1.3.5(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
+ '@types/d3-hierarchy': 1.1.11
+ clone: 2.1.2
+ d3-hierarchy: 1.1.9
+ d3-selection: 3.0.0
+ d3-shape: 1.3.7
+ d3-zoom: 3.0.0
+ dequal: 2.0.3
+ react: 19.0.0
+ react-dom: 19.0.0(react@19.0.0)
+ uuid: 8.3.2
+
+ react-diff-viewer-continued@4.0.6(@types/react@19.0.8)(react-dom@19.0.0(react@19.0.0))(react@19.0.0):
+ dependencies:
+ '@emotion/css': 11.13.5
+ '@emotion/react': 11.14.0(@types/react@19.0.8)(react@19.0.0)
+ classnames: 2.5.1
+ diff: 5.2.0
+ memoize-one: 6.0.0
+ react: 19.0.0
+ react-dom: 19.0.0(react@19.0.0)
+ transitivePeerDependencies:
+ - '@types/react'
+ - supports-color
+
+ react-dom@19.0.0(react@19.0.0):
+ dependencies:
+ react: 19.0.0
+ scheduler: 0.25.0
+
+ react-hotkeys-hook@4.6.2(react-dom@19.0.0(react@19.0.0))(react@19.0.0):
+ dependencies:
+ react: 19.0.0
+ react-dom: 19.0.0(react@19.0.0)
+
+ react-i18next@15.4.0(i18next@24.2.2(typescript@5.7.3))(react-dom@19.0.0(react@19.0.0))(react@19.0.0):
+ dependencies:
+ '@babel/runtime': 7.28.4
+ html-parse-stringify: 3.0.1
+ i18next: 24.2.2(typescript@5.7.3)
+ react: 19.0.0
+ optionalDependencies:
+ react-dom: 19.0.0(react@19.0.0)
+
+ react-is@16.13.1: {}
+
+ react-is@17.0.2: {}
+
+ react-lifecycles-compat@3.0.4: {}
+
+ react-refresh@0.14.2: {}
+
+ react-remove-scroll-bar@2.3.8(@types/react@19.0.8)(react@19.0.0):
+ dependencies:
+ react: 19.0.0
+ react-style-singleton: 2.2.3(@types/react@19.0.8)(react@19.0.0)
+ tslib: 2.8.1
+ optionalDependencies:
+ '@types/react': 19.0.8
+
+ react-remove-scroll@2.7.1(@types/react@19.0.8)(react@19.0.0):
+ dependencies:
+ react: 19.0.0
+ react-remove-scroll-bar: 2.3.8(@types/react@19.0.8)(react@19.0.0)
+ react-style-singleton: 2.2.3(@types/react@19.0.8)(react@19.0.0)
+ tslib: 2.8.1
+ use-callback-ref: 1.3.3(@types/react@19.0.8)(react@19.0.0)
+ use-sidecar: 1.1.3(@types/react@19.0.8)(react@19.0.0)
+ optionalDependencies:
+ '@types/react': 19.0.8
+
+ react-router-devtools@5.0.4(@types/react-dom@19.0.3(@types/react@19.0.8))(@types/react@19.0.8)(react-dom@19.0.0(react@19.0.0))(react-router@7.2.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react@19.0.0)(vite@6.2.0(@types/node@22.13.1)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.19.2)(yaml@2.8.1)):
+ dependencies:
+ '@babel/core': 7.28.4
+ '@babel/generator': 7.28.3
+ '@babel/parser': 7.28.4
+ '@babel/traverse': 7.28.4
+ '@babel/types': 7.28.4
+ '@radix-ui/react-accordion': 1.2.12(@types/react-dom@19.0.3(@types/react@19.0.8))(@types/react@19.0.8)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
+ '@radix-ui/react-select': 2.2.6(@types/react-dom@19.0.3(@types/react@19.0.8))(@types/react@19.0.8)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
+ '@types/react': 19.0.8
+ '@types/react-dom': 19.0.3(@types/react@19.0.8)
+ beautify: 0.0.8
+ bippy: 0.3.27(@types/react@19.0.8)(react@19.0.0)
+ chalk: 5.4.1
+ clsx: 2.1.1
+ date-fns: 4.1.0
+ framer-motion: 12.23.22(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
+ react: 19.0.0
+ react-d3-tree: 3.6.6(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
+ react-diff-viewer-continued: 4.0.6(@types/react@19.0.8)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
+ react-dom: 19.0.0(react@19.0.0)
+ react-hotkeys-hook: 4.6.2(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
+ react-router: 7.2.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
+ react-tooltip: 5.29.1(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
+ tailwind-merge: 3.0.1
+ vite: 6.2.0(@types/node@22.13.1)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.19.2)(yaml@2.8.1)
+ optionalDependencies:
+ '@biomejs/cli-darwin-arm64': 1.9.4
+ '@rollup/rollup-darwin-arm64': 4.52.4
+ '@rollup/rollup-linux-x64-gnu': 4.52.4
+ transitivePeerDependencies:
+ - '@emotion/is-prop-valid'
+ - supports-color
+
+ react-router-hono-server@2.10.0(@react-router/dev@7.2.0(@types/node@22.13.1)(babel-plugin-macros@3.1.0)(jiti@2.6.1)(lightningcss@1.30.2)(react-router@7.2.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(tsx@4.19.2)(typescript@5.7.3)(vite@6.2.0(@types/node@22.13.1)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.19.2)(yaml@2.8.1))(yaml@2.8.1))(@types/react@19.0.8)(react-router@7.2.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(vite@6.2.0(@types/node@22.13.1)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.19.2)(yaml@2.8.1)):
+ dependencies:
+ '@drizzle-team/brocli': 0.11.0
+ '@hono/node-server': 1.19.5(hono@4.6.20)
+ '@hono/node-ws': 1.2.0(@hono/node-server@1.19.5(hono@4.6.20))(hono@4.6.20)
+ '@hono/vite-dev-server': 0.17.0(hono@4.6.20)
+ '@react-router/dev': 7.2.0(@types/node@22.13.1)(babel-plugin-macros@3.1.0)(jiti@2.6.1)(lightningcss@1.30.2)(react-router@7.2.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(tsx@4.19.2)(typescript@5.7.3)(vite@6.2.0(@types/node@22.13.1)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.19.2)(yaml@2.8.1))(yaml@2.8.1)
+ '@types/react': 19.0.8
+ hono: 4.6.20
+ react-router: 7.2.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
+ vite: 6.2.0(@types/node@22.13.1)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.19.2)(yaml@2.8.1)
+ transitivePeerDependencies:
+ - bufferutil
+ - utf-8-validate
+
+ react-router@7.2.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0):
+ dependencies:
+ '@types/cookie': 0.6.0
+ cookie: 1.0.2
+ react: 19.0.0
+ set-cookie-parser: 2.7.1
+ turbo-stream: 2.4.0
+ optionalDependencies:
+ react-dom: 19.0.0(react@19.0.0)
+
+ react-style-singleton@2.2.3(@types/react@19.0.8)(react@19.0.0):
+ dependencies:
+ get-nonce: 1.0.1
+ react: 19.0.0
+ tslib: 2.8.1
+ optionalDependencies:
+ '@types/react': 19.0.8
+
+ react-tooltip@5.29.1(react-dom@19.0.0(react@19.0.0))(react@19.0.0):
+ dependencies:
+ '@floating-ui/dom': 1.7.4
+ classnames: 2.5.1
+ react: 19.0.0
+ react-dom: 19.0.0(react@19.0.0)
+
+ react@19.0.0: {}
+
+ read@1.0.7:
+ dependencies:
+ mute-stream: 0.0.8
+
+ readable-stream@2.3.8:
+ dependencies:
+ core-util-is: 1.0.3
+ inherits: 2.0.4
+ isarray: 1.0.0
+ process-nextick-args: 2.0.1
+ safe-buffer: 5.1.2
+ string_decoder: 1.1.1
+ util-deprecate: 1.0.2
+
+ readdirp@4.1.2: {}
+
+ recma-build-jsx@1.0.0:
+ dependencies:
+ '@types/estree': 1.0.8
+ estree-util-build-jsx: 3.0.1
+ vfile: 6.0.3
+
+ recma-jsx@1.0.1(acorn@8.15.0):
+ dependencies:
+ acorn: 8.15.0
+ acorn-jsx: 5.3.2(acorn@8.15.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.8
+ 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.8
+ estree-util-to-js: 2.0.0
+ unified: 11.0.5
+ vfile: 6.0.3
+
+ rehype-recma@1.0.0:
+ dependencies:
+ '@types/estree': 1.0.8
+ '@types/hast': 3.0.4
+ hast-util-to-estree: 3.1.3
+ transitivePeerDependencies:
+ - supports-color
+
+ rehype-slug@6.0.0:
+ dependencies:
+ '@types/hast': 3.0.4
+ github-slugger: 2.0.0
+ hast-util-heading-rank: 3.0.0
+ hast-util-to-string: 3.0.1
+ unist-util-visit: 5.0.0
+
+ remark-frontmatter@5.0.0:
+ dependencies:
+ '@types/mdast': 4.0.4
+ mdast-util-frontmatter: 2.0.1
+ micromark-extension-frontmatter: 2.0.0
+ unified: 11.0.5
+ transitivePeerDependencies:
+ - supports-color
+
+ remark-mdx-frontmatter@4.0.0:
+ dependencies:
+ '@types/mdast': 4.0.4
+ estree-util-is-identifier-name: 3.0.0
+ estree-util-value-to-estree: 3.4.0
+ toml: 3.0.0
+ unified: 11.0.5
+ yaml: 2.8.1
+
+ 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.2
+ micromark-util-types: 2.0.2
+ unified: 11.0.5
+ transitivePeerDependencies:
+ - supports-color
+
+ remark-rehype@11.1.2:
+ dependencies:
+ '@types/hast': 3.0.4
+ '@types/mdast': 4.0.4
+ mdast-util-to-hast: 13.2.0
+ unified: 11.0.5
+ vfile: 6.0.3
+
+ remix-hono@0.0.18(hono@4.6.20)(i18next@24.2.2(typescript@5.7.3))(pretty-cache-header@1.0.0)(react-router@7.2.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(remix-i18next@7.0.2(i18next@24.2.2(typescript@5.7.3))(react-i18next@15.4.0(i18next@24.2.2(typescript@5.7.3))(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react-router@7.2.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react@19.0.0))(zod@4.0.17):
+ dependencies:
+ hono: 4.6.20
+ pretty-cache-header: 1.0.0
+ optionalDependencies:
+ i18next: 24.2.2(typescript@5.7.3)
+ react-router: 7.2.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
+ remix-i18next: 7.0.2(i18next@24.2.2(typescript@5.7.3))(react-i18next@15.4.0(i18next@24.2.2(typescript@5.7.3))(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react-router@7.2.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react@19.0.0)
+ zod: 4.0.17
+
+ remix-i18next@7.0.2(i18next@24.2.2(typescript@5.7.3))(react-i18next@15.4.0(i18next@24.2.2(typescript@5.7.3))(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react-router@7.2.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react@19.0.0):
+ dependencies:
+ i18next: 24.2.2(typescript@5.7.3)
+ react: 19.0.0
+ react-i18next: 15.4.0(i18next@24.2.2(typescript@5.7.3))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
+ react-router: 7.2.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
+
+ require-directory@2.1.1:
+ optional: true
+
+ resolve-from@4.0.0: {}
+
+ resolve-pkg-maps@1.0.0: {}
+
+ resolve@1.22.10:
+ dependencies:
+ is-core-module: 2.16.1
+ path-parse: 1.0.7
+ supports-preserve-symlinks-flag: 1.0.0
+
+ retry@0.12.0: {}
+
+ rettime@0.7.0:
+ optional: true
+
+ reusify@1.1.0: {}
+
+ revalidator@0.1.8: {}
+
+ rollup@4.52.4:
+ dependencies:
+ '@types/estree': 1.0.8
+ optionalDependencies:
+ '@rollup/rollup-android-arm-eabi': 4.52.4
+ '@rollup/rollup-android-arm64': 4.52.4
+ '@rollup/rollup-darwin-arm64': 4.52.4
+ '@rollup/rollup-darwin-x64': 4.52.4
+ '@rollup/rollup-freebsd-arm64': 4.52.4
+ '@rollup/rollup-freebsd-x64': 4.52.4
+ '@rollup/rollup-linux-arm-gnueabihf': 4.52.4
+ '@rollup/rollup-linux-arm-musleabihf': 4.52.4
+ '@rollup/rollup-linux-arm64-gnu': 4.52.4
+ '@rollup/rollup-linux-arm64-musl': 4.52.4
+ '@rollup/rollup-linux-loong64-gnu': 4.52.4
+ '@rollup/rollup-linux-ppc64-gnu': 4.52.4
+ '@rollup/rollup-linux-riscv64-gnu': 4.52.4
+ '@rollup/rollup-linux-riscv64-musl': 4.52.4
+ '@rollup/rollup-linux-s390x-gnu': 4.52.4
+ '@rollup/rollup-linux-x64-gnu': 4.52.4
+ '@rollup/rollup-linux-x64-musl': 4.52.4
+ '@rollup/rollup-openharmony-arm64': 4.52.4
+ '@rollup/rollup-win32-arm64-msvc': 4.52.4
+ '@rollup/rollup-win32-ia32-msvc': 4.52.4
+ '@rollup/rollup-win32-x64-gnu': 4.52.4
+ '@rollup/rollup-win32-x64-msvc': 4.52.4
+ fsevents: 2.3.3
+
+ run-parallel@1.2.0:
+ dependencies:
+ queue-microtask: 1.2.3
+
+ safe-buffer@5.1.2: {}
+
+ safe-buffer@5.2.1: {}
+
+ scheduler@0.25.0: {}
+
+ section-matter@1.0.0:
+ dependencies:
+ extend-shallow: 2.0.1
+ kind-of: 6.0.3
+
+ semver@6.3.1: {}
+
+ semver@7.7.2: {}
+
+ serialize-javascript@6.0.2:
+ dependencies:
+ randombytes: 2.1.0
+
+ set-cookie-parser@2.7.1: {}
+
+ shebang-command@2.0.0:
+ dependencies:
+ shebang-regex: 3.0.0
+
+ shebang-regex@3.0.0: {}
+
+ siginfo@2.0.0: {}
+
+ signal-exit@3.0.7: {}
+
+ 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
+
+ slug@11.0.0: {}
+
+ smol-toml@1.4.2: {}
+
+ source-map-js@1.2.1: {}
+
+ source-map-support@0.5.21:
+ dependencies:
+ buffer-from: 1.1.2
+ source-map: 0.6.1
+
+ source-map@0.5.7: {}
+
+ source-map@0.6.1: {}
+
+ source-map@0.7.6: {}
+
+ space-separated-tokens@2.0.2: {}
+
+ spdx-correct@3.2.0:
+ dependencies:
+ spdx-expression-parse: 3.0.1
+ spdx-license-ids: 3.0.22
+
+ spdx-exceptions@2.5.0: {}
+
+ spdx-expression-parse@3.0.1:
+ dependencies:
+ spdx-exceptions: 2.5.0
+ spdx-license-ids: 3.0.22
+
+ spdx-license-ids@3.0.22: {}
+
+ sprintf-js@1.0.3: {}
+
+ stack-trace@0.0.10: {}
+
+ stackback@0.0.2: {}
+
+ statuses@2.0.2:
+ optional: true
+
+ std-env@3.9.0: {}
+
+ stream-shift@1.0.3: {}
+
+ stream-slice@0.1.2: {}
+
+ strict-event-emitter@0.5.1:
+ optional: true
+
+ string-width@4.2.3:
+ dependencies:
+ emoji-regex: 8.0.0
+ is-fullwidth-code-point: 3.0.0
+ strip-ansi: 6.0.1
+
+ string-width@5.1.2:
+ dependencies:
+ eastasianwidth: 0.2.0
+ emoji-regex: 9.2.2
+ strip-ansi: 7.1.2
+
+ string-width@6.1.0:
+ dependencies:
+ eastasianwidth: 0.2.0
+ emoji-regex: 10.5.0
+ strip-ansi: 7.1.2
+
+ string_decoder@1.1.1:
+ dependencies:
+ safe-buffer: 5.1.2
+
+ stringify-entities@4.0.4:
+ dependencies:
+ character-entities-html4: 2.1.0
+ character-entities-legacy: 3.0.0
+
+ strip-ansi@6.0.1:
+ dependencies:
+ ansi-regex: 5.0.1
+
+ strip-ansi@7.1.2:
+ dependencies:
+ ansi-regex: 6.2.2
+
+ strip-bom-string@1.0.0: {}
+
+ strip-final-newline@2.0.0: {}
+
+ strip-json-comments@5.0.1: {}
+
+ strip-literal@3.1.0:
+ dependencies:
+ js-tokens: 9.0.1
+
+ style-to-js@1.1.17:
+ dependencies:
+ style-to-object: 1.0.9
+
+ style-to-object@1.0.9:
+ dependencies:
+ inline-style-parser: 0.2.4
+
+ stylis@4.2.0: {}
+
+ summary@2.1.0: {}
+
+ supports-color@7.2.0:
+ dependencies:
+ has-flag: 4.0.0
+
+ supports-preserve-symlinks-flag@1.0.0: {}
+
+ tailwind-merge@3.0.1: {}
+
+ tailwindcss@4.0.9: {}
+
+ tapable@2.3.0: {}
+
+ test-exclude@7.0.1:
+ dependencies:
+ '@istanbuljs/schema': 0.1.3
+ glob: 10.4.5
+ minimatch: 9.0.5
+
+ text-table@0.2.0: {}
+
+ through2@2.0.5:
+ dependencies:
+ readable-stream: 2.3.8
+ xtend: 4.0.2
+
+ timestring@6.0.0: {}
+
+ tinybench@2.9.0: {}
+
+ tinyexec@0.3.2: {}
+
+ tinyglobby@0.2.15:
+ dependencies:
+ fdir: 6.5.0(picomatch@4.0.3)
+ picomatch: 4.0.3
+
+ tinypool@1.1.1: {}
+
+ tinyrainbow@2.0.0: {}
+
+ tinyspy@4.0.4: {}
+
+ tldts-core@7.0.16:
+ optional: true
+
+ tldts@7.0.16:
+ dependencies:
+ tldts-core: 7.0.16
+ optional: true
+
+ to-regex-range@5.0.1:
+ dependencies:
+ is-number: 7.0.0
+
+ toml@3.0.0: {}
+
+ totalist@3.0.1: {}
+
+ tough-cookie@6.0.0:
+ dependencies:
+ tldts: 7.0.16
+ optional: true
+
+ tr46@0.0.3: {}
+
+ trim-lines@3.0.1: {}
+
+ trough@2.2.0: {}
+
+ tsconfck@3.1.6(typescript@5.7.3):
+ optionalDependencies:
+ typescript: 5.7.3
+
+ tslib@2.8.1: {}
+
+ tsx@4.19.2:
+ dependencies:
+ esbuild: 0.23.1
+ get-tsconfig: 4.10.1
+ optionalDependencies:
+ fsevents: 2.3.3
+
+ turbo-stream@2.4.0: {}
+
+ turbo-stream@2.4.1:
+ optional: true
+
+ type-fest@4.41.0: {}
+
+ type-flag@3.0.0: {}
+
+ typedarray@0.0.6: {}
+
+ typescript@5.7.3: {}
+
+ undici-types@6.20.0: {}
+
+ undici@6.22.0: {}
+
+ 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
+
+ unist-util-is@6.0.0:
+ dependencies:
+ '@types/unist': 3.0.3
+
+ 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-stringify-position@4.0.0:
+ dependencies:
+ '@types/unist': 3.0.3
+
+ unist-util-visit-parents@6.0.1:
+ dependencies:
+ '@types/unist': 3.0.3
+ unist-util-is: 6.0.0
+
+ unist-util-visit@5.0.0:
+ dependencies:
+ '@types/unist': 3.0.3
+ unist-util-is: 6.0.0
+ unist-util-visit-parents: 6.0.1
+
+ universalify@2.0.1: {}
+
+ until-async@3.0.2:
+ optional: true
+
+ update-browserslist-db@1.1.3(browserslist@4.26.3):
+ dependencies:
+ browserslist: 4.26.3
+ escalade: 3.2.0
+ picocolors: 1.1.1
+
+ url-pattern@1.0.3: {}
+
+ use-callback-ref@1.3.3(@types/react@19.0.8)(react@19.0.0):
+ dependencies:
+ react: 19.0.0
+ tslib: 2.8.1
+ optionalDependencies:
+ '@types/react': 19.0.8
+
+ use-sidecar@1.1.3(@types/react@19.0.8)(react@19.0.0):
+ dependencies:
+ detect-node-es: 1.1.0
+ react: 19.0.0
+ tslib: 2.8.1
+ optionalDependencies:
+ '@types/react': 19.0.8
+
+ util-deprecate@1.0.2: {}
+
+ uuid@8.3.2: {}
+
+ uuid@9.0.1: {}
+
+ valibot@0.41.0(typescript@5.7.3):
+ optionalDependencies:
+ typescript: 5.7.3
+
+ validate-npm-package-license@3.0.4:
+ dependencies:
+ spdx-correct: 3.2.0
+ spdx-expression-parse: 3.0.1
+
+ validate-npm-package-name@5.0.1: {}
+
+ 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-node@3.0.0-beta.2(@types/node@22.13.1)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.19.2)(yaml@2.8.1):
+ dependencies:
+ cac: 6.7.14
+ debug: 4.4.3
+ es-module-lexer: 1.7.0
+ pathe: 1.1.2
+ vite: 6.2.0(@types/node@22.13.1)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.19.2)(yaml@2.8.1)
+ transitivePeerDependencies:
+ - '@types/node'
+ - jiti
+ - less
+ - lightningcss
+ - sass
+ - sass-embedded
+ - stylus
+ - sugarss
+ - supports-color
+ - terser
+ - tsx
+ - yaml
+
+ vite-node@3.2.4(@types/node@22.13.1)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.19.2)(yaml@2.8.1):
+ dependencies:
+ cac: 6.7.14
+ debug: 4.4.3
+ es-module-lexer: 1.7.0
+ pathe: 2.0.3
+ vite: 6.2.0(@types/node@22.13.1)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.19.2)(yaml@2.8.1)
+ transitivePeerDependencies:
+ - '@types/node'
+ - jiti
+ - less
+ - lightningcss
+ - sass
+ - sass-embedded
+ - stylus
+ - sugarss
+ - supports-color
+ - terser
+ - tsx
+ - yaml
+
+ vite-plugin-babel@1.3.0(@babel/core@7.28.4)(vite@6.2.0(@types/node@22.13.1)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.19.2)(yaml@2.8.1)):
+ dependencies:
+ '@babel/core': 7.28.4
+ vite: 6.2.0(@types/node@22.13.1)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.19.2)(yaml@2.8.1)
+
+ vite-plugin-icons-spritesheet@3.0.1(vite@6.2.0(@types/node@22.13.1)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.19.2)(yaml@2.8.1)):
+ dependencies:
+ chalk: 5.4.1
+ glob: 11.0.3
+ node-html-parser: 7.0.1
+ tinyexec: 0.3.2
+ vite: 6.2.0(@types/node@22.13.1)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.19.2)(yaml@2.8.1)
+
+ vite-tsconfig-paths@5.1.4(typescript@5.7.3)(vite@6.2.0(@types/node@22.13.1)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.19.2)(yaml@2.8.1)):
+ dependencies:
+ debug: 4.4.3
+ globrex: 0.1.2
+ tsconfck: 3.1.6(typescript@5.7.3)
+ optionalDependencies:
+ vite: 6.2.0(@types/node@22.13.1)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.19.2)(yaml@2.8.1)
+ transitivePeerDependencies:
+ - supports-color
+ - typescript
+
+ vite@6.2.0(@types/node@22.13.1)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.19.2)(yaml@2.8.1):
+ dependencies:
+ esbuild: 0.25.10
+ postcss: 8.5.6
+ rollup: 4.52.4
+ optionalDependencies:
+ '@types/node': 22.13.1
+ fsevents: 2.3.3
+ jiti: 2.6.1
+ lightningcss: 1.30.2
+ tsx: 4.19.2
+ yaml: 2.8.1
+
+ vitest-browser-react@1.0.1(@types/react-dom@19.0.3(@types/react@19.0.8))(@types/react@19.0.8)(@vitest/browser@3.2.4)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(vitest@3.2.4):
+ dependencies:
+ '@vitest/browser': 3.2.4(msw@2.11.3(@types/node@22.13.1)(typescript@5.7.3))(playwright@1.50.1)(vite@6.2.0(@types/node@22.13.1)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.19.2)(yaml@2.8.1))(vitest@3.2.4)
+ react: 19.0.0
+ react-dom: 19.0.0(react@19.0.0)
+ vitest: 3.2.4(@types/debug@4.1.12)(@types/node@22.13.1)(@vitest/browser@3.2.4)(@vitest/ui@3.2.4)(happy-dom@16.8.1)(jiti@2.6.1)(lightningcss@1.30.2)(msw@2.11.3(@types/node@22.13.1)(typescript@5.7.3))(tsx@4.19.2)(yaml@2.8.1)
+ optionalDependencies:
+ '@types/react': 19.0.8
+ '@types/react-dom': 19.0.3(@types/react@19.0.8)
+
+ vitest@3.2.4(@types/debug@4.1.12)(@types/node@22.13.1)(@vitest/browser@3.2.4)(@vitest/ui@3.2.4)(happy-dom@16.8.1)(jiti@2.6.1)(lightningcss@1.30.2)(msw@2.11.3(@types/node@22.13.1)(typescript@5.7.3))(tsx@4.19.2)(yaml@2.8.1):
+ dependencies:
+ '@types/chai': 5.2.2
+ '@vitest/expect': 3.2.4
+ '@vitest/mocker': 3.2.4(msw@2.11.3(@types/node@22.13.1)(typescript@5.7.3))(vite@6.2.0(@types/node@22.13.1)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.19.2)(yaml@2.8.1))
+ '@vitest/pretty-format': 3.2.4
+ '@vitest/runner': 3.2.4
+ '@vitest/snapshot': 3.2.4
+ '@vitest/spy': 3.2.4
+ '@vitest/utils': 3.2.4
+ chai: 5.3.3
+ debug: 4.4.3
+ expect-type: 1.2.2
+ magic-string: 0.30.19
+ pathe: 2.0.3
+ picomatch: 4.0.3
+ std-env: 3.9.0
+ tinybench: 2.9.0
+ tinyexec: 0.3.2
+ tinyglobby: 0.2.15
+ tinypool: 1.1.1
+ tinyrainbow: 2.0.0
+ vite: 6.2.0(@types/node@22.13.1)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.19.2)(yaml@2.8.1)
+ vite-node: 3.2.4(@types/node@22.13.1)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.19.2)(yaml@2.8.1)
+ why-is-node-running: 2.3.0
+ optionalDependencies:
+ '@types/debug': 4.1.12
+ '@types/node': 22.13.1
+ '@vitest/browser': 3.2.4(msw@2.11.3(@types/node@22.13.1)(typescript@5.7.3))(playwright@1.50.1)(vite@6.2.0(@types/node@22.13.1)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.19.2)(yaml@2.8.1))(vitest@3.2.4)
+ '@vitest/ui': 3.2.4(vitest@3.2.4)
+ happy-dom: 16.8.1
+ transitivePeerDependencies:
+ - jiti
+ - less
+ - lightningcss
+ - msw
+ - sass
+ - sass-embedded
+ - stylus
+ - sugarss
+ - supports-color
+ - terser
+ - tsx
+ - yaml
+
+ void-elements@3.1.0: {}
+
+ warning@3.0.0:
+ dependencies:
+ loose-envify: 1.4.0
+
+ wcwidth@1.0.1:
+ dependencies:
+ defaults: 1.0.4
+ optional: true
+
+ webidl-conversions@3.0.1: {}
+
+ webidl-conversions@7.0.0: {}
+
+ whatwg-mimetype@3.0.0: {}
+
+ whatwg-url@5.0.0:
+ dependencies:
+ tr46: 0.0.3
+ webidl-conversions: 3.0.1
+
+ which@2.0.2:
+ dependencies:
+ isexe: 2.0.0
+
+ which@3.0.1:
+ dependencies:
+ isexe: 2.0.0
+
+ which@4.0.0:
+ dependencies:
+ isexe: 3.1.1
+
+ why-is-node-running@2.3.0:
+ dependencies:
+ siginfo: 2.0.0
+ stackback: 0.0.2
+
+ winston@2.4.7:
+ dependencies:
+ async: 2.6.4
+ colors: 1.0.3
+ cycle: 1.0.3
+ eyes: 0.1.8
+ isstream: 0.1.2
+ stack-trace: 0.0.10
+
+ wrap-ansi@6.2.0:
+ dependencies:
+ ansi-styles: 4.3.0
+ string-width: 4.2.3
+ strip-ansi: 6.0.1
+ optional: true
+
+ wrap-ansi@7.0.0:
+ dependencies:
+ ansi-styles: 4.3.0
+ string-width: 4.2.3
+ strip-ansi: 6.0.1
+
+ wrap-ansi@8.1.0:
+ dependencies:
+ ansi-styles: 6.2.3
+ string-width: 5.1.2
+ strip-ansi: 7.1.2
+
+ wrappy@1.0.2: {}
+
+ ws@8.18.3: {}
+
+ xtend@4.0.2: {}
+
+ y18n@5.0.8:
+ optional: true
+
+ yallist@3.1.1: {}
+
+ yaml@1.10.2: {}
+
+ yaml@2.8.1: {}
+
+ yargs-parser@21.1.1:
+ optional: true
+
+ yargs@17.7.2:
+ dependencies:
+ cliui: 8.0.1
+ escalade: 3.2.0
+ get-caller-file: 2.0.5
+ require-directory: 2.1.1
+ string-width: 4.2.3
+ y18n: 5.0.8
+ yargs-parser: 21.1.1
+ optional: true
+
+ yocto-queue@1.2.1: {}
+
+ yoctocolors-cjs@2.1.3:
+ optional: true
+
+ yoctocolors@1.0.0: {}
+
+ zod-validation-error@3.5.3(zod@3.25.76):
+ dependencies:
+ zod: 3.25.76
+
+ zod@3.25.76: {}
+
+ zod@4.0.17: {}
+
+ zwitch@2.0.4: {}
diff --git a/docs/public/favicon.ico b/docs/public/favicon.ico
new file mode 100644
index 0000000..4cf2d38
Binary files /dev/null and b/docs/public/favicon.ico differ
diff --git a/docs/public/static/images/docs-template-home.png b/docs/public/static/images/docs-template-home.png
new file mode 100644
index 0000000..92c1be3
Binary files /dev/null and b/docs/public/static/images/docs-template-home.png differ
diff --git a/docs/public/static/images/docs-template-photo.png b/docs/public/static/images/docs-template-photo.png
new file mode 100644
index 0000000..beac4bd
Binary files /dev/null and b/docs/public/static/images/docs-template-photo.png differ
diff --git a/docs/public/static/images/package-logo-1200x630.png b/docs/public/static/images/package-logo-1200x630.png
new file mode 100644
index 0000000..904df3f
Binary files /dev/null and b/docs/public/static/images/package-logo-1200x630.png differ
diff --git a/docs/public/static/images/package-logo.png b/docs/public/static/images/package-logo.png
new file mode 100644
index 0000000..7c11090
Binary files /dev/null and b/docs/public/static/images/package-logo.png differ
diff --git a/docs/react-router.config.ts b/docs/react-router.config.ts
new file mode 100644
index 0000000..117b8d5
--- /dev/null
+++ b/docs/react-router.config.ts
@@ -0,0 +1,9 @@
+import type { Config } from "@react-router/dev/config"
+
+export default {
+ future: {
+ unstable_viteEnvironmentApi: true,
+ unstable_splitRouteModules: true,
+ unstable_optimizeDeps: true,
+ },
+} satisfies Config
diff --git a/docs/resources/fonts/dyna-puff/DynaPuff-Bold.ttf b/docs/resources/fonts/dyna-puff/DynaPuff-Bold.ttf
new file mode 100644
index 0000000..ae02869
Binary files /dev/null and b/docs/resources/fonts/dyna-puff/DynaPuff-Bold.ttf differ
diff --git a/docs/resources/fonts/dyna-puff/DynaPuff-Medium.ttf b/docs/resources/fonts/dyna-puff/DynaPuff-Medium.ttf
new file mode 100644
index 0000000..bfa6ff8
Binary files /dev/null and b/docs/resources/fonts/dyna-puff/DynaPuff-Medium.ttf differ
diff --git a/docs/resources/fonts/dyna-puff/DynaPuff-Regular.ttf b/docs/resources/fonts/dyna-puff/DynaPuff-Regular.ttf
new file mode 100644
index 0000000..1910fbe
Binary files /dev/null and b/docs/resources/fonts/dyna-puff/DynaPuff-Regular.ttf differ
diff --git a/docs/resources/fonts/dyna-puff/DynaPuff-SemiBold.ttf b/docs/resources/fonts/dyna-puff/DynaPuff-SemiBold.ttf
new file mode 100644
index 0000000..34a715e
Binary files /dev/null and b/docs/resources/fonts/dyna-puff/DynaPuff-SemiBold.ttf differ
diff --git a/docs/resources/fonts/inter/Inter-Black.ttf b/docs/resources/fonts/inter/Inter-Black.ttf
new file mode 100644
index 0000000..dbb1b3b
Binary files /dev/null and b/docs/resources/fonts/inter/Inter-Black.ttf differ
diff --git a/docs/resources/fonts/inter/Inter-BlackItalic.ttf b/docs/resources/fonts/inter/Inter-BlackItalic.ttf
new file mode 100644
index 0000000..b89d61c
Binary files /dev/null and b/docs/resources/fonts/inter/Inter-BlackItalic.ttf differ
diff --git a/docs/resources/fonts/inter/Inter-Bold.ttf b/docs/resources/fonts/inter/Inter-Bold.ttf
new file mode 100644
index 0000000..46b3583
Binary files /dev/null and b/docs/resources/fonts/inter/Inter-Bold.ttf differ
diff --git a/docs/resources/fonts/inter/Inter-BoldItalic.ttf b/docs/resources/fonts/inter/Inter-BoldItalic.ttf
new file mode 100644
index 0000000..d1c0f53
Binary files /dev/null and b/docs/resources/fonts/inter/Inter-BoldItalic.ttf differ
diff --git a/docs/resources/fonts/inter/Inter-ExtraBold.ttf b/docs/resources/fonts/inter/Inter-ExtraBold.ttf
new file mode 100644
index 0000000..b775c08
Binary files /dev/null and b/docs/resources/fonts/inter/Inter-ExtraBold.ttf differ
diff --git a/docs/resources/fonts/inter/Inter-ExtraBoldItalic.ttf b/docs/resources/fonts/inter/Inter-ExtraBoldItalic.ttf
new file mode 100644
index 0000000..3461a92
Binary files /dev/null and b/docs/resources/fonts/inter/Inter-ExtraBoldItalic.ttf differ
diff --git a/docs/resources/fonts/inter/Inter-ExtraLight.ttf b/docs/resources/fonts/inter/Inter-ExtraLight.ttf
new file mode 100644
index 0000000..2ec6ca3
Binary files /dev/null and b/docs/resources/fonts/inter/Inter-ExtraLight.ttf differ
diff --git a/docs/resources/fonts/inter/Inter-ExtraLightItalic.ttf b/docs/resources/fonts/inter/Inter-ExtraLightItalic.ttf
new file mode 100644
index 0000000..c634a5d
Binary files /dev/null and b/docs/resources/fonts/inter/Inter-ExtraLightItalic.ttf differ
diff --git a/docs/resources/fonts/inter/Inter-Italic.ttf b/docs/resources/fonts/inter/Inter-Italic.ttf
new file mode 100644
index 0000000..1048b07
Binary files /dev/null and b/docs/resources/fonts/inter/Inter-Italic.ttf differ
diff --git a/docs/resources/fonts/inter/Inter-Light.ttf b/docs/resources/fonts/inter/Inter-Light.ttf
new file mode 100644
index 0000000..1a2a6f2
Binary files /dev/null and b/docs/resources/fonts/inter/Inter-Light.ttf differ
diff --git a/docs/resources/fonts/inter/Inter-LightItalic.ttf b/docs/resources/fonts/inter/Inter-LightItalic.ttf
new file mode 100644
index 0000000..ded5a75
Binary files /dev/null and b/docs/resources/fonts/inter/Inter-LightItalic.ttf differ
diff --git a/docs/resources/fonts/inter/Inter-Medium.ttf b/docs/resources/fonts/inter/Inter-Medium.ttf
new file mode 100644
index 0000000..5c88739
Binary files /dev/null and b/docs/resources/fonts/inter/Inter-Medium.ttf differ
diff --git a/docs/resources/fonts/inter/Inter-MediumItalic.ttf b/docs/resources/fonts/inter/Inter-MediumItalic.ttf
new file mode 100644
index 0000000..be091b1
Binary files /dev/null and b/docs/resources/fonts/inter/Inter-MediumItalic.ttf differ
diff --git a/docs/resources/fonts/inter/Inter-Regular.ttf b/docs/resources/fonts/inter/Inter-Regular.ttf
new file mode 100644
index 0000000..6b088a7
Binary files /dev/null and b/docs/resources/fonts/inter/Inter-Regular.ttf differ
diff --git a/docs/resources/fonts/inter/Inter-SemiBold.ttf b/docs/resources/fonts/inter/Inter-SemiBold.ttf
new file mode 100644
index 0000000..ceb8576
Binary files /dev/null and b/docs/resources/fonts/inter/Inter-SemiBold.ttf differ
diff --git a/docs/resources/fonts/inter/Inter-SemiBoldItalic.ttf b/docs/resources/fonts/inter/Inter-SemiBoldItalic.ttf
new file mode 100644
index 0000000..6921df2
Binary files /dev/null and b/docs/resources/fonts/inter/Inter-SemiBoldItalic.ttf differ
diff --git a/docs/resources/fonts/inter/Inter-Thin.ttf b/docs/resources/fonts/inter/Inter-Thin.ttf
new file mode 100644
index 0000000..3505b35
Binary files /dev/null and b/docs/resources/fonts/inter/Inter-Thin.ttf differ
diff --git a/docs/resources/fonts/inter/Inter-ThinItalic.ttf b/docs/resources/fonts/inter/Inter-ThinItalic.ttf
new file mode 100644
index 0000000..a3e6feb
Binary files /dev/null and b/docs/resources/fonts/inter/Inter-ThinItalic.ttf differ
diff --git a/docs/resources/fonts/space/Space.woff2 b/docs/resources/fonts/space/Space.woff2
new file mode 100644
index 0000000..0adf4d1
Binary files /dev/null and b/docs/resources/fonts/space/Space.woff2 differ
diff --git a/docs/resources/icons/arrow-left.svg b/docs/resources/icons/arrow-left.svg
new file mode 100644
index 0000000..d316096
--- /dev/null
+++ b/docs/resources/icons/arrow-left.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/docs/resources/icons/arrow-right.svg b/docs/resources/icons/arrow-right.svg
new file mode 100644
index 0000000..8405ae2
--- /dev/null
+++ b/docs/resources/icons/arrow-right.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/docs/resources/icons/bot.svg b/docs/resources/icons/bot.svg
new file mode 100644
index 0000000..812ca34
--- /dev/null
+++ b/docs/resources/icons/bot.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/docs/resources/icons/chevron-down.svg b/docs/resources/icons/chevron-down.svg
new file mode 100644
index 0000000..e576b4f
--- /dev/null
+++ b/docs/resources/icons/chevron-down.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/docs/resources/icons/chevron-right.svg b/docs/resources/icons/chevron-right.svg
new file mode 100644
index 0000000..5db5d6e
--- /dev/null
+++ b/docs/resources/icons/chevron-right.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/docs/resources/icons/clipboard-check.svg b/docs/resources/icons/clipboard-check.svg
new file mode 100644
index 0000000..084efca
--- /dev/null
+++ b/docs/resources/icons/clipboard-check.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/docs/resources/icons/clipboard-copy.svg b/docs/resources/icons/clipboard-copy.svg
new file mode 100644
index 0000000..62568ac
--- /dev/null
+++ b/docs/resources/icons/clipboard-copy.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/docs/resources/icons/clock.svg b/docs/resources/icons/clock.svg
new file mode 100644
index 0000000..98c2fac
--- /dev/null
+++ b/docs/resources/icons/clock.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/docs/resources/icons/code.svg b/docs/resources/icons/code.svg
new file mode 100644
index 0000000..c8c1105
--- /dev/null
+++ b/docs/resources/icons/code.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/docs/resources/icons/file-text.svg b/docs/resources/icons/file-text.svg
new file mode 100644
index 0000000..4be6f34
--- /dev/null
+++ b/docs/resources/icons/file-text.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/docs/resources/icons/ghost.svg b/docs/resources/icons/ghost.svg
new file mode 100644
index 0000000..185fd34
--- /dev/null
+++ b/docs/resources/icons/ghost.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/docs/resources/icons/github.svg b/docs/resources/icons/github.svg
new file mode 100644
index 0000000..7b643a9
--- /dev/null
+++ b/docs/resources/icons/github.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/docs/resources/icons/hash.svg b/docs/resources/icons/hash.svg
new file mode 100644
index 0000000..4155d9d
--- /dev/null
+++ b/docs/resources/icons/hash.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/docs/resources/icons/info.svg b/docs/resources/icons/info.svg
new file mode 100644
index 0000000..a2771b4
--- /dev/null
+++ b/docs/resources/icons/info.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/docs/resources/icons/menu.svg b/docs/resources/icons/menu.svg
new file mode 100644
index 0000000..b6f8033
--- /dev/null
+++ b/docs/resources/icons/menu.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/docs/resources/icons/moon.svg b/docs/resources/icons/moon.svg
new file mode 100644
index 0000000..07320f1
--- /dev/null
+++ b/docs/resources/icons/moon.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/docs/resources/icons/palette.svg b/docs/resources/icons/palette.svg
new file mode 100644
index 0000000..47a44c2
--- /dev/null
+++ b/docs/resources/icons/palette.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/docs/resources/icons/pilcrow.svg b/docs/resources/icons/pilcrow.svg
new file mode 100644
index 0000000..a56bd2a
--- /dev/null
+++ b/docs/resources/icons/pilcrow.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/docs/resources/icons/rocket.svg b/docs/resources/icons/rocket.svg
new file mode 100644
index 0000000..df6c40f
--- /dev/null
+++ b/docs/resources/icons/rocket.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/docs/resources/icons/search.svg b/docs/resources/icons/search.svg
new file mode 100644
index 0000000..2fa5416
--- /dev/null
+++ b/docs/resources/icons/search.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/docs/resources/icons/shield-check.svg b/docs/resources/icons/shield-check.svg
new file mode 100644
index 0000000..e9dc0d9
--- /dev/null
+++ b/docs/resources/icons/shield-check.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/docs/resources/icons/sun-moon.svg b/docs/resources/icons/sun-moon.svg
new file mode 100644
index 0000000..91a2370
--- /dev/null
+++ b/docs/resources/icons/sun-moon.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/docs/resources/icons/sun.svg b/docs/resources/icons/sun.svg
new file mode 100644
index 0000000..65c97c7
--- /dev/null
+++ b/docs/resources/icons/sun.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/docs/resources/icons/trash-2.svg b/docs/resources/icons/trash-2.svg
new file mode 100644
index 0000000..d82ac24
--- /dev/null
+++ b/docs/resources/icons/trash-2.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/docs/resources/icons/triangle-alert.svg b/docs/resources/icons/triangle-alert.svg
new file mode 100644
index 0000000..1a5b698
--- /dev/null
+++ b/docs/resources/icons/triangle-alert.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/docs/resources/icons/x.svg b/docs/resources/icons/x.svg
new file mode 100644
index 0000000..eb194fd
--- /dev/null
+++ b/docs/resources/icons/x.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/docs/resources/icons/zap.svg b/docs/resources/icons/zap.svg
new file mode 100644
index 0000000..dd5ea1f
--- /dev/null
+++ b/docs/resources/icons/zap.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/docs/resources/locales/bs/common.json b/docs/resources/locales/bs/common.json
new file mode 100644
index 0000000..71d9587
--- /dev/null
+++ b/docs/resources/locales/bs/common.json
@@ -0,0 +1,66 @@
+{
+ "hi": "React Router je zakon!",
+ "navigation": {
+ "back": "Idi nazad",
+ "home": "Nazad na početnu"
+ },
+ "error": {
+ "500": {
+ "title": "Nešto je krenulo po zlu!",
+ "description": "Izgleda da se nešto desilo na serveru."
+ },
+ "404": {
+ "title": "Stranica nije pronađena.",
+ "description": "Izgleda da stranica koju tražite ne postoji."
+ },
+ "403": {
+ "title": "Nemate pristup!",
+ "description": "Izgleda da nemate pristup ovoj stranici."
+ },
+ "200": {
+ "title": "Nešto je krenulo po zlu!",
+ "description": "Izgleda da se nešto desilo na serveru."
+ }
+ },
+ "links": {
+ "previous": "Prethodni",
+ "next": "Sljedeći",
+ "report_an_issue_on_this_page": "Prijavi problem na ovoj stranici",
+ "edit_this_page": "Uredi ovu stranicu"
+ },
+ "p": {
+ "last_update": "Posljednja izmjena:",
+ "version": "Verzija",
+ "all_rights_reserved": "Sva prava zadržana.",
+ "search_by": "Pretraga od"
+ },
+ "buttons": {
+ "copy": "Kopiraj",
+ "copied": "Kopirano",
+ "home": "Nazad na početnu",
+ "back": "Idi nazad",
+ "clear": "Obriši"
+ },
+ "titles": {
+ "good_to_know": "Dobro je znati",
+ "warning": "Upozorenje"
+ },
+ "text": {
+ "result_one": "{{count}} rezultat",
+ "result_other": "{{count}} rezultata",
+ "adjust_search": "Probajte prilagoditi pojmove za pretragu ili provjerite greške u kucanju",
+ "no_results_for": "Nema rezultata za",
+ "start_typing_to_search": "Počnite kucati za pretragu...",
+ "recent_searches": "Nedavne pretrage"
+ },
+ "controls": {
+ "navigate": "Navigiraj",
+ "open": "Otvori",
+ "tab": "Tab",
+ "select": "Odaberi",
+ "cycle": "Kruži"
+ },
+ "placeholders": {
+ "search_documentation": "Pretraži dokumentaciju..."
+ }
+}
diff --git a/docs/resources/locales/en/common.json b/docs/resources/locales/en/common.json
new file mode 100644
index 0000000..6336df5
--- /dev/null
+++ b/docs/resources/locales/en/common.json
@@ -0,0 +1,66 @@
+{
+ "hi": "React Router is awesome!",
+ "navigation": {
+ "back": "Go back",
+ "home": "Back to home"
+ },
+ "error": {
+ "500": {
+ "title": "Something went wrong!",
+ "description": "Looks like something unexpected happened on the server."
+ },
+ "404": {
+ "title": "Page Not found!",
+ "description": "Oops! The page you're looking for seems to have vanished into thin air."
+ },
+ "403": {
+ "title": "Unauthorized!",
+ "description": "Looks like you can't access this page."
+ },
+ "200": {
+ "title": "Something went wrong!",
+ "description": "Looks like something unexpected happened on the server."
+ }
+ },
+ "links": {
+ "previous": "Previous",
+ "next": "Next",
+ "report_an_issue_on_this_page": "Report an issue on this page",
+ "edit_this_page": "Edit this page"
+ },
+ "p": {
+ "last_update": "Last updated: ",
+ "version": "Version",
+ "all_rights_reserved": "All rights reserved.",
+ "search_by": "Search by"
+ },
+ "buttons": {
+ "copy": "Copy",
+ "copied": "Copied",
+ "home": "Back to home",
+ "back": "Go back",
+ "clear": "Clear"
+ },
+ "titles": {
+ "good_to_know": "Good to know",
+ "warning": "Warning"
+ },
+ "text": {
+ "result_one": "{{count}} result",
+ "result_other": "{{count}} results",
+ "adjust_search": "Try adjusting your search terms or check for typos",
+ "no_results_for": "No results found for",
+ "start_typing_to_search": "Start typing to search...",
+ "recent_searches": "Recent searches"
+ },
+ "controls": {
+ "navigate": "Navigate",
+ "open": "Open",
+ "tab": "Tab",
+ "select": "Select",
+ "cycle": "Cycle"
+ },
+ "placeholders": {
+ "search_documentation": "Search documentation..."
+ }
+}
diff --git a/docs/scripts/generate-docs.ts b/docs/scripts/generate-docs.ts
new file mode 100644
index 0000000..bf9090f
--- /dev/null
+++ b/docs/scripts/generate-docs.ts
@@ -0,0 +1,250 @@
+import { type ExecSyncOptions, execSync } from "node:child_process"
+import { cpSync, existsSync, mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs"
+import os from "node:os"
+import { join, resolve } from "node:path"
+import { parseArgs } from "node:util"
+import chalk from "chalk"
+import semver from "semver"
+
+const contentDir = "content"
+const workspaceRoot = process.cwd()
+const outputDir = resolve(workspaceRoot, "generated-docs")
+
+type RunOpts = { cwd?: string; inherit?: boolean }
+function run(cmd: string, opts: RunOpts = {}) {
+ const exOpts: ExecSyncOptions = {
+ cwd: opts.cwd,
+ stdio: opts.inherit ? "inherit" : "pipe",
+ encoding: "utf8",
+ }
+ try {
+ const res = execSync(cmd, exOpts)
+ if (opts.inherit) return ""
+ if (typeof res === "string") return res.trim()
+ return (res?.toString?.("utf8") ?? "").trim()
+ } catch (err: unknown) {
+ const msg = err instanceof Error ? err.message : String(err)
+ throw new Error(`Command failed: ${cmd}\n${msg}`)
+ }
+}
+
+const ensureDir = (p: string) => mkdirSync(p, { recursive: true })
+const resetDir = (p: string) => {
+ if (existsSync(p)) rmSync(p, { recursive: true, force: true })
+ ensureDir(p)
+}
+
+let repoRoot = workspaceRoot
+let workspaceRelativePath = ""
+try {
+ repoRoot = run("git rev-parse --show-toplevel")
+ // biome-ignore lint/style/useTemplate:
+ workspaceRelativePath = repoRoot === workspaceRoot ? "" : workspaceRoot.replace(repoRoot + "/", "")
+} catch {
+ workspaceRelativePath = ""
+}
+
+const allTags = () => run("git tag --list").split("\n").filter(Boolean)
+
+function resolveTagsFromSpec(spec: string) {
+ const tags = allTags().filter((t) => semver.valid(t))
+ const tokens = spec
+ .split(",")
+ .map((t) => t.trim())
+ .filter(Boolean)
+ const matched = tags.filter((tag) =>
+ tokens.some((token) => semver.satisfies(tag, token, { includePrerelease: true }))
+ )
+ return matched.sort(semver.rcompare)
+}
+
+function hasLocalRef(ref: string) {
+ try {
+ run(`git show-ref --verify --quiet ${ref}`)
+ return true
+ } catch {
+ return false
+ }
+}
+
+function buildDocs(sourceDir: string, outDir: string) {
+ if (!existsSync(sourceDir)) {
+ throw new Error(
+ `❌ Documentation workspace not found at: ${sourceDir}
+ Cannot build documentation without a valid workspace directory.`
+ )
+ }
+
+ // biome-ignore lint/suspicious/noConsole: TODO remove this
+ console.log(chalk.cyan(`Building docs from: ${sourceDir} → ${outDir}`))
+ const docsContentDir = resolve(sourceDir, contentDir)
+ if (!existsSync(docsContentDir)) {
+ throw new Error(
+ `❌ Content directory "${contentDir}" not found at: ${docsContentDir}
+ Cannot build documentation without content files.
+ Please ensure you have a "${contentDir}/" directory with your documentation content.`
+ )
+ }
+
+ const packageJsonPath = resolve(sourceDir, "package.json")
+ if (!existsSync(packageJsonPath)) {
+ throw new Error(
+ `❌ package.json not found at: ${packageJsonPath}
+ Cannot build documentation without package.json.
+ Please ensure your workspace has a valid package.json file.`
+ )
+ }
+
+ resetDir(outDir)
+ run("pnpm run content-collections:build", { cwd: sourceDir, inherit: true })
+
+ const ccSrc = resolve(sourceDir, ".content-collections")
+ const ccDest = join(outDir, ".content-collections")
+ if (!existsSync(ccSrc)) {
+ throw new Error(
+ `❌ Build output missing at: ${ccSrc}
+ Content collections build failed or did not produce output.
+ Please check the build logs above for errors.`
+ )
+ }
+
+ resetDir(ccDest)
+ cpSync(ccSrc, ccDest, { recursive: true })
+
+ // biome-ignore lint/suspicious/noConsole: keep for debugging
+ console.log(chalk.green(`✔ Built docs → ${ccDest}`))
+}
+
+function buildRef(ref: string, labelForOutDir: string) {
+ const tmpBase = mkdtempSync(resolve(os.tmpdir(), "docs-wt-"))
+ const safeLabel = labelForOutDir.replace(/[^\w.-]+/g, "_")
+ const worktreePath = resolve(tmpBase, safeLabel)
+
+ run(`git worktree add --detach "${worktreePath}" "${ref}"`, {
+ cwd: workspaceRoot,
+ inherit: true,
+ })
+
+ try {
+ const rootPkg = existsSync(resolve(worktreePath, "package.json"))
+ const rootLock = existsSync(resolve(worktreePath, "pnpm-lock.yaml"))
+ if (rootPkg) {
+ run(`pnpm install ${rootLock ? "--frozen-lockfile" : "--no-frozen-lockfile"}`, {
+ cwd: worktreePath,
+ inherit: true,
+ })
+ }
+
+ const sourceDir = workspaceRelativePath ? resolve(worktreePath, workspaceRelativePath) : worktreePath
+ const outDir = resolve(outputDir, labelForOutDir)
+ buildDocs(sourceDir, outDir)
+ } finally {
+ run(`git worktree remove "${worktreePath}" --force`, {
+ cwd: workspaceRoot,
+ inherit: true,
+ })
+ rmSync(tmpBase, { recursive: true, force: true })
+ }
+}
+
+function buildBranch(branch: string, labelForOutDir: string) {
+ run(`git fetch --tags --prune origin ${branch}`, {
+ cwd: workspaceRoot,
+ inherit: true,
+ })
+ const localRef = `refs/heads/${branch}`
+ const targetRef = hasLocalRef(localRef) ? localRef : `origin/${branch}`
+ return buildRef(targetRef, labelForOutDir)
+}
+
+function buildTag(tag: string) {
+ return buildRef(`refs/tags/${tag}`, tag)
+}
+
+function getCurrentBranch(): string {
+ try {
+ return run("git rev-parse --abbrev-ref HEAD")
+ } catch {
+ throw new Error("Failed to get current branch")
+ }
+}
+
+function isOnDefaultBranch(defaultBranch: string): boolean {
+ const currentBranch = getCurrentBranch()
+ return currentBranch === defaultBranch
+}
+
+function parseCliArgs() {
+ const { values } = parseArgs({
+ args: process.argv.slice(2),
+ options: {
+ versions: { type: "string" },
+ branch: { type: "string" },
+ },
+ })
+
+ const defaultBranch = (values.branch as string | undefined)?.trim()
+ if (!defaultBranch) {
+ throw new Error(
+ "❌ Missing required --branch flag.\n" +
+ " Please specify the default branch name (e.g., --branch main)\n" +
+ " Example: pnpm run generate:docs --branch main"
+ )
+ }
+
+ const versionsSpec = (values.versions as string | undefined)?.trim() || undefined
+
+ return { defaultBranch, versionsSpec }
+}
+
+function buildLatestVersion(onDefaultBranch: boolean, defaultBranch: string) {
+ if (onDefaultBranch) {
+ buildBranch(defaultBranch, "latest")
+ } else {
+ buildDocs(workspaceRoot, join(outputDir, "latest"))
+ }
+}
+
+function writeVersionsFile(versions: string[]) {
+ const versionsFile = resolve("app/utils/versions.ts")
+ const content = `// Auto-generated file. Do not edit manually.\nexport const versions = ${JSON.stringify(versions, null, 2)} as const\n`
+
+ writeFileSync(versionsFile, content)
+}
+
+async function main() {
+ const { defaultBranch, versionsSpec } = parseCliArgs()
+
+ const onDefaultBranch = isOnDefaultBranch(defaultBranch)
+
+ let builtVersions: string[]
+
+ if (versionsSpec) {
+ const tags = resolveTagsFromSpec(versionsSpec)
+ if (tags.length === 0) {
+ throw new Error(`No tags matched spec "${versionsSpec}".`)
+ }
+
+ // biome-ignore lint/suspicious/noConsole: keep for debugging
+ console.log(chalk.cyan(`Building tags: ${tags.join(", ")}`))
+ for (const tag of tags) {
+ buildTag(tag)
+ }
+
+ buildLatestVersion(onDefaultBranch, defaultBranch)
+ builtVersions = ["latest", ...tags]
+ } else {
+ buildLatestVersion(onDefaultBranch, defaultBranch)
+ builtVersions = ["latest"]
+ }
+
+ writeVersionsFile(builtVersions)
+ // biome-ignore lint/suspicious/noConsole: keep for debugging
+ console.log(chalk.green("✅ Done"))
+}
+
+main().catch((error) => {
+ // biome-ignore lint/suspicious/noConsole: keep for debugging
+ console.error(chalk.red("❌ Build failed:"), error)
+ process.exit(1)
+})
diff --git a/docs/scripts/setup.ts b/docs/scripts/setup.ts
new file mode 100644
index 0000000..9c194d9
--- /dev/null
+++ b/docs/scripts/setup.ts
@@ -0,0 +1,97 @@
+import { spawn } from "node:child_process"
+import dotenvx from "@dotenvx/dotenvx"
+import chalk from "chalk"
+import prompt from "prompt"
+// add all the env you wish here
+const ENVIRONMENTS = ["stage", "prod", "test"]
+
+const log = (message: string) => console.log(chalk.green(message))
+
+const getEnvInfo = () => {
+ // Gets the environment from the command line arguments if set, otherwise defaults to dev
+ const env = process.argv.find((arg) => ENVIRONMENTS.includes(arg)) ?? ""
+ // Sets the environment name to be console logged for info
+ const envName = env !== "" ? env : "dev"
+ // Allows for reading from .env .env.prod .env.stage etc
+ const path = `.env${env ? `.${env}` : ""}`
+ return { env, envName, path }
+}
+
+const setupEnv = () => {
+ const { envName, path } = getEnvInfo()
+ dotenvx.config({ path })
+ log(`Environment loaded: ${chalk.green(envName)} from ${chalk.green(path)}`)
+}
+
+// Helper method used to confirm the run
+const confirmRun = async () => {
+ const { envName } = getEnvInfo()
+ log(`About to execute the command in ${chalk.bold.red(envName)} environment.`)
+
+ const { sure } = await prompt.get([
+ {
+ name: "sure",
+ description: "Are you sure? (y/n)",
+ type: "string",
+ required: true,
+ },
+ ])
+
+ if (sure !== "y") {
+ log(chalk.bold.red("Command aborted!\n"))
+ process.exit(1)
+ }
+}
+
+if (!process.argv[2]) {
+ chalk.red("Missing command to run argument")
+ process.exit(1)
+}
+// Injects .env variables into the process
+setupEnv()
+
+// Main command to run
+const main = () => {
+ // Allows us to run scripts from the scripts folder without having to wrap them in package.json with npm run execute
+ const command = process.argv[2].startsWith("scripts/") ? `npm run execute ${process.argv[2]}` : process.argv[2]
+ // Filter out the script command and the environment (the slice(3) part) and remove our custom args and pass everything else down
+ const filteredArgs = process.argv.slice(3).filter((arg) => !ENVIRONMENTS.includes(arg) && arg !== "confirm")
+ // Spawns a child process with the command to run
+ // param 1 - command to run
+ // param 2 - arguments to pass to the command
+ // param 3 - options for the child process
+ const child = spawn(command, filteredArgs, {
+ cwd: process.cwd(),
+ stdio: "inherit",
+ shell: true,
+ })
+ // If the child process exits, exit the parent process too if the exit code is not 0
+ child.on("exit", (exitCode) => {
+ if (exitCode !== 0) {
+ process.exit(exitCode ?? 1)
+ }
+ })
+ //
+
+ for (const signal of ["SIGINT", "SIGTERM"]) {
+ process.on(signal, () => {
+ // Kills the child only if it is still connected and alive
+ if (child.connected) {
+ child.kill(child.pid)
+ }
+ process.exit(1)
+ })
+ }
+}
+// Makes the user confirm the run if the confirm argument is passed
+if (process.argv.includes("confirm")) {
+ confirmRun()
+ .then(() => {
+ main()
+ })
+ .catch(() => process.exit(1))
+
+ // If the confirm argument is not passed, just run the command
+} else {
+ main()
+}
diff --git a/docs/tests/setup.browser.tsx b/docs/tests/setup.browser.tsx
new file mode 100644
index 0000000..2be6f94
--- /dev/null
+++ b/docs/tests/setup.browser.tsx
@@ -0,0 +1,76 @@
+import "../app/tailwind.css"
+import { renderHook as renderReactHook } from "@testing-library/react"
+import { createInstance } from "i18next"
+import { I18nextProvider, initReactI18next } from "react-i18next"
+import { Outlet, type RoutesTestStubProps, createRoutesStub } from "react-router"
+import { render } from "vitest-browser-react"
+import i18n from "~/localization/i18n"
+import { type Language, type Namespace, resources } from "~/localization/resource"
+export type StubRouteEntry = Parameters[0][0]
+
+const renderStub = async (args?: {
+ props?: RoutesTestStubProps
+ entries?: StubRouteEntry[]
+ i18n?: {
+ lng?: Language
+ ns?: Namespace | Namespace[]
+ }
+}) => {
+ const instance = createInstance()
+ // Initialize the i18next instance
+ await instance
+ .use(initReactI18next) // Tell our instance to use react-i18next
+ .init({
+ ...i18n, // spread the configuration
+ lng: args?.i18n?.lng ?? "en", // The locale can be set per test or defaults to english
+ ns: args?.i18n?.ns ?? "common", // The namespaces can be set in the test or defaults to common
+ resources,
+ })
+
+ // We create the entries array to be rendered by react-router
+ const entries: StubRouteEntry[] = [
+ {
+ id: "root",
+ path: "/",
+ children: args?.entries ?? [],
+ Component: () => (
+
+
+
+
+
+ ),
+ },
+ ]
+ // We generate the props to be passed into the react-router stub
+ const props: RoutesTestStubProps = {
+ ...args?.props,
+ initialEntries: args?.props?.initialEntries ?? ["/"],
+ }
+ // We generate the stub using the entries and props
+ const Stub = createRoutesStub(entries)
+ // We render the container so it can be used in tests
+ const renderedScreen = render( )
+
+ return renderedScreen
+}
+
+const renderHook = renderReactHook
+
+// We extend the global test context with our custom functions that we pass into the context in beforeEach
+declare module "vitest" {
+ export interface TestContext {
+ renderStub: typeof renderStub
+ renderHook: typeof renderHook
+ }
+}
+// We pass in our custom functions to the test context
+beforeEach((ctx) => {
+ ctx.renderStub = renderStub
+ ctx.renderHook = renderHook
+})
+
+// We clear all mocks after each test (optional, feel free to remove it)
+afterEach(() => {
+ vi.clearAllMocks()
+})
diff --git a/docs/tsconfig.json b/docs/tsconfig.json
new file mode 100644
index 0000000..3eccfdd
--- /dev/null
+++ b/docs/tsconfig.json
@@ -0,0 +1,28 @@
+{
+ "include": ["env.d.ts", "**/*.ts", "**/*.tsx", "**/**/.server/**/*.ts", ".react-router/types/**/*"],
+ "compilerOptions": {
+ "types": ["vitest/globals", "@vitest/browser/providers/playwright"],
+ "lib": ["DOM", "DOM.Iterable", "ES2023"],
+ "isolatedModules": true,
+ "esModuleInterop": true,
+ "jsx": "react-jsx",
+ "module": "ESNext",
+ "moduleResolution": "Bundler",
+ "resolveJsonModule": true,
+ "target": "ES2023",
+ "strict": true,
+ "allowJs": true,
+ "skipLibCheck": true,
+ "forceConsistentCasingInFileNames": true,
+ "baseUrl": ".",
+ "paths": {
+ "~/*": ["./app/*"],
+ "content-collections": [".content-collections/generated"],
+ "content-collections-types": ["./content-collections.ts"]
+ },
+ "rootDirs": [".", "./.react-router/types"],
+ "plugins": [{ "name": "@react-router/dev" }],
+ // Vite takes care of building everything, not tsc.
+ "noEmit": true
+ }
+}
diff --git a/docs/vite.config.ts b/docs/vite.config.ts
new file mode 100644
index 0000000..7c712d4
--- /dev/null
+++ b/docs/vite.config.ts
@@ -0,0 +1,49 @@
+import contentCollections from "@content-collections/remix-vite"
+import { reactRouter } from "@react-router/dev/vite"
+import tailwindcss from "@tailwindcss/vite"
+import { reactRouterDevTools } from "react-router-devtools"
+import { reactRouterHonoServer } from "react-router-hono-server/dev"
+import { defineConfig } from "vite"
+import babel from "vite-plugin-babel"
+import { iconsSpritesheet } from "vite-plugin-icons-spritesheet"
+import tsconfigPaths from "vite-tsconfig-paths"
+
+export default defineConfig(({ mode }) => ({
+ plugins: [
+ tailwindcss(),
+ // Run the react-compiler on .tsx files only when bundling
+ {
+ ...babel({
+ filter: /\.tsx?$/,
+ babelConfig: {
+ presets: ["@babel/preset-typescript"],
+ plugins: ["babel-plugin-react-compiler"],
+ },
+ }),
+ apply: "build",
+ },
+ reactRouterDevTools(),
+ reactRouter(),
+ reactRouterHonoServer({
+ dev: {
+ exclude: [/^\/(resources)\/.+/, /^\/(.content-collections)\/.+/],
+ },
+ }),
+ tsconfigPaths(),
+ iconsSpritesheet({
+ inputDir: "./resources/icons",
+ outputDir: "./app/ui/icon/icons",
+ fileName: "icon.svg",
+ withTypes: true,
+ formatter: "biome",
+ }),
+ // Only load content-collections plugin in development
+ // In production, we load pre-generated docs from generated-docs/
+ ...(mode === "development" ? [contentCollections()] : []),
+ ],
+ server: {
+ open: true,
+ // biome-ignore lint/nursery/noProcessEnv: Its ok to use process.env here
+ port: Number(process.env.PORT || 4280),
+ },
+}))
diff --git a/docs/vitest.config.ts b/docs/vitest.config.ts
new file mode 100644
index 0000000..8bccd45
--- /dev/null
+++ b/docs/vitest.config.ts
@@ -0,0 +1,16 @@
+import tsconfigPaths from "vite-tsconfig-paths"
+import { defineConfig } from "vitest/config"
+
+export default defineConfig({
+ plugins: [tsconfigPaths()],
+ test: {
+ globals: true,
+ css: true,
+ coverage: {
+ all: false,
+ include: ["app/**"],
+ reporter: ["text", "json-summary", "json"],
+ reportOnFailure: true,
+ },
+ },
+})
diff --git a/docs/vitest.workspace.ts b/docs/vitest.workspace.ts
new file mode 100644
index 0000000..4f428cc
--- /dev/null
+++ b/docs/vitest.workspace.ts
@@ -0,0 +1,40 @@
+import { defineWorkspace } from "vitest/config"
+
+export default defineWorkspace([
+ {
+ extends: "./vitest.config.ts",
+ test: {
+ name: "server tests",
+ environment: "node",
+ // Include generic .test files that should work anywhere and .server.test files for server only, ignore .browser.test
+ include: ["./**/*.server.test.{ts,tsx}", "!./**/*.browser.test.{ts,tsx}", "./**/*.test.{ts,tsx}"],
+ },
+ },
+ {
+ extends: "./vitest.config.ts",
+ optimizeDeps: {
+ include: ["react/jsx-dev-runtime"],
+ },
+ server: {
+ fs: {
+ strict: false,
+ },
+ },
+ test: {
+ includeTaskLocation: true,
+ // Include generic .test files that should work anywhere and .browser.test files for browser only, ignore .server.test
+ include: ["./**/*.test.{ts,tsx}", "./**/*.browser.test.{ts,tsx}", "!./**/*.server.test.{ts,tsx}"],
+ setupFiles: ["./tests/setup.browser.tsx"],
+ name: "browser tests",
+
+ browser: {
+ enabled: true,
+ instances: [{ browser: "chromium" }],
+
+ provider: "playwright",
+ // https://playwright.dev
+ //providerOptions: {},
+ },
+ },
+ },
+])
diff --git a/vitest.config.ts b/vitest.config.ts
new file mode 100644
index 0000000..9ad62c5
--- /dev/null
+++ b/vitest.config.ts
@@ -0,0 +1,11 @@
+import { defineConfig } from "vitest/config";
+
+export default defineConfig({
+ test: {
+ // deploykit's own tests live under src/. The docs/ subproject is a
+ // self-contained app with its own test runner (browser + React), so keep
+ // the root suite from picking up its *.test.ts files.
+ include: ["src/**/*.test.ts"],
+ exclude: ["**/node_modules/**", "dist/**", "docs/**"],
+ },
+});