diff --git a/.autorel.yaml b/.autorel.yaml new file mode 100644 index 0000000..35660bd --- /dev/null +++ b/.autorel.yaml @@ -0,0 +1,13 @@ +commitTypes: + - {type: feat, title: ✨ Features, release: minor} + - {type: fix, title: πŸ› Bug Fixes, release: patch} + - {type: perf, title: πŸš€ Performance Improvements, release: patch} + - {type: revert, title: βͺ Reverts, release: patch} + - {type: docs, title: πŸ“š Documentation, release: patch} + - {type: style, title: πŸ’… Styles, release: patch} + - {type: refactor, title: πŸ›  Code Refactoring, release: patch} + - {type: test, title: πŸ§ͺ Tests, release: patch} + - {type: build, title: πŸ— Build System, release: patch} + - {type: ci, title: πŸ”§ Continuous Integration, release: patch} +branches: + - {name: main} diff --git a/.c8rc.json b/.c8rc.json new file mode 100644 index 0000000..1f34bf4 --- /dev/null +++ b/.c8rc.json @@ -0,0 +1,20 @@ +{ + "include": ["src/**/*.ts"], + "exclude": [ + "**/*.spec.ts", + "**/*.spec.js", + "src/components/**", + "src/overrides/**", + "src/pages/**", + "src/cs-pagefind/**", + "src/content/**", + "src/content.config.ts", + "src/integrations/**", + "src/styles/**", + "src/env.d.ts" + ], + "reporter": [], + "all": true, + "cache": false, + "clean": true +} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..f2cd250 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,52 @@ +# Keep `name: CI` β€” release.yml triggers on it via workflow_run. +name: CI + +on: + pull_request: + branches: [main] + push: + branches: [main] + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: ci-${{ github.ref }} + cancel-in-progress: true + +jobs: + ci: + runs-on: ubuntu-latest + timeout-minutes: 10 + strategy: + fail-fast: false + matrix: + node-version: [20, 22, 24] + steps: + - uses: actions/checkout@v4 + + - name: Setup Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v6 + with: + node-version: ${{ matrix.node-version }} + cache: 'npm' + + - name: Install dependencies + run: npm ci + + - name: Lint + run: npm run lint:check + + - name: Type check + run: npx tsc --noEmit + + - name: Build + run: npm run build + + - name: Test + run: npm test + + - name: Docs site + if: matrix.node-version == 24 + run: npm run astro:build diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..1b2093e --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,70 @@ +name: Release + +# Triggered by CI completing successfully on main. CI is the gate; release +# never runs unless CI passed. This catches admin force-pushes that bypass checks. +on: + workflow_run: + workflows: [CI] + types: [completed] + branches: [main] + +permissions: + id-token: write # npm trusted publishing + AWS OIDC + contents: write # Required for autorel to push release tag (and changelog) + +jobs: + release: + name: Release + if: >- + github.event.workflow_run.event == 'push' + && github.event.workflow_run.conclusion == 'success' + && github.event.workflow_run.head_repository.full_name == github.repository + runs-on: ubuntu-latest + timeout-minutes: 15 + concurrency: + group: deploy-${{ github.event.workflow_run.head_branch }} + cancel-in-progress: true + env: + # prod CloudFront β€” stack: infra/prod/us-east-2/static/busker + PROD_ACCOUNT: ${{ secrets.AWS_ACCOUNT_PROD }} + PROD_AWS_REGION: us-east-2 + PROD_DEPLOY_ROLE: gh-deploy-busker + PROD_SSM_BUCKET_PARAM: /logfox/prod/busker/assetsBucket + PROD_SSM_DISTRIBUTION_PARAM: /logfox/prod/busker/cloudfrontDistributionId + steps: + - uses: actions/checkout@v4 + with: + ref: ${{ github.event.workflow_run.head_sha }} + fetch-depth: 0 + fetch-tags: true + + - uses: actions/setup-node@v6 + with: + node-version: '24' # npm 11+ required for trusted publishing (OIDC) + registry-url: 'https://registry.npmjs.org' + cache: 'npm' + + - run: npm ci + + - name: Publish npm package + run: npx autorel@^2 --publish + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Configure AWS credentials + uses: aws-actions/configure-aws-credentials@v4 + with: + aws-region: ${{ env.PROD_AWS_REGION }} + role-to-assume: arn:aws:iam::${{ env.PROD_ACCOUNT }}:role/${{ env.PROD_DEPLOY_ROLE }} + + - name: Load publish targets from SSM + run: | + set -eux + echo "S3_BUCKET=$(aws ssm get-parameter --name "$PROD_SSM_BUCKET_PARAM" --query Parameter.Value --output text)" >> "$GITHUB_ENV" + echo "DISTRIBUTION_ID=$(aws ssm get-parameter --name "$PROD_SSM_DISTRIBUTION_PARAM" --query Parameter.Value --output text)" >> "$GITHUB_ENV" + + - name: Deploy docs site + run: ./deploy-docs.sh + env: + S3_BUCKET: ${{ env.S3_BUCKET }} + DISTRIBUTION_ID: ${{ env.DISTRIBUTION_ID }} diff --git a/.gitignore b/.gitignore index 533b6fd..c52b970 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ .idea -.vscode +.vscode/* +!.vscode/settings.json .DS_Store node_modules dist diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..70bf6bf --- /dev/null +++ b/AGENTS.md @@ -0,0 +1 @@ +For coding agents: read [skills/busker/SKILL.md](skills/busker/SKILL.md). diff --git a/README.md b/README.md new file mode 100644 index 0000000..b7c82a3 --- /dev/null +++ b/README.md @@ -0,0 +1,73 @@ +
+ + + + busker + +

Scripted cursor demos that really click.

+

+ busker.logfox.ai +

+
+ +Fake product demos on a landing page usually rot. The cursor is on one timeline, the page state is on another, and the moment you change a duration they drift — the cursor presses a button 200ms before the modal it opened, forever. + +Busker gets rid of the second timeline. You give it a list of places the cursor goes, it glides there and **really clicks the element**, and your own handlers change the page. Nothing is timed except the pauses, so there is nothing to keep in sync. + +```typescript +import {busk} from 'busker'; +import 'busker/busker.css'; + +busk(document.querySelector('.app'), { + scene: 'home', + steps: [ + {click: '[data-nav-item="alerts"]', wait: 900}, + {click: '[data-row="p0"]', wait: 1500}, + {to: [0.55, 0.25], wait: 2600}, + ], + routes: [ + {click: '[data-nav-item="alerts"]', scene: 'alerts'}, + {click: '[data-row="p0"]', scene: 'case'}, + ], +}); +``` + +The show loops while the mock is on screen and stops when the tab is in the background. The first time a visitor clicks, busker steps aside — the cursor disappears and the mock is theirs to poke at. Click something dead and it flashes what is not. + +Zero dependencies, about 4kB, no build step, no framework. + +## Quick start + +```bash +npm i busker +``` + +[Getting started](src/content/docs/getting-started.md) is a working demo in four steps. + +## Contents + +Same guides as [busker.logfox.ai](https://busker.logfox.ai). Links are repo-relative, so they work in a checkout and on GitHub. + +### Introduction + +- [Getting started](src/content/docs/getting-started.md) +- [Markup](src/content/docs/markup.md) — the five attributes busker reads and the classes it writes + +### Routines + +- [Click-driven routines](src/content/docs/routines.md) — steps, timing, and routes +- [Hand-timed routines](src/content/docs/timeline.md) — for what no click causes: streaming logs, typing, countdowns +- [When a visitor takes over](src/content/docs/taking-over.md) + +### Reference + +- [API reference](src/content/docs/api-reference.md) +- [Styling](src/content/docs/styling.md) + +### Project + +- [Development](src/content/docs/development.md) + +## License + +MIT diff --git a/assets/busker-lockup-dark.svg b/assets/busker-lockup-dark.svg new file mode 100644 index 0000000..c41a29a --- /dev/null +++ b/assets/busker-lockup-dark.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/assets/busker-lockup-light.svg b/assets/busker-lockup-light.svg new file mode 100644 index 0000000..64109cd --- /dev/null +++ b/assets/busker-lockup-light.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/assets/favicon.svg b/assets/favicon.svg new file mode 100644 index 0000000..72bda34 --- /dev/null +++ b/assets/favicon.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/fonts/inter-latin-wght-normal.woff2 b/assets/fonts/inter-latin-wght-normal.woff2 new file mode 100644 index 0000000..d15208d Binary files /dev/null and b/assets/fonts/inter-latin-wght-normal.woff2 differ diff --git a/assets/fonts/jetbrains-mono-nerd-mono-400.woff2 b/assets/fonts/jetbrains-mono-nerd-mono-400.woff2 new file mode 100644 index 0000000..02a57c6 Binary files /dev/null and b/assets/fonts/jetbrains-mono-nerd-mono-400.woff2 differ diff --git a/assets/fonts/jetbrains-mono-nerd-mono-600.woff2 b/assets/fonts/jetbrains-mono-nerd-mono-600.woff2 new file mode 100644 index 0000000..bc40b74 Binary files /dev/null and b/assets/fonts/jetbrains-mono-nerd-mono-600.woff2 differ diff --git a/assets/mark-dark.svg b/assets/mark-dark.svg new file mode 100644 index 0000000..132873e --- /dev/null +++ b/assets/mark-dark.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/mark-light.svg b/assets/mark-light.svg new file mode 100644 index 0000000..3e90602 --- /dev/null +++ b/assets/mark-light.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/og.png b/assets/og.png new file mode 100644 index 0000000..5d886fb Binary files /dev/null and b/assets/og.png differ diff --git a/assets/og.svg b/assets/og.svg new file mode 100644 index 0000000..8b76d71 --- /dev/null +++ b/assets/og.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/astro.config.mjs b/astro.config.mjs new file mode 100644 index 0000000..159a9e9 --- /dev/null +++ b/astro.config.mjs @@ -0,0 +1,142 @@ +import {defineConfig} from 'astro/config'; +import {unified} from '@astrojs/markdown-remark'; +import starlight from '@astrojs/starlight'; +import {devPagefindPlugin} from './src/integrations/devPagefind.mjs'; +import {pagefindShimIntegration} from './src/integrations/pagefindShim.mjs'; +import {remarkStarlightMdLinks} from './src/integrations/remark-starlight-md-links.mjs'; +import {remarkDropTitle} from './src/integrations/remark-drop-title.mjs'; +import {rehypeWrapTables} from './src/integrations/rehype-wrap-tables.mjs'; + +const isDev = process.env.NODE_ENV !== 'production'; +const site = 'https://busker.logfox.ai'; +const ogImage = `${site}/og.png`; +const ogImageAlt = 'busker β€” scripted cursor demos that really click'; +const description = + 'Drive a fake product UI with a routine, not a stopwatch. The cursor really clicks, so the page changes through your own handlers β€” and visitors can take over.'; + +const ogTag = (property, content) => ({tag: 'meta', attrs: {property, content}}); + +export default defineConfig({ + // Hover-prefetching every sidebar link hammers Vite in dev and freezes tabs. + prefetch: isDev ? false : {prefetchAll: true, defaultStrategy: 'hover'}, + devToolbar: {enabled: false}, + markdown: { + processor: unified({ + remarkPlugins: [remarkDropTitle, remarkStarlightMdLinks], + rehypePlugins: [rehypeWrapTables], + }), + }, + site, + outDir: './docs-site', + // Brand / docs static media β€” single source of truth (also used by README) + publicDir: './assets', + // Dev server only: Origin-bearing cross-site requests (proxies / some IDE previews). + security: { + allowedDomains: [{}], + }, + // HTML + Vite modules β€” Cursor Simple Browser and Chrome otherwise keep stale CSS. + server: { + headers: { + 'Cache-Control': 'no-store', + }, + }, + vite: { + plugins: [...(isDev ? [devPagefindPlugin()] : [])], + // Vite 8 defaults cssMinify to lightningcss, which drops unprefixed + // backdrop-filter when -webkit- is present β€” Chromium then skips frost. + // https://github.com/vitejs/vite/issues/22649 + build: { + cssMinify: 'esbuild', + }, + server: { + cors: true, + headers: { + 'Cache-Control': 'no-store', + }, + watch: { + // Build output must not reload dev β€” corrupts Starlight content sync. + // c8 HTML reports during `npm test` / validate must not restart dev mid-session. + ignored: ['**/docs-site/**', '**/coverage/**'], + }, + }, + }, + integrations: [ + starlight({ + title: 'busker', + description, + // Custom 404 is src/pages/404.astro β€” Starlight's injected route would collide. + disable404Route: true, + logo: { + light: './assets/busker-lockup-light.svg', + dark: './assets/busker-lockup-dark.svg', + replacesTitle: true, + }, + favicon: '/favicon.svg', + head: [ + ogTag('og:image', ogImage), + ogTag('og:image:type', 'image/png'), + ogTag('og:image:width', '1200'), + ogTag('og:image:height', '630'), + ogTag('og:image:alt', ogImageAlt), + {tag: 'meta', attrs: {name: 'twitter:card', content: 'summary_large_image'}}, + {tag: 'meta', attrs: {name: 'twitter:image', content: ogImage}}, + {tag: 'meta', attrs: {name: 'twitter:image:alt', content: ogImageAlt}}, + ], + // Code block chrome lives in ec.config.mjs (ui-components Code look) + expressiveCode: true, + customCss: [ + './src/styles/fonts.css', + './src/styles/docs-shared.css', + './src/styles/starlight-custom.css', + ], + components: { + Head: './src/overrides/Head.astro', + Header: './src/overrides/Header.astro', + Hero: './src/overrides/Hero.astro', + Search: './src/overrides/Search.astro', + ThemeSelect: './src/overrides/ThemeSelect.astro', + MobileMenuToggle: './src/overrides/MobileMenuToggle.astro', + MobileMenuFooter: './src/overrides/MobileMenuFooter.astro', + PageFrame: './src/overrides/PageFrame.astro', + PageTitle: './src/overrides/PageTitle.astro', + Footer: './src/overrides/Footer.astro', + }, + social: [ + { + icon: 'github', + label: 'GitHub', + href: 'https://github.com/logfoxai/busker', + }, + ], + sidebar: [ + { + label: 'Introduction', + items: [ + {label: 'Getting started', slug: 'getting-started'}, + {label: 'Markup', slug: 'markup'}, + ], + }, + { + label: 'Routines', + items: [ + {label: 'Click-driven routines', slug: 'routines'}, + {label: 'Hand-timed routines', slug: 'timeline'}, + {label: 'When a visitor takes over', slug: 'taking-over'}, + ], + }, + { + label: 'Reference', + items: [ + {label: 'API reference', slug: 'api-reference'}, + {label: 'Styling', slug: 'styling'}, + ], + }, + { + label: 'Project', + items: [{label: 'Development', slug: 'development'}], + }, + ], + }), + pagefindShimIntegration(), + ], +}); diff --git a/busker.css b/busker.css new file mode 100644 index 0000000..7bf3519 --- /dev/null +++ b/busker.css @@ -0,0 +1,123 @@ +/** + * Optional styling for the cursor and the affordances busker adds. + * + * Import it as-is or copy the bits you want. Everything is scoped to `.busker`, + * the class `busk()` puts on the root, so nothing leaks into the rest of the page. + * The two animation lengths here are also in the JavaScript (RING_MS, HINT_MS) β€” + * change one, change the other. + */ + +.busker { + --busker-accent: #7c3aed; + --busker-cursor-size: 0.95rem; + --busker-cursor-fill: rgb(0 0 0 / 0.42); + --busker-cursor-edge: #fff; + + /* The cursor is positioned against the root. */ + position: relative; +} + +/* Hidden until busk() takes over, so it never flashes on a page whose + JavaScript has not run (or will not). */ +[data-cursor] { + display: none; +} + +.busker [data-cursor] { + background: var(--busker-cursor-fill); + border: 2px solid var(--busker-cursor-edge); + border-radius: 999px; + box-shadow: 0 1px 6px rgb(0 0 0 / 0.3); + display: block; + height: var(--busker-cursor-size); + opacity: 0; + pointer-events: none; + position: absolute; + scale: 1; + transition: + background 0.15s ease, + scale 0.15s ease; + translate: -50% -50%; + width: var(--busker-cursor-size); + z-index: 20; +} + +.busker [data-cursor].is-visible { + opacity: 1; +} + +.busker [data-cursor].is-pressing { + background: color-mix(in srgb, var(--busker-accent) 75%, transparent); + scale: 0.6; +} + +.busker [data-cursor]::after { + border: 2px solid color-mix(in srgb, var(--busker-accent) 70%, transparent); + border-radius: 999px; + content: ''; + inset: -3px; + opacity: 0; + position: absolute; +} + +.busker [data-cursor].is-ringing::after { + animation: busker-ring 0.5s ease-out; +} + +@keyframes busker-ring { + from { + opacity: 0.9; + scale: 0.5; + } + to { + opacity: 0; + scale: 2.8; + } +} + +/* Only what the show has wired up gets a pointer, so what looks clickable is. */ +.busker .is-interactive { + cursor: pointer; +} + +/* The visitor clicked something dead β€” show them what is not. */ +.busker .is-hint { + animation: busker-hint 1.5s ease-out; +} + +@keyframes busker-hint { + from { + box-shadow: + 0 0 0 2px var(--busker-accent), + 0 0 12px 4px color-mix(in srgb, var(--busker-accent) 50%, transparent); + } + to { + box-shadow: + 0 0 0 2px transparent, + 0 0 0 0 transparent; + } +} + +/* They took over: the show is off the pitch. */ +.busker.is-aside [data-cursor] { + display: none; +} + +/* Scenes: one at a time. */ +.busker [data-scene] { + display: none; +} + +.busker [data-scene].is-active { + display: block; +} + +@media (prefers-reduced-motion: reduce) { + .busker [data-cursor] { + transition: none; + } + + .busker .is-hint { + animation: none; + } +} diff --git a/deploy-docs.sh b/deploy-docs.sh new file mode 100755 index 0000000..0f3fd46 --- /dev/null +++ b/deploy-docs.sh @@ -0,0 +1,33 @@ +#!/bin/bash +# Publish the Astro docs site to S3 + CloudFront. +# +# Order matters. Astro emits content-hashed assets under `_astro/`. A naive +# `aws s3 sync --delete` can remove an old hash while CloudFront is still +# serving HTML that points at it β†’ CSS 404 β†’ fully unstyled page. Publish new +# hashes first, swap HTML second, invalidate third, and never delete `_astro/` +# objects in the same deploy. + +set -euo pipefail + +: "${S3_BUCKET:?S3_BUCKET is required}" +: "${DISTRIBUTION_ID:?DISTRIBUTION_ID is required}" + +npm run astro:build + +# 1) New hashed assets only β€” additive. Old hashes stay until orphaned forever +# (cheap) so any still-cached HTML keeps working during CDN catch-up. +aws s3 sync docs-site/_astro/ "s3://${S3_BUCKET}/_astro/" \ + --cache-control "public,max-age=31536000,immutable" + +# 2) HTML + everything else. `--delete` may drop removed pages, but never +# touches `_astro/` (excluded), so stylesheet hashes are not yanked. +aws s3 sync docs-site/ "s3://${S3_BUCKET}/" \ + --exclude "_astro/*" \ + --delete \ + --cache-control "public,max-age=60,must-revalidate" + +# 3) Drop stale HTML from edges. Assets are immutable + still present, so a +# slow invalidation cannot recreate the unstyled-page failure mode. +aws cloudfront create-invalidation \ + --distribution-id "${DISTRIBUTION_ID}" \ + --paths "/*" diff --git a/ec.config.mjs b/ec.config.mjs new file mode 100644 index 0000000..8345bc3 --- /dev/null +++ b/ec.config.mjs @@ -0,0 +1,53 @@ +/** + * When a fence has no `title="…"`, use the language id so every block gets + * editor/terminal chrome (our CSS + codeBlockTitles enhancer). + */ +function titleFromLangPlugin() { + return { + name: 'cs-title-from-lang', + hooks: { + preprocessMetadata({codeBlock}) { + if (codeBlock.props.title) { + return; + } + const lang = (codeBlock.language ?? '').trim().toLowerCase(); + codeBlock.props.title = lang || 'code'; + }, + }, + }; +} + +/** @type {import('astro-expressive-code').AstroExpressiveCodeOptions} */ +export default { + // Inline theme CSS in the first block β€” avoids a body that FOUCs on navigation. + emitExternalStylesheet: false, + plugins: [titleFromLangPlugin()], + // Auto frame + lang title for untitled fences; shell langs stay terminal/bash + defaultProps: { + frame: 'auto', + overridesByLang: { + 'bash,sh,shell,zsh,shellscript,shellsession,console': { + frame: 'terminal', + title: 'bash', + }, + }, + }, + frames: { + showCopyToClipboardButton: true, + }, + // Monokai (dark) + github-light β€” Starlight toggles via html[data-theme] + themes: ['monokai', 'github-light'], + useDarkModeMediaQuery: false, + styleOverrides: { + borderRadius: '0.5rem', + borderWidth: '0px', + codeFontSize: '0.9rem', + codePaddingBlock: '0.6rem', + codePaddingInline: '0.75rem', + // Resolved via CSS so light/dark panels match ui-components --color-bg-dark1 + codeBackground: 'var(--cs-code-bg)', + frames: { + shadowColor: 'transparent', + }, + }, +}; diff --git a/eslint.config.mjs b/eslint.config.mjs new file mode 100644 index 0000000..86a134c --- /dev/null +++ b/eslint.config.mjs @@ -0,0 +1,29 @@ +import tseslint from 'typescript-eslint'; + +export default tseslint.config( + { + ignores: [ + 'dist/**', + 'docs-site/**', + 'coverage/**', + '.astro/**', + 'node_modules/**', + 'eslint.config.mjs', + 'astro.config.mjs', + 'scripts/**', + 'src/content/**', + 'src/styles/**', + 'assets/**', + ], + }, + ...tseslint.configs.recommended, + { + files: ['**/*.ts'], + rules: { + '@typescript-eslint/explicit-function-return-type': 'error', + '@typescript-eslint/no-explicit-any': 'off', + '@typescript-eslint/no-unused-vars': ['error', {argsIgnorePattern: '^_'}], + 'max-lines-per-function': 'off', + }, + }, +); diff --git a/knip.json b/knip.json new file mode 100644 index 0000000..1c21a3c --- /dev/null +++ b/knip.json @@ -0,0 +1,30 @@ +{ + "$schema": "https://unpkg.com/knip@6/schema.json", + "ignoreBinaries": ["report"], + "ignoreDependencies": [ + "@fontsource-variable/inter", + "@pagefind/default-ui", + "astro-expressive-code", + "happy-dom", + "esbuild" + ], + "ignoreIssues": { + "src/components/searchPendingDelay.ts": ["exports"], + "src/components/searchStaleResults.ts": ["exports"], + "src/integrations/assertAstroDevFree.ts": ["files"], + "src/integrations/cleanAstroCache.ts": ["files"], + "src/integrations/pagefindShim.ts": ["files"], + "src/integrations/rehype-wrap-tables.ts": ["files"], + "src/integrations/remark-starlight-md-links.ts": ["files"] + }, + "entry": [ + "src/cs-pagefind/pagefind.ts", + "src/components/codeBlockTitles.ts", + "src/components/pagefindMount.ts", + "src/components/searchDialog.ts", + "src/components/searchKeyboardNav.ts", + "src/**/*.spec.ts", + "scripts/*.{mjs,cjs,js}" + ], + "project": ["src/**/*.{ts,js}", "scripts/**/*.{mjs,cjs,js}"] +} diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..9aec1b9 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,10497 @@ +{ + "name": "busker", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "busker", + "version": "0.0.0", + "license": "MIT", + "devDependencies": { + "@astrojs/markdown-remark": "^7.3.0", + "@astrojs/starlight": "^0.42.0", + "@fontsource-variable/inter": "^5.3.0", + "@happy-dom/global-registrator": "^20.13.2", + "astro": "^7.2.10", + "c8": "^12.0.0", + "eslint": "^10.9.1", + "happy-dom": "^20.13.2", + "kizu": "^5.1.1", + "knip": "^6.34.0", + "typescript": "^6.0.3", + "typescript-eslint": "^8.69.0" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@astrojs/compiler-binding": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@astrojs/compiler-binding/-/compiler-binding-0.4.0.tgz", + "integrity": "sha512-x2RjDUuWfwLNtc3mjAdSRInwqh/rqbLar9cm/5FOMbHvmYZB7yfKewzSclAxWjIZsypJDXv1lhaP2WG+P8TK3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "optionalDependencies": { + "@astrojs/compiler-binding-darwin-arm64": "0.4.0", + "@astrojs/compiler-binding-darwin-x64": "0.4.0", + "@astrojs/compiler-binding-linux-arm64-gnu": "0.4.0", + "@astrojs/compiler-binding-linux-arm64-musl": "0.4.0", + "@astrojs/compiler-binding-linux-x64-gnu": "0.4.0", + "@astrojs/compiler-binding-linux-x64-musl": "0.4.0", + "@astrojs/compiler-binding-wasm32-wasi": "0.4.0", + "@astrojs/compiler-binding-win32-arm64-msvc": "0.4.0", + "@astrojs/compiler-binding-win32-x64-msvc": "0.4.0" + } + }, + "node_modules/@astrojs/compiler-binding-darwin-arm64": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@astrojs/compiler-binding-darwin-arm64/-/compiler-binding-darwin-arm64-0.4.0.tgz", + "integrity": "sha512-ZVUwHundaQyFNjE6uoa0usaC0WOCitDCLS/4mdb4rOiJXwVUuKJBMxI5WMzXLWmamsXtK/Z//ifLXvV5Yeh4Hw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@astrojs/compiler-binding-darwin-x64": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@astrojs/compiler-binding-darwin-x64/-/compiler-binding-darwin-x64-0.4.0.tgz", + "integrity": "sha512-FI6G8AY8u6fR1SI/QRR5yGMwtvZwP34CDmZpZ5HwJGa50UM1VISTLhqkhV4a476pmgd25X1Aur2dqw6hUnrlKA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@astrojs/compiler-binding-linux-arm64-gnu": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@astrojs/compiler-binding-linux-arm64-gnu/-/compiler-binding-linux-arm64-gnu-0.4.0.tgz", + "integrity": "sha512-lB9gLFJK7m82EnjaU8nlRBEfcwGNeHidW3sSjODTUjMNaoewVuUz9fwwdY5M4jiSXIqWLH3yl6TX8FTDKA74Sw==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@astrojs/compiler-binding-linux-arm64-musl": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@astrojs/compiler-binding-linux-arm64-musl/-/compiler-binding-linux-arm64-musl-0.4.0.tgz", + "integrity": "sha512-HPbvWqbxFxyaoQJhLxCaSjtYBx9KBo7JGVzEFZCmMl968a2PsSH0UfiODYgYPXofTOIsIH2aoCcrHXML0IA3ig==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@astrojs/compiler-binding-linux-x64-gnu": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@astrojs/compiler-binding-linux-x64-gnu/-/compiler-binding-linux-x64-gnu-0.4.0.tgz", + "integrity": "sha512-tQKolMxoJ/+0AmLWm1PmJ/i+z3i10ZU1bNuVjEDulCf48azEMtUNjTZgHJ5MPtpYRNc7dlETr8QujUfduzoC7Q==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@astrojs/compiler-binding-linux-x64-musl": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@astrojs/compiler-binding-linux-x64-musl/-/compiler-binding-linux-x64-musl-0.4.0.tgz", + "integrity": "sha512-5v5YymudsxMHp3NBLCS8BUlu5CRqeLtWD9cKS/4nIhIEHCbpz9okmVV6I0HWqmBAPhWYcDa3vw/vltYPrOQCTA==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@astrojs/compiler-binding-wasm32-wasi": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@astrojs/compiler-binding-wasm32-wasi/-/compiler-binding-wasm32-wasi-0.4.0.tgz", + "integrity": "sha512-m/phuH3x3PREvv1OnkM44NoPh4MatUadix1fB1u5SvMLCyDTUZykDJbKnWf1cjnYmHdlB8HcjTjl6JrCqAIXcw==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@napi-rs/wasm-runtime": "^1.2.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@astrojs/compiler-binding-win32-arm64-msvc": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@astrojs/compiler-binding-win32-arm64-msvc/-/compiler-binding-win32-arm64-msvc-0.4.0.tgz", + "integrity": "sha512-B9zYf3okEY83kM8gydlpH2BHP00w4ifxPqlYlWrgTwuD6wnkrJDCwBlgy1q31cERjCJRXN1lrE2VmkLvFjv/6g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@astrojs/compiler-binding-win32-x64-msvc": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@astrojs/compiler-binding-win32-x64-msvc/-/compiler-binding-win32-x64-msvc-0.4.0.tgz", + "integrity": "sha512-zB0Nrv0dGc0zZWPGDRmmETTPhDRqyZjAjk+gWMlVrJX5U89obpB3VUUE1ZiHxOCN5LQojeLK6O8L/dnoHolvNQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@astrojs/compiler-rs": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@astrojs/compiler-rs/-/compiler-rs-0.4.0.tgz", + "integrity": "sha512-koVikeon1kreEy+/JzLQRy3vzHHQVOjycs4degg4vFufKApZOwMZvSSAEztYNhmcQVfNVsVZZI4cEge3cexAbQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@astrojs/compiler-binding": "0.4.0" + }, + "engines": { + "node": ">=22.12.0" + } + }, + "node_modules/@astrojs/internal-helpers": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@astrojs/internal-helpers/-/internal-helpers-0.11.0.tgz", + "integrity": "sha512-3rzxJ+xbo0+8YyqOzLziIN32wmsHdCjEVz2sGOpRxJ+Ben/KiLph4ItxBy1abEL+E8fkRzqjg0rfXmaHJGw9JA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.4", + "@types/mdast": "^4.0.4", + "js-yaml": "^4.3.0", + "picomatch": "^4.0.4", + "retext-smartypants": "^6.2.0", + "shiki": "^4.0.2", + "smol-toml": "^1.6.0", + "unified": "^11.0.5" + } + }, + "node_modules/@astrojs/markdown-remark": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/@astrojs/markdown-remark/-/markdown-remark-7.3.0.tgz", + "integrity": "sha512-mRwn2W2PKkOj8XEAiD3b9RIF4qq6FKB6U4c98GxFLkEJH7uA6ZZv8TiqVjJSPMANLb1NtwBTgkY+KO4mTXnT/Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@astrojs/internal-helpers": "0.11.0", + "@astrojs/prism": "4.0.2", + "@mdx-js/mdx": "^3.1.1", + "acorn": "^8.16.0", + "estree-util-visit": "^2.0.0", + "github-slugger": "^2.0.0", + "hast-util-from-html": "^2.0.3", + "hast-util-to-html": "^9.0.5", + "hast-util-to-text": "^4.0.2", + "mdast-util-definitions": "^6.0.0", + "rehype-raw": "^7.0.0", + "rehype-stringify": "^10.0.1", + "remark-gfm": "^4.0.1", + "remark-parse": "^11.0.0", + "remark-rehype": "^11.1.2", + "remark-smartypants": "^3.0.2", + "source-map": "^0.7.6", + "unified": "^11.0.5", + "unist-util-remove-position": "^5.0.0", + "unist-util-visit": "^5.1.0", + "unist-util-visit-parents": "^6.0.2", + "vfile": "^6.0.3" + } + }, + "node_modules/@astrojs/markdown-satteri": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@astrojs/markdown-satteri/-/markdown-satteri-0.4.0.tgz", + "integrity": "sha512-wykOOW9KsUVcZweOpY/CeXpdKcCKZy6fQbdcteWFuI75+sQCiqxYM7VKsGa5b+aGl3cYQscFY37rsbbyal5MRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@astrojs/internal-helpers": "0.11.0", + "@astrojs/prism": "4.0.2", + "github-slugger": "^2.0.0", + "satteri": "^0.10.3" + } + }, + "node_modules/@astrojs/mdx": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@astrojs/mdx/-/mdx-8.0.0.tgz", + "integrity": "sha512-4I/z+VgUO0B9I/+yhzIEpLyBYQZNJsInmrtqClP4lqX9yvUpbuV72zfRTZ8VqJLKABETUE5CYOl+23va8nqxZg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@astrojs/internal-helpers": "0.11.0", + "@astrojs/markdown-satteri": "0.4.0", + "es-module-lexer": "^2.0.0" + }, + "engines": { + "node": ">=22.12.0" + }, + "peerDependencies": { + "@astrojs/markdown-remark": "^7.3.0", + "@astrojs/markdown-satteri": "^0.4.0", + "astro": "^7.2.6" + }, + "peerDependenciesMeta": { + "@astrojs/markdown-remark": { + "optional": true + } + } + }, + "node_modules/@astrojs/prism": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@astrojs/prism/-/prism-4.0.2.tgz", + "integrity": "sha512-KTivpmnz6lDsC6o9H4+DNm2SrE/GHzw8cNAvEJwAvUT+eoaEnn/4NtbDNfRRaxaJHdp15gf+tfHAWiXR4wB3BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "prismjs": "^1.30.0" + }, + "engines": { + "node": ">=22.12.0" + } + }, + "node_modules/@astrojs/sitemap": { + "version": "3.7.4", + "resolved": "https://registry.npmjs.org/@astrojs/sitemap/-/sitemap-3.7.4.tgz", + "integrity": "sha512-LbKNC24bdUWcQf/pThB6qLlSqHojxGjZDURIzFocY8rlWnAn2t74nnhnK6S5x0NHriHoAduLEpVjRykmeGiVvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "sitemap": "^9.0.0", + "zod": "^4.3.6" + } + }, + "node_modules/@astrojs/starlight": { + "version": "0.42.0", + "resolved": "https://registry.npmjs.org/@astrojs/starlight/-/starlight-0.42.0.tgz", + "integrity": "sha512-EVsGnyGJ6rRNwGRZFYmGABo0qTQ55F7OSmfSL0VK+5lsqD+u6GWcB8tFqXETcUvP8kyn90W+QBLSL2aer9jNJQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@astrojs/markdown-satteri": "^0.4.0", + "@astrojs/mdx": "^8.0.0", + "@astrojs/sitemap": "^3.7.3", + "@pagefind/default-ui": "^1.3.0", + "@types/hast": "^3.0.5", + "@types/js-yaml": "^4.0.9", + "@types/mdast": "^4.0.4", + "astro-expressive-code": "^0.44.0", + "bcp-47": "^2.1.0", + "hast-util-format": "^1.1.0", + "hast-util-select": "^6.0.4", + "hast-util-to-html": "^9.0.5", + "hast-util-to-string": "^3.0.1", + "hastscript": "^9.0.1", + "i18next": "^26.0.7", + "js-yaml": "^4.1.1", + "klona": "^2.0.6", + "magic-string": "^1.2.3", + "mdast-util-directive": "^3.1.0", + "mdast-util-to-markdown": "^2.1.2", + "mdast-util-to-string": "^4.0.0", + "pagefind": "^1.5.2", + "remark-directive": "^4.0.0", + "satteri": "^0.10.3", + "ultrahtml": "^1.6.0", + "unified": "^11.0.5", + "unist-util-visit": "^5.1.0", + "vfile": "^6.0.3" + }, + "peerDependencies": { + "@astrojs/markdown-remark": "^7.3.0", + "astro": "^7.2.10" + }, + "peerDependenciesMeta": { + "@astrojs/markdown-remark": { + "optional": true + } + } + }, + "node_modules/@astrojs/telemetry": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/@astrojs/telemetry/-/telemetry-3.3.3.tgz", + "integrity": "sha512-C1TLn5sPJr0x4vk56piHWKbnqlEB8BKyte5Y45V02U+D7BGO5eMqZDH5aPjnkXQWJggvmsTXxH03QMZ9NgWLzQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ci-info": "^4.4.0", + "dset": "^3.1.4", + "is-docker": "^4.0.0", + "package-manager-detector": "^1.6.0" + }, + "engines": { + "node": "18.20.8 || ^20.3.0 || >=22.0.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", + "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.8.tgz", + "integrity": "sha512-E8lTAYNB1KW+FH+VGJuZM1ioAx2E6oVlvQFRrf5P8ZZmsiJXYAD9vTFV7yyEURNzgh1dFqMZuO6tUwcARbqFCA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.8" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.8.tgz", + "integrity": "sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@bcoe/v8-coverage": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-1.0.2.tgz", + "integrity": "sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@bruits/satteri-darwin-arm64": { + "version": "0.10.5", + "resolved": "https://registry.npmjs.org/@bruits/satteri-darwin-arm64/-/satteri-darwin-arm64-0.10.5.tgz", + "integrity": "sha512-27KTVl4TJkVahMy/ohyA7qd4938G5UNneFUz/PsScYfpIhj0IVAS23mpcJXdPF44sa6nva198lmV/cKIb2YPyA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@bruits/satteri-darwin-x64": { + "version": "0.10.5", + "resolved": "https://registry.npmjs.org/@bruits/satteri-darwin-x64/-/satteri-darwin-x64-0.10.5.tgz", + "integrity": "sha512-IjnLe3nKspq6qaeqGgjT7MT8VrTV74yWRlaag7ZdNsI8TDAYZ0iPxMCo+9KQZHUk5EyVB+reBI/PFWL5KuFw9Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@bruits/satteri-linux-arm64-gnu": { + "version": "0.10.5", + "resolved": "https://registry.npmjs.org/@bruits/satteri-linux-arm64-gnu/-/satteri-linux-arm64-gnu-0.10.5.tgz", + "integrity": "sha512-glkYXZCJywjP13v67eAyAMSJdF+ncvEbYvgi/wOtffL9tQ27lr/zsyzUfgs+ovjJ9d8JNQKiXeiArJcX8PJL9w==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@bruits/satteri-linux-arm64-musl": { + "version": "0.10.5", + "resolved": "https://registry.npmjs.org/@bruits/satteri-linux-arm64-musl/-/satteri-linux-arm64-musl-0.10.5.tgz", + "integrity": "sha512-yWdgG1g17Nh2QyGVlFUxGRa3FEFwiMcpZEyMNWkbM3deC94cmVc+/i9OuyFpdKuWo3GkgoCtYVOoxk1uCnCZIA==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@bruits/satteri-linux-x64-gnu": { + "version": "0.10.5", + "resolved": "https://registry.npmjs.org/@bruits/satteri-linux-x64-gnu/-/satteri-linux-x64-gnu-0.10.5.tgz", + "integrity": "sha512-FVaLoPT1fBgGl0J+AYebyyXJYBachGl8Oyyrf1lye4RTqCB4S0Gwkj1uM9RJyThUOvx5VUmAT1CnNh1SFHA+kw==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@bruits/satteri-linux-x64-musl": { + "version": "0.10.5", + "resolved": "https://registry.npmjs.org/@bruits/satteri-linux-x64-musl/-/satteri-linux-x64-musl-0.10.5.tgz", + "integrity": "sha512-EHpVAx2bqW3GINHTKkljtxVfQmVDGWIuwOYOP5YghTj+0PkBa2o8oKPRtQ9Kbsr1Fye8jtUcDjhwj2jMNugZKg==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@bruits/satteri-wasm32-wasi": { + "version": "0.10.5", + "resolved": "https://registry.npmjs.org/@bruits/satteri-wasm32-wasi/-/satteri-wasm32-wasi-0.10.5.tgz", + "integrity": "sha512-ypz8c/Zmipxp4IoeDa228Gstv6TLzVmNs3yC6wKCoNSOjx1iwpgzu87Y3hTkXFdwChVGU85qeUDuOIarGUZQLw==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "1.11.1", + "@emnapi/runtime": "1.11.1", + "@napi-rs/wasm-runtime": "^1.2.3" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@bruits/satteri-wasm32-wasi/node_modules/@emnapi/core": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.11.1.tgz", + "integrity": "sha512-RSvbQmHzdKzNsLYa/wHrbc3KN4sYLKAdPZxqiM2HATqv/SBk2/ENSHpvXGaLOMcsAyz0poEGqkmmKYG3OWiJEQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.2.2", + "tslib": "^2.4.0" + } + }, + "node_modules/@bruits/satteri-wasm32-wasi/node_modules/@emnapi/runtime": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.1.tgz", + "integrity": "sha512-vgj7R3y3Wgx24IQaGPA/R6YFXLHVMOZ0uVEyIQPaWs+rd1AzfEMXlAC22FYwO1XkKR6NPsq7mUandH8oIRdZFw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@bruits/satteri-wasm32-wasi/node_modules/@emnapi/wasi-threads": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.2.tgz", + "integrity": "sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@bruits/satteri-win32-arm64-msvc": { + "version": "0.10.5", + "resolved": "https://registry.npmjs.org/@bruits/satteri-win32-arm64-msvc/-/satteri-win32-arm64-msvc-0.10.5.tgz", + "integrity": "sha512-siTV88nb0LRqNpkL2gXboqCwVdq95sLtzMHS1/3eONV2gLbB3NAK46wmSMvCO/yquBvI2lvaFIfd8P12ecsxBw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@bruits/satteri-win32-x64-msvc": { + "version": "0.10.5", + "resolved": "https://registry.npmjs.org/@bruits/satteri-win32-x64-msvc/-/satteri-win32-x64-msvc-0.10.5.tgz", + "integrity": "sha512-C3IfPvfvMXmlzBxaMPKFS1XiuV9pu2mC7YqkPk7PSvTgPZ8gbdASIpHpztDLvTTQjqZ0z1Ol8tK5X+V6XXC0wQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@capsizecss/unpack": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@capsizecss/unpack/-/unpack-4.0.1.tgz", + "integrity": "sha512-CuNiSqg7+e1cO/GjffyMOm5Tt2jUF9CWHHnvQ/UkqvtkGfHdgwEC0wpmq7fkN3gxwpRnrAN0WzO3vREKmNolMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fontkitten": "^1.0.3" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@clack/core": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/@clack/core/-/core-1.4.3.tgz", + "integrity": "sha512-/kr3UWNtdJfxZtPgDqUOmG2pvwlmcLGheex5yiZKdwbzZJxhV+HMNR9QNmyY5cGwTNV6LrR7Jtp+KjhUAP1qBQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-wrap-ansi": "^0.2.0", + "sisteransi": "^1.0.5" + }, + "engines": { + "node": ">= 20.12.0" + } + }, + "node_modules/@clack/prompts": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@clack/prompts/-/prompts-1.7.0.tgz", + "integrity": "sha512-y7/yvZ2TPAnR9+jnc00klvNNLkJiXFFrQA/hlLCcxA9a2A4zQIOimyFQ9XfwYKiGD1fb5GY8vbKIIgO8d5Tb2A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@clack/core": "1.4.3", + "fast-string-width": "^3.0.2", + "fast-wrap-ansi": "^0.2.0", + "sisteransi": "^1.0.5" + }, + "engines": { + "node": ">= 20.12.0" + } + }, + "node_modules/@ctrl/tinycolor": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@ctrl/tinycolor/-/tinycolor-4.2.0.tgz", + "integrity": "sha512-kzyuwOAQnXJNLS9PSyrk0CWk35nWJW/zl/6KvnTBMFK65gm7U1/Z5BqjxeapjZCIhQcM/DsrEmcbRwDyXyXK4A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + } + }, + "node_modules/@emnapi/core": { + "version": "1.11.3", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.11.3.tgz", + "integrity": "sha512-zLpS5asjEb7lq8jYLq37N6XKaE41DIexlY1rF/z4/tIl3wo13Sqm28fRyfIsKZD+NZ8mM5RoKkpW/rBcuoSZSg==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "@emnapi/wasi-threads": "1.2.3", + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.11.3", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.3.tgz", + "integrity": "sha512-Xz4Tpyki7XyrpbUK1jR1AhdAdaXyhhY4lZ3neLodmhpuWfy2PAQN5B46sAiU4liOXGLkHypn/qU+jvfWSCYYLA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/wasi-threads": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.3.tgz", + "integrity": "sha512-ELEBe8PsLvvJ6QMr0zLt8ffvOHW/dc1m3CEzNMg7aJUv3bMaoDtw2TXyDAwkYBuroxxuHEwhRTLJSe5sya547g==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.2.tgz", + "integrity": "sha512-XExcO+dvLKvVtNTibSTBej1NCAbaGhWn9Ww1ZPx80qsahhPFe/8jgWP0IchNe0F3HwkU7n8ejhH8bjonqht8mQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.2.tgz", + "integrity": "sha512-kXXoiPVVGQcnIYGOeaovwOURpniDBpSq4A03qkQ+BMQqtGG6HYap3xne9C1O1yo4TR3qxlCX5IqqmX6fFo2Lqg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.2.tgz", + "integrity": "sha512-5YfKeeI8qWfBZIX+u2xZC3Zlb3Os/gLS2sbEKM+I4ZOcsWmHS2WLysCcQZDAFRslDUU5Oiq44gf6PYN1vGwG5A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.2.tgz", + "integrity": "sha512-O387ite7SzUyCcy3JQX4P4bLtEA7bLLkx+esve5JHnyYfNTxcVpXZo9jhdB0lTKN44gztELTdU7nS8Nr16Fs1Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.2.tgz", + "integrity": "sha512-n4KqkOQrraxHJcgjM1RvwbigfQKIKJVpM7xp+KsxiyUSrRdIXnt73VhrPAx0fV44hgfmIVKjxMN9J1t5jySVkw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.2.tgz", + "integrity": "sha512-uq6suIWYP37qzGddBKPw5QEQPi6HiLGsO7UmkpfyaYNQ3D+rN6w6WfwH+nuqcGXWvawGwxOEroO4YGnFh95azw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.2.tgz", + "integrity": "sha512-n+I0BTSRIoy+d6RPKnEVwql5UwBJolytvY4mAOIEJorKlqgPII8ix6slVVrfZ5Tnj7glIZvloylbB/EJPMWEXw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.2.tgz", + "integrity": "sha512-78XJTJkvPs0kz2w61301PJjXl4g7q3JqiYMZ/M/yVI73EHBrCRTgkhu9oqG7vPqq+a/yadEW8aD+agKlk5xrmg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.2.tgz", + "integrity": "sha512-XlDnu2q5yoqems+xay6wSAcg9DDD7K9RLKZEBOMZm3ckNpJBvOX20tSfby8KfrrhINDyv9V2YVZKY/SpoGJI8w==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.2.tgz", + "integrity": "sha512-pW4AC0P3it8c7do9MVM4p51FzHzdM/TZrerurgRcHJ2WTa1VQ1CIq18xncfpBJw4ojkiZZrKW2yIBWBP92j6Ug==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.2.tgz", + "integrity": "sha512-CYbnj78HsIeA+DhgUKgFCfvNsTHFhMMrinUrMZpDXJXKN8T3XViTZ/+wtHeVxEWY8ewSzTFN+nRmSwO2tZaLUQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.2.tgz", + "integrity": "sha512-buwkd8nsph4R+ajRvw0qM5Hja/TXQow3ptzWO2EbG/cqcIkHloRrdlBtQlshyYGTNFvfkfJ5tpPLVkY4DtsPfQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.2.tgz", + "integrity": "sha512-ZVykbDyk7519VwiNb9Lcj9m8XM6v5V9uKPvrEMkkEedVewf+0itkhahp4HDpgERXhwLRpWFypsGbG/J8s0QjJA==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.2.tgz", + "integrity": "sha512-CAXl+Dtd9UUuJd8pKKdwh6MLm3MUMiqMPmhZ3tTSXPqfyQ3vDl6R5hZdZ/kYojK4ofXtdfSv1tFq8XzWx3heNQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.2.tgz", + "integrity": "sha512-GeXCej4IQtU1B+QlDV8W/RRvbzI3O/Stss+/bCXv4lZls5WGRtu2a+3JkA3i4qIUlMXpcHebWpF8AkJhATowuA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.2.tgz", + "integrity": "sha512-3H1weTYZPxt/WOhByszQZybS9w5lKzUn1FDMsgEChbHWQwHYQQRfBxgCcZvPhjHfKyJjIievvMmEUawJrdY9Dg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.2.tgz", + "integrity": "sha512-4xTZr1FUmSoQW4XIWmit3tzQrUTZM+N3P0XV8xROKYF50XfI7xeO90+1bZvNwxIufQ9hDQVRJH5YhgPVF8A/HQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.2.tgz", + "integrity": "sha512-sSATRjPeDBg3pdgHoQfoYBob11Kk1FGa9lui5RIHZCoCkJa9QKlvl3/vKz2usCmYYjs7ymJR/2Nnsqe+Hjt5nw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.2.tgz", + "integrity": "sha512-lqnzCV+mM0gIADaKihiCg6ifgfU2L3h5E33rNQBN1Y4MaVGnzryzmvvf7UHxprpQdE8hpqLolJ9Rl+SkIRDpyw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.2.tgz", + "integrity": "sha512-AL2qJILH7lNjrDmCQDvdxMfAUIv8KMNZOvrwAQ8i8//ntL9FflhOyMJ8OZSMBb8/AWXe3/5v5S20y3zCoZWKoQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.2.tgz", + "integrity": "sha512-QtiuPytchRyC4rwUKhexJdQKvDuZ6hWloi3igqPQNUJCS1/v9EiO3UTOXR6A3FoMo4fnAKbWJdqaIwhOzh8qEw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.2.tgz", + "integrity": "sha512-WkhYDmpTjLvGlScA1rwjRUmhl4k8oXR3cIbtqWmELgU/dFeHHlEllxDvdWcNJV9rbzCexB5vz8gtNewWLgCT7Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.2.tgz", + "integrity": "sha512-GPMSkTOtMnv2U2F8gxe4Io6qmVs+YKyp832Etqqxr0hFngmXQ3rzwytelm3GIn7T4VviRUlf3sOgBOiTdvaf7g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.2.tgz", + "integrity": "sha512-PIhhEkE9uPBleRBrQEJpUn7MBnibZzbGzYWPmY3x+YoVg/95zbjB4CxPPOQ8l5tYYM4mMaCthF8/1DIfBQQyWQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.2.tgz", + "integrity": "sha512-YmJbfTlvU7Sdn9BB+4PRES4oB6pxgS37MAONj+hBr/cpXS1aBPKXxNnDbu+QCWPj0o9dgyxeq79g6c5P8KeuYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.2.tgz", + "integrity": "sha512-5ebpxr3nWMzrL/rnUI755Jkuee0bHL/Gq0WTF9lvcpv73wAp5eu8MfBUgWK9bhWvZjj7yX8etf/8tI8Ney695g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.10.1.tgz", + "integrity": "sha512-cuadcxVFE8sDK6iWJbs8Sn0av2Nrh2QSGQhVlBW9AaAHqHwjWsZHT8LJ4hFGPh7ASBV2deFdM7H/DPjulmh8rg==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.23.5", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.23.5.tgz", + "integrity": "sha512-Y3kKLvC1dvTOT+oGlqNQ1XLqK6D1HU2YXPc52NmAlJZbMMWDzGYXMiPRJ8TYD39muD/OTjlZmNJ4ib7dvSrMBA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^3.0.5", + "debug": "^4.3.1", + "minimatch": "^10.2.4" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.7.0.tgz", + "integrity": "sha512-DObd/KKUsU+FaFv4PLxSRenpXfQWmPXXP3pPZ6/K1PCrMu2vQpMDMuQe/BqYeoLcz8ro0bVDF1RxOJgfVEdhUw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^1.2.1" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@eslint/core": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-1.2.1.tgz", + "integrity": "sha512-MwcE1P+AZ4C6DWlpin/OmOA54mmIZ/+xZuJiQd4SyB29oAJjN30UW9wkKNptW2ctp4cEsvhlLY/CsQ1uoHDloQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@eslint/object-schema": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-3.0.5.tgz", + "integrity": "sha512-vqTaUEgxzm+YDSdElad6PiRoX4t8VGDjCtt05zn4nU810UIx/uNEV7/lZJ6KwFThKZOzOxzXy48da+No7HZaMw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.7.2.tgz", + "integrity": "sha512-+CNAzxglkrpNf/kKywqQfk74QjtceuOE7Qm+AF8miRvPF/wmmK5+OJOgVh3AVTT3RP2mH3+FOaxlE5v72owk0A==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^1.2.1", + "levn": "^0.4.1" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@expressive-code/core": { + "version": "0.44.2", + "resolved": "https://registry.npmjs.org/@expressive-code/core/-/core-0.44.2.tgz", + "integrity": "sha512-Yw5KonQCD1HoOO2P89y4wifqS9qrJcGVY0LkVMUsVZjhi++bhz4WwQ37inenKFNOEVo50P7cjoVsCdaVnUbq8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@ctrl/tinycolor": "^4.0.4", + "hast-util-select": "^6.0.2", + "hast-util-to-html": "^9.0.1", + "hast-util-to-text": "^4.0.1", + "hastscript": "^9.0.0", + "postcss": "^8.4.38", + "postcss-nested": "^6.0.1", + "unist-util-visit": "^5.0.0", + "unist-util-visit-parents": "^6.0.1" + } + }, + "node_modules/@expressive-code/plugin-frames": { + "version": "0.44.2", + "resolved": "https://registry.npmjs.org/@expressive-code/plugin-frames/-/plugin-frames-0.44.2.tgz", + "integrity": "sha512-gVLm6TQMNIRWIaM7o+mCtxkGNjLZY5/2faSTLoXeo9wv/Pkj/0QAtZyXPVPrRsPARqiipATNFmfvQFLJudxPHQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@expressive-code/core": "^0.44.2" + } + }, + "node_modules/@expressive-code/plugin-shiki": { + "version": "0.44.2", + "resolved": "https://registry.npmjs.org/@expressive-code/plugin-shiki/-/plugin-shiki-0.44.2.tgz", + "integrity": "sha512-ebWIvCDzXvjyD8rH6T6DfytYJw1GoArLR4nusIAE4VxXrCQf3d+owytiv010KU/dUg+SRLZskDn0N+HaFXeuSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@expressive-code/core": "^0.44.2", + "shiki": "^4.0.2" + } + }, + "node_modules/@expressive-code/plugin-text-markers": { + "version": "0.44.2", + "resolved": "https://registry.npmjs.org/@expressive-code/plugin-text-markers/-/plugin-text-markers-0.44.2.tgz", + "integrity": "sha512-SNI7EgUSiADqbqUzzMbj1ZeC/r2pa5eg3KyFOGAd3a7EsIA4qzjXQLp+ODWQtHVDKFCdGs4J3ovSJUqvKIK96g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@expressive-code/core": "^0.44.2" + } + }, + "node_modules/@fontsource-variable/inter": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/@fontsource-variable/inter/-/inter-5.3.0.tgz", + "integrity": "sha512-OupL48va4JNofb97w6NYeF9S7W/kHNKM0Er8Dem5nqi4jeOLrVJDoE8tZEpnMJmtkvNbB1EIPPwHcdkF6b1oUA==", + "dev": true, + "license": "OFL-1.1", + "funding": { + "url": "https://github.com/sponsors/ayuhito" + } + }, + "node_modules/@happy-dom/global-registrator": { + "version": "20.13.2", + "resolved": "https://registry.npmjs.org/@happy-dom/global-registrator/-/global-registrator-20.13.2.tgz", + "integrity": "sha512-pGO7WhgHVLi7mi7zCLyJ+FkTad2aPTcjUXhRbgA4T5n1vLRJ/9uw3EsLieScNSX6HOfPF947bOP7N42l+KrWjg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": ">=20.0.0", + "happy-dom": "^20.13.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@humanfs/core": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.2.tgz", + "integrity": "sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/types": "^0.15.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.8", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.8.tgz", + "integrity": "sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.2", + "@humanfs/types": "^0.15.0", + "@humanwhocodes/retry": "^0.4.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/types": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/@humanfs/types/-/types-0.15.0.tgz", + "integrity": "sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@img/colour": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.1.0.tgz", + "integrity": "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@img/sharp-darwin-arm64": { + "version": "0.35.4", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.35.4.tgz", + "integrity": "sha512-Uhfl4V4lhP2nbUVF9+hyH1+luj86f1gUFeo8ALYxFoULoU+G87D43BfeMP8XHsk9boxAnCY/bf2EHwhA7MuGsA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-arm64": "1.3.3" + } + }, + "node_modules/@img/sharp-darwin-x64": { + "version": "0.35.4", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.35.4.tgz", + "integrity": "sha512-hWniXY3bG5qKpkKrAwPe4y+VTPmf086YQAnkxWh7uA1YrlRouWGa0M0Mxj3ZjnXFkv7/TD1bTy9lGUK26vRvWw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-x64": "1.3.3" + } + }, + "node_modules/@img/sharp-freebsd-wasm32": { + "version": "0.35.4", + "resolved": "https://registry.npmjs.org/@img/sharp-freebsd-wasm32/-/sharp-freebsd-wasm32-0.35.4.tgz", + "integrity": "sha512-lIsKw/BU+kjB4eZjxrYrZmwOJYi3Ajrv66iAlBmUPyKc3HpnloevB1g3wxGD9P/5BbQ1brBGl65VRRrCvQDEqA==", + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "dependencies": { + "@img/sharp-wasm32": "0.35.4" + }, + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-darwin-arm64": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.3.3.tgz", + "integrity": "sha512-suTBPTDGrI9WodccaDdwZItTSaBYASlBk1NSfElSHrUfzu3szG6lvIF58+WiFvnfzuK8ZBFS5zE00PxqxnRiPg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-darwin-x64": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.3.3.tgz", + "integrity": "sha512-FVJZ5mITMobmXIz/hPDTw0EintTW5H3WfrxwLqEqjiIihlu+hVRyGrFQ60xl0Lxn7Bt3zdpevPaQi0HEzqz9fw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.3.3.tgz", + "integrity": "sha512-3rbU4vqXXc3hY/OiXdl52xZvT0F1yEngWfvqudtPJg/KkyiaQw2DRsFrNzpmLvfavbwOq3qXn36GP8obHRULQA==", + "cpu": [ + "arm" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm64": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.3.3.tgz", + "integrity": "sha512-0DaL0A6Xu6sQSQFwe4iVCrKWU2cCTItnRsYsCdxAMm9NF6twAA9BKnoqy4hqz4+azQ0JHuA26qiUKsf1XJ/v5A==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-ppc64": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.3.3.tgz", + "integrity": "sha512-cdn1OvUBwsXhbC0zSzJnNzf5MZ/mTrobawDvNXBTxe8VtqKAm0sRuEY2Evzovb/w9JMk4TvRxqt1mekSuJz64w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-riscv64": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.3.3.tgz", + "integrity": "sha512-HjPVx7yKz+0lqdhDlTw1tt90wamBoxhiXpvl1XZpJLiHH4RCJ5yDTqH+VlYPv2fwFs89JFw4c1IexYOcQUi4IQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-s390x": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.3.3.tgz", + "integrity": "sha512-neWLh+3yCNThxnfy3c4BbVBeGgt9aftno+XbT56iK28RgeDs3UOFWviLWlUu0bArYVYJaFDK+RRohbicUNCm8Q==", + "cpu": [ + "s390x" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-x64": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.3.3.tgz", + "integrity": "sha512-4vKmvAst9nrowcqquKFAyZJUDolUaIp8uRiN0mWFguJ1IplC9/pitXtlnnlU4aa/eJw3J7i67V+pwUL+wZGdsA==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-arm64": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.3.3.tgz", + "integrity": "sha512-Y9kQaLMuNoB0bPYOOdcZMaseNrFpPodIWWMrx+CZyydf2xn68j9WYc6sWWRrDwNkzCQjKYfc68L7jKjGlHMibw==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-x64": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.3.3.tgz", + "integrity": "sha512-fj8Mv0HHfD1Rr+4I68+3agJynxDWtBFgicTbSOb9Bke6pIwzGcJ+RX/yHjmiEGFMCavY/dxvem7MyNaJF+wDiw==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-linux-arm": { + "version": "0.35.4", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.35.4.tgz", + "integrity": "sha512-7OAS8gI0EReKGVN2HssHlM6umJgxF5VI3xN0p9FA91p/YO+ou5hiNghLdZ5BEHztwaaK5+bLKRf8x/o2L2nk9A==", + "cpu": [ + "arm" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm": "1.3.3" + } + }, + "node_modules/@img/sharp-linux-arm64": { + "version": "0.35.4", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.35.4.tgz", + "integrity": "sha512-De4jpEnAU8Hd5oT0j1G3uL4ZvTuipVMn7YC6vPaJhy6/7EwEae0SVAoBrUMYQbkLGDm85taVWwuPc1a44LTzCQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm64": "1.3.3" + } + }, + "node_modules/@img/sharp-linux-ppc64": { + "version": "0.35.4", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.35.4.tgz", + "integrity": "sha512-2oYZJeIl4kCcMGk4ouZVjnkCtFrpQFlNEtJ6GbxzhHQchwH0NH/qEb9ykmOl29dqwMq+JhFdZn+1ak2FKhI9fQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-ppc64": "1.3.3" + } + }, + "node_modules/@img/sharp-linux-riscv64": { + "version": "0.35.4", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.35.4.tgz", + "integrity": "sha512-cPbNChoRURAWdebDIHSenxRpgEdy7JkPydSnUxRm9VvKD7m0/xVaR/8Fzlu81pk5nHEvHH87UZUA7cTtwnbJSA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-riscv64": "1.3.3" + } + }, + "node_modules/@img/sharp-linux-s390x": { + "version": "0.35.4", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.35.4.tgz", + "integrity": "sha512-RY0JFY8Fd6RonCBtHz+DvadaPkXDSI1AUn6yWL9TipqkZ1vY8w8evqdgyDFnkm4/K1ve1TvZiaePP5oSd4+WVQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-s390x": "1.3.3" + } + }, + "node_modules/@img/sharp-linux-x64": { + "version": "0.35.4", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.35.4.tgz", + "integrity": "sha512-9qvvEAuk8k89TfWUoX2htWjbAMX8p+NxCppjpcg5k6xMsjhBQPTsoIh36h9Qde4WRuGpJeYnOjdosDn/cnv+OA==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-x64": "1.3.3" + } + }, + "node_modules/@img/sharp-linuxmusl-arm64": { + "version": "0.35.4", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.35.4.tgz", + "integrity": "sha512-KB5jxpfWQTr0nc3xdHtWChdbifHrBGsd2SM62Eyxrl8afikm+f5qGBU75SJIZBT/S1MC8XyacdlXBMSWq6OURA==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-arm64": "1.3.3" + } + }, + "node_modules/@img/sharp-linuxmusl-x64": { + "version": "0.35.4", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.35.4.tgz", + "integrity": "sha512-f+eZJZIQNEEd26RPSW+76chwOf1XtA2Y/O+5ocVyLliHkeih3e+jhLVBdNTd2rS3IbNXK8+ug93Vf5ZXtF5Lxg==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-x64": "1.3.3" + } + }, + "node_modules/@img/sharp-wasm32": { + "version": "0.35.4", + "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.35.4.tgz", + "integrity": "sha512-zQnl4Kwp7Q6NHsENtU2T/00Zi+w3AQNwz3+UaTyVBy2FpXrzXzGjndpK61onhZjRtRpQXxCTeqw19bVyXOh7jA==", + "dev": true, + "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", + "optional": true, + "dependencies": { + "@emnapi/runtime": "^1.11.3" + }, + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-webcontainers-wasm32": { + "version": "0.35.4", + "resolved": "https://registry.npmjs.org/@img/sharp-webcontainers-wasm32/-/sharp-webcontainers-wasm32-0.35.4.tgz", + "integrity": "sha512-ESfNkywmCfPNyaZjxooddJQiQ+l/nTpGEOGthxiLnIHXC/CmcBixnfwUleX9mCz9ovrUUvKMap/pm8RYbzfwaA==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@img/sharp-wasm32": "0.35.4" + }, + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-arm64": { + "version": "0.35.4", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.35.4.tgz", + "integrity": "sha512-iNdlBX9gLVvqe2I3uIJSIKTq6wckP/DYxZtcqxm09x5Gi24DnFBmPAWZmr60ZyYMG0xlzo6goG3670ar+RXvRw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-ia32": { + "version": "0.35.4", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.35.4.tgz", + "integrity": "sha512-kqRsbaa5CS6KHlpxnN7WhE6vAAugXyZButpRdvDWetlv6Qv4N9WTcrWzF7tXfB9T7MsoadqdI8hmwLq6UlLvtw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-x64": { + "version": "0.35.4", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.35.4.tgz", + "integrity": "sha512-XtmnYhBcrORsJ4XJngyzr/EWP0hRZLAZRFaApdKuviyqF78+ylxh2y06ZmtULAMOnObJ3ucpN0AcwSWnMowTRg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@istanbuljs/schema": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.6.tgz", + "integrity": "sha512-+Sg6GCR/wy1oSmQDFq4LQDAhm3ETKnorxN+y5nbLULOR3P0c14f2Wurzj3/xqPXtasLFfHd5iRFQ7AJt4KH2cw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.6.0.tgz", + "integrity": "sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@mdx-js/mdx": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@mdx-js/mdx/-/mdx-3.1.1.tgz", + "integrity": "sha512-f6ZO2ifpwAQIpzGWaBQT2TXxPv6z3RBzQKpVftEWN78Vl/YweF1uwussDx8ECAXVtr3Rs89fKyG9YlzUs9DyGQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdx": "^2.0.0", + "acorn": "^8.0.0", + "collapse-white-space": "^2.0.0", + "devlop": "^1.0.0", + "estree-util-is-identifier-name": "^3.0.0", + "estree-util-scope": "^1.0.0", + "estree-walker": "^3.0.0", + "hast-util-to-jsx-runtime": "^2.0.0", + "markdown-extensions": "^2.0.0", + "recma-build-jsx": "^1.0.0", + "recma-jsx": "^1.0.0", + "recma-stringify": "^1.0.0", + "rehype-recma": "^1.0.0", + "remark-mdx": "^3.0.0", + "remark-parse": "^11.0.0", + "remark-rehype": "^11.0.0", + "source-map": "^0.7.0", + "unified": "^11.0.0", + "unist-util-position-from-estree": "^2.0.0", + "unist-util-stringify-position": "^4.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/@napi-rs/wasm-runtime": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.2.3.tgz", + "integrity": "sha512-UMduMbqO5s5zF2NkNacMT/yK5Y5QiKvWr2+50bzIIxFDwVJ2h49b+oyjaCGPhJxd2/gC2x39EHv/gHVuu36x2Q==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@tybys/wasm-util": "^0.10.3" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=23.5.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + }, + "peerDependencies": { + "@emnapi/core": "^1.7.1 || ^2.0.0-alpha.4", + "@emnapi/runtime": "^1.7.1 || ^2.0.0-alpha.4" + } + }, + "node_modules/@oslojs/encoding": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@oslojs/encoding/-/encoding-1.1.0.tgz", + "integrity": "sha512-70wQhgYmndg4GCPxPPxPGevRKqTIJ2Nh4OkiMWmDAVYsTQ+Ta7Sq+rPevXyXGdzr30/qZBnyOalCszoMxlyldQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@oxc-parser/binding-android-arm-eabi": { + "version": "0.147.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-android-arm-eabi/-/binding-android-arm-eabi-0.147.0.tgz", + "integrity": "sha512-fOtoGvIoirkvxQVw9J1WJPxz571XPgLsPf9uhRD+PJteUnvrJHMDmK9pw2yZEGGyismtRoEsp+JcXUdF/JDMDw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-android-arm64": { + "version": "0.147.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-android-arm64/-/binding-android-arm64-0.147.0.tgz", + "integrity": "sha512-emjQHOYJaomo4ykaXQ1EItunr/I94Nk01oqBmU4dSkKSTupIDx6OysVDf2e8Eytm77rb+4ZxzgElyWP7rcEX7A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-darwin-arm64": { + "version": "0.147.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-darwin-arm64/-/binding-darwin-arm64-0.147.0.tgz", + "integrity": "sha512-kXvBPJL7RmDPJ2mze/vXPPVQimCDtFr9OFLjf7dyhV5Dx64cgcXh9KKrA1sMWvCObvJll9CZZUO0FBlFwD0l6A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-darwin-x64": { + "version": "0.147.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-darwin-x64/-/binding-darwin-x64-0.147.0.tgz", + "integrity": "sha512-mgFF8pLU6R64LbT27lSrtVRspVC/3IcZ0qyIikzmi78Y3Ik2OPnlAHHI0UEBRcC3qmNgtjaef7zkFt7/uPxIcw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-freebsd-x64": { + "version": "0.147.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-freebsd-x64/-/binding-freebsd-x64-0.147.0.tgz", + "integrity": "sha512-v38aiF11qufOTBcCAKL4skgQf0zJ4NEvRlivq7B5kHrlyvjCLjvNrMtNWDTz1SDUL6/xVsJRmLDxv2e+Cp4oWw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-linux-arm-gnueabihf": { + "version": "0.147.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-0.147.0.tgz", + "integrity": "sha512-AeIiBbwUaP0H1+4/qGW9l5qHecS/+XA5iMuieVcGb1T+tyc2dVGspFW13BWk/XrLsiGP/CiDJTJqAPLLCzZHkw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-linux-arm-musleabihf": { + "version": "0.147.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-arm-musleabihf/-/binding-linux-arm-musleabihf-0.147.0.tgz", + "integrity": "sha512-/41MKPW4RgPY4DJco0NCF0RYX3IMZaVlRNMNzvhaxRavc7tN3Txm+qllZbh0aMRs0VHdgUlbI8TcAOiTai4TKg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-linux-arm64-gnu": { + "version": "0.147.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-0.147.0.tgz", + "integrity": "sha512-bmpw/RPhVXgZbtb3xBDuwW5s8+LvZYdqcDSX/sP2ltL77aTio3DP/B5ZTwwgoJ6Mr9vJs4RrmgEKW9XkLNUU1g==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-linux-arm64-musl": { + "version": "0.147.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-arm64-musl/-/binding-linux-arm64-musl-0.147.0.tgz", + "integrity": "sha512-gd7VX/FDVOw6mjQcu45iIcp4QkgybgJwh3a0OFG2NxmPCj628mQWD96QGu1kK8+mZF9qK4b/gIEyC63vQoB7+Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-linux-ppc64-gnu": { + "version": "0.147.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-0.147.0.tgz", + "integrity": "sha512-HnAzcfki7dSUNHf510Q2NmbJlz8Ys7rn8l9l588Pkx0tYe1BHLZnmELIgqizJ4WPhHGSwN8Ce+B/menVxS3odA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-linux-riscv64-gnu": { + "version": "0.147.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-riscv64-gnu/-/binding-linux-riscv64-gnu-0.147.0.tgz", + "integrity": "sha512-qlkOL6wT44U+fT5s/+sR6Shx0OdwvQF83JyIPZUxG/ovqZF5/7atOtjH+JPZ5/7ATQLbFBBSmghcy/+2NVB/ew==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-linux-riscv64-musl": { + "version": "0.147.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-riscv64-musl/-/binding-linux-riscv64-musl-0.147.0.tgz", + "integrity": "sha512-DlefD7L7sMXs/3hIBH23Egk0phj8kG0SA81dVGOQ3S1ekjOlmTLH2E+F2Thwfh1slKx6aH+lNc5fQYAw0GU7/g==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-linux-s390x-gnu": { + "version": "0.147.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-0.147.0.tgz", + "integrity": "sha512-Xqpagk/031IvZ4svrk2FF01YEqM/iN3MJV3SVZadKg/CsGlDCGoREqKHXYnoV5+8SfGe/m6RM1szXFLusTu/Uw==", + "cpu": [ + "s390x" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-linux-x64-gnu": { + "version": "0.147.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-x64-gnu/-/binding-linux-x64-gnu-0.147.0.tgz", + "integrity": "sha512-QioQOeUbI4ATUr0S2z88uA3Cds2R3Mm5Ge7U8XNYtlTb2GJF3rWlcj70z0AJhhOlbdm0YgVjqPBldUNbFylDIg==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-linux-x64-musl": { + "version": "0.147.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-x64-musl/-/binding-linux-x64-musl-0.147.0.tgz", + "integrity": "sha512-NXy1tv/OdC+pPTwf9RiCZWPK53V/Xq/2cjSnjOSyKopajdaDqMIkgtDY+jXZemp2e8px5FeWfY2L2LwhKZQovg==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-openharmony-arm64": { + "version": "0.147.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-openharmony-arm64/-/binding-openharmony-arm64-0.147.0.tgz", + "integrity": "sha512-GpGWZ6oKz4bjCWW9Mz5pCaGPyk2Aaze6zEoaslIQqpSLtpx5pXj/ap5gUNb5Jn2LIbqWyjkGLX9yv3NMuNcBVQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-win32-arm64-msvc": { + "version": "0.147.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-0.147.0.tgz", + "integrity": "sha512-a8mlt7CC8z7LUdCfaxhff4kCd+vSjE+NEFL0cxA8ukfuSnvAto/pWTjytW4BuVLnQGcCVdHJwcRKOfs++H+tjw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-win32-ia32-msvc": { + "version": "0.147.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-0.147.0.tgz", + "integrity": "sha512-M5ViVDBcFLnl2632AuuWuP35zEL5oikK1jTx8r3+902VEDeSNHxFZAB6RZyfZ7MU6Oi5QTOG8MmMkIeHsudSaw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-win32-x64-msvc": { + "version": "0.147.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-win32-x64-msvc/-/binding-win32-x64-msvc-0.147.0.tgz", + "integrity": "sha512-DUaE13OwnUSlHpLZNcC/nuT10ivlWqc5EZgsfgXuAmWYw0r3nDxGeLD1zlGwYwIgVk1/ZMAxoXpV+05stvbHaA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-project/types": { + "version": "0.148.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.148.0.tgz", + "integrity": "sha512-Nm4s/jB+4FpFsPhWGEC4h7rzksesmtnMXomo6rCMcg/b8zLQuOziRgkCS1fxDCXOlJB/6Q8oABOZ/OP6RIPj9A==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/oxc-project" + } + }, + "node_modules/@oxc-resolver/binding-android-arm-eabi": { + "version": "11.24.2", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-android-arm-eabi/-/binding-android-arm-eabi-11.24.2.tgz", + "integrity": "sha512-y09e0L0SRI2OA2tUIrjBgoV3eH5hvUKXNkJqXmNo5V2WxIjyC7I7aJfRLMEVpA8yi95f90gFDvO0VMgrDw+vwA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@oxc-resolver/binding-android-arm64": { + "version": "11.24.2", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-android-arm64/-/binding-android-arm64-11.24.2.tgz", + "integrity": "sha512-cl4icWaZFnLdg8m6qtnh5rBMuGbxc/ptStFHLeCNwr+2cZjkjNwQu/jYRS0CHlnPecOJMpuS5M6/BH+0J/YkEg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@oxc-resolver/binding-darwin-arm64": { + "version": "11.24.2", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-darwin-arm64/-/binding-darwin-arm64-11.24.2.tgz", + "integrity": "sha512-At29QEMF6HajbQvgY8K6OXnHD1x9rad74xBEfmCB6ZqCGsdq75aK7tOYcTbOanMy8qdIBrfL3SMr3p/lfSlb9w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@oxc-resolver/binding-darwin-x64": { + "version": "11.24.2", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-darwin-x64/-/binding-darwin-x64-11.24.2.tgz", + "integrity": "sha512-A5Kqr1EUj4oIL5CF4WRssq/o5P0Y11cwoFouMRmQ7YnC/A8V93nv1nb7aSU8HwcgmXropjLNkVTl4MN87cu28Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@oxc-resolver/binding-freebsd-x64": { + "version": "11.24.2", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-freebsd-x64/-/binding-freebsd-x64-11.24.2.tgz", + "integrity": "sha512-R5xkRBRRz7ceH/P5Jrc6G7FmdUdgpLYyESFAUDVTNQ9K0sGPxcp4ljiwEwEqsvNcQ4sYbMRrWcHHBCu7ksAJVw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@oxc-resolver/binding-linux-arm-gnueabihf": { + "version": "11.24.2", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-11.24.2.tgz", + "integrity": "sha512-k/RuYL4L/R58IBn3wT5ma3Wh4k62bp1eYCFRWCmMsasUOqL+H6sW0VGFadEzKWXFFlz+2uIMoeMk9ySSZJHgbg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oxc-resolver/binding-linux-arm-musleabihf": { + "version": "11.24.2", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-arm-musleabihf/-/binding-linux-arm-musleabihf-11.24.2.tgz", + "integrity": "sha512-bnHAak3ujYfH5pKk4NieFNbvYvernfoQDgwLddbZ3OtMYrem87/qjlA+u+aKG0oZcqSLGCful/6/CEA+aeAgaA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oxc-resolver/binding-linux-arm64-gnu": { + "version": "11.24.2", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-11.24.2.tgz", + "integrity": "sha512-vDT3KHgzYp47gmtNOqL2VNhCyl5Zv643eyxm//A68J8DeUGXrvD1pZFiaT4jSfe+RInfnn1R2yVHye4enx6RnA==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oxc-resolver/binding-linux-arm64-musl": { + "version": "11.24.2", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-arm64-musl/-/binding-linux-arm64-musl-11.24.2.tgz", + "integrity": "sha512-+kMlQvbzfyEYtu5FcjE4p+ttBLpKW4d/AsAsuE69BxV6V4twZJeIQZFfD8gh/wqglY0MkPSezWXQH0jBV13MUw==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oxc-resolver/binding-linux-ppc64-gnu": { + "version": "11.24.2", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-11.24.2.tgz", + "integrity": "sha512-shjfMhmZ3gq9fv/w7bi3PnZlgOPG+2QAOFf0BJF0EgBSIGZ6PMLN2zbGEblTUYB/NKVDRyYhE2ff3dJ1QqNPkA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oxc-resolver/binding-linux-riscv64-gnu": { + "version": "11.24.2", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-riscv64-gnu/-/binding-linux-riscv64-gnu-11.24.2.tgz", + "integrity": "sha512-zGelwFR5oRo+b69k8Lrzun86DyUHzfKN6cnjbR9l7Z7NIRznOE/2ZvPa1IUKqAL2PzAXOdwkfVqNvO1H2RlpAw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oxc-resolver/binding-linux-riscv64-musl": { + "version": "11.24.2", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-riscv64-musl/-/binding-linux-riscv64-musl-11.24.2.tgz", + "integrity": "sha512-qxZ1SWCXJY0eyhAlP6Lmo9F2Nrtx7EkYj9oCgL8apDPCwXwCEDA2U697bbT81JIc2IrVjxO4KX6WU2N+oN9Z4w==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oxc-resolver/binding-linux-s390x-gnu": { + "version": "11.24.2", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-11.24.2.tgz", + "integrity": "sha512-sGCecF3cx2DFlH4t/z7ApnOnXqN48p5p5mlHDEnHTAukQa2P+qMVE4CwyWE9W+q/m3QJ7kKfGrIjax31f44oFQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oxc-resolver/binding-linux-x64-gnu": { + "version": "11.24.2", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-x64-gnu/-/binding-linux-x64-gnu-11.24.2.tgz", + "integrity": "sha512-k/VlMMcSzMlahb3/fENM4rTlsJ0s3fFROA0KXPBmKggqmTSaE383sl8F3KCOXPLmVsYfW6hCitMhXCEtNeZxxg==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oxc-resolver/binding-linux-x64-musl": { + "version": "11.24.2", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-x64-musl/-/binding-linux-x64-musl-11.24.2.tgz", + "integrity": "sha512-8hbnZyNi97b/8wapYaIF9+t9GmZKBW2vunaOc3h9HGJptH7b7XpvZqOTBSm/MpTjr7H497BlgOaSfLUdhmy2bw==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oxc-resolver/binding-openharmony-arm64": { + "version": "11.24.2", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-openharmony-arm64/-/binding-openharmony-arm64-11.24.2.tgz", + "integrity": "sha512-MvyGik3a6pVgZ0t/kWlbmFxFLmXQJwgLsY2eYFHLpy0wGwRbfzeIGgDwQ3kXqE30z+kSXennRkCrT7TUvkptNg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@oxc-resolver/binding-wasm32-wasi": { + "version": "11.24.2", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-wasm32-wasi/-/binding-wasm32-wasi-11.24.2.tgz", + "integrity": "sha512-vHcssMPwO08RTvj/c0iOBz90attxyG3wQJ0dTcyEQK43LRpcdLWZlV5feBhv6Isn6ahbQIzHbCgfa81+RiML0Q==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "1.11.2", + "@emnapi/runtime": "1.11.2", + "@napi-rs/wasm-runtime": "^1.1.6" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@oxc-resolver/binding-wasm32-wasi/node_modules/@emnapi/core": { + "version": "1.11.2", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.11.2.tgz", + "integrity": "sha512-TC8MkTuZUtcTSiFeuC0ksCh9QIJ5+F21MvZ4Wn4ORfYaFJ/0dsiudv5tVkejgwZlwQ39jL9WWDe2lz8x0WglOA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.2.2", + "tslib": "^2.4.0" + } + }, + "node_modules/@oxc-resolver/binding-wasm32-wasi/node_modules/@emnapi/runtime": { + "version": "1.11.2", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.2.tgz", + "integrity": "sha512-kyOl3X0DuTiT1h2ft8r2fYO8JYtU9a9Xis/zBSiGArNaagCOWx90N1k2wxp18czFDH+OgcWGb5ZP/XMt3dcyPA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@oxc-resolver/binding-wasm32-wasi/node_modules/@emnapi/wasi-threads": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.2.tgz", + "integrity": "sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@oxc-resolver/binding-win32-arm64-msvc": { + "version": "11.24.2", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-11.24.2.tgz", + "integrity": "sha512-uokJqro2iBqkFvJdKQLP7d8/BUmFwESQFVmIJUQKj1Xn1a/LysJoe1vmeECLF5b3jsV8CAL5sEMJXX6SdK9Nhg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@oxc-resolver/binding-win32-x64-msvc": { + "version": "11.24.2", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-win32-x64-msvc/-/binding-win32-x64-msvc-11.24.2.tgz", + "integrity": "sha512-UqGPmo56KDfLlfXFAFIrNflHT8tFxWGEivWg3Zeyp4Uy2NlKN1FGPr6/BxcLGG3+kZ6Wp14g5Uj+n71boqZfiw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@pagefind/darwin-arm64": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/@pagefind/darwin-arm64/-/darwin-arm64-1.5.2.tgz", + "integrity": "sha512-MXpI+7HsAdPkvJ0gk9xj9g541BCqBZOBbdwj9g6lB5LCj6kSV6nqDSjzcAJwvOsfu0fjwvC8hQU+ecfhp+MpiQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@pagefind/darwin-x64": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/@pagefind/darwin-x64/-/darwin-x64-1.5.2.tgz", + "integrity": "sha512-IojxFWMEJe0RQ7PQ3KXQsPIImNsbpPYpoZ+QUDrL8fAl/O27IX+LVLs74/UzEZy5uA2LD8Nz1AiwKr72vrkZQw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@pagefind/default-ui": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/@pagefind/default-ui/-/default-ui-1.5.2.tgz", + "integrity": "sha512-pm1LMnQg8N2B3n2TnjKlhaFihpz6zTiA4HiGQ6/slKO/+8K9CAU5kcjdSSPgpuk1PMuuN4hxLipUIifnrkl3Sg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@pagefind/freebsd-x64": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/@pagefind/freebsd-x64/-/freebsd-x64-1.5.2.tgz", + "integrity": "sha512-7EVzo9+0w+2cbe671BtMj10UlNo83I+HrLVLfRxO731svHRJKUfJ/mo05gU14pe9PCfpKNQT8FS3Xc/oDN6pOA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@pagefind/linux-arm64": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/@pagefind/linux-arm64/-/linux-arm64-1.5.2.tgz", + "integrity": "sha512-Ovt9+K35sqzn8H3ZMXGwls4TD/wMJuvRtShHIsmUQREmaxjrDEX7gHckRCrwYJ4XE1H1p6HkLz3wukrAnsfXQw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@pagefind/linux-x64": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/@pagefind/linux-x64/-/linux-x64-1.5.2.tgz", + "integrity": "sha512-V+tFqHKXhQKq/WqPBD67AFy7scn1/aZID00ws4fSDd+1daSi5UHR9VVlRrOUYKxn3VuFQYRD7lYXdZK1WED1YA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@pagefind/windows-arm64": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/@pagefind/windows-arm64/-/windows-arm64-1.5.2.tgz", + "integrity": "sha512-hN9Nh90fNW61nNRCW9ZyQrAj/mD0eRvmJ8NlTUzkbuW8kIzGJUi3cxjFkEcMZ5h/8FsKWD/VcouZl4yo1F7B6g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@pagefind/windows-x64": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/@pagefind/windows-x64/-/windows-x64-1.5.2.tgz", + "integrity": "sha512-Fa2Iyw7kaDRzGMfNYNUXNW2zbL5FQVDgSOcbDHdzBrDEdpqOqg8TcZ68F22ol6NJ9IGzvUdmeyZypLW5dyhqsg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rolldown/binding-android-arm-eabi": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm-eabi/-/binding-android-arm-eabi-1.2.7.tgz", + "integrity": "sha512-EypzgnYCwyVY4NDHKzGmNJT5b+XaQEBniHxsMdeIQLB/tcCzZnhqrzHpZFbX9iaxx+5RiB8caATBtfvZP7zVxQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-android-arm64": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.2.7.tgz", + "integrity": "sha512-l17HE9EweWaqJZhuUuNBN/FzM62xw+DECVnJyvMsxn8vJFAGLy5QfLDoYAcronkAN8VxKZHezDpulHDPx95vFw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-arm64": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.2.7.tgz", + "integrity": "sha512-8ED8ELFvHXc6OCETIn4gXObPiaR6bckM/ipXtbzlPVDRMBfEGjCKgO90F9YtfdpDatVx/ZQw7aZ1vUMf/+T3Mw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-x64": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.2.7.tgz", + "integrity": "sha512-/WPripjtiAIZ2tWY7ddijORT0Ujg87wxWW/qcoFVCKAWVDPhtY0xr7Dj0M3GyNGz60jGwTElhro/mkF9dT7dDQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-freebsd-x64": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.2.7.tgz", + "integrity": "sha512-14DI4NcqpvbICxSnGLx3PmtDaWqRP/KGSGb6C+JLLVPeZRl6dKdHba3pGsqT3vpdTqhEYIPG0MMQ8c0xYqoJxA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm-gnueabihf": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.2.7.tgz", + "integrity": "sha512-bxrWIRvHWQvbJwi+VIie/kDJmQxcNE6xxWwZdqF/ExVAigtHkv54WTLQPb+QsZdnFy18fg7JPfWGL0RH6vwIlQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-gnu": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.2.7.tgz", + "integrity": "sha512-toOY2BChBZyuxU7OYX6Tn389di4IzAqPTycVcci0O7FSfBqzRB3RZn+K5Is6ANf4tmgRd/K1yZTsNTXbkXsnLg==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-musl": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.2.7.tgz", + "integrity": "sha512-lAIXTH/aiLRLxsTgQvfhjo4K1ydWIp00+V0voOr9beb/9ZmkUFrSIb03dXNFRgMNvkE6oGsF10ioQ6UsI+vS5Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-ppc64-gnu": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.2.7.tgz", + "integrity": "sha512-kdnwS28Pkenp/mZMRwjXXXwxQ7pIsm+bF919LUK93BOyhcLsrVKdP2p9fxpiPNPAbNuch8ypQt0pm2P2LYCAGg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-s390x-gnu": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.2.7.tgz", + "integrity": "sha512-516OdsyLdr5E65paF3yBF55t8mfm9+gmtCsK3xI7XKXIT7EfRlHhxL8K/NR6Hu8BWSgF5+1w74lTL0+nxcc8Qw==", + "cpu": [ + "s390x" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-gnu": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.2.7.tgz", + "integrity": "sha512-r8/z8n7GFaYRln3xmP1Cxy0HH/HLM0uBUPkEuSVEfKGDA89M0FsZRZJRSwe/tJjRx+fpH/gjorfhB8tmEbSFLA==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-musl": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.2.7.tgz", + "integrity": "sha512-pAsE8iiDxUg1xBqdhrTfg45AVDVpirjz00sblEYClGNNcMnDb+e8beQgqIAw6LvauX/APvgxUnwrgun/YYGBhw==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-openharmony-arm64": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.2.7.tgz", + "integrity": "sha512-lTcIYmmnQQA8Or/2DatS6oSqcdLHvendjS+zLu+FwgToynWMRSmQdpM65fTANJgIS4mjbMOo5KT2lnT9SAb96w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-arm64-msvc": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.2.7.tgz", + "integrity": "sha512-e3Gu3WxbNk/UqQhxqU7YIYO+9ZBvWNz3U+h/qRFosscMFzdRPbXYSaSWgSnklv2fz1TgzBTcti2z35c/7irsHw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-x64-msvc": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.2.7.tgz", + "integrity": "sha512-W/jg5qoRSqjsEv0+dZi4e687mcHqmVuU0P4fK6qS/xjetW2Gmc1W8j//z5nAeNcC8Ttm0hV46IjcYeuVwYhuiw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz", + "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@shikijs/core": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/@shikijs/core/-/core-4.4.3.tgz", + "integrity": "sha512-QCR4q2ZO/ILJEuwiBMel4wdcTDb1JGwfjKTxPDF6x8ixOaluPrVqIn06C99AcRPhmYlBR56d/Fb+GN58GzExpg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shikijs/primitive": "4.4.3", + "@shikijs/types": "4.4.3", + "@shikijs/vscode-textmate": "^10.0.2", + "@types/hast": "^3.0.5", + "hast-util-to-html": "^9.0.5" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@shikijs/engine-javascript": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/@shikijs/engine-javascript/-/engine-javascript-4.4.3.tgz", + "integrity": "sha512-FbOjFJp9VLdo1Wevs10BBtVxiTWwNLqZh5Gkhjgda/ioL15YOgeSl9n+6XMa3qRlPQzfhFNe641SrynFHYG0nQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shikijs/types": "4.4.3", + "@shikijs/vscode-textmate": "^10.0.2", + "oniguruma-to-es": "^4.3.6" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@shikijs/engine-oniguruma": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-4.4.3.tgz", + "integrity": "sha512-EcOQkxdxGQrc1Row/cC2c96/v1dbZqGnEVu1qTuT/MJmp6+cXCvQussowVmCv5Tqr3KuY3c7IbM6HTW3LJ1k9w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shikijs/types": "4.4.3", + "@shikijs/vscode-textmate": "^10.0.2" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@shikijs/langs": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/@shikijs/langs/-/langs-4.4.3.tgz", + "integrity": "sha512-ePic0yfAJGOF83D5wBHK/00EjK65oahBYxFk5epgq33WRv7X9UuxLEV8PtR0szC0z8dl7INIpIodB99JRFlR+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shikijs/types": "4.4.3" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@shikijs/primitive": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/@shikijs/primitive/-/primitive-4.4.3.tgz", + "integrity": "sha512-m0wBeLDQDeIxRdUmrCPdQqfuUamDwRL5isCfYbguKD6NiaKpVbsv+3J81DyIKgNW5h4WAIIr8T4EkgQrBBxvaQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shikijs/types": "4.4.3", + "@shikijs/vscode-textmate": "^10.0.2", + "@types/hast": "^3.0.5" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@shikijs/themes": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/@shikijs/themes/-/themes-4.4.3.tgz", + "integrity": "sha512-w8UHjeUnIR965KMWJHUPXOc2mNJUnK3vpVLYLvw5IYU2mnTTJ89E24OrJDBNiJDQ0qzb0tc4l7mrIXx5cFeIyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shikijs/types": "4.4.3" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@shikijs/types": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-4.4.3.tgz", + "integrity": "sha512-UEJxmRR++MAGR6hugn0vgVS2W/6lWAts84FFSrnlH9sP0LNol7E5+NQ792pH8liWUhyMyjhTgSUH3k7iD7tc5g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shikijs/vscode-textmate": "^10.0.2", + "@types/hast": "^3.0.5" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@shikijs/vscode-textmate": { + "version": "10.0.2", + "resolved": "https://registry.npmjs.org/@shikijs/vscode-textmate/-/vscode-textmate-10.0.2.tgz", + "integrity": "sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tybys/wasm-util": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.3.tgz", + "integrity": "sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@types/debug": { + "version": "4.1.13", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.13.tgz", + "integrity": "sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/ms": "*" + } + }, + "node_modules/@types/esrecurse": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@types/esrecurse/-/esrecurse-4.3.1.tgz", + "integrity": "sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/estree-jsx": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree-jsx/-/estree-jsx-1.0.5.tgz", + "integrity": "sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "*" + } + }, + "node_modules/@types/hast": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.5.tgz", + "integrity": "sha512-rp/ezSWaD1m44dPKICGhiskI13nVr7qTloFwDa/IYkhhf5nzwP+zIQcIJh3WIFSBOy/H1PzB40jPjMDksN4F+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/@types/istanbul-lib-coverage": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", + "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/js-yaml": { + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/@types/js-yaml/-/js-yaml-4.0.9.tgz", + "integrity": "sha512-k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpX1qGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/mdast": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz", + "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/@types/mdx": { + "version": "2.0.14", + "resolved": "https://registry.npmjs.org/@types/mdx/-/mdx-2.0.14.tgz", + "integrity": "sha512-T48PeuJtvLosNTPVhfnIp3i/n3a4g4Bad7YCq5k64D4u7NwDrAotikQ+5+sjtUvBmxCMlbo3dVL+C2dP0rWHzg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/ms": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", + "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/nlcst": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/nlcst/-/nlcst-2.0.3.tgz", + "integrity": "sha512-vSYNSDe6Ix3q+6Z7ri9lyWqgGhJTmzRjZRqyq15N0Z/1/UnVsno9G/N40NBijoYx2seFDIl0+B2mgAb9mezUCA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/@types/node": { + "version": "26.4.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-26.4.1.tgz", + "integrity": "sha512-k97ENvZWtvA6yqz5/FS6a7duDgOPEeOQOc2iKS/nY6mX6qJUKtLnWzQS+Xj6tXweyj6ZcTAK2Qecetnvi9nCLA==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~8.3.0" + } + }, + "node_modules/@types/sax": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@types/sax/-/sax-1.2.7.tgz", + "integrity": "sha512-rO73L89PJxeYM3s3pPPjiPgVVcymqU490g0YO5n5By0k2Erzj6tay/4lr1CHAAU4JyOWd1rpQ8bCf6cZfHU96A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/whatwg-mimetype": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/whatwg-mimetype/-/whatwg-mimetype-3.0.2.tgz", + "integrity": "sha512-c2AKvDT8ToxLIOUlN51gTiHXflsfIFisS4pO7pDPoKouJCESkhZnEy623gwP9laCy5lnLDAw1vAzu2vM2YLOrA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/ws": { + "version": "8.18.1", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", + "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.69.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.69.0.tgz", + "integrity": "sha512-t5jQTKPIgVW1PE6dR6H6Qz5gm8zjMlX5/2gRaOGd9eO6V7J+tQc6iWKukEe7dY8u9HyYasQ0yfF0/FSSTEO2gA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.12.2", + "@typescript-eslint/scope-manager": "8.69.0", + "@typescript-eslint/type-utils": "8.69.0", + "@typescript-eslint/utils": "8.69.0", + "@typescript-eslint/visitor-keys": "8.69.0", + "ignore": "^7.0.5", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.69.0", + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { + "version": "7.0.8", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.8.tgz", + "integrity": "sha512-YYNsSlXBjMk92SKnkwvB5LOVSa6OznlFUGcsvrFgNJbJCd0M1XKeFVRc8ZByeCqz32FivYNHJVooLmdqrmvp/Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "8.69.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.69.0.tgz", + "integrity": "sha512-l4b0DhWioGg6Gt2ebGlvfkFMOjRsauxtsnDRwUSRX1qHq3HdTfQHV8wW9zEXeciai6HfeaKOedQn2Zoofx3WBw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/scope-manager": "8.69.0", + "@typescript-eslint/types": "8.69.0", + "@typescript-eslint/typescript-estree": "8.69.0", + "@typescript-eslint/visitor-keys": "8.69.0", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/project-service": { + "version": "8.69.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.69.0.tgz", + "integrity": "sha512-yi4obFrHMmnsesWehHbkg9zMA7Jt8cXT+mKM08G999pH1yT6nqgsHx7MYm0uY1wAj8CqiBXYRJ7WAT0QdQHQXg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.69.0", + "@typescript-eslint/types": "^8.69.0", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.69.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.69.0.tgz", + "integrity": "sha512-ewfspqWvSxKSOaplqAUNbaSFO0eB6w1EtQ+esfYFRm3614Ty4uNtExkcbgd6nWsXphbqKyf9ZYdbZdv2xEoWEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.69.0", + "@typescript-eslint/visitor-keys": "8.69.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.69.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.69.0.tgz", + "integrity": "sha512-xNqK7YTDZsLniQMV/4rpFR8Z5JlqeRvVjuG1YgF/mdPVH84HSD19L8CczMA0qg2RfwEV231GHH3VnToJDo4MfQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.69.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.69.0.tgz", + "integrity": "sha512-ZfoJAVg3JZndQEpEl9petVlxau3lRuElc4HRMuAlLCf8to04/iHz692RUSNmXKDjEuJmIL+KZ2/BsOcBc16dsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.69.0", + "@typescript-eslint/typescript-estree": "8.69.0", + "@typescript-eslint/utils": "8.69.0", + "debug": "^4.4.3", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "8.69.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.69.0.tgz", + "integrity": "sha512-K3VrubUPhlo9VDBS6QdI8YB5j7ClpqLRdefcz6PFrhnwicehBweqQ9Evhl4l+FYz0HdDmMqIiSX0aldGRYtDCA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.69.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.69.0.tgz", + "integrity": "sha512-AdFkgqck3Vudb/kWnxlyafU/4aBhHrbQ9locP2N4psXTy5mOBg0SHJumnLvx7r6g1gV4DKvUFwV2nJZBoqOD8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.69.0", + "@typescript-eslint/tsconfig-utils": "8.69.0", + "@typescript-eslint/types": "8.69.0", + "@typescript-eslint/visitor-keys": "8.69.0", + "debug": "^4.4.3", + "minimatch": "^10.2.2", + "semver": "^7.7.3", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "8.69.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.69.0.tgz", + "integrity": "sha512-tUbx60BBqQa31kXF5MCsOOLL5E/WzUuxIn7YpAvq+eaUlqvk8/NXnXMBNAdLCr0icjkzem7iUA5QqWHe/hJ1aw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.9.1", + "@typescript-eslint/scope-manager": "8.69.0", + "@typescript-eslint/types": "8.69.0", + "@typescript-eslint/typescript-estree": "8.69.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.69.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.69.0.tgz", + "integrity": "sha512-+rmdgPA+EXkNgKYvHvFfhrs35utXbwaC5PGpDquSXcoXQDKUA5UjV0LmTucG/4JXkM31BTu4TilHtrN8IVBe8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.69.0", + "eslint-visitor-keys": "^5.0.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@ungap/structured-clone": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.4.0.tgz", + "integrity": "sha512-1mEZtMKPM09vDmQt5y7YvmN2+DFTP7Tg0EWXdic8/C6VRnpb33e4ghisCIE3WZjsE2N8mf+QV1Zqh7ZFYLWInQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/acorn": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.18.0.tgz", + "integrity": "sha512-lGq+9yr1/GuAWaVYIHRjvvySG5/4VfKIvC8EWxStPdcDh/Ka7FG3twP6v4d5BkravUilhIAsG4Qj83t02LWUPQ==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ajv": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", + "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/am-i-vibing": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/am-i-vibing/-/am-i-vibing-0.4.0.tgz", + "integrity": "sha512-MxT4XZL7pzLHpuvhDKdMaQHMGGkJDLluKBLsbstn+8wv9sWcFT6h+0ve9qkml95amVTZtZV83gQe2hY+ojgHLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "process-ancestry": "^0.1.0" + }, + "bin": { + "am-i-vibing": "dist/cli.mjs" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/anymatch/node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/arg": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", + "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", + "dev": true, + "license": "MIT" + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/aria-query": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz", + "integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/array-iterate": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/array-iterate/-/array-iterate-2.0.1.tgz", + "integrity": "sha512-I1jXZMjAgCMmxT4qxXfPXa6SthSoE8h6gkSI9BGGNv8mP8G/v0blc+qFnZu6K42vTOiuME596QaLO0TP3Lk0xg==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/astring": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/astring/-/astring-1.9.0.tgz", + "integrity": "sha512-LElXdjswlqjWrPpJFg1Fx4wpkOCxj1TDHlSV4PlaRxHGWko024xICaa97ZkMfs6DRKlCguiAI+rbXv5GWwXIkg==", + "dev": true, + "license": "MIT", + "bin": { + "astring": "bin/astring" + } + }, + "node_modules/astro": { + "version": "7.2.10", + "resolved": "https://registry.npmjs.org/astro/-/astro-7.2.10.tgz", + "integrity": "sha512-uPtD+nK6kQXugyq4kiMPwj9OI3Sae6HSerA5X+fuv2CPaDwxmokFPPFlGRKIAXH0QAKu+zot2q6yrLG61wFmqg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@astrojs/compiler-rs": "^0.4.0", + "@astrojs/internal-helpers": "0.11.0", + "@astrojs/markdown-satteri": "0.4.0", + "@astrojs/telemetry": "3.3.3", + "@capsizecss/unpack": "^4.0.0", + "@clack/prompts": "^1.1.0", + "@oslojs/encoding": "^1.1.0", + "am-i-vibing": "^0.4.0", + "aria-query": "^5.3.2", + "axobject-query": "^4.1.0", + "ci-info": "^4.4.0", + "clsx": "^2.1.1", + "common-ancestor-path": "^2.0.0", + "cookie": "^2.0.1", + "devalue": "^5.8.1", + "diff": "^9.0.0", + "dset": "^3.1.4", + "es-module-lexer": "^2.0.0", + "esbuild": "^0.28.0", + "find-proc": "0.1.0", + "flattie": "^1.1.1", + "fontace": "~0.4.1", + "get-tsconfig": "5.0.0-beta.4", + "github-slugger": "^2.0.0", + "html-escaper": "3.0.3", + "http-cache-semantics": "^4.2.0", + "js-yaml": "^4.3.0", + "jsonc-parser": "^3.3.1", + "magic-string": "^1.0.0", + "magicast": "^0.5.2", + "mrmime": "^2.0.1", + "neotraverse": "^1.0.1", + "obug": "^2.1.1", + "p-limit": "^7.3.0", + "p-queue": "^9.1.0", + "package-manager-detector": "^1.6.0", + "piccolore": "^0.1.3", + "picomatch": "^4.0.4", + "semver": "^7.7.4", + "shiki": "^4.0.2", + "smol-toml": "^1.6.0", + "svgo": "^4.0.1", + "tinyclip": "^0.1.12", + "tinyexec": "^1.0.4", + "tinyglobby": "^0.2.15", + "ultrahtml": "^1.6.0", + "unifont": "~0.7.5", + "unstorage": "^1.17.5", + "vite": "^8.0.13", + "vitefu": "^1.1.2", + "xxhash-wasm": "^1.1.0", + "yargs-parser": "^22.0.0", + "zod": "^4.3.6" + }, + "bin": { + "astro": "bin/astro.mjs" + }, + "engines": { + "node": ">=22.12.0", + "npm": ">=9.6.5", + "pnpm": ">=7.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/astrodotbuild" + }, + "optionalDependencies": { + "sharp": "^0.35.4" + }, + "peerDependencies": { + "@astrojs/markdown-remark": "^7.3.0" + }, + "peerDependenciesMeta": { + "@astrojs/markdown-remark": { + "optional": true + } + } + }, + "node_modules/astro-expressive-code": { + "version": "0.44.2", + "resolved": "https://registry.npmjs.org/astro-expressive-code/-/astro-expressive-code-0.44.2.tgz", + "integrity": "sha512-avop7nZcRwC7quvIxVguhwEZ9Tp6IoTzKUCLwP30Fb7+er+pWQgcysP01dcuzRIJo81kLGg30q/YAI2Yb1Sdzg==", + "dev": true, + "license": "MIT", + "dependencies": { + "rehype-expressive-code": "^0.44.2", + "url-extras": "^0.1.0" + }, + "peerDependencies": { + "astro": "^4.0.0-beta || ^5.0.0-beta || ^3.3.0 || ^6.0.0-beta || ^7.0.0" + } + }, + "node_modules/astro/node_modules/diff": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-9.0.0.tgz", + "integrity": "sha512-svtcdpS8CgJyqAjEQIXdb3OjhFVVYjzGAPO8WGCmRbrml64SPw/jJD4GoE98aR7r25A0XcgrK3F02yw9R/vhQw==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/astro/node_modules/html-escaper": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-3.0.3.tgz", + "integrity": "sha512-RuMffC89BOWQoY0WKGpIhn5gX3iI54O6nRA0yC124NYVtzjmFWBIiFd8M0x+ZdX0P9R4lADg1mgP8C7PxGOWuQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/astro/node_modules/p-limit": { + "version": "7.3.2", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-7.3.2.tgz", + "integrity": "sha512-Ll0w3fU24vYpXoZmjjZIee6bJQDgG0oAyo1PdmFYI8UDwJJddaHAypxIH9avUu+t+lSsAwKVsb1jDCMIIChliw==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^1.2.1" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/astro/node_modules/yargs-parser": { + "version": "22.0.0", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-22.0.0.tgz", + "integrity": "sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=23" + } + }, + "node_modules/astro/node_modules/yocto-queue": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.2.2.tgz", + "integrity": "sha512-4LCcse/U2MHZ63HAJVE+v71o7yOdIe4cZ70Wpf8D/IyjDKYQLV5GD46B+hSTjJsvV5PztjvHoU580EftxjDZFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/axobject-query": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz", + "integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/bail": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz", + "integrity": "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/bcp-47": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/bcp-47/-/bcp-47-2.1.1.tgz", + "integrity": "sha512-KLw+H/gd2p4zly1X7Yh/qziuyae5/w/QFnvTng9eZL5fvszL7Whl3MBoWF8yxL7ksUjBfOD+OxkytiqbBpG+Fw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-alphabetical": "^2.0.0", + "is-alphanumerical": "^2.0.0", + "is-decimal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/bcp-47-match": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/bcp-47-match/-/bcp-47-match-2.0.3.tgz", + "integrity": "sha512-JtTezzbAibu8G0R9op9zb3vcWZd9JF6M0xOYGPn0fNCd7wOpRB1mU2mH9T8gaBGbAAyIIVgB2G7xG0GP98zMAQ==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", + "dev": true, + "license": "ISC" + }, + "node_modules/brace-expansion": { + "version": "5.0.9", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.9.tgz", + "integrity": "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/buffer-image-size": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/buffer-image-size/-/buffer-image-size-0.6.4.tgz", + "integrity": "sha512-nEh+kZOPY1w+gcCMobZ6ETUp9WfibndnosbpwB1iJk/8Gt5ZF2bhS6+B6bPYz424KtwsR6Rflc3tCz1/ghX2dQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/c8": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/c8/-/c8-12.0.0.tgz", + "integrity": "sha512-4zpJvrd1nKWutnnKC2pXkFmb6iM1l+ffN//o1CzlTNwW7GSOs9a1xrLqkC48nU8oEkjmPZLPiwMsIaOvoF4Pqg==", + "dev": true, + "license": "ISC", + "dependencies": { + "@bcoe/v8-coverage": "^1.0.1", + "@istanbuljs/schema": "^0.1.3", + "find-up": "^5.0.0", + "foreground-child": "^3.1.1", + "istanbul-lib-coverage": "^3.2.0", + "istanbul-lib-report": "^3.0.1", + "istanbul-reports": "^3.1.6", + "test-exclude": "^8.0.0", + "v8-to-istanbul": "^9.0.0", + "yargs": "^18.0.0", + "yargs-parser": "^21.1.1" + }, + "bin": { + "c8": "bin/c8.js" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=23" + }, + "peerDependencies": { + "monocart-coverage-reports": "^2" + }, + "peerDependenciesMeta": { + "monocart-coverage-reports": { + "optional": true + } + } + }, + "node_modules/ccount": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz", + "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/character-entities": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz", + "integrity": "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-html4": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz", + "integrity": "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-legacy": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz", + "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-reference-invalid": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-2.0.1.tgz", + "integrity": "sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/chokidar": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-5.0.0.tgz", + "integrity": "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==", + "dev": true, + "license": "MIT", + "dependencies": { + "readdirp": "^5.0.0" + }, + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/ci-info": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.4.0.tgz", + "integrity": "sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/cli-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", + "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "restore-cursor": "^3.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cli-spinners": { + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz", + "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cliui": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-9.0.1.tgz", + "integrity": "sha512-k7ndgKhwoQveBL+/1tqGJYNz097I7WOvwbmmU2AR5+magtbjPWQTS1C5vzGkBC8Ym8UWRzfKUzUUqFLypY4Q+w==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^7.2.0", + "strip-ansi": "^7.1.0", + "wrap-ansi": "^9.0.0" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/cliui/node_modules/ansi-regex": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.3.0.tgz", + "integrity": "sha512-WpDfL7NO6j7tH88IDBNVdUJxDh9nmCteAVW9dsep846XdwF4naCBK+/tGLX3KJgcpgMRXCFlTM2hKGoK9FsdrQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/cliui/node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cliui/node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.2.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/clone": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", + "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/collapse-white-space": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/collapse-white-space/-/collapse-white-space-2.1.0.tgz", + "integrity": "sha512-loKTxY1zCOuG4j9f6EPnuyyYkf58RnhhWTvRoZEokgB+WbdXehfjFviyOVYkqzEWz1Q5kRiZdBYS5SwxbQYwzw==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/comma-separated-tokens": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", + "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/commander": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.0.tgz", + "integrity": "sha512-2uM9rYjPvyq39NwLRqaiLtWHyDC1FvryJDa2ATTVims5YAS4PupsEQsDvP14FqhFr0P49CYDugi59xaxJlTXRA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + } + }, + "node_modules/common-ancestor-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/common-ancestor-path/-/common-ancestor-path-2.0.0.tgz", + "integrity": "sha512-dnN3ibLeoRf2HNC+OlCiNc5d2zxbLJXOtiZUudNFSXZrNSydxcCsSpRzXwfu7BBWCIfHPw+xTayeBvJCP/D8Ng==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">= 18" + } + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/cookie": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-2.0.1.tgz", + "integrity": "sha512-yuToqVvRrj6pfDXREyQAAv8SkAEk/8GS3jQRTiUMm66TVtBYmqQeoEjL2Lmq8Rpo6271vH76InTChTitEAm65w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=22" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/cookie-es": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/cookie-es/-/cookie-es-1.2.3.tgz", + "integrity": "sha512-lXVyvUvrNXblMqzIRrxHb57UUVmqsSWlxqt3XIjCkUP0wDAf6uicO6KMbEgYrMNtEvWgWHwe42CKxPu9MYAnWw==", + "dev": true, + "license": "MIT" + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/crossws": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/crossws/-/crossws-0.3.5.tgz", + "integrity": "sha512-ojKiDvcmByhwa8YYqbQI/hg7MEU0NC03+pSdEq4ZUnZR9xXpwk7E43SMNGkn+JxJGPFtNvQ48+vV2p+P1ml5PA==", + "dev": true, + "license": "MIT", + "dependencies": { + "uncrypto": "^0.1.3" + } + }, + "node_modules/css-select": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-6.0.0.tgz", + "integrity": "sha512-rZZVSLle8v0+EY8QAkDWrKhpgt6SA5OtHsgBnsj6ZaLb5dmDVOWUDtQitd9ydxxvEjhewNudS6eTVU7uOyzvXw==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0", + "css-what": "^7.0.0", + "domhandler": "^5.0.3", + "domutils": "^3.2.2", + "nth-check": "^2.1.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/css-selector-parser": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/css-selector-parser/-/css-selector-parser-3.3.0.tgz", + "integrity": "sha512-Y2asgMGFqJKF4fq4xHDSlFYIkeVfRsm69lQC1q9kbEsH5XtnINTMrweLkjYMeaUgiXBy/uvKeO/a1JHTNnmB2g==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/mdevils" + }, + { + "type": "patreon", + "url": "https://patreon.com/mdevils" + } + ], + "license": "MIT" + }, + "node_modules/css-tree": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.2.1.tgz", + "integrity": "sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "mdn-data": "2.27.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" + } + }, + "node_modules/css-what": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-7.0.0.tgz", + "integrity": "sha512-wD5oz5xibMOPHzy13CyGmogB3phdvcDaB5t0W/Nr5Z2O/agcB8YwOz6e2Lsp10pNDzBoDO9nVa3RGs/2BttpHQ==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "dev": true, + "license": "MIT", + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/csso": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/csso/-/csso-5.0.5.tgz", + "integrity": "sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "css-tree": "~2.2.0" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/csso/node_modules/css-tree": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.2.1.tgz", + "integrity": "sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA==", + "dev": true, + "license": "MIT", + "dependencies": { + "mdn-data": "2.0.28", + "source-map-js": "^1.0.1" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/csso/node_modules/mdn-data": { + "version": "2.0.28", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.28.tgz", + "integrity": "sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g==", + "dev": true, + "license": "CC0-1.0" + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decode-named-character-reference": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.3.0.tgz", + "integrity": "sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "character-entities": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/deep-object-diff": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/deep-object-diff/-/deep-object-diff-1.1.9.tgz", + "integrity": "sha512-Rn+RuwkmkDwCi2/oXOFS9Gsr5lJZu/yTGpK7wAaAIE75CC+LCGEZHpY6VQJa/RoJcrmaA/docWJZvYohlNkWPA==", + "dev": true, + "license": "MIT" + }, + "node_modules/defaults": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz", + "integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "clone": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/defu": { + "version": "6.1.7", + "resolved": "https://registry.npmjs.org/defu/-/defu-6.1.7.tgz", + "integrity": "sha512-7z22QmUWiQ/2d0KkdYmANbRUVABpZ9SNYyH5vx6PZ+nE5bcC0l7uFvEfHlyld/HcGBFTL536ClDt3DEcSlEJAQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/destr": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/destr/-/destr-2.0.5.tgz", + "integrity": "sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==", + "dev": true, + "license": "MIT" + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/devalue": { + "version": "5.9.2", + "resolved": "https://registry.npmjs.org/devalue/-/devalue-5.9.2.tgz", + "integrity": "sha512-po4PAY5c53tw5XMocSnf8A/5OHhbbUftpr93aEN6BBoAdntUmK7vu7wOATqvt7cXO7m1Cl4gMVn6p7n6n4mj0w==", + "dev": true, + "license": "MIT" + }, + "node_modules/devlop": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz", + "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==", + "dev": true, + "license": "MIT", + "dependencies": { + "dequal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/diff": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.2.tgz", + "integrity": "sha512-vtcDfH3TOjP8UekytvnHH1o1P4FcUdt4eQ1Y+Abap1tk/OB2MWQvcwS2ClCd1zuIhc3JKOx6p3kod8Vfys3E+A==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/direction": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/direction/-/direction-2.0.1.tgz", + "integrity": "sha512-9S6m9Sukh1cZNknO1CWAr2QAWsbKLafQiyM5gZ7VgXHeuaoUwffKN4q6NC4A/Mf9iiPlOXQEKW/Mv/mh9/3YFA==", + "dev": true, + "license": "MIT", + "bin": { + "direction": "cli.js" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/dom-serializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "dev": true, + "license": "MIT", + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/dom-serializer/node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "BSD-2-Clause" + }, + "node_modules/domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "domelementtype": "^2.3.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/domutils": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz", + "integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, + "node_modules/dset": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/dset/-/dset-3.1.4.tgz", + "integrity": "sha512-2QF/g9/zTaPDc3BjNcVTGoBbXBgYfMTTceLaYcFJ/W9kggFUkhxD/hMEeuLKbugyef9SqAx8cpgwlIP/jinUTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/emoji-regex": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", + "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", + "dev": true, + "license": "MIT" + }, + "node_modules/entities": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz", + "integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/es-module-lexer": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.3.2.tgz", + "integrity": "sha512-poHGpORABojJJucnV9KbOavETW8lBVnphkW77ER5/BQ5Fz7oXSoCNek7IH3vR5nRjdsEz926ibFYX8KtLQmdyw==", + "dev": true, + "license": "MIT" + }, + "node_modules/esast-util-from-estree": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/esast-util-from-estree/-/esast-util-from-estree-2.0.0.tgz", + "integrity": "sha512-4CyanoAudUSBAn5K13H4JhsMH6L9ZP7XbLVe/dKybkxMO7eDyLsT8UHl9TRNrU2Gr9nz+FovfSIjuXWJ81uVwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "devlop": "^1.0.0", + "estree-util-visit": "^2.0.0", + "unist-util-position-from-estree": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/esast-util-from-js": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/esast-util-from-js/-/esast-util-from-js-2.0.1.tgz", + "integrity": "sha512-8Ja+rNJ0Lt56Pcf3TAmpBZjmx8ZcK5Ts4cAzIOjsjevg9oSXJnl6SUQ2EevU8tv3h6ZLWmoKL5H4fgWvdvfETw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "acorn": "^8.0.0", + "esast-util-from-estree": "^2.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/esbuild": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.2.tgz", + "integrity": "sha512-HKVLS8dvII+xoKW9kmqxbRKrnWEXfJJr/FZhhJmiqIB0e053QNYFqOBouTMO/k5sID4MvCiUCvv8b9M4h32wIA==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.28.2", + "@esbuild/android-arm": "0.28.2", + "@esbuild/android-arm64": "0.28.2", + "@esbuild/android-x64": "0.28.2", + "@esbuild/darwin-arm64": "0.28.2", + "@esbuild/darwin-x64": "0.28.2", + "@esbuild/freebsd-arm64": "0.28.2", + "@esbuild/freebsd-x64": "0.28.2", + "@esbuild/linux-arm": "0.28.2", + "@esbuild/linux-arm64": "0.28.2", + "@esbuild/linux-ia32": "0.28.2", + "@esbuild/linux-loong64": "0.28.2", + "@esbuild/linux-mips64el": "0.28.2", + "@esbuild/linux-ppc64": "0.28.2", + "@esbuild/linux-riscv64": "0.28.2", + "@esbuild/linux-s390x": "0.28.2", + "@esbuild/linux-x64": "0.28.2", + "@esbuild/netbsd-arm64": "0.28.2", + "@esbuild/netbsd-x64": "0.28.2", + "@esbuild/openbsd-arm64": "0.28.2", + "@esbuild/openbsd-x64": "0.28.2", + "@esbuild/openharmony-arm64": "0.28.2", + "@esbuild/sunos-x64": "0.28.2", + "@esbuild/win32-arm64": "0.28.2", + "@esbuild/win32-ia32": "0.28.2", + "@esbuild/win32-x64": "0.28.2" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "10.9.1", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-10.9.1.tgz", + "integrity": "sha512-9VaAkDURekixUQJy0oJYl2DcN6oKMfxay7XzaGYAWQwsb6qfKf+x76R2k1L8kb1boc+FyCAaTA9GmiKaaiaF+A==", + "dev": true, + "license": "MIT", + "workspaces": [ + "packages/*" + ], + "dependencies": { + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.2", + "@eslint/config-array": "^0.23.5", + "@eslint/config-helpers": "^0.7.0", + "@eslint/core": "^1.2.1", + "@eslint/plugin-kit": "^0.7.2", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "ajv": "^6.14.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^9.1.2", + "eslint-visitor-keys": "^5.0.1", + "espree": "^11.2.0", + "esquery": "^1.7.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "minimatch": "^10.2.5", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-scope": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-9.1.2.tgz", + "integrity": "sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@types/esrecurse": "^4.3.1", + "@types/estree": "^1.0.8", + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree": { + "version": "11.2.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-11.2.0.tgz", + "integrity": "sha512-7p3DrVEIopW1B1avAGLuCSh1jubc01H2JHc8B4qqGblmg5gI9yumBgACjWo4JlIc04ufug4xJ3SQI8HkS/Rgzw==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.16.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^5.0.1" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", + "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estree-util-attach-comments": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/estree-util-attach-comments/-/estree-util-attach-comments-3.0.0.tgz", + "integrity": "sha512-cKUwm/HUcTDsYh/9FgnuFqpfquUbwIqwKM26BVCGDPVgvaCl/nDCCjUfiLlx6lsEZ3Z4RFxNbOQ60pkaEwFxGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/estree-util-build-jsx": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/estree-util-build-jsx/-/estree-util-build-jsx-3.0.1.tgz", + "integrity": "sha512-8U5eiL6BTrPxp/CHbs2yMgP8ftMhR5ww1eIKoWRMlqvltHF8fZn5LRDvTKuxD3DUn+shRbLGqXemcP51oFCsGQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "devlop": "^1.0.0", + "estree-util-is-identifier-name": "^3.0.0", + "estree-walker": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/estree-util-is-identifier-name": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/estree-util-is-identifier-name/-/estree-util-is-identifier-name-3.0.0.tgz", + "integrity": "sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg==", + "dev": true, + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/estree-util-scope": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/estree-util-scope/-/estree-util-scope-1.0.1.tgz", + "integrity": "sha512-B0np3dcdxqILX5e9nEi5/Fr4K7gL4oYFVPV1zRa2e9wRCbQoZZNWOZFYyoInvXUPJXBXjss+QXlWLJChDEHDkA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "devlop": "^1.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/estree-util-to-js": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/estree-util-to-js/-/estree-util-to-js-2.0.0.tgz", + "integrity": "sha512-WDF+xj5rRWmD5tj6bIqRi6CkLIXbbNQUcxQHzGysQzvHmdYG2G7p/Tf0J0gpxGgkeMZNTIjT/AoSvC9Xehcgdg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "astring": "^1.8.0", + "source-map": "^0.7.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/estree-util-visit": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/estree-util-visit/-/estree-util-visit-2.0.0.tgz", + "integrity": "sha512-m5KgiH85xAhhW8Wta0vShLcUvOsh3LLPI2YVwcbio1l7E09NTLL1EyMZFM1OyWowoH0skScNbhOPl4kcBgzTww==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eventemitter3": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.4.tgz", + "integrity": "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==", + "dev": true, + "license": "MIT" + }, + "node_modules/expressive-code": { + "version": "0.44.2", + "resolved": "https://registry.npmjs.org/expressive-code/-/expressive-code-0.44.2.tgz", + "integrity": "sha512-MLNghkvhyFgVW4oCD5DxBuYnnTXIf0PygzGmfT6OZeOOYWh/D4sH1j931biElkA5RG/J9wY/iuSMGAPF5vzHOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@expressive-code/core": "^0.44.2", + "@expressive-code/plugin-frames": "^0.44.2", + "@expressive-code/plugin-shiki": "^0.44.2", + "@expressive-code/plugin-text-markers": "^0.44.2" + } + }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-string-truncated-width": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/fast-string-truncated-width/-/fast-string-truncated-width-3.0.3.tgz", + "integrity": "sha512-0jjjIEL6+0jag3l2XWWizO64/aZVtpiGE3t0Zgqxv0DPuxiMjvB3M24fCyhZUO4KomJQPj3LTSUnDP3GpdwC0g==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-string-width": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/fast-string-width/-/fast-string-width-3.0.2.tgz", + "integrity": "sha512-gX8LrtNEI5hq8DVUfRQMbr5lpaS4nMIWV+7XEbXk2b8kiQIizgnlr12B4dA3ZEx3308ze0O4Q1R+cHts8kyUJg==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-string-truncated-width": "^3.0.2" + } + }, + "node_modules/fast-wrap-ansi": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/fast-wrap-ansi/-/fast-wrap-ansi-0.2.2.tgz", + "integrity": "sha512-7F2Fl+TjRSenLqlU3UjSH0iyqopqoZIu7eZVpEirP2g1GtWa2G/ecEmBdgz31+Mxr+ELclgg6sokpSFIQiZ02Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-string-width": "^3.0.2" + } + }, + "node_modules/fd-package-json": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fd-package-json/-/fd-package-json-2.0.0.tgz", + "integrity": "sha512-jKmm9YtsNXN789RS/0mSzOC1NUq9mkVd65vbSSVsKdjGvYXBuE4oWe2QOEoFeRmJg+lPuZxpmrfFclNhoRMneQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "walk-up-path": "^4.0.0" + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/find-proc": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/find-proc/-/find-proc-0.1.0.tgz", + "integrity": "sha512-OaOpEYv2PiQ7SQ5LIrl+deA1XaWcxEjnpM6VuWXTUvn+teIXxeFTLDmu18/zDQpFmHN4o3oDBX+BT0AGwEhemg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.4.4", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.4.tgz", + "integrity": "sha512-5+ybhBZANEJxaH3X5evAFatUxLfEHSr7n6kYJ+1Qd0mUqr4eu9gIf6GDbWHf8RJijHrjjO8G+la14SlL2SeS1Q==", + "dev": true, + "license": "ISC" + }, + "node_modules/flattie": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/flattie/-/flattie-1.1.1.tgz", + "integrity": "sha512-9UbaD6XdAL97+k/n+N7JwX46K/M6Zc6KcFYskrYL8wbBV/Uyk0CTAMY0VT+qiK5PM7AIc9aTWYtq65U7T+aCNQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/fontace": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/fontace/-/fontace-0.4.1.tgz", + "integrity": "sha512-lDMvbAzSnHmbYMTEld5qdtvNH2/pWpICOqpean9IgC7vUbUJc3k+k5Dokp85CegamqQpFbXf0rAVkbzpyTA8aw==", + "dev": true, + "license": "MIT", + "dependencies": { + "fontkitten": "^1.0.2" + } + }, + "node_modules/fontkitten": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/fontkitten/-/fontkitten-1.0.3.tgz", + "integrity": "sha512-Wp1zXWPVUPBmfoa3Cqc9ctaKuzKAV6uLstRqlR56kSjplf5uAce+qeyYym7F+PHbGTk+tCEdkCW6RD7DX/gBZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tiny-inflate": "^1.0.3" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "dev": true, + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/formatly": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/formatly/-/formatly-0.7.0.tgz", + "integrity": "sha512-7CXJtIIA0zy/u12StsYk25qVKxvdLA2ep2sTNxK3ov0mGNIIDqIvAXDSgTnAfDJFsPfWjuz0WjfYSdpvnLA5Tg==", + "dev": true, + "license": "MIT", + "dependencies": { + "fd-package-json": "^2.0.0", + "package-manager-detector": "^1.8.0" + }, + "bin": { + "formatly": "bin/index.mjs" + }, + "engines": { + "node": ">=18.3.0" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-east-asian-width": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.6.0.tgz", + "integrity": "sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-tsconfig": { + "version": "5.0.0-beta.4", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-5.0.0-beta.4.tgz", + "integrity": "sha512-7nF7C9fIPFEMHgEMEfgIlO9wDdZ8CyHw27rWciFZfHvHDReIiPhsYuzPRXsfvBCqFy1l8RRyyWV7QLM+ZhUJsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "engines": { + "node": ">=20.20.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, + "node_modules/github-slugger": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/github-slugger/-/github-slugger-2.0.0.tgz", + "integrity": "sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw==", + "dev": true, + "license": "ISC" + }, + "node_modules/glob": { + "version": "13.0.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.6.tgz", + "integrity": "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "minimatch": "^10.2.2", + "minipass": "^7.1.3", + "path-scurry": "^2.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/globalyzer": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/globalyzer/-/globalyzer-0.1.0.tgz", + "integrity": "sha512-40oNTM9UfG6aBmuKxk/giHn5nQ8RVz/SS4Ir6zgzOv9/qC3kKZ9v4etGTcJbEl/NyVQH7FGU7d+X1egr57Md2Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/globrex": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/globrex/-/globrex-0.1.2.tgz", + "integrity": "sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==", + "dev": true, + "license": "MIT" + }, + "node_modules/h3": { + "version": "1.15.11", + "resolved": "https://registry.npmjs.org/h3/-/h3-1.15.11.tgz", + "integrity": "sha512-L3THSe2MPeBwgIZVSH5zLdBBU90TOxarvhK9d04IDY2AmVS8j2Jz2LIWtwsGOU3lu2I5jCN7FNvVfY2+XyF+mg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cookie-es": "^1.2.3", + "crossws": "^0.3.5", + "defu": "^6.1.6", + "destr": "^2.0.5", + "iron-webcrypto": "^1.2.1", + "node-mock-http": "^1.0.4", + "radix3": "^1.1.2", + "ufo": "^1.6.3", + "uncrypto": "^0.1.3" + } + }, + "node_modules/happy-dom": { + "version": "20.13.2", + "resolved": "https://registry.npmjs.org/happy-dom/-/happy-dom-20.13.2.tgz", + "integrity": "sha512-VC9HoEaT3Jf3K/1g/GjMYGAZCCg+ULgGVcXJlAeSzdTj8mIoPJRp14KVclP5gpUcF/kauuaVB2ri2/WxXbl4vg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": ">=20.0.0", + "@types/whatwg-mimetype": "^3.0.2", + "@types/ws": "^8.18.1", + "buffer-image-size": "^0.6.4", + "entities": "^7.0.1", + "whatwg-mimetype": "^3.0.0", + "ws": "^8.21.0" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/hast-util-embedded": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-embedded/-/hast-util-embedded-3.0.0.tgz", + "integrity": "sha512-naH8sld4Pe2ep03qqULEtvYr7EjrLK2QHY8KJR6RJkTUjPGObe1vnx585uzem2hGra+s1q08DZZpfgDVYRbaXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "hast-util-is-element": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-format": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/hast-util-format/-/hast-util-format-1.1.0.tgz", + "integrity": "sha512-yY1UDz6bC9rDvCWHpx12aIBGRG7krurX0p0Fm6pT547LwDIZZiNr8a+IHDogorAdreULSEzP82Nlv5SZkHZcjA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "hast-util-embedded": "^3.0.0", + "hast-util-minify-whitespace": "^1.0.0", + "hast-util-phrasing": "^3.0.0", + "hast-util-whitespace": "^3.0.0", + "html-whitespace-sensitive-tag-names": "^3.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-from-html": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/hast-util-from-html/-/hast-util-from-html-2.0.3.tgz", + "integrity": "sha512-CUSRHXyKjzHov8yKsQjGOElXy/3EKpyX56ELnkHH34vDVw1N1XSQ1ZcAvTyAPtGqLTuKP/uxM+aLkSPqF/EtMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "devlop": "^1.1.0", + "hast-util-from-parse5": "^8.0.0", + "parse5": "^7.0.0", + "vfile": "^6.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-from-parse5": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/hast-util-from-parse5/-/hast-util-from-parse5-8.0.3.tgz", + "integrity": "sha512-3kxEVkEKt0zvcZ3hCRYI8rqrgwtlIOFMWkbclACvjlDw8Li9S2hk/d51OI0nr/gIpdMHNepwgOKqZ/sy0Clpyg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "devlop": "^1.0.0", + "hastscript": "^9.0.0", + "property-information": "^7.0.0", + "vfile": "^6.0.0", + "vfile-location": "^5.0.0", + "web-namespaces": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-has-property": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-has-property/-/hast-util-has-property-3.0.0.tgz", + "integrity": "sha512-MNilsvEKLFpV604hwfhVStK0usFY/QmM5zX16bo7EjnAEGofr5YyI37kzopBlZJkHD4t887i+q/C8/tr5Q94cA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-is-body-ok-link": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/hast-util-is-body-ok-link/-/hast-util-is-body-ok-link-3.0.1.tgz", + "integrity": "sha512-0qpnzOBLztXHbHQenVB8uNuxTnm/QBFUOmdOSsEn7GnBtyY07+ENTWVFBAnXd/zEgd9/SUG3lRY7hSIBWRgGpQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-is-element": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-is-element/-/hast-util-is-element-3.0.0.tgz", + "integrity": "sha512-Val9mnv2IWpLbNPqc/pUem+a7Ipj2aHacCwgNfTiK0vJKl0LF+4Ba4+v1oPHFpf3bLYmreq0/l3Gud9S5OH42g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-minify-whitespace": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/hast-util-minify-whitespace/-/hast-util-minify-whitespace-1.0.1.tgz", + "integrity": "sha512-L96fPOVpnclQE0xzdWb/D12VT5FabA7SnZOUMtL1DbXmYiHJMXZvFkIZfiMmTCNJHUeO2K9UYNXoVyfz+QHuOw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "hast-util-embedded": "^3.0.0", + "hast-util-is-element": "^3.0.0", + "hast-util-whitespace": "^3.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-parse-selector": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-4.0.0.tgz", + "integrity": "sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-phrasing": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/hast-util-phrasing/-/hast-util-phrasing-3.0.1.tgz", + "integrity": "sha512-6h60VfI3uBQUxHqTyMymMZnEbNl1XmEGtOxxKYL7stY2o601COo62AWAYBQR9lZbYXYSBoxag8UpPRXK+9fqSQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "hast-util-embedded": "^3.0.0", + "hast-util-has-property": "^3.0.0", + "hast-util-is-body-ok-link": "^3.0.0", + "hast-util-is-element": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-raw": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/hast-util-raw/-/hast-util-raw-9.1.0.tgz", + "integrity": "sha512-Y8/SBAHkZGoNkpzqqfCldijcuUKh7/su31kEBp67cFY09Wy0mTRgtsLYsiIxMJxlu0f6AA5SUTbDR8K0rxnbUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "@ungap/structured-clone": "^1.0.0", + "hast-util-from-parse5": "^8.0.0", + "hast-util-to-parse5": "^8.0.0", + "html-void-elements": "^3.0.0", + "mdast-util-to-hast": "^13.0.0", + "parse5": "^7.0.0", + "unist-util-position": "^5.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0", + "web-namespaces": "^2.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-select": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/hast-util-select/-/hast-util-select-6.0.4.tgz", + "integrity": "sha512-RqGS1ZgI0MwxLaKLDxjprynNzINEkRHY2i8ln4DDjgv9ZhcYVIHN9rlpiYsqtFwrgpYU361SyWDQcGNIBVu3lw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "bcp-47-match": "^2.0.0", + "comma-separated-tokens": "^2.0.0", + "css-selector-parser": "^3.0.0", + "devlop": "^1.0.0", + "direction": "^2.0.0", + "hast-util-has-property": "^3.0.0", + "hast-util-to-string": "^3.0.0", + "hast-util-whitespace": "^3.0.0", + "nth-check": "^2.0.0", + "property-information": "^7.0.0", + "space-separated-tokens": "^2.0.0", + "unist-util-visit": "^5.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-to-estree": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/hast-util-to-estree/-/hast-util-to-estree-3.1.3.tgz", + "integrity": "sha512-48+B/rJWAp0jamNbAAf9M7Uf//UVqAoMmgXhBdxTDJLGKY+LRnZ99qcG+Qjl5HfMpYNzS5v4EAwVEF34LeAj7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "comma-separated-tokens": "^2.0.0", + "devlop": "^1.0.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.0", + "mdast-util-mdx-jsx": "^3.0.0", + "mdast-util-mdxjs-esm": "^2.0.0", + "property-information": "^7.0.0", + "space-separated-tokens": "^2.0.0", + "style-to-js": "^1.0.0", + "unist-util-position": "^5.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-to-html": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/hast-util-to-html/-/hast-util-to-html-9.0.5.tgz", + "integrity": "sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "ccount": "^2.0.0", + "comma-separated-tokens": "^2.0.0", + "hast-util-whitespace": "^3.0.0", + "html-void-elements": "^3.0.0", + "mdast-util-to-hast": "^13.0.0", + "property-information": "^7.0.0", + "space-separated-tokens": "^2.0.0", + "stringify-entities": "^4.0.0", + "zwitch": "^2.0.4" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-to-jsx-runtime": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/hast-util-to-jsx-runtime/-/hast-util-to-jsx-runtime-2.3.6.tgz", + "integrity": "sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "comma-separated-tokens": "^2.0.0", + "devlop": "^1.0.0", + "estree-util-is-identifier-name": "^3.0.0", + "hast-util-whitespace": "^3.0.0", + "mdast-util-mdx-expression": "^2.0.0", + "mdast-util-mdx-jsx": "^3.0.0", + "mdast-util-mdxjs-esm": "^2.0.0", + "property-information": "^7.0.0", + "space-separated-tokens": "^2.0.0", + "style-to-js": "^1.0.0", + "unist-util-position": "^5.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-to-parse5": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/hast-util-to-parse5/-/hast-util-to-parse5-8.0.1.tgz", + "integrity": "sha512-MlWT6Pjt4CG9lFCjiz4BH7l9wmrMkfkJYCxFwKQic8+RTZgWPuWxwAfjJElsXkex7DJjfSJsQIt931ilUgmwdA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "comma-separated-tokens": "^2.0.0", + "devlop": "^1.0.0", + "property-information": "^7.0.0", + "space-separated-tokens": "^2.0.0", + "web-namespaces": "^2.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-to-string": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/hast-util-to-string/-/hast-util-to-string-3.0.1.tgz", + "integrity": "sha512-XelQVTDWvqcl3axRfI0xSeoVKzyIFPwsAGSLIsKdJKQMXDYJS4WYrBNF/8J7RdhIcFI2BOHgAifggsvsxp/3+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-to-text": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/hast-util-to-text/-/hast-util-to-text-4.0.2.tgz", + "integrity": "sha512-KK6y/BN8lbaq654j7JgBydev7wuNMcID54lkRav1P0CaE1e47P72AWWPiGKXTJU271ooYzcvTAn/Zt0REnvc7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "hast-util-is-element": "^3.0.0", + "unist-util-find-after": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-whitespace": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz", + "integrity": "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hastscript": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/hastscript/-/hastscript-9.0.1.tgz", + "integrity": "sha512-g7df9rMFX/SPi34tyGCyUBREQoKkapwdY/T04Qn9TDWfHhAYt4/I0gMVirzK5wEzeUqIjEB+LXC/ypb7Aqno5w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "comma-separated-tokens": "^2.0.0", + "hast-util-parse-selector": "^4.0.0", + "property-information": "^7.0.0", + "space-separated-tokens": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true, + "license": "MIT" + }, + "node_modules/html-void-elements": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-3.0.0.tgz", + "integrity": "sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/html-whitespace-sensitive-tag-names": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/html-whitespace-sensitive-tag-names/-/html-whitespace-sensitive-tag-names-3.0.1.tgz", + "integrity": "sha512-q+310vW8zmymYHALr1da4HyXUQ0zgiIwIicEfotYPWGN0OJVEN/58IJ3A4GBYcEq3LGAZqKb+ugvP0GNB9CEAA==", + "dev": true, + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/http-cache-semantics": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz", + "integrity": "sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/i18next": { + "version": "26.4.1", + "resolved": "https://registry.npmjs.org/i18next/-/i18next-26.4.1.tgz", + "integrity": "sha512-9YbX5E6gd1H+yaOSX3izCsPj5iWXyH7X4oC+iuHHJJw8AeHglzOK5SJf+1CHxaYKYigcea+8jvzSxqYx46YvyA==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://www.locize.com/i18next" + }, + { + "type": "individual", + "url": "https://www.i18next.com/how-to/faq#i18next-is-awesome.-how-can-i-support-the-project" + }, + { + "type": "individual", + "url": "https://www.locize.com" + } + ], + "license": "MIT", + "peerDependencies": { + "typescript": "^5 || ^6 || ^7" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/inline-style-parser": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.2.7.tgz", + "integrity": "sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA==", + "dev": true, + "license": "MIT" + }, + "node_modules/iron-webcrypto": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/iron-webcrypto/-/iron-webcrypto-1.2.1.tgz", + "integrity": "sha512-feOM6FaSr6rEABp/eDfVseKyTMDt+KGpeB35SkVn9Tyn0CqvVsY3EwI0v5i8nMHyJnzCIQf7nsy3p41TPkJZhg==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/brc-dd" + } + }, + "node_modules/is-alphabetical": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-2.0.1.tgz", + "integrity": "sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-alphanumerical": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-2.0.1.tgz", + "integrity": "sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-alphabetical": "^2.0.0", + "is-decimal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-decimal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-2.0.1.tgz", + "integrity": "sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-docker": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-4.0.0.tgz", + "integrity": "sha512-LHE+wROyG/Y/0ZnbktRCoTix2c1RhgWaZraMZ8o1Q7zCh0VSrICJQO5oqIIISrcSBtrXv0o233w1IYwsWCjTzA==", + "dev": true, + "license": "MIT", + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-hexadecimal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-2.0.1.tgz", + "integrity": "sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-interactive": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", + "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-plain-obj": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", + "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/istanbul-lib-coverage": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", + "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-reports": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz", + "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jiti": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.7.0.tgz", + "integrity": "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==", + "dev": true, + "license": "MIT", + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, + "node_modules/js-yaml": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.2.tgz", + "integrity": "sha512-SFNOvSJ+Dgf/9An904Yx+CgSlIPCkIpao4qo51lpee25TIRejdH3rhR4EZMGoNx3/TP3O+wzWuiTFl4sqbltzA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/nodeca" + } + ], + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/jsonc-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.3.1.tgz", + "integrity": "sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/kizu": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/kizu/-/kizu-5.1.1.tgz", + "integrity": "sha512-mE8guHFglQsOLRvQBQsVSrwUiG8bxhBlqxF+daJzVRf7xAgRm1gp9f6kVWJOd9qYSHpzjLu9mBEikb/ngaKbmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "commander": "14.0.0", + "deep-object-diff": "^1.1.0", + "diff": "^5.0.0", + "kleur": "^4.1.4", + "ora": "^5.4.1", + "serialize-error": "^8.1.0", + "tiny-glob": "^0.2.9" + }, + "bin": { + "kizu": "bin/entrypoint.js" + }, + "peerDependencies": { + "tsx": "^4.20.5" + } + }, + "node_modules/kleur": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", + "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/klona": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/klona/-/klona-2.0.6.tgz", + "integrity": "sha512-dhG34DXATL5hSxJbIexCft8FChFXtmskoZYnoPWjXQuebWYCNkVeV3KkGegCK9CP1oswI/vQibS2GY7Em/sJJA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/knip": { + "version": "6.34.0", + "resolved": "https://registry.npmjs.org/knip/-/knip-6.34.0.tgz", + "integrity": "sha512-bbHIrnGspYwe4EBPjjx+lvkUor0F2qfKQc5BPzPI4SOAImYA+k2ueVpIcF7d/W1LEVT7XoJUPt6zELeGZhXBgA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/webpro" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/knip" + } + ], + "license": "ISC", + "dependencies": { + "fdir": "^6.5.0", + "formatly": "^0.7.0", + "get-tsconfig": "4.14.3", + "jiti": "^2.7.0", + "oxc-parser": "^0.147.0", + "oxc-resolver": "11.24.2", + "picomatch": "^4.0.7", + "smol-toml": "^1.8.0", + "strip-json-comments": "5.0.3", + "tinyglobby": "^0.2.17", + "unbash": "^4.0.10", + "yaml": "^2.9.0", + "zod": "^4.4.3" + }, + "bin": { + "knip": "bin/knip.js", + "knip-bun": "bin/knip-bun.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/knip/node_modules/get-tsconfig": { + "version": "4.14.3", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.14.3.tgz", + "integrity": "sha512-++QEw4DIY7WGoukz+/+A/8dGYPT9l9yIadnmSgZ8Rjr3YVSVDipQSO9CdnJo9ePqFqUUqh+wk9uIaoiAwsiPkA==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/lightningcss": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.33.0.tgz", + "integrity": "sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA==", + "dev": true, + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.33.0", + "lightningcss-darwin-arm64": "1.33.0", + "lightningcss-darwin-x64": "1.33.0", + "lightningcss-freebsd-x64": "1.33.0", + "lightningcss-linux-arm-gnueabihf": "1.33.0", + "lightningcss-linux-arm64-gnu": "1.33.0", + "lightningcss-linux-arm64-musl": "1.33.0", + "lightningcss-linux-x64-gnu": "1.33.0", + "lightningcss-linux-x64-musl": "1.33.0", + "lightningcss-win32-arm64-msvc": "1.33.0", + "lightningcss-win32-x64-msvc": "1.33.0" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.33.0.tgz", + "integrity": "sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.33.0.tgz", + "integrity": "sha512-Sciaz8eenNTKn9b3t7+xr0ipTp9YxKQY4npwQ3mrRuL0BAVHBLyZxofhaKBAVtzmtRZ/zTyo0/to4B1uWG/Djg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.33.0.tgz", + "integrity": "sha512-Z5UPAxzrjlWNNyGy6i65cJzzvgJ5D3T6wMvs+gWpY9d7qRhANrxqAp6LhxIgZhWEw18RfJTGcRxjuLIBr+m8XQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.33.0.tgz", + "integrity": "sha512-QQM/Ti/hQajJwCY+RiWuCZ9sdtI/XQk7nDK5vC8kkdwixezOlDgvDx7+RT+QjK6FcFT4MpsuoBnHIo/O3StRRg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.33.0.tgz", + "integrity": "sha512-N7FVBe6iS24MlM6R/4RBTxGhQheZGs7tiQ9U32UtF75NzP5Q7xWPRqLBCKxlRQRk3rY1jCIPLzx7WzOhuUIRLQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.33.0.tgz", + "integrity": "sha512-j2v/itmy4HlNxlc6voKXYgBqNi0Ng2LShg4z7GufpEgs05P+2suBVyi9I6YHq5uoVFx9ETin3eCEhLVyXGQnKg==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.33.0.tgz", + "integrity": "sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.33.0.tgz", + "integrity": "sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.33.0.tgz", + "integrity": "sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.33.0.tgz", + "integrity": "sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.33.0.tgz", + "integrity": "sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/longest-streak": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz", + "integrity": "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/lru-cache": { + "version": "11.5.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.2.tgz", + "integrity": "sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/magic-string": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-1.2.3.tgz", + "integrity": "sha512-Bpb0W2TbLKOZ7vJnOUnVRGq3WL2p+ISV29M6hYPL1AFCpyKZpdr5ytiXoTSSxRVhg8YW7f65+6gbG8WG6PCa/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/magicast": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.5.4.tgz", + "integrity": "sha512-llBEhWm1SacoRwgHUoQJYtwp4PBLF4faQi5TCpIGyGs9n4y5+juI0tDgyKIfpqxckRHaHzouUEph3THklWh03w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7", + "source-map-js": "^1.2.1" + } + }, + "node_modules/make-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/markdown-extensions": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/markdown-extensions/-/markdown-extensions-2.0.0.tgz", + "integrity": "sha512-o5vL7aDWatOTX8LzaS1WMoaoxIiLRQJuIKKe2wAw6IeULDHaqbiqiggmx+pKvZDb1Sj+pE46Sn1T7lCqfFtg1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/markdown-table": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-3.0.4.tgz", + "integrity": "sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/mdast-util-definitions": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-definitions/-/mdast-util-definitions-6.0.0.tgz", + "integrity": "sha512-scTllyX6pnYNZH/AIp/0ePz6s4cZtARxImwoPJ7kS42n+MnVsI4XbnG6d4ibehRIldYMWM2LD7ImQblVhUejVQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "unist-util-visit": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-directive": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-directive/-/mdast-util-directive-3.1.0.tgz", + "integrity": "sha512-I3fNFt+DHmpWCYAT7quoM6lHf9wuqtI+oCOfvILnoicNIqjh5E3dEJWiXuYME2gNe8vl1iMQwyUHa7bgFmak6Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "ccount": "^2.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0", + "parse-entities": "^4.0.0", + "stringify-entities": "^4.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-find-and-replace": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mdast-util-find-and-replace/-/mdast-util-find-and-replace-3.0.2.tgz", + "integrity": "sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "escape-string-regexp": "^5.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-find-and-replace/node_modules/escape-string-regexp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", + "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mdast-util-from-markdown": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.3.tgz", + "integrity": "sha512-W4mAWTvSlKvf8L6J+VN9yLSqQ9AOAAvHuoDAmPkz4dHf553m5gVj2ejadHJhoJmcmxEnOv6Pa8XJhpxE93kb8Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "mdast-util-to-string": "^4.0.0", + "micromark": "^4.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-decode-string": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm/-/mdast-util-gfm-3.1.0.tgz", + "integrity": "sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-gfm-autolink-literal": "^2.0.0", + "mdast-util-gfm-footnote": "^2.0.0", + "mdast-util-gfm-strikethrough": "^2.0.0", + "mdast-util-gfm-table": "^2.0.0", + "mdast-util-gfm-task-list-item": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-autolink-literal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-autolink-literal/-/mdast-util-gfm-autolink-literal-2.0.1.tgz", + "integrity": "sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "ccount": "^2.0.0", + "devlop": "^1.0.0", + "mdast-util-find-and-replace": "^3.0.0", + "micromark-util-character": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-footnote": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-footnote/-/mdast-util-gfm-footnote-2.1.0.tgz", + "integrity": "sha512-sqpDWlsHn7Ac9GNZQMeUzPQSMzR6Wv0WKRNvQRg0KqHh02fpTz69Qc1QSseNX29bhz1ROIyNyxExfawVKTm1GQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "devlop": "^1.1.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-strikethrough": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-strikethrough/-/mdast-util-gfm-strikethrough-2.0.0.tgz", + "integrity": "sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-table": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-table/-/mdast-util-gfm-table-2.0.0.tgz", + "integrity": "sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "markdown-table": "^3.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-task-list-item": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-task-list-item/-/mdast-util-gfm-task-list-item-2.0.0.tgz", + "integrity": "sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdx": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-mdx/-/mdast-util-mdx-3.0.0.tgz", + "integrity": "sha512-JfbYLAW7XnYTTbUsmpu0kdBUVe+yKVJZBItEjwyYJiDJuZ9w4eeaqks4HQO+R7objWgS2ymV60GYpI14Ug554w==", + "dev": true, + "license": "MIT", + "dependencies": { + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-mdx-expression": "^2.0.0", + "mdast-util-mdx-jsx": "^3.0.0", + "mdast-util-mdxjs-esm": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdx-expression": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-mdx-expression/-/mdast-util-mdx-expression-2.0.1.tgz", + "integrity": "sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdx-jsx": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/mdast-util-mdx-jsx/-/mdast-util-mdx-jsx-3.2.0.tgz", + "integrity": "sha512-lj/z8v0r6ZtsN/cGNNtemmmfoLAFZnjMbNyLzBafjzikOM+glrjNHPlf6lQDOTccj9n5b0PPihEBbhneMyGs1Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "ccount": "^2.0.0", + "devlop": "^1.1.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0", + "parse-entities": "^4.0.0", + "stringify-entities": "^4.0.0", + "unist-util-stringify-position": "^4.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdxjs-esm": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-mdxjs-esm/-/mdast-util-mdxjs-esm-2.0.1.tgz", + "integrity": "sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-phrasing": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-phrasing/-/mdast-util-phrasing-4.1.0.tgz", + "integrity": "sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-hast": { + "version": "13.2.1", + "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.1.tgz", + "integrity": "sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "@ungap/structured-clone": "^1.0.0", + "devlop": "^1.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "trim-lines": "^3.0.0", + "unist-util-position": "^5.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-markdown": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-2.1.2.tgz", + "integrity": "sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "longest-streak": "^3.0.0", + "mdast-util-phrasing": "^4.0.0", + "mdast-util-to-string": "^4.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-decode-string": "^2.0.0", + "unist-util-visit": "^5.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-4.0.0.tgz", + "integrity": "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdn-data": { + "version": "2.27.1", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.27.1.tgz", + "integrity": "sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==", + "dev": true, + "license": "CC0-1.0" + }, + "node_modules/micromark": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.2.tgz", + "integrity": "sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "@types/debug": "^4.0.0", + "debug": "^4.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-core-commonmark": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-2.0.3.tgz", + "integrity": "sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-factory-destination": "^2.0.0", + "micromark-factory-label": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-factory-title": "^2.0.0", + "micromark-factory-whitespace": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-html-tag-name": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-extension-directive": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-directive/-/micromark-extension-directive-4.0.0.tgz", + "integrity": "sha512-/C2nqVmXXmiseSSuCdItCMho7ybwwop6RrrRPk0KbOHW21JKoCldC+8rFOaundDoRBUWBnJJcxeA/Kvi34WQXg==", + "dev": true, + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-factory-whitespace": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "parse-entities": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm/-/micromark-extension-gfm-3.0.0.tgz", + "integrity": "sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "micromark-extension-gfm-autolink-literal": "^2.0.0", + "micromark-extension-gfm-footnote": "^2.0.0", + "micromark-extension-gfm-strikethrough": "^2.0.0", + "micromark-extension-gfm-table": "^2.0.0", + "micromark-extension-gfm-tagfilter": "^2.0.0", + "micromark-extension-gfm-task-list-item": "^2.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-autolink-literal": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-2.1.0.tgz", + "integrity": "sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==", + "dev": true, + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-footnote": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-footnote/-/micromark-extension-gfm-footnote-2.1.0.tgz", + "integrity": "sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw==", + "dev": true, + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-strikethrough": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-strikethrough/-/micromark-extension-gfm-strikethrough-2.1.0.tgz", + "integrity": "sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw==", + "dev": true, + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-table": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-table/-/micromark-extension-gfm-table-2.1.1.tgz", + "integrity": "sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg==", + "dev": true, + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-tagfilter": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-tagfilter/-/micromark-extension-gfm-tagfilter-2.0.0.tgz", + "integrity": "sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-task-list-item": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-task-list-item/-/micromark-extension-gfm-task-list-item-2.1.0.tgz", + "integrity": "sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw==", + "dev": true, + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-mdx-expression": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/micromark-extension-mdx-expression/-/micromark-extension-mdx-expression-3.0.1.tgz", + "integrity": "sha512-dD/ADLJ1AeMvSAKBwO22zG22N4ybhe7kFIZ3LsDI0GlsNr2A3KYxb0LdC1u5rj4Nw+CHKY0RVdnHX8vj8ejm4Q==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "devlop": "^1.0.0", + "micromark-factory-mdx-expression": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-events-to-acorn": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-extension-mdx-jsx": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/micromark-extension-mdx-jsx/-/micromark-extension-mdx-jsx-3.0.2.tgz", + "integrity": "sha512-e5+q1DjMh62LZAJOnDraSSbDMvGJ8x3cbjygy2qFEi7HCeUT4BDKCvMozPozcD6WmOt6sVvYDNBKhFSz3kjOVQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "devlop": "^1.0.0", + "estree-util-is-identifier-name": "^3.0.0", + "micromark-factory-mdx-expression": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-events-to-acorn": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-mdx-md": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-mdx-md/-/micromark-extension-mdx-md-2.0.0.tgz", + "integrity": "sha512-EpAiszsB3blw4Rpba7xTOUptcFeBFi+6PY8VnJ2hhimH+vCQDirWgsMpz7w1XcZE7LVrSAUGb9VJpG9ghlYvYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-mdxjs": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-mdxjs/-/micromark-extension-mdxjs-3.0.0.tgz", + "integrity": "sha512-A873fJfhnJ2siZyUrJ31l34Uqwy4xIFmvPY1oj+Ean5PHcPBYzEsvqvWGaWcfEIr11O5Dlw3p2y0tZWpKHDejQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.0.0", + "acorn-jsx": "^5.0.0", + "micromark-extension-mdx-expression": "^3.0.0", + "micromark-extension-mdx-jsx": "^3.0.0", + "micromark-extension-mdx-md": "^2.0.0", + "micromark-extension-mdxjs-esm": "^3.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-mdxjs-esm": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-mdxjs-esm/-/micromark-extension-mdxjs-esm-3.0.0.tgz", + "integrity": "sha512-DJFl4ZqkErRpq/dAPyeWp15tGrcrrJho1hKK5uBS70BCtfrIFg81sqcTVu3Ta+KD1Tk5vAtBNElWxtAa+m8K9A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-events-to-acorn": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unist-util-position-from-estree": "^2.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-factory-destination": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-2.0.1.tgz", + "integrity": "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-label": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-2.0.1.tgz", + "integrity": "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-mdx-expression": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/micromark-factory-mdx-expression/-/micromark-factory-mdx-expression-2.0.3.tgz", + "integrity": "sha512-kQnEtA3vzucU2BkrIa8/VaSAsP+EJ3CKOvhMuJgOEGg9KDC6OAY6nSnNDVRiVNRqj7Y4SlSzcStaH/5jge8JdQ==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-events-to-acorn": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unist-util-position-from-estree": "^2.0.0", + "vfile-message": "^4.0.0" + } + }, + "node_modules/micromark-factory-space": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", + "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-title": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-2.0.1.tgz", + "integrity": "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-whitespace": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-2.0.1.tgz", + "integrity": "sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-character": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-chunked": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-2.0.1.tgz", + "integrity": "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-classify-character": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-2.0.1.tgz", + "integrity": "sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-combine-extensions": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-2.0.1.tgz", + "integrity": "sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-chunked": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-decode-numeric-character-reference": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-2.0.2.tgz", + "integrity": "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-decode-string": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-2.0.1.tgz", + "integrity": "sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-encode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz", + "integrity": "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-events-to-acorn": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/micromark-util-events-to-acorn/-/micromark-util-events-to-acorn-2.0.3.tgz", + "integrity": "sha512-jmsiEIiZ1n7X1Rr5k8wVExBQCg5jy4UXVADItHmNk1zkwEVhBuIUKRu3fqv+hs4nxLISi2DQGlqIOGiFxgbfHg==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "@types/unist": "^3.0.0", + "devlop": "^1.0.0", + "estree-util-visit": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "vfile-message": "^4.0.0" + } + }, + "node_modules/micromark-util-html-tag-name": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.1.tgz", + "integrity": "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-normalize-identifier": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-2.0.1.tgz", + "integrity": "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-resolve-all": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-2.0.1.tgz", + "integrity": "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-sanitize-uri": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz", + "integrity": "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-subtokenize": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-2.1.0.tgz", + "integrity": "sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-types": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.2.tgz", + "integrity": "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/minimatch": { + "version": "10.2.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.6.tgz", + "integrity": "sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.8" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minipass": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/mrmime": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.1.tgz", + "integrity": "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.18", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.18.tgz", + "integrity": "sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/neotraverse": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/neotraverse/-/neotraverse-1.0.1.tgz", + "integrity": "sha512-WmmLty1YWwJl9yZi77v2dVIV6X2kuYV8YYBI/G3LWGKdGHmHUvL1z7FW0iDvEvGAwNEoc5x1tOOOyDnf5jJw/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, + "node_modules/nlcst-to-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/nlcst-to-string/-/nlcst-to-string-4.0.0.tgz", + "integrity": "sha512-YKLBCcUYKAg0FNlOBT6aI91qFmSiFKiluk655WzPF+DDMA02qIyy8uiRqI8QXtcFpEvll12LpL5MXqEmAZ+dcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/nlcst": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/node-fetch-native": { + "version": "1.6.7", + "resolved": "https://registry.npmjs.org/node-fetch-native/-/node-fetch-native-1.6.7.tgz", + "integrity": "sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-mock-http": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/node-mock-http/-/node-mock-http-1.0.5.tgz", + "integrity": "sha512-KQyt/wLjG3TAc7DOUhpqWzgd4ERxR80JOlTK5VE5R1S12IaPVN5qkj4klBce9HPG1Njuup4Sb5bljaT34lIyjw==", + "dev": true, + "license": "MIT" + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0" + }, + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" + } + }, + "node_modules/obug": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.4.tgz", + "integrity": "sha512-4a+OsYv9UktOJKE+l1A4OufDgdRF9PifWj+tJnHURo/P+WOxpG4GzUFL9qCalmWauao6ogiG+QvnCovwPoyAWA==", + "dev": true, + "funding": [ + "https://github.com/sponsors/sxzz", + "https://opencollective.com/debug" + ], + "license": "MIT", + "engines": { + "node": ">=12.20.0" + } + }, + "node_modules/ofetch": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/ofetch/-/ofetch-1.5.1.tgz", + "integrity": "sha512-2W4oUZlVaqAPAil6FUg/difl6YhqhUR7x2eZY4bQCko22UXg3hptq9KLQdqFClV+Wu85UX7hNtdGTngi/1BxcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "destr": "^2.0.5", + "node-fetch-native": "^1.6.7", + "ufo": "^1.6.1" + } + }, + "node_modules/ohash": { + "version": "2.0.12", + "resolved": "https://registry.npmjs.org/ohash/-/ohash-2.0.12.tgz", + "integrity": "sha512-65S/5gk9YSsaRjcyf7Nfa6h/d3E8/1gslpXfI4W7Dxn/oap8IKRuNT5VXkLQ1YFKIEg4apRY4Pj6aiwFzrDdmw==", + "dev": true, + "license": "MIT" + }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/oniguruma-parser": { + "version": "0.12.2", + "resolved": "https://registry.npmjs.org/oniguruma-parser/-/oniguruma-parser-0.12.2.tgz", + "integrity": "sha512-6HVa5oIrgMC6aA6WF6XyyqbhRPJrKR02L20+2+zpDtO5QAzGHAUGw5TKQvwi5vctNnRHkJYmjAhRVQF2EKdTQw==", + "dev": true, + "license": "MIT" + }, + "node_modules/oniguruma-to-es": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/oniguruma-to-es/-/oniguruma-to-es-4.3.6.tgz", + "integrity": "sha512-csuQ9x3Yr0cEIs/Zgx/OEt9iBw9vqIunAPQkx19R/fiMq2oGVTgcMqO/V3Ybqefr1TBvosI6jU539ksaBULJyA==", + "dev": true, + "license": "MIT", + "dependencies": { + "oniguruma-parser": "^0.12.2", + "regex": "^6.1.0", + "regex-recursion": "^6.0.2" + } + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/ora": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", + "integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "bl": "^4.1.0", + "chalk": "^4.1.0", + "cli-cursor": "^3.1.0", + "cli-spinners": "^2.5.0", + "is-interactive": "^1.0.0", + "is-unicode-supported": "^0.1.0", + "log-symbols": "^4.1.0", + "strip-ansi": "^6.0.0", + "wcwidth": "^1.0.1" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/oxc-parser": { + "version": "0.147.0", + "resolved": "https://registry.npmjs.org/oxc-parser/-/oxc-parser-0.147.0.tgz", + "integrity": "sha512-5xaug6t7GfV3BO5Iv+xHW1rmQkDEQ3BEu3L8g3InsvWO5i8CYGc4tCZ2X985QcwWNycFJam+aOns6Nr2XAThTA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@oxc-project/types": "^0.147.0" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/sponsors/Boshen" + }, + "optionalDependencies": { + "@oxc-parser/binding-android-arm-eabi": "0.147.0", + "@oxc-parser/binding-android-arm64": "0.147.0", + "@oxc-parser/binding-darwin-arm64": "0.147.0", + "@oxc-parser/binding-darwin-x64": "0.147.0", + "@oxc-parser/binding-freebsd-x64": "0.147.0", + "@oxc-parser/binding-linux-arm-gnueabihf": "0.147.0", + "@oxc-parser/binding-linux-arm-musleabihf": "0.147.0", + "@oxc-parser/binding-linux-arm64-gnu": "0.147.0", + "@oxc-parser/binding-linux-arm64-musl": "0.147.0", + "@oxc-parser/binding-linux-ppc64-gnu": "0.147.0", + "@oxc-parser/binding-linux-riscv64-gnu": "0.147.0", + "@oxc-parser/binding-linux-riscv64-musl": "0.147.0", + "@oxc-parser/binding-linux-s390x-gnu": "0.147.0", + "@oxc-parser/binding-linux-x64-gnu": "0.147.0", + "@oxc-parser/binding-linux-x64-musl": "0.147.0", + "@oxc-parser/binding-openharmony-arm64": "0.147.0", + "@oxc-parser/binding-win32-arm64-msvc": "0.147.0", + "@oxc-parser/binding-win32-ia32-msvc": "0.147.0", + "@oxc-parser/binding-win32-x64-msvc": "0.147.0" + } + }, + "node_modules/oxc-parser/node_modules/@oxc-project/types": { + "version": "0.147.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.147.0.tgz", + "integrity": "sha512-IJ3s6ltHLp45S0bh7phkX+gJO7A1Wuz2EaqpAhb8WjqDwbzMiWKHhyyT42tskaWjEYXtHtVCPpnBJVT9+dcRLg==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/Boshen" + } + }, + "node_modules/oxc-resolver": { + "version": "11.24.2", + "resolved": "https://registry.npmjs.org/oxc-resolver/-/oxc-resolver-11.24.2.tgz", + "integrity": "sha512-FY91FiDBj7ls5MsFS9jN3tjz2o0/zsdSsymlakySaBwVJZorHhkWyICLZMKxlu1R9vYo+sd3z1jwb4J8x7bNDw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/Boshen" + }, + "optionalDependencies": { + "@oxc-resolver/binding-android-arm-eabi": "11.24.2", + "@oxc-resolver/binding-android-arm64": "11.24.2", + "@oxc-resolver/binding-darwin-arm64": "11.24.2", + "@oxc-resolver/binding-darwin-x64": "11.24.2", + "@oxc-resolver/binding-freebsd-x64": "11.24.2", + "@oxc-resolver/binding-linux-arm-gnueabihf": "11.24.2", + "@oxc-resolver/binding-linux-arm-musleabihf": "11.24.2", + "@oxc-resolver/binding-linux-arm64-gnu": "11.24.2", + "@oxc-resolver/binding-linux-arm64-musl": "11.24.2", + "@oxc-resolver/binding-linux-ppc64-gnu": "11.24.2", + "@oxc-resolver/binding-linux-riscv64-gnu": "11.24.2", + "@oxc-resolver/binding-linux-riscv64-musl": "11.24.2", + "@oxc-resolver/binding-linux-s390x-gnu": "11.24.2", + "@oxc-resolver/binding-linux-x64-gnu": "11.24.2", + "@oxc-resolver/binding-linux-x64-musl": "11.24.2", + "@oxc-resolver/binding-openharmony-arm64": "11.24.2", + "@oxc-resolver/binding-wasm32-wasi": "11.24.2", + "@oxc-resolver/binding-win32-arm64-msvc": "11.24.2", + "@oxc-resolver/binding-win32-x64-msvc": "11.24.2" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-queue": { + "version": "9.3.3", + "resolved": "https://registry.npmjs.org/p-queue/-/p-queue-9.3.3.tgz", + "integrity": "sha512-NXAOdnEe5FsZJfT4oK84lE1Y5cFFdWlRuOo5tww8DyNMxyRXwn39fIkUtNLKppcPC+UYU/bXujNCUGDv01y7CA==", + "dev": true, + "license": "MIT", + "dependencies": { + "eventemitter3": "^5.0.4", + "p-timeout": "^7.0.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-timeout": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-7.0.1.tgz", + "integrity": "sha512-AxTM2wDGORHGEkPCt8yqxOTMgpfbEHqF51f/5fJCmwFC3C/zNcGT63SymH2ttOAaiIws2zVg4+izQCjrakcwHg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/package-manager-detector": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/package-manager-detector/-/package-manager-detector-1.8.0.tgz", + "integrity": "sha512-yQA4H19AmPEoMUeavPMDIe1higySl/gH/yaQrkT/s07Qp+7pp2hYz30N3z2l5BkjVkF9Ow6o0wjJamm2y7Sn0A==", + "dev": true, + "license": "MIT" + }, + "node_modules/pagefind": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/pagefind/-/pagefind-1.5.2.tgz", + "integrity": "sha512-XTUaK0hXMCu2jszWE584JGQT7y284TmMV9l/HX3rnG5uo3rHI/uHU56XTyyyPFjeWEBxECbAi0CaFDJOONtG0Q==", + "dev": true, + "license": "MIT", + "bin": { + "pagefind": "lib/runner/bin.cjs" + }, + "optionalDependencies": { + "@pagefind/darwin-arm64": "1.5.2", + "@pagefind/darwin-x64": "1.5.2", + "@pagefind/freebsd-x64": "1.5.2", + "@pagefind/linux-arm64": "1.5.2", + "@pagefind/linux-x64": "1.5.2", + "@pagefind/windows-arm64": "1.5.2", + "@pagefind/windows-x64": "1.5.2" + } + }, + "node_modules/parse-entities": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-4.0.2.tgz", + "integrity": "sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^2.0.0", + "character-entities-legacy": "^3.0.0", + "character-reference-invalid": "^2.0.0", + "decode-named-character-reference": "^1.0.0", + "is-alphanumerical": "^2.0.0", + "is-decimal": "^2.0.0", + "is-hexadecimal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/parse-entities/node_modules/@types/unist": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz", + "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==", + "dev": true, + "license": "MIT" + }, + "node_modules/parse-latin": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/parse-latin/-/parse-latin-7.0.0.tgz", + "integrity": "sha512-mhHgobPPua5kZ98EF4HWiH167JWBfl4pvAIXXdbaVohtK7a6YBOy56kvhCqduqyo/f3yrHFWmqmiMg/BkBkYYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/nlcst": "^2.0.0", + "@types/unist": "^3.0.0", + "nlcst-to-string": "^4.0.0", + "unist-util-modify-children": "^4.0.0", + "unist-util-visit-children": "^3.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/parse5": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", + "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "entities": "^6.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parse5/node_modules/entities": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-scurry": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.2.tgz", + "integrity": "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/piccolore": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/piccolore/-/piccolore-0.1.3.tgz", + "integrity": "sha512-o8bTeDWjE086iwKrROaDf31K0qC/BENdm15/uH9usSC/uZjJOKb2YGiVHfLY4GhwsERiPI1jmwI2XrA7ACOxVw==", + "dev": true, + "license": "ISC" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz", + "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.26", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.26.tgz", + "integrity": "sha512-u82N74LFzG8ca+dD8puPnplTXoGH4fTPpVGuIbt36G3qvNlkvfD0lEAZSxaly3KX8TS/L1A1gsCEmvKmBcVbkQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.17", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-nested": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.2.0.tgz", + "integrity": "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "postcss-selector-parser": "^6.1.1" + }, + "engines": { + "node": ">=12.0" + }, + "peerDependencies": { + "postcss": "^8.2.14" + } + }, + "node_modules/postcss-selector-parser": { + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.4.tgz", + "integrity": "sha512-bIoJLOmjCO1S9XdY/DcnR5hJxvrDir1PbGChrzXG3vw0/FOliy/fA3dmdhQ441kah4gKv+TwckGzex6wNS5cnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prismjs": { + "version": "1.30.0", + "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.30.0.tgz", + "integrity": "sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/process-ancestry": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/process-ancestry/-/process-ancestry-0.1.0.tgz", + "integrity": "sha512-tGqJW/UnclpYASFcM6Xh8D8l/BMtaQ9+CSG0vlJSJTcdMM4lDRv4c6H0Pdcsfted+bVczdYSfk2fdukg2gQkZg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/property-information": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-7.2.0.tgz", + "integrity": "sha512-IAtzIB6sUiWaJYrX9smp3V46pBGbBeLFRGdh25kg1334VcBlD8HzhPeNIWQH9zhGmo2itIe25EHt9dQP7G5hmg==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/radix3": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/radix3/-/radix3-1.1.2.tgz", + "integrity": "sha512-b484I/7b8rDEdSDKckSSBA8knMpcdsXudlE/LNL639wFoHKwLbEkQFZHWEYwDC0wa0FKUcCY+GAF73Z7wxNVFA==", + "dev": true, + "license": "MIT" + }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/readdirp": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-5.1.1.tgz", + "integrity": "sha512-Kko+Y5XQ6fM+Ce3dq3m9YGxnacYZYl9cA1wZjaF3Vbry2L3i1qVg8+CAgNPsXRArPMUMCaOR7oa9Nqntc43JKA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/recma-build-jsx": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/recma-build-jsx/-/recma-build-jsx-1.0.0.tgz", + "integrity": "sha512-8GtdyqaBcDfva+GUKDr3nev3VpKAhup1+RvkMvUxURHpW7QyIvk9F5wz7Vzo06CEMSilw6uArgRqhpiUcWp8ew==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "estree-util-build-jsx": "^3.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/recma-jsx": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/recma-jsx/-/recma-jsx-1.0.1.tgz", + "integrity": "sha512-huSIy7VU2Z5OLv6oFLosQGGDqPqdO1iq6bWNAdhzMxSJP7RAso4fCZ1cKu8j9YHCZf3TPrq4dw3okhrylgcd7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn-jsx": "^5.0.0", + "estree-util-to-js": "^2.0.0", + "recma-parse": "^1.0.0", + "recma-stringify": "^1.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + }, + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/recma-parse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/recma-parse/-/recma-parse-1.0.0.tgz", + "integrity": "sha512-OYLsIGBB5Y5wjnSnQW6t3Xg7q3fQ7FWbw/vcXtORTnyaSFscOtABg+7Pnz6YZ6c27fG1/aN8CjfwoUEUIdwqWQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "esast-util-from-js": "^2.0.0", + "unified": "^11.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/recma-stringify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/recma-stringify/-/recma-stringify-1.0.0.tgz", + "integrity": "sha512-cjwII1MdIIVloKvC9ErQ+OgAtwHBmcZ0Bg4ciz78FtbT8In39aAYbaA7zvxQ61xVMSPE8WxhLwLbhif4Js2C+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "estree-util-to-js": "^2.0.0", + "unified": "^11.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/regex/-/regex-6.1.0.tgz", + "integrity": "sha512-6VwtthbV4o/7+OaAF9I5L5V3llLEsoPyq9P1JVXkedTP33c7MfCG0/5NOPcSJn0TzXcG9YUrR0gQSWioew3LDg==", + "dev": true, + "license": "MIT", + "dependencies": { + "regex-utilities": "^2.3.0" + } + }, + "node_modules/regex-recursion": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/regex-recursion/-/regex-recursion-6.0.2.tgz", + "integrity": "sha512-0YCaSCq2VRIebiaUviZNs0cBz1kg5kVS2UKUfNIx8YVs1cN3AV7NTctO5FOKBA+UT2BPJIWZauYHPqJODG50cg==", + "dev": true, + "license": "MIT", + "dependencies": { + "regex-utilities": "^2.3.0" + } + }, + "node_modules/regex-utilities": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/regex-utilities/-/regex-utilities-2.3.0.tgz", + "integrity": "sha512-8VhliFJAWRaUiVvREIiW2NXXTmHs4vMNnSzuJVhscgmGav3g9VDxLrQndI3dZZVVdp0ZO/5v0xmX516/7M9cng==", + "dev": true, + "license": "MIT" + }, + "node_modules/rehype-expressive-code": { + "version": "0.44.2", + "resolved": "https://registry.npmjs.org/rehype-expressive-code/-/rehype-expressive-code-0.44.2.tgz", + "integrity": "sha512-jvjQpsfWKBquDrEl5YQA3CJybRRIuDOYsui1BJTs5oKDUK8pPkJSSxY8FjxNlIFIdtgrTjC1zuahuY4Cc1j9SQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "expressive-code": "^0.44.2" + } + }, + "node_modules/rehype-raw": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/rehype-raw/-/rehype-raw-7.0.0.tgz", + "integrity": "sha512-/aE8hCfKlQeA8LmyeyQvQF3eBiLRGNlfBJEvWH7ivp9sBqs7TNqBL5X3v157rM4IFETqDnIOO+z5M/biZbo9Ww==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "hast-util-raw": "^9.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/rehype-recma": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/rehype-recma/-/rehype-recma-1.0.0.tgz", + "integrity": "sha512-lqA4rGUf1JmacCNWWZx0Wv1dHqMwxzsDWYMTowuplHF3xH0N/MmrZ/G3BDZnzAkRmxDadujCjaKM2hqYdCBOGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "@types/hast": "^3.0.0", + "hast-util-to-estree": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/rehype-stringify": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/rehype-stringify/-/rehype-stringify-10.0.1.tgz", + "integrity": "sha512-k9ecfXHmIPuFVI61B9DeLPN0qFHfawM6RsuX48hoqlaKSF61RskNjSm1lI8PhBEM0MRdLxVVm4WmTqJQccH9mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "hast-util-to-html": "^9.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-directive": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/remark-directive/-/remark-directive-4.0.0.tgz", + "integrity": "sha512-7sxn4RfF1o3izevPV1DheyGDD6X4c9hrGpfdUpm7uC++dqrnJxIZVkk7CoKqcLm0VUMAuOol7Mno3m6g8cfMuA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-directive": "^3.0.0", + "micromark-extension-directive": "^4.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-gfm": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/remark-gfm/-/remark-gfm-4.0.1.tgz", + "integrity": "sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-gfm": "^3.0.0", + "micromark-extension-gfm": "^3.0.0", + "remark-parse": "^11.0.0", + "remark-stringify": "^11.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-mdx": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/remark-mdx/-/remark-mdx-3.1.1.tgz", + "integrity": "sha512-Pjj2IYlUY3+D8x00UJsIOg5BEvfMyeI+2uLPn9VO9Wg4MEtN/VTIq2NEJQfde9PnX15KgtHyl9S0BcTnWrIuWg==", + "dev": true, + "license": "MIT", + "dependencies": { + "mdast-util-mdx": "^3.0.0", + "micromark-extension-mdxjs": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-parse": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-11.0.0.tgz", + "integrity": "sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-from-markdown": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-rehype": { + "version": "11.1.2", + "resolved": "https://registry.npmjs.org/remark-rehype/-/remark-rehype-11.1.2.tgz", + "integrity": "sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "mdast-util-to-hast": "^13.0.0", + "unified": "^11.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-smartypants": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/remark-smartypants/-/remark-smartypants-3.0.3.tgz", + "integrity": "sha512-gCaK+ndZ0hYezlqFegHFCVh2CQemsi0Npdh1qVM9bxlUFknjkbP6VmojWhddOCrbK0PbbacmYLWfTULRiT1eWA==", + "dev": true, + "license": "MIT", + "dependencies": { + "retext": "^9.0.0", + "retext-smartypants": "^6.0.0", + "unified": "^11.0.4", + "unist-util-visit": "^5.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/remark-stringify": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/remark-stringify/-/remark-stringify-11.0.0.tgz", + "integrity": "sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-to-markdown": "^2.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, + "node_modules/restore-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", + "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/restore-cursor/node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/retext": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/retext/-/retext-9.0.0.tgz", + "integrity": "sha512-sbMDcpHCNjvlheSgMfEcVrZko3cDzdbe1x/e7G66dFp0Ff7Mldvi2uv6JkJQzdRcvLYE8CA8Oe8siQx8ZOgTcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/nlcst": "^2.0.0", + "retext-latin": "^4.0.0", + "retext-stringify": "^4.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/retext-latin": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/retext-latin/-/retext-latin-4.0.0.tgz", + "integrity": "sha512-hv9woG7Fy0M9IlRQloq/N6atV82NxLGveq+3H2WOi79dtIYWN8OaxogDm77f8YnVXJL2VD3bbqowu5E3EMhBYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/nlcst": "^2.0.0", + "parse-latin": "^7.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/retext-smartypants": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/retext-smartypants/-/retext-smartypants-6.2.0.tgz", + "integrity": "sha512-kk0jOU7+zGv//kfjXEBjdIryL1Acl4i9XNkHxtM7Tm5lFiCog576fjNC9hjoR7LTKQ0DsPWy09JummSsH1uqfQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/nlcst": "^2.0.0", + "nlcst-to-string": "^4.0.0", + "unist-util-visit": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/retext-stringify": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/retext-stringify/-/retext-stringify-4.0.0.tgz", + "integrity": "sha512-rtfN/0o8kL1e+78+uxPTqu1Klt0yPzKuQ2BfWwwfgIUSayyzxpM1PJzkKt4V8803uB9qSy32MvI7Xep9khTpiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/nlcst": "^2.0.0", + "nlcst-to-string": "^4.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/rolldown": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.2.7.tgz", + "integrity": "sha512-g0EtLvBjTUB7jhyV0S/TCup3v/XSVl45vUIGbOGU4QPiyjTenCe4mKuFvW9fEgYmS2Fo42AUssRmNuMziXdrig==", + "dev": true, + "license": "MIT", + "dependencies": { + "@oxc-project/types": "=0.148.0", + "@rolldown/pluginutils": "^1.0.0" + }, + "bin": { + "rolldown": "bin/cli.mjs" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "optionalDependencies": { + "@rolldown/binding-android-arm-eabi": "1.2.7", + "@rolldown/binding-android-arm64": "1.2.7", + "@rolldown/binding-darwin-arm64": "1.2.7", + "@rolldown/binding-darwin-x64": "1.2.7", + "@rolldown/binding-freebsd-x64": "1.2.7", + "@rolldown/binding-linux-arm-gnueabihf": "1.2.7", + "@rolldown/binding-linux-arm64-gnu": "1.2.7", + "@rolldown/binding-linux-arm64-musl": "1.2.7", + "@rolldown/binding-linux-ppc64-gnu": "1.2.7", + "@rolldown/binding-linux-s390x-gnu": "1.2.7", + "@rolldown/binding-linux-x64-gnu": "1.2.7", + "@rolldown/binding-linux-x64-musl": "1.2.7", + "@rolldown/binding-openharmony-arm64": "1.2.7", + "@rolldown/binding-win32-arm64-msvc": "1.2.7", + "@rolldown/binding-win32-x64-msvc": "1.2.7" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/satteri": { + "version": "0.10.5", + "resolved": "https://registry.npmjs.org/satteri/-/satteri-0.10.5.tgz", + "integrity": "sha512-Ao1LKpAEa9Wdg0otgbVKViZHEq9ebdXe4DMrp3s9vQAU0HNIuHnFEuMuOcm0ZIXyV0Yzxj91NvhLpvXZJO/5ZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.5", + "@types/hast": "^3.0.5", + "@types/mdast": "^4.0.4", + "@types/unist": "^3.0.3" + }, + "optionalDependencies": { + "@bruits/satteri-darwin-arm64": "0.10.5", + "@bruits/satteri-darwin-x64": "0.10.5", + "@bruits/satteri-linux-arm64-gnu": "0.10.5", + "@bruits/satteri-linux-arm64-musl": "0.10.5", + "@bruits/satteri-linux-x64-gnu": "0.10.5", + "@bruits/satteri-linux-x64-musl": "0.10.5", + "@bruits/satteri-wasm32-wasi": "0.10.5", + "@bruits/satteri-win32-arm64-msvc": "0.10.5", + "@bruits/satteri-win32-x64-msvc": "0.10.5" + } + }, + "node_modules/sax": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.6.1.tgz", + "integrity": "sha512-42tBVwLWnaQvW5zc4HbZrTuWccECCZfBi92FDuwtqxasH+JbPB3/FOKb1m222K42R4WxuxzzMsTswfzgtSu64Q==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=11.0.0" + } + }, + "node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/serialize-error": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-8.1.0.tgz", + "integrity": "sha512-3NnuWfM6vBYoy5gZFvHiYsVbafvI9vZv/+jlIigFn4oP4zjNPK3LhcY0xSCgeb1a5L8jO71Mit9LlNoi2UfDDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/sharp": { + "version": "0.35.4", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.35.4.tgz", + "integrity": "sha512-n++8XWcj+jCOr2IOl7h8LbKnGBDY4aPbmprMONBNFdn0ImXqpGVv5zliDs0V9HbmbCQLpbuo2ej9rAoOQTvMDA==", + "dev": true, + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@img/colour": "^1.1.0", + "detect-libc": "^2.1.2", + "semver": "^7.8.5" + }, + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-darwin-arm64": "0.35.4", + "@img/sharp-darwin-x64": "0.35.4", + "@img/sharp-freebsd-wasm32": "0.35.4", + "@img/sharp-libvips-darwin-arm64": "1.3.3", + "@img/sharp-libvips-darwin-x64": "1.3.3", + "@img/sharp-libvips-linux-arm": "1.3.3", + "@img/sharp-libvips-linux-arm64": "1.3.3", + "@img/sharp-libvips-linux-ppc64": "1.3.3", + "@img/sharp-libvips-linux-riscv64": "1.3.3", + "@img/sharp-libvips-linux-s390x": "1.3.3", + "@img/sharp-libvips-linux-x64": "1.3.3", + "@img/sharp-libvips-linuxmusl-arm64": "1.3.3", + "@img/sharp-libvips-linuxmusl-x64": "1.3.3", + "@img/sharp-linux-arm": "0.35.4", + "@img/sharp-linux-arm64": "0.35.4", + "@img/sharp-linux-ppc64": "0.35.4", + "@img/sharp-linux-riscv64": "0.35.4", + "@img/sharp-linux-s390x": "0.35.4", + "@img/sharp-linux-x64": "0.35.4", + "@img/sharp-linuxmusl-arm64": "0.35.4", + "@img/sharp-linuxmusl-x64": "0.35.4", + "@img/sharp-webcontainers-wasm32": "0.35.4", + "@img/sharp-win32-arm64": "0.35.4", + "@img/sharp-win32-ia32": "0.35.4", + "@img/sharp-win32-x64": "0.35.4" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/shiki": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/shiki/-/shiki-4.4.3.tgz", + "integrity": "sha512-Mb/GvXPHBAXdgGIcnfU5L3ldpn1XcxrGkPHwqgRx17/I2XRfqlFKk2vGkHWINn1kdXvzJZeuO3is6I9KLPFm0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shikijs/core": "4.4.3", + "@shikijs/engine-javascript": "4.4.3", + "@shikijs/engine-oniguruma": "4.4.3", + "@shikijs/langs": "4.4.3", + "@shikijs/themes": "4.4.3", + "@shikijs/types": "4.4.3", + "@shikijs/vscode-textmate": "^10.0.2", + "@types/hast": "^3.0.5" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "dev": true, + "license": "MIT" + }, + "node_modules/sitemap": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/sitemap/-/sitemap-9.0.1.tgz", + "integrity": "sha512-S6hzjGJSG3d6if0YoF5kTyeRJvia6FSTBroE5fQ0bu1QNxyJqhhinfUsXi9fH3MgtXODWvwo2BDyQSnhPQ88uQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "^24.9.2", + "@types/sax": "^1.2.1", + "arg": "^5.0.0", + "sax": "^1.4.1" + }, + "bin": { + "sitemap": "dist/esm/cli.js" + }, + "engines": { + "node": ">=20.19.5", + "npm": ">=10.8.2" + } + }, + "node_modules/sitemap/node_modules/@types/node": { + "version": "24.13.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.13.3.tgz", + "integrity": "sha512-Dh8vAsV36ig5wa9OX4pXvMc9D3Veibfw2wix0CUwYODLD8nkj9UsLjASr49nPg+2eKzxhBV+v7L8pXvT4e639Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.18.0" + } + }, + "node_modules/sitemap/node_modules/undici-types": { + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz", + "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==", + "dev": true, + "license": "MIT" + }, + "node_modules/smol-toml": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/smol-toml/-/smol-toml-1.8.0.tgz", + "integrity": "sha512-kCZr2V3ch9i00x8zXRhjUNVcjG9ijES5dDudkXvUVCT5QlJNQWElSJdZqyPemffHoLNUYwOcou0Fy+ojN0uHSQ==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">= 18" + }, + "funding": { + "url": "https://github.com/sponsors/cyyynthia" + } + }, + "node_modules/source-map": { + "version": "0.7.6", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz", + "integrity": "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">= 12" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/space-separated-tokens": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz", + "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string-width": { + "version": "8.2.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-8.2.2.tgz", + "integrity": "sha512-GaPUh5gfdrYzqeVNZvUfT23vYYxXzKYidUcnMtJg/3rxRV63EFZy3k6xfKlmfeJD0176lnUV/Usr3XcwSvFzpg==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-east-asian-width": "^1.5.0", + "strip-ansi": "^7.1.2" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/string-width/node_modules/ansi-regex": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.3.0.tgz", + "integrity": "sha512-WpDfL7NO6j7tH88IDBNVdUJxDh9nmCteAVW9dsep846XdwF4naCBK+/tGLX3KJgcpgMRXCFlTM2hKGoK9FsdrQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/string-width/node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.2.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/stringify-entities": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz", + "integrity": "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==", + "dev": true, + "license": "MIT", + "dependencies": { + "character-entities-html4": "^2.0.0", + "character-entities-legacy": "^3.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-json-comments": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-5.0.3.tgz", + "integrity": "sha512-1tB5mhVo7U+ETBKNf92xT4hrQa3pm0MZ0PQvuDnWgAAGHDsfp4lPSpiS6psrSiet87wyGPh9ft6wmhOMQ0hDiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/style-to-js": { + "version": "1.1.21", + "resolved": "https://registry.npmjs.org/style-to-js/-/style-to-js-1.1.21.tgz", + "integrity": "sha512-RjQetxJrrUJLQPHbLku6U/ocGtzyjbJMP9lCNK7Ag0CNh690nSH8woqWH9u16nMjYBAok+i7JO1NP2pOy8IsPQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "style-to-object": "1.0.14" + } + }, + "node_modules/style-to-object": { + "version": "1.0.14", + "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-1.0.14.tgz", + "integrity": "sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw==", + "dev": true, + "license": "MIT", + "dependencies": { + "inline-style-parser": "0.2.7" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/svgo": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/svgo/-/svgo-4.1.0.tgz", + "integrity": "sha512-bkxnTg1kSU0guhIBmibA6UUhrQmPVA1XsQLN+ylCd+UWzbnLkySOcXpyk1mrl05f+pcaCx2eHb+sp6BgMZWX+Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "commander": "^11.1.0", + "css-select": "^6.0.0", + "css-tree": "^3.0.1", + "css-what": "^7.0.0", + "csso": "^5.0.5", + "picocolors": "^1.1.1", + "sax": "1.6.1" + }, + "bin": { + "svgo": "bin/svgo.js" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/svgo" + } + }, + "node_modules/svgo/node_modules/commander": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-11.1.0.tgz", + "integrity": "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16" + } + }, + "node_modules/test-exclude": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-8.0.0.tgz", + "integrity": "sha512-ZOffsNrXYggvU1mDGHk54I96r26P8SyMjO5slMKSc7+IWmtB/MQKnEC2fP51imB3/pT6YK5cT5E8f+Dd9KdyOQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^13.0.6", + "minimatch": "^10.2.2" + }, + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/tiny-glob": { + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/tiny-glob/-/tiny-glob-0.2.9.tgz", + "integrity": "sha512-g/55ssRPUjShh+xkfx9UPDXqhckHEsHr4Vd9zX55oSdGZc/MD0m3sferOkwWtp98bv+kcVfEHtRJgBVJzelrzg==", + "dev": true, + "license": "MIT", + "dependencies": { + "globalyzer": "0.1.0", + "globrex": "^0.1.2" + } + }, + "node_modules/tiny-inflate": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tiny-inflate/-/tiny-inflate-1.0.3.tgz", + "integrity": "sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyclip": { + "version": "0.1.15", + "resolved": "https://registry.npmjs.org/tinyclip/-/tinyclip-0.1.15.tgz", + "integrity": "sha512-uo33abH+Ays0xYaDysoBt494Hb3hsEczMpcC0MwFl773pazORx4fmvKhclhR1wonUbB6vvpRsvVMwnhfqeMc+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^16.14.0 || >= 17.3.0" + } + }, + "node_modules/tinyexec": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.3.0.tgz", + "integrity": "sha512-QKAl9m8gWWGHV8jZcPeym6j+XULi6tOf1mT83WYJ4Lk2ytW/uwAWkrP0uFsdoYMdueVJ0qs26wZ+23xeB4ibNQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/trim-lines": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz", + "integrity": "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/trough": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/trough/-/trough-2.2.0.tgz", + "integrity": "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/ts-api-utils": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz", + "integrity": "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD", + "optional": true + }, + "node_modules/tsx": { + "version": "4.23.13", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.23.13.tgz", + "integrity": "sha512-BL5MGkRln6aDYhb0xbQlEAGw743BaZYWdbWtdJOBriYJboKgUUYCadFp2/FpBBZquBC/ezNBn7wMMPx7FDZUDw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "esbuild": "~0.28.0" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typescript": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz", + "integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/typescript-eslint": { + "version": "8.69.0", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.69.0.tgz", + "integrity": "sha512-B3MltX0VqjUBNEe3b3sSuiRbfa6XrfHFtBiPamjT5AsW/dfq+y+bc0wyuS9DxAS1LyzCxRp2+rxzpLUvqM2BvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/eslint-plugin": "8.69.0", + "@typescript-eslint/parser": "8.69.0", + "@typescript-eslint/typescript-estree": "8.69.0", + "@typescript-eslint/utils": "8.69.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/ufo": { + "version": "1.6.4", + "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.4.tgz", + "integrity": "sha512-JFNbkD1Svwe0KvGi8GOeLcP4kAWQ609twvCdcHxq1oSL8svv39ZuSvajcD8B+5D0eL4+s1Is2D/O6KN3qcTeRA==", + "dev": true, + "license": "MIT" + }, + "node_modules/ultrahtml": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/ultrahtml/-/ultrahtml-1.7.0.tgz", + "integrity": "sha512-2xRd0VHoAQE4M+vF/DvFFB7pUV0ZxTW1TLi7lHQWnF/Sb5TPeEUV/l+hxcNnGO00ZXGnR0voCMmYRKQf+rvJ2g==", + "dev": true, + "license": "MIT" + }, + "node_modules/unbash": { + "version": "4.0.11", + "resolved": "https://registry.npmjs.org/unbash/-/unbash-4.0.11.tgz", + "integrity": "sha512-FoSOKV7NEofQSkAefMVHam4ZPKYMxjAydxiV72UFEDNV/YofxjGfiZ2A9pZjdL/lRJzTjcu4PABo1JYJX8N5iQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + } + }, + "node_modules/uncrypto": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/uncrypto/-/uncrypto-0.1.3.tgz", + "integrity": "sha512-Ql87qFHB3s/De2ClA9e0gsnS6zXG27SkTiSJwjCc9MebbfapQfuPzumMIUMi38ezPZVNFcHI9sUIepeQfw8J8Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/undici": { + "version": "8.10.1", + "resolved": "https://registry.npmjs.org/undici/-/undici-8.10.1.tgz", + "integrity": "sha512-YQ3WlbqjYMmNpdvDH64jAgLjxuAR9+649calDWhbshYaeQGO2bR4nI94ORJmwI3J9YhoKQnpyGOK+0zlWS5N5Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=22.19.0" + } + }, + "node_modules/undici-types": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-8.3.0.tgz", + "integrity": "sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/unified": { + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz", + "integrity": "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "bail": "^2.0.0", + "devlop": "^1.0.0", + "extend": "^3.0.0", + "is-plain-obj": "^4.0.0", + "trough": "^2.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unifont": { + "version": "0.7.5", + "resolved": "https://registry.npmjs.org/unifont/-/unifont-0.7.5.tgz", + "integrity": "sha512-ULe/Cs+ZIsq+dcFofNkhqielCrUJnb5mr+Yc4EBM2VlL+6OZR6+cjtI2mT1bJvRBrVncqHAbLURxmPLcCXzWMg==", + "dev": true, + "license": "MIT", + "dependencies": { + "css-tree": "^3.1.0", + "ohash": "^2.0.11", + "undici": "^8.0.0" + } + }, + "node_modules/unist-util-find-after": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-find-after/-/unist-util-find-after-5.0.0.tgz", + "integrity": "sha512-amQa0Ep2m6hE2g72AugUItjbuM8X8cGQnFoHk0pGfrFeT9GZhzN5SW8nRsiGKK7Aif4CrACPENkA6P/Lw6fHGQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-is": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.1.tgz", + "integrity": "sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-modify-children": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unist-util-modify-children/-/unist-util-modify-children-4.0.0.tgz", + "integrity": "sha512-+tdN5fGNddvsQdIzUF3Xx82CU9sMM+fA0dLgR9vOmT0oPT2jH+P1nd5lSqfCfXAw+93NhcXNY2qqvTUtE4cQkw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "array-iterate": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-position": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-5.0.0.tgz", + "integrity": "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-position-from-estree": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unist-util-position-from-estree/-/unist-util-position-from-estree-2.0.0.tgz", + "integrity": "sha512-KaFVRjoqLyF6YXCbVLNad/eS4+OfPQQn2yOd7zF/h5T/CSL2v8NpN6a5TPvtbXthAGw5nG+PuTtq+DdIZr+cRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-remove-position": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-remove-position/-/unist-util-remove-position-5.0.0.tgz", + "integrity": "sha512-Hp5Kh3wLxv0PHj9m2yZhhLt58KzPtEYKQQ4yxfYFEO7EvHwzyDYnduhHnY1mDxoqr7VUwVuHXk9RXKIiYS1N8Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-visit": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-stringify-position": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", + "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.1.0.tgz", + "integrity": "sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit-children": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/unist-util-visit-children/-/unist-util-visit-children-3.0.0.tgz", + "integrity": "sha512-RgmdTfSBOg04sdPcpTSD1jzoNBjt9a80/ZCzp5cI9n1qPzLZWF9YdvWGN2zmTumP1HWhXKdUWexjy/Wy/lJ7tA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit-parents": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.2.tgz", + "integrity": "sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unstorage": { + "version": "1.17.5", + "resolved": "https://registry.npmjs.org/unstorage/-/unstorage-1.17.5.tgz", + "integrity": "sha512-0i3iqvRfx29hkNntHyQvJTpf5W9dQ9ZadSoRU8+xVlhVtT7jAX57fazYO9EHvcRCfBCyi5YRya7XCDOsbTgkPg==", + "dev": true, + "license": "MIT", + "dependencies": { + "anymatch": "^3.1.3", + "chokidar": "^5.0.0", + "destr": "^2.0.5", + "h3": "^1.15.10", + "lru-cache": "^11.2.7", + "node-fetch-native": "^1.6.7", + "ofetch": "^1.5.1", + "ufo": "^1.6.3" + }, + "peerDependencies": { + "@azure/app-configuration": "^1.8.0", + "@azure/cosmos": "^4.2.0", + "@azure/data-tables": "^13.3.0", + "@azure/identity": "^4.6.0", + "@azure/keyvault-secrets": "^4.9.0", + "@azure/storage-blob": "^12.26.0", + "@capacitor/preferences": "^6 || ^7 || ^8", + "@deno/kv": ">=0.9.0", + "@netlify/blobs": "^6.5.0 || ^7.0.0 || ^8.1.0 || ^9.0.0 || ^10.0.0", + "@planetscale/database": "^1.19.0", + "@upstash/redis": "^1.34.3", + "@vercel/blob": ">=0.27.1", + "@vercel/functions": "^2.2.12 || ^3.0.0", + "@vercel/kv": "^1 || ^2 || ^3", + "aws4fetch": "^1.0.20", + "db0": ">=0.2.1", + "idb-keyval": "^6.2.1", + "ioredis": "^5.4.2", + "uploadthing": "^7.4.4" + }, + "peerDependenciesMeta": { + "@azure/app-configuration": { + "optional": true + }, + "@azure/cosmos": { + "optional": true + }, + "@azure/data-tables": { + "optional": true + }, + "@azure/identity": { + "optional": true + }, + "@azure/keyvault-secrets": { + "optional": true + }, + "@azure/storage-blob": { + "optional": true + }, + "@capacitor/preferences": { + "optional": true + }, + "@deno/kv": { + "optional": true + }, + "@netlify/blobs": { + "optional": true + }, + "@planetscale/database": { + "optional": true + }, + "@upstash/redis": { + "optional": true + }, + "@vercel/blob": { + "optional": true + }, + "@vercel/functions": { + "optional": true + }, + "@vercel/kv": { + "optional": true + }, + "aws4fetch": { + "optional": true + }, + "db0": { + "optional": true + }, + "idb-keyval": { + "optional": true + }, + "ioredis": { + "optional": true + }, + "uploadthing": { + "optional": true + } + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/url-extras": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/url-extras/-/url-extras-0.1.0.tgz", + "integrity": "sha512-8tzwTeXFPuX/5PHuCDQE5Dd9Ts4rwoq2t9aIT+HS4iAVpmj5l4Ao7Q+BuuFjvWRqrLswBhQDk8O96ZicgCqQqw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true, + "license": "MIT" + }, + "node_modules/v8-to-istanbul": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz", + "integrity": "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==", + "dev": true, + "license": "ISC", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.12", + "@types/istanbul-lib-coverage": "^2.0.1", + "convert-source-map": "^2.0.0" + }, + "engines": { + "node": ">=10.12.0" + } + }, + "node_modules/vfile": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz", + "integrity": "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile-location": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/vfile-location/-/vfile-location-5.0.3.tgz", + "integrity": "sha512-5yXvWDEgqeiYiBe1lbxYF7UMAIm/IcopxMHrMQDq3nvKcjPKIhZklUKL+AE7J7uApI4kwe2snsK+eI6UTj9EHg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile-message": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.3.tgz", + "integrity": "sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vite": { + "version": "8.2.2", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.2.2.tgz", + "integrity": "sha512-cFKLV/PRgAUlIRm5WjMjJ86jrftzpqcgH+Us+DS8mI3CDNiH30Whrz8uHL3+MOLPAgqbMBAqWdAHAphOAM+z/Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "lightningcss": "^1.33.0", + "picomatch": "^4.0.5", + "postcss": "^8.5.26", + "rolldown": "~1.2.4", + "tinyglobby": "^0.2.17" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "@vitejs/devtools": "^0.4.0 || ^0.5.0", + "esbuild": "^0.27.0 || ^0.28.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "@vitejs/devtools": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vitefu": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/vitefu/-/vitefu-1.1.3.tgz", + "integrity": "sha512-ub4okH7Z5KLjb6hDyjqrGXqWtWvoYdU3IGm/NorpgHncKoLTCfRIbvlhBm7r0YstIaQRYlp4yEbFqDcKSzXSSg==", + "dev": true, + "license": "MIT", + "workspaces": [ + "tests/deps/*", + "tests/projects/*", + "tests/projects/workspace/packages/*" + ], + "peerDependencies": { + "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "vite": { + "optional": true + } + } + }, + "node_modules/walk-up-path": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/walk-up-path/-/walk-up-path-4.0.0.tgz", + "integrity": "sha512-3hu+tD8YzSLGuFYtPRb48vdhKMi0KQV5sn+uWr8+7dMEq/2G/dtLrdDinkLjqq5TIbIBjYJ4Ax/n3YiaW7QM8A==", + "dev": true, + "license": "ISC", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/wcwidth": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", + "integrity": "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==", + "dev": true, + "license": "MIT", + "dependencies": { + "defaults": "^1.0.3" + } + }, + "node_modules/web-namespaces": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/web-namespaces/-/web-namespaces-2.0.1.tgz", + "integrity": "sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/whatwg-mimetype": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz", + "integrity": "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wrap-ansi": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz", + "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-regex": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.3.0.tgz", + "integrity": "sha512-WpDfL7NO6j7tH88IDBNVdUJxDh9nmCteAVW9dsep846XdwF4naCBK+/tGLX3KJgcpgMRXCFlTM2hKGoK9FsdrQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/wrap-ansi/node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.2.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/ws": { + "version": "8.21.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.3.tgz", + "integrity": "sha512-201TZ/kPWxoPr/OKWjquZR1SWKXcvxdH+e1xrx89b3YbmzLMFCLfnaG1HFIgWzJOEWZ7MvpK++odZufgYR50Rw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/xxhash-wasm": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/xxhash-wasm/-/xxhash-wasm-1.1.0.tgz", + "integrity": "sha512-147y/6YNh+tlp6nd/2pWq38i9h6mz/EuQ6njIrmW8D1BS5nCqs0P6DG+m6zTGnNz5I+uhZ0SHxBs9BsPrwcKDA==", + "dev": true, + "license": "MIT" + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yaml": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.0.tgz", + "integrity": "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==", + "dev": true, + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + }, + "funding": { + "url": "https://github.com/sponsors/eemeli" + } + }, + "node_modules/yargs": { + "version": "18.1.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-18.1.0.tgz", + "integrity": "sha512-2rAgRKu54VsHkqI0/tYkmluGXHD4KW7yZoycuqDQ15QOTnc2VVfy0nN/1eMhnQLO00A+dwtK20xuCnc1YGeUyg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^9.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "string-width": "^8.2.1", + "y18n": "^5.0.5", + "yargs-parser": "^22.0.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=23" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs/node_modules/yargs-parser": { + "version": "22.0.0", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-22.0.0.tgz", + "integrity": "sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=23" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zod": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.5.4.tgz", + "integrity": "sha512-sC95tT5iHHH9gtpj6A81kh+NEaRAUFN+qlUPDUbRfOMvNf5QCBqsb3WgvnpVtK5Y+4UfA6KqufotuTvMGiTlsA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/zwitch": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", + "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..3a9021c --- /dev/null +++ b/package.json @@ -0,0 +1,77 @@ +{ + "name": "busker", + "version": "0.0.0", + "description": "Scripted cursor demos that really click. Drive a fake product UI with a routine, not a stopwatch.", + "keywords": [ + "demo", + "product-tour", + "cursor", + "animation", + "mock", + "marketing", + "landing-page", + "typescript" + ], + "repository": { + "type": "git", + "url": "git+https://github.com/logfoxai/busker.git" + }, + "homepage": "https://busker.logfox.ai", + "directories": { + "doc": "src/content/docs" + }, + "license": "MIT", + "author": "Marc H. Weiner ", + "publishConfig": { + "access": "public" + }, + "main": "dist/index.js", + "types": "dist/index.d.ts", + "exports": { + ".": { + "types": "./dist/index.d.ts", + "default": "./dist/index.js" + }, + "./busker.css": "./busker.css" + }, + "sideEffects": [ + "*.css" + ], + "files": [ + "dist", + "busker.css", + "AGENTS.md", + "skills", + "!dist/**/*.spec.*" + ], + "engines": { + "node": ">=20" + }, + "scripts": { + "prepare": "npm run build", + "build": "tsc", + "lint": "eslint . --fix", + "lint:check": "eslint .", + "test": "c8 kizu -f 'src/**/*.spec.ts' && c8 report -r text -r html", + "deadcode:check": "knip", + "sync:fonts": "node scripts/sync-fonts.mjs", + "astro:sync": "astro sync", + "astro:dev": "node scripts/assert-astro-dev-free.mjs && node scripts/clean-astro-cache.mjs && astro sync && astro dev", + "astro:build": "node scripts/assert-astro-dev-free.mjs && astro sync && astro build && node scripts/write-cs-pagefind-shim.mjs", + "validate": "npm run lint && npm run build && npm run deadcode:check && npm test && npm run sync:fonts && npm run astro:build && node scripts/assert-404-page.mjs && node scripts/assert-splash-page.mjs" + }, + "devDependencies": { + "@astrojs/markdown-remark": "^7.3.0", + "@astrojs/starlight": "^0.42.0", + "@fontsource-variable/inter": "^5.3.0", + "@happy-dom/global-registrator": "^20.13.2", + "astro": "^7.2.10", + "c8": "^12.0.0", + "eslint": "^10.9.1", + "happy-dom": "^20.13.2", + "kizu": "^5.1.1", + "knip": "^6.34.0", + "typescript": "^6.0.3", + "typescript-eslint": "^8.69.0" + } +} diff --git a/scripts/assert-404-page.mjs b/scripts/assert-404-page.mjs new file mode 100644 index 0000000..4ef32fa --- /dev/null +++ b/scripts/assert-404-page.mjs @@ -0,0 +1,42 @@ +#!/usr/bin/env node +/** + * Built Starlight 404 must be a dedicated page β€” not the homepage demo pile. + * Run after `astro:build` (wired into npm run validate). + */ +import fs from 'node:fs'; +import path from 'node:path'; +import {fileURLToPath} from 'node:url'; + +const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..'); +const htmlPath = path.join(root, 'docs-site', '404.html'); + +if (!fs.existsSync(htmlPath)) { + console.error('assert-404-page: missing docs-site/404.html (run astro:build first)'); + process.exit(1); +} + +const html = fs.readFileSync(htmlPath, 'utf8'); + +const forbidden = ['splash__demo', 'data-mock', 'busk(root,']; +for (const needle of forbidden) { + if (html.includes(needle)) { + console.error(`assert-404-page: 404.html must not include homepage demo marker: ${needle}`); + process.exit(1); + } +} + +const required = [ + 'Page not found', + 'getting-started', + 'not-found-page', + 'not-found-hero', + 'not-found-hero__code', +]; +for (const needle of required) { + if (!html.includes(needle)) { + console.error(`assert-404-page: 404.html missing expected content: ${needle}`); + process.exit(1); + } +} + +console.log('assert-404-page: ok'); diff --git a/scripts/assert-astro-dev-free.mjs b/scripts/assert-astro-dev-free.mjs new file mode 100644 index 0000000..1584081 --- /dev/null +++ b/scripts/assert-astro-dev-free.mjs @@ -0,0 +1,9 @@ +#!/usr/bin/env node +import {assertAstroDevFree} from '../src/integrations/assertAstroDevFree.mjs'; + +try { + await assertAstroDevFree(); +} catch (err) { + console.error(err instanceof Error ? err.message : err); + process.exit(1); +} diff --git a/scripts/assert-splash-page.mjs b/scripts/assert-splash-page.mjs new file mode 100644 index 0000000..89d5ca1 --- /dev/null +++ b/scripts/assert-splash-page.mjs @@ -0,0 +1,64 @@ +#!/usr/bin/env node +/** + * Built splash homepage must render the Astro page sections β€” not an empty + * collection stub or the 404 body. + * Run after `astro:build` (wired into npm run validate). + */ +import fs from 'node:fs'; +import path from 'node:path'; +import {fileURLToPath} from 'node:url'; + +const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..'); +const htmlPath = path.join(root, 'docs-site', 'index.html'); + +if (!fs.existsSync(htmlPath)) { + console.error('assert-splash-page: missing docs-site/index.html (run astro:build first)'); + process.exit(1); +} + +const html = fs.readFileSync(htmlPath, 'utf8'); + +const required = [ + 'splash', + 'splash__hero', + 'splash__demo', + 'splash__code', + 'mock', + 'data-mock', + 'data-cursor', + 'busk(root,', + 'Scripted cursor demos', + 'really click', + 'Zero dependencies', + '/getting-started/', + 'https://github.com/logfoxai/busker', + 'busker β€” scripted cursor demos that really click', + 'property="og:image" content="https://busker.logfox.ai/og.png"', + 'name="twitter:image" content="https://busker.logfox.ai/og.png"', +]; +for (const needle of required) { + if (!html.includes(needle)) { + console.error(`assert-splash-page: index.html missing expected content: ${needle}`); + process.exit(1); + } +} + +const forbidden = [ + 'not-found-page', + 'not-found-hero', + 'Page not found', +]; +for (const needle of forbidden) { + if (html.includes(needle)) { + console.error(`assert-splash-page: index.html must not include 404 marker: ${needle}`); + process.exit(1); + } +} + +const ogPng = path.join(root, 'docs-site', 'og.png'); +if (!fs.existsSync(ogPng)) { + console.error('assert-splash-page: missing docs-site/og.png (copy publicDir assets/og.png)'); + process.exit(1); +} + +console.log('assert-splash-page: ok'); diff --git a/scripts/clean-astro-cache.mjs b/scripts/clean-astro-cache.mjs new file mode 100644 index 0000000..215bf9a --- /dev/null +++ b/scripts/clean-astro-cache.mjs @@ -0,0 +1,20 @@ +#!/usr/bin/env node +import path from 'node:path'; +import {fileURLToPath} from 'node:url'; +import {astroDevPortsInUse} from '../src/integrations/assertAstroDevFree.mjs'; +import {cleanAstroCache} from '../src/integrations/cleanAstroCache.mjs'; + +const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..'); + +const busy = await astroDevPortsInUse(); + +if (busy.length > 0) { + console.error( + `Refusing to wipe .astro while dev is listening on port(s) ${busy.join(', ')}. ` + + 'Stop astro:dev first β€” deleting the content cache mid-session breaks sidebar slugs.', + ); + process.exit(1); +} + +cleanAstroCache(root); +console.log('clean-astro-cache: wiped .astro and node_modules/.vite'); diff --git a/scripts/sync-fonts.mjs b/scripts/sync-fonts.mjs new file mode 100644 index 0000000..93bbaa0 --- /dev/null +++ b/scripts/sync-fonts.mjs @@ -0,0 +1,67 @@ +#!/usr/bin/env node +/** + * Copy self-hosted Latin woff2 files into assets/fonts/. + * - Inter: @fontsource-variable/inter (resolved from node_modules) + * - JetBrainsMono Nerd Font Mono: committed OFL woff2 (upstream restructured + * away from serving raw files, so the committed copies are the source of + * truth and are only verified here) + * + * Committed copies are served from publicDir (/fonts/…) so docs work without + * relying on node_modules path resolution at runtime. + */ +import fs from 'node:fs'; +import path from 'node:path'; +import {createRequire} from 'node:module'; +import {fileURLToPath} from 'node:url'; + +const require = createRequire(import.meta.url); +const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..'); +const destDir = path.join(root, 'assets/fonts'); + +const fontsource = [ + [ + '@fontsource-variable/inter/files/inter-latin-wght-normal.woff2', + 'inter-latin-wght-normal.woff2', + ], +]; + +const nerdFonts = ['jetbrains-mono-nerd-mono-400.woff2', 'jetbrains-mono-nerd-mono-600.woff2']; + +const retired = [ + 'geist-latin-wght-normal.woff2', + 'outfit-latin-wght-normal.woff2', + 'jetbrains-mono-latin-400-normal.woff2', + 'jetbrains-mono-latin-600-normal.woff2', + 'plus-jakarta-sans-latin-wght-normal.woff2', + 'space-grotesk-latin-wght-normal.woff2', + 'ibm-plex-sans-latin-wght-normal.woff2', + 'ibm-plex-mono-latin-400-normal.woff2', + 'ibm-plex-mono-latin-600-normal.woff2', + 'caveat-latin-600-normal.woff2', +]; + +fs.mkdirSync(destDir, {recursive: true}); + +for (const [pkgPath, filename] of fontsource) { + const src = require.resolve(pkgPath); + const dest = path.join(destDir, filename); + fs.copyFileSync(src, dest); + console.log(`sync-fonts: ${filename}`); +} + +for (const filename of nerdFonts) { + const dest = path.join(destDir, filename); + if (!fs.existsSync(dest)) { + console.error(`sync-fonts: missing committed font ${filename} (re-add from upstream nerd-fonts-woff2)`); + process.exit(1); + } + console.log(`sync-fonts: ${filename} (committed)`); +} + +for (const filename of retired) { + const dest = path.join(destDir, filename); + if (fs.existsSync(dest)) { + fs.unlinkSync(dest); + console.log(`sync-fonts: removed ${filename}`); + } +} diff --git a/scripts/write-cs-pagefind-shim.mjs b/scripts/write-cs-pagefind-shim.mjs new file mode 100644 index 0000000..3be8e1b --- /dev/null +++ b/scripts/write-cs-pagefind-shim.mjs @@ -0,0 +1,31 @@ +import fs from 'node:fs/promises'; +import path from 'node:path'; +import {fileURLToPath} from 'node:url'; +import * as esbuild from 'esbuild'; + +const engineSpecifier = '../pagefind/pagefind.js'; +const root = path.join(path.dirname(fileURLToPath(import.meta.url)), '..'); +const outDir = path.join(root, 'docs-site'); +const enginePath = path.join(outDir, 'pagefind', 'pagefind.js'); +const outfile = path.join(outDir, 'cs-pagefind', 'pagefind.js'); +const entry = path.join(root, 'src', 'cs-pagefind', 'pagefind.ts'); + +try { + await fs.access(enginePath); +} catch { + console.error('write-cs-pagefind-shim: no pagefind index at', enginePath); + process.exit(1); +} + +await esbuild.build({ + entryPoints: [entry], + bundle: true, + format: 'esm', + platform: 'browser', + outfile, + // Keep the real engine as a runtime import next to the shim. + external: [engineSpecifier], + logLevel: 'silent', +}); + +console.log('write-cs-pagefind-shim: wrote', path.relative(root, outfile)); diff --git a/scripts/write-cs-pagefind-shim.ts b/scripts/write-cs-pagefind-shim.ts new file mode 100644 index 0000000..009cf24 --- /dev/null +++ b/scripts/write-cs-pagefind-shim.ts @@ -0,0 +1,43 @@ +import fs from 'node:fs/promises'; +import path from 'node:path'; +import {fileURLToPath} from 'node:url'; +import * as esbuild from 'esbuild'; + +const engineSpecifier = '../pagefind/pagefind.js'; + +export async function writeCsPagefindShim(root?: string): Promise { + const repoRoot = root ?? path.join(path.dirname(fileURLToPath(import.meta.url)), '..'); + const outDir = path.join(repoRoot, 'docs-site'); + const enginePath = path.join(outDir, 'pagefind', 'pagefind.js'); + const outfile = path.join(outDir, 'cs-pagefind', 'pagefind.js'); + const entry = path.join(repoRoot, 'src', 'cs-pagefind', 'pagefind.ts'); + + try { + await fs.access(enginePath); + } catch { + console.error('write-cs-pagefind-shim: no pagefind index at', enginePath); + process.exit(1); + } + + await esbuild.build({ + entryPoints: [entry], + bundle: true, + format: 'esm', + platform: 'browser', + outfile, + // Keep the real engine as a runtime import next to the shim. + external: [engineSpecifier], + logLevel: 'silent', + }); + + console.log('write-cs-pagefind-shim: wrote', path.relative(repoRoot, outfile)); +} + +const isMain = process.argv[1] && fileURLToPath(import.meta.url) === path.resolve(process.argv[1]); + +if (isMain) { + writeCsPagefindShim().catch((err: unknown) => { + console.error(err); + process.exit(1); + }); +} diff --git a/skills/busker/SKILL.md b/skills/busker/SKILL.md new file mode 100644 index 0000000..508a2c2 --- /dev/null +++ b/skills/busker/SKILL.md @@ -0,0 +1,24 @@ +--- +name: busker +description: Rules for building or changing a busker demo β€” scripted cursor animations over a fake product UI. Use when editing a routine, a mock's markup, or busker itself. +disable-model-invocation: true +--- + +# busker + +The [README](https://raw.githubusercontent.com/logfoxai/busker/main/README.md) indexes the guides (Β§ Contents). Read the ones you need before changing a routine. + +1. **Do not time page changes.** If a click causes it, use `steps` and let the real click do it. A scene that changes on a timer will drift the first time a duration changes. +2. **`routes` is the only list of clickable things.** Adding a route wires the scene change *and* the pointer affordance. Never hand-maintain a parallel CSS list of `cursor: pointer` selectors. +3. **Routes are only for scenes.** Modals, filters, and toggles belong in your own click handlers β€” busker clicked the element, your code does the rest. +4. **Put time in `wait`, not `moveFor`.** `wait` is reading time for whatever the last press opened. A slow glide reads as lag. +5. **`press` in `moves` does not click.** Hand-timed mode animates the press only. If you want a real click, it is a `step`. +6. **Never use `event.isTrusted` to tell busker's clicks from a visitor's.** Scripted clicks from any source are untrusted. Busker already tracks its own. +7. **Set `freezeAt`** to a frame that shows the point of the demo. It is what people on `prefers-reduced-motion` see. +8. **Targets are selectors, resolved every frame.** Do not pass coordinates for things that exist in the DOM β€” the cursor should follow the button when the layout changes. +9. **Call `destroy()` on unmount.** It removes every listener, observer, and class. +10. **No tests for CSS.** Cursor look, ripple, and hint styling are judged in a browser, not asserted as strings. + +## Changing busker itself + +Pure timing, easing, interpolation, and text belong in `src/timeline.ts` and get exhaustive unit tests. `src/busk.ts` is the DOM adapter and is tested through happy-dom with a hand-driven clock. Keep new logic on the pure side of that line where you can. diff --git a/src/busk.spec.ts b/src/busk.spec.ts new file mode 100644 index 0000000..e6afe6d --- /dev/null +++ b/src/busk.spec.ts @@ -0,0 +1,238 @@ +import {test} from 'kizu'; +import {GlobalRegistrator} from '@happy-dom/global-registrator'; +import {busk} from './busk'; +import type {Busker, Routine} from './types'; + +GlobalRegistrator.register({url: 'https://busker.test', width: 1024, height: 768}); + +const MOCK = ` + +

home

+
+ +`; + +interface Stage { + root: HTMLElement; + show: Busker; + /** Let the show know it is on screen. */ + startShow(): void; + /** Run the loop forward. */ + tick(ms: number): void; + /** A visitor click, as opposed to one the show made. */ + clickAsVisitor(selector: string): void; +} + +const observers: FakeObserver[] = []; + +class FakeObserver implements IntersectionObserver { + + readonly root = null; + readonly rootMargin = ''; + readonly thresholds: readonly number[] = []; + + constructor(private readonly callback: IntersectionObserverCallback) { + observers.push(this); + } + + observe(): void {} + unobserve(): void {} + disconnect(): void {} + takeRecords(): IntersectionObserverEntry[] { + return []; + } + + fire(): void { + this.callback([], this); + } + +} + +/** + * happy-dom does no layout, so every rect is zero and the show would think it + * is off screen. Give the root a size; nothing here depends on where the + * cursor lands (that is covered in timeline.spec.ts). + */ +function stage(routine: Routine): Stage { + document.body.innerHTML = `
${MOCK}
`; + + const root = document.getElementById('root'); + + if (!root) throw new Error('no root'); + + root.getBoundingClientRect = (): DOMRect => new DOMRect(0, 0, 800, 600); + + observers.length = 0; + + let now = 0; + let queued: FrameRequestCallback[] = []; + + globalThis.IntersectionObserver = FakeObserver; + globalThis.requestAnimationFrame = (cb: FrameRequestCallback): number => queued.push(cb); + globalThis.cancelAnimationFrame = (): void => {}; + performance.now = (): number => now; + + const show = busk(root, routine); + + return { + root, + show, + startShow: () => observers[0].fire(), + tick: (ms): void => { + now += ms; + + const due = queued; + + queued = []; + for (const cb of due) cb(now); + }, + clickAsVisitor: (selector) => root.querySelector(selector)?.click(), + }; +} + +const routine: Routine = { + scene: 'home', + steps: [ + {click: '[data-nav-item="alerts"]', moveFor: 100, dwell: 0}, + {click: '[data-row="p0"]', wait: 100, moveFor: 100, dwell: 0}, + ], + routes: [ + {click: '[data-nav-item="home"]', scene: 'home'}, + {click: '[data-nav-item="alerts"]', scene: 'list'}, + ], +}; + +test('the show clicks for real, so the mock changes through its own handlers', (assert) => { + + const {root, startShow, tick} = stage(routine); + + assert.equal(root.querySelector('[data-scene="home"]')?.classList.contains('is-active'), true); + + startShow(); + tick(350); + + assert.equal(root.querySelector('[data-scene="list"]')?.classList.contains('is-active'), true); + assert.equal(root.querySelector('[data-nav-item="alerts"]')?.classList.contains('is-active'), true); + +}); + +test('the page holds still until the press lifts, so the click reads first', (assert) => { + + const {root, startShow, tick} = stage(routine); + + startShow(); + + // The cursor has arrived and gone down on the nav item. Changing the scene + // now would take the button away mid-press, before the click could read. + tick(150); + assert.equal(root.querySelector('[data-scene="home"]')?.classList.contains('is-active'), true); + + tick(200); + assert.equal(root.querySelector('[data-scene="list"]')?.classList.contains('is-active'), true); + +}); + +test('a routine that ends on a click still lands it', (assert) => { + + const {root, startShow, tick} = stage({ + ...routine, + steps: [{click: '[data-nav-item="alerts"]', moveFor: 100, dwell: 0}], + }); + + startShow(); + tick(350); + + assert.equal(root.querySelector('[data-scene="list"]')?.classList.contains('is-active'), true); + +}); + +test('the show does not mistake its own click for a visitor taking over', (assert) => { + + const {root, startShow, tick} = stage(routine); + + startShow(); + tick(350); + + assert.equal(root.classList.contains('is-aside'), false); + +}); + +test('a visitor click stops the show for good', (assert) => { + + const {root, startShow, tick, clickAsVisitor} = stage(routine); + + startShow(); + clickAsVisitor('[data-nav-item="alerts"]'); + + assert.equal(root.classList.contains('is-aside'), true); + assert.equal(root.querySelector('[data-scene="list"]')?.classList.contains('is-active'), true); + + // The loop is over: the second step never presses. + tick(1000); + assert.equal(root.querySelector('[data-scene="list"]')?.classList.contains('is-active'), true); + +}); + +test('a visitor click on nothing lights up what is clickable', (assert) => { + + const {root, startShow, clickAsVisitor} = stage(routine); + + startShow(); + clickAsVisitor('[data-scene="home"] p'); + + assert.equal(root.querySelector('[data-nav-item="alerts"]')?.classList.contains('is-hint'), true); + +}); + +test('every clickable thing looks clickable', (assert) => { + + const {root} = stage(routine); + + assert.equal(root.querySelector('[data-nav-item="home"]')?.classList.contains('is-interactive'), true); + assert.equal(root.querySelector('[data-scene="home"] p')?.classList.contains('is-interactive'), false); + +}); + +test('each beat presses once, and the loop starts over from the top', (assert) => { + + let clicks = 0; + const {root, startShow, tick} = stage(routine); + + root.querySelector('[data-nav-item="alerts"]')?.addEventListener('click', () => { + clicks += 1; + }); + + startShow(); + tick(350); + assert.equal(clicks, 1); + + // Still in the same pass β€” the beat must not fire again on every frame. + tick(50); + tick(50); + assert.equal(clicks, 1); + + // Past the end of the routine: back to the top, and it presses again. + tick(600); + assert.equal(root.querySelector('[data-scene="home"]')?.classList.contains('is-active'), true); + tick(350); + assert.equal(clicks, 2); + +}); + +test('destroy puts the mock back the way it was found', (assert) => { + + const {root, show, clickAsVisitor} = stage(routine); + + show.destroy(); + + assert.equal(root.classList.contains('busker'), false); + assert.equal(root.querySelector('[data-nav-item="home"]')?.classList.contains('is-interactive'), false); + + // The listener is gone too, so clicks fall through to the page. + clickAsVisitor('[data-nav-item="alerts"]'); + assert.equal(root.classList.contains('is-aside'), false); + +}); diff --git a/src/busk.ts b/src/busk.ts new file mode 100644 index 0000000..de400f8 --- /dev/null +++ b/src/busk.ts @@ -0,0 +1,332 @@ +import { + PRESS_MS, + RING_MS, + compile, + countdownText, + isOn, + moveIndexAt, + positionAt, + typedText, +} from './timeline'; +import type {Busker, Move, Point, Routine} from './types'; + +const DEFAULT_START: Point = [0.5, 0.5]; +/** IntersectionObserver ratios are floating point; 1 is rarely exactly 1. */ +const VISIBILITY_SLACK = 0.001; +/** How long a missed click keeps the clickable things lit up. */ +const HINT_MS = 1500; + +/** How much of `el` is inside the viewport, as a fraction of its own area. */ +function visibleFraction(el: Element): number { + const rect = el.getBoundingClientRect(); + + if (rect.width <= 0 || rect.height <= 0) return 0; + + const w = Math.min(rect.right, window.innerWidth) - Math.max(rect.left, 0); + const h = Math.min(rect.bottom, window.innerHeight) - Math.max(rect.top, 0); + + if (w <= 0 || h <= 0) return 0; + + return (w * h) / (rect.width * rect.height); +} + +/** + * Put on a show inside `root`. + * + * See the docs for the markup contract: `[data-cursor]` for the pointer, + * `[data-scene]` for the pages, `[data-nav-item]` for the nav. + */ +export function busk(root: HTMLElement, routine: Routine): Busker { + const cursor = root.querySelector('[data-cursor]'); + const scenes = new Map(); + const navItems = new Map(); + + root.querySelectorAll('[data-scene]').forEach((el) => { + scenes.set(el.dataset.scene as string, el); + }); + root.querySelectorAll('[data-nav-item]').forEach((el) => { + navItems.set(el.dataset.navItem as string, el); + }); + + // Script mode presses for real; timeline mode only animates the press. + const script = routine.steps ? compile(routine.steps) : null; + const moves = script?.moves ?? routine.moves ?? []; + const duration = routine.duration ?? script?.duration ?? 0; + const start = routine.start ?? DEFAULT_START; + const routes = routine.routes ?? []; + const visibility = routine.visibility ?? 1; + + /** Selectors are resolved once; the elements they point at may not exist. */ + const found = (items: T[] | undefined): (T & {el: HTMLElement})[] => + (items ?? []).flatMap((item) => { + const el = root.querySelector(item.target); + + return el ? [{...item, el}] : []; + }); + + const toggles = found(routine.toggles); + const typings = found(routine.typing); + const countdowns = found(routine.countdowns); + + /** Where a move target sits, in px relative to the root's top-left. */ + function resolve(target: string | Point): Point | null { + if (Array.isArray(target)) return [target[0] * root.clientWidth, target[1] * root.clientHeight]; + + const el = root.querySelector(target); + + if (!el) return null; + + const rootRect = root.getBoundingClientRect(); + const rect = el.getBoundingClientRect(); + + return [ + rect.left - rootRect.left + rect.width / 2, + rect.top - rootRect.top + rect.height / 2, + ]; + } + + let elapsed = 0; + let last = 0; + let rafId = 0; + let playing = false; + let aside = false; + let destroyed = false; + /** Steps already pressed this time round, so each one fires exactly once. */ + const pressed = new Set(); + /** Set while the show clicks for itself, so it does not mistake that for a visitor. */ + let clickingItself = false; + + // What the DOM already shows, so a 60fps loop is not writing the same values. + let shownScene: string | null = null; + let shownHover: Element | null = null; + let shownPressing = false; + let shownRinging = false; + const shownToggle = new WeakMap(); + const shownText = new WeakMap(); + + /** Show one scene and light up its nav item. Only ever caused by a click. */ + function activate(scene: string | null): void { + if (scene === shownScene) return; + shownScene = scene; + scenes.forEach((el, key) => el.classList.toggle('is-active', key === scene)); + + const nav = scene ? scenes.get(scene)?.dataset.nav : undefined; + + navItems.forEach((el, key) => el.classList.toggle('is-active', key === nav)); + } + + /** + * Really click the steps whose press has lifted, each once per loop. The + * click lands at the end of the stroke, the way a real one does, so the + * cursor reads on the element before the click takes it away. + */ + function press(t: number): void { + if (!script) return; + + moves.forEach((move, i) => { + if (move.press === undefined || t < move.press + PRESS_MS || pressed.has(i)) return; + pressed.add(i); + if (typeof move.to !== 'string') return; + + const el = root.querySelector(move.to); + + if (!el) return; + + clickingItself = true; + try { + el.click(); + } finally { + clickingItself = false; + } + }); + } + + function drawCursor(move: Move | null, index: number, t: number): void { + if (!cursor) return; + + const from = resolve(index > 0 ? moves[index - 1].to : start); + const to = resolve(move ? move.to : start); + + if (from && to) { + const [x, y] = positionAt(from, to, move, t); + + cursor.style.left = `${x}px`; + cursor.style.top = `${y}px`; + } + + // Hovering starts the moment the cursor lands and ends when it sets off again. + const hover = move && typeof move.to === 'string' && t >= move.until + ? root.querySelector(move.to) + : null; + + if (hover !== shownHover) { + shownHover?.classList.remove('is-hover'); + shownHover = hover; + hover?.classList.add('is-hover'); + } + + const pressing = move?.press !== undefined && t >= move.press && t < move.press + PRESS_MS; + + if (pressing !== shownPressing) { + shownPressing = pressing; + cursor.classList.toggle('is-pressing', pressing); + } + + const ringing = move?.press !== undefined && t >= move.press && t < move.press + RING_MS; + + if (ringing !== shownRinging) { + shownRinging = ringing; + cursor.classList.toggle('is-ringing', ringing); + } + } + + function write(el: HTMLElement, text: string): void { + if (shownText.get(el) === text) return; + shownText.set(el, text); + el.textContent = text; + } + + function render(t: number): void { + for (const toggle of toggles) { + const key = `${toggle.class}:${isOn(toggle, t)}`; + + if (shownToggle.get(toggle.el) === key) continue; + shownToggle.set(toggle.el, key); + toggle.el.classList.toggle(toggle.class, isOn(toggle, t)); + } + + for (const typing of typings) write(typing.el, typedText(typing, t)); + for (const countdown of countdowns) write(countdown.el, countdownText(countdown, t)); + + const index = moveIndexAt(moves, t); + + drawCursor(index >= 0 ? moves[index] : null, index, t); + } + + function frame(now: number): void { + if (!playing) return; + + const next = elapsed + (now - last); + + // Back to the top. Every pass restarts at 0 rather than carrying the + // overshoot, so a stalled frame (backgrounded tab, slow paint) cannot + // drop the start of the routine or fire a burst of catch-up clicks. + if (next >= duration) { + pressed.clear(); + activate(routine.scene ?? null); + elapsed = 0; + } else { + elapsed = next; + } + + last = now; + render(elapsed); + press(elapsed); + rafId = requestAnimationFrame(frame); + } + + const reducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches; + + function play(): void { + if (playing || aside || destroyed || reducedMotion || duration <= 0) return; + playing = true; + last = performance.now(); + rafId = requestAnimationFrame(frame); + } + + function pause(): void { + playing = false; + cancelAnimationFrame(rafId); + } + + function stepAside(): void { + if (aside) return; + aside = true; + pause(); + root.classList.add('is-aside'); + shownHover?.classList.remove('is-hover'); + shownHover = null; + shownPressing = false; + shownRinging = false; + } + + const onClick = (e: Event): void => { + if (destroyed) return; + + const hit = routes.find((route) => { + const el = (e.target as Element).closest(route.click); + + return el !== null && root.contains(el); + }); + + if (hit) activate(hit.scene); + + // The show's own clicks route the mock but must not take it away from itself. + if (clickingItself) return; + + stepAside(); + + // They clicked something dead β€” show them what is not. + if (hit) return; + + const targets = new Set(); + + for (const route of routes) root.querySelectorAll(route.click).forEach((el) => targets.add(el)); + targets.forEach((el) => el.classList.add('is-hint')); + setTimeout(() => targets.forEach((el) => el.classList.remove('is-hint')), HINT_MS); + }; + + const onVisibilityChange = (): void => { + if (document.hidden) pause(); + else if (visibleFraction(root) >= visibility - VISIBILITY_SLACK) play(); + }; + + function destroy(): void { + if (destroyed) return; + destroyed = true; + pause(); + observer?.disconnect(); + root.removeEventListener('click', onClick); + document.removeEventListener('visibilitychange', onVisibilityChange); + shownHover?.classList.remove('is-hover'); + shownHover = null; + root.querySelectorAll('.is-hint').forEach((el) => el.classList.remove('is-hint')); + root.querySelectorAll('.is-interactive').forEach((el) => el.classList.remove('is-interactive')); + root.classList.remove('busker', 'is-aside'); + cursor?.classList.remove('is-visible', 'is-pressing', 'is-ringing'); + } + + root.classList.add('busker'); + + // Anything the show can click, a visitor can click β€” so the pointer and the + // wiring come from the same list, with no CSS to keep in step by hand. + if (routes.length) { + for (const route of routes) { + root.querySelectorAll(route.click).forEach((el) => el.classList.add('is-interactive')); + } + root.addEventListener('click', onClick); + } + + activate(routine.scene ?? null); + + const observer = reducedMotion + ? undefined + : new IntersectionObserver( + () => { + if (visibleFraction(root) >= visibility - VISIBILITY_SLACK) play(); + else pause(); + }, + {threshold: [...new Set([0, visibility, 1])]}, + ); + + if (reducedMotion) { + render(routine.freezeAt ?? 0); + } else { + observer?.observe(root); + document.addEventListener('visibilitychange', onVisibilityChange); + cursor?.classList.add('is-visible'); + render(elapsed); + } + + return {duration, play, pause, stepAside, destroy}; +} diff --git a/src/components/CodeBlockTitles.astro b/src/components/CodeBlockTitles.astro new file mode 100644 index 0000000..0579c05 --- /dev/null +++ b/src/components/CodeBlockTitles.astro @@ -0,0 +1,6 @@ + diff --git a/src/components/NotFoundHero.astro b/src/components/NotFoundHero.astro new file mode 100644 index 0000000..ddcf160 --- /dev/null +++ b/src/components/NotFoundHero.astro @@ -0,0 +1,24 @@ +--- +/** + * Standalone 404 hero β€” rendered from src/pages/404.astro. + */ +import {LinkButton} from '@astrojs/starlight/components'; + +const PAGE_TITLE_ID = '_top'; +--- + +
+ +

Page not found

+

+ That URL isn’t part of the busker docs. Head home, or jump into getting started. +

+
+ + Home + + + Get started + +
+
diff --git a/src/components/NotFoundPage.astro b/src/components/NotFoundPage.astro new file mode 100644 index 0000000..3de36b2 --- /dev/null +++ b/src/components/NotFoundPage.astro @@ -0,0 +1,9 @@ +--- +import NotFoundHero from './NotFoundHero.astro'; +import NotFoundStyles from './NotFoundStyles.astro'; +--- + + +
+ +
diff --git a/src/components/NotFoundStyles.astro b/src/components/NotFoundStyles.astro new file mode 100644 index 0000000..b1efb51 --- /dev/null +++ b/src/components/NotFoundStyles.astro @@ -0,0 +1,4 @@ +--- +/** 404-only styles β€” separate from splash homepage CSS. */ +import '../styles/not-found.css'; +--- diff --git a/src/components/SearchModalPolish.astro b/src/components/SearchModalPolish.astro new file mode 100644 index 0000000..c6bb8dd --- /dev/null +++ b/src/components/SearchModalPolish.astro @@ -0,0 +1,497 @@ +--- +/** + * Idle / empty / in-flight polish for Starlight’s Pagefind search dialog. + * Injects suggestion chips when idle, GitHub help on zero results, a + * shimmer skeleton while a query is running, and arrow/Enter keyboard nav. + */ +const githubIssues = 'https://github.com/logfoxai/busker/issues'; +--- + + + + + + + + diff --git a/src/components/SiteFooter.astro b/src/components/SiteFooter.astro new file mode 100644 index 0000000..4d490ea --- /dev/null +++ b/src/components/SiteFooter.astro @@ -0,0 +1,42 @@ +--- +import {Icon} from '@astrojs/starlight/components'; +import './site-footer.css'; +--- + + + + diff --git a/src/components/codeBlockTitles.spec.ts b/src/components/codeBlockTitles.spec.ts new file mode 100644 index 0000000..e82d433 --- /dev/null +++ b/src/components/codeBlockTitles.spec.ts @@ -0,0 +1,113 @@ +import {test} from 'kizu'; +import { + COPY_FEEDBACK_MS, + copyButtonContent, + copyButtonMarkup, + defaultTitleFromLang, + ecDataCodeToText, + fileKindFromName, + isFilePathTitle, + kindFromTitle, + tryCopyText, +} from './codeBlockTitles'; + +test('copyButtonContent idle and copied labels', (assert) => { + assert.equal(copyButtonContent(false), {label: 'Copy', state: 'idle'}); + assert.equal(copyButtonContent(true), {label: 'Copied!', state: 'copied'}); +}); + +test('copyButtonContent keeps a custom idle label after Copied!', (assert) => { + assert.equal(copyButtonContent(false, 'Copy curl'), {label: 'Copy curl', state: 'idle'}); + assert.equal(copyButtonContent(true, 'Copy curl'), {label: 'Copied!', state: 'copied'}); +}); + +test('copyButtonMarkup is the docs chrome idle control', (assert) => { + const html = copyButtonMarkup({copyTarget: 'cursor-mcp-config'}); + assert.equal(html.includes('class="cs-copy-btn"'), true); + assert.equal(html.includes('cs-copy-icon'), true); + assert.equal(html.includes('cs-copy-label'), true); + assert.equal(html.includes('Copy to clipboard'), true); + assert.equal(html.includes('data-copy-target="cursor-mcp-config"'), true); +}); + +test('copyButtonMarkup accepts id, copy value, and a custom idle label', (assert) => { + const html = copyButtonMarkup({ + id: 'copy-curl-try', + copyValue: 'http://127.0.0.1:3000/v1/mcp', + label: 'Copy curl', + }); + assert.equal(html.includes('id="copy-curl-try"'), true); + assert.equal(html.includes('data-copy="http://127.0.0.1:3000/v1/mcp"'), true); + assert.equal(html.includes('data-cs-copy-idle-label="Copy curl"'), true); + assert.equal(html.includes('>Copy curl'), true); + assert.equal(html.includes('class="btn'), false); +}); + +test('COPY_FEEDBACK_MS is 1500ms', (assert) => { + assert.equal(COPY_FEEDBACK_MS, 1500); +}); + +test('ecDataCodeToText turns EC delimiter into newlines', (assert) => { + assert.equal(ecDataCodeToText(`a\u007fb`), 'a\nb'); + assert.equal(ecDataCodeToText('plain'), 'plain'); +}); + +test('tryCopyText returns true only when write succeeds', async (assert) => { + assert.equal(await tryCopyText('hi', {writeText: async () => undefined}), true); + assert.equal( + await tryCopyText('hi', { + writeText: async () => { + throw new Error('denied'); + }, + }), + false, + ); + assert.equal(await tryCopyText('', {writeText: async () => undefined}), false); +}); + +test('defaultTitleFromLang maps fence langs to tab labels', (assert) => { + assert.equal(defaultTitleFromLang('typescript'), 'typescript'); + assert.equal(defaultTitleFromLang('tsx'), 'tsx'); + assert.equal(defaultTitleFromLang('javascript'), 'javascript'); + assert.equal(defaultTitleFromLang('json'), 'json'); + assert.equal(defaultTitleFromLang('text'), 'text'); + assert.equal(defaultTitleFromLang(''), 'code'); +}); + +test('isFilePathTitle distinguishes filenames from bare lang titles', (assert) => { + assert.equal(isFilePathTitle('server/routes.ts'), true); + assert.equal(isFilePathTitle('getProductById.ts'), true); + assert.equal(isFilePathTitle('typescript'), false); + assert.equal(isFilePathTitle('bash'), false); + assert.equal(isFilePathTitle('SKILL.md'), true); +}); + +test('kindFromTitle maps langs and filenames to chips', (assert) => { + assert.equal(kindFromTitle('typescript'), 'TS'); + assert.equal(kindFromTitle('tsx'), 'TSX'); + assert.equal(kindFromTitle('server/app.ts'), 'TS'); + assert.equal(kindFromTitle('text'), null); +}); + +test('fileKindFromName maps common extensions', (assert) => { + assert.equal(fileKindFromName('getProductById.ts'), 'TS'); + assert.equal(fileKindFromName('App.tsx'), 'TSX'); + assert.equal(fileKindFromName('index.js'), 'JS'); + assert.equal(fileKindFromName('util.mjs'), 'JS'); + assert.equal(fileKindFromName('package.json'), 'JSON'); + assert.equal(fileKindFromName('README.md'), 'MD'); + assert.equal(fileKindFromName('styles.css'), 'CSS'); + assert.equal(fileKindFromName('main.py'), 'PY'); + assert.equal(fileKindFromName('main.go'), 'GO'); +}); + +test('fileKindFromName is case-insensitive and path-aware', (assert) => { + assert.equal(fileKindFromName('server/routes/Foo.TS'), 'TS'); + assert.equal(fileKindFromName('nested/dir/app.tsx'), 'TSX'); +}); + +test('fileKindFromName returns null for unknown extensions', (assert) => { + assert.equal(fileKindFromName('Makefile'), null); + assert.equal(fileKindFromName('notes.txt'), null); + assert.equal(fileKindFromName(''), null); +}); diff --git a/src/components/codeBlockTitles.ts b/src/components/codeBlockTitles.ts new file mode 100644 index 0000000..d24b3f3 --- /dev/null +++ b/src/components/codeBlockTitles.ts @@ -0,0 +1,326 @@ +export const COPY_FEEDBACK_MS = 1500; + +export function copyButtonContent( + isCopied: boolean, + idleLabel = 'Copy', +): {label: string; state: 'idle' | 'copied'} { + return isCopied + ? {label: 'Copied!', state: 'copied'} + : {label: idleLabel, state: 'idle'}; +} + +/** Expressive Code joins multi-line `data-code` with U+007F. */ +export function ecDataCodeToText(dataCode: string): string { + return dataCode.replace(/\u007f/g, '\n'); +} + +export type TryCopyTextDeps = { + writeText?: (text: string) => Promise +}; + +/** + * Copy text to the clipboard: succeed β†’ true; failure β†’ false. + * Inject `writeText` in tests; browser path uses `navigator.clipboard.writeText`. + */ +export async function tryCopyText(text: string, deps: TryCopyTextDeps = {}): Promise { + if (!text) { + return false; + } + + const writeText = deps.writeText + ?? (typeof navigator !== 'undefined' && navigator.clipboard?.writeText + ? (value: string): Promise => navigator.clipboard.writeText(value) + : undefined); + + if (!writeText) { + return false; + } + + try { + await writeText(text); + return true; + } catch { + return false; + } +} + +/** File-extension β†’ short language label for code-block chrome. */ +const EXT_KIND: ReadonlyArray = [ + [/\.tsx$/i, 'TSX'], + [/\.ts$/i, 'TS'], + [/\.jsx$/i, 'JSX'], + [/\.(?:js|mjs|cjs)$/i, 'JS'], + [/\.json$/i, 'JSON'], + [/\.(?:md|mdx)$/i, 'MD'], + [/\.(?:css|scss)$/i, 'CSS'], + [/\.py$/i, 'PY'], + [/\.go$/i, 'GO'], + [/\.(?:rs)$/i, 'RS'], + [/\.(?:ya?ml)$/i, 'YML'], +]; + +export function fileKindFromName(name: string): string | null { + for (const [re, kind] of EXT_KIND) { + if (re.test(name)) { + return kind; + } + } + return null; +} + +/** Fence language β†’ tab label when the block has no `title="…"`. */ +export function defaultTitleFromLang(lang: string): string { + const id = lang.trim().toLowerCase(); + return id || 'code'; +} + +/** True when the title looks like a path/filename (not a bare language id). */ +export function isFilePathTitle(title: string): boolean { + return title.includes('/') || /\.[a-z0-9]+$/i.test(title); +} + +/** Language id / filename β†’ short chip (TS, JS, …). */ +export function kindFromTitle(title: string): string | null { + if (isFilePathTitle(title)) { + const base = title.includes('/') ? title.slice(title.lastIndexOf('/') + 1) : title; + return fileKindFromName(base); + } + const lang = title.trim().toLowerCase(); + const LANG_KIND: Record = { + typescript: 'TS', + ts: 'TS', + tsx: 'TSX', + javascript: 'JS', + js: 'JS', + jsx: 'JSX', + mjs: 'JS', + cjs: 'JS', + json: 'JSON', + markdown: 'MD', + md: 'MD', + mdx: 'MD', + css: 'CSS', + scss: 'CSS', + python: 'PY', + py: 'PY', + go: 'GO', + rust: 'RS', + rs: 'RS', + yaml: 'YML', + yml: 'YML', + }; + return LANG_KIND[lang] ?? null; +} + +const COPY_ICON_SVG = + ''; + +const CHECK_ICON_SVG = + ''; + +function idleLabelOf(button: HTMLButtonElement): string { + return button.dataset.csCopyIdleLabel || copyButtonContent(false).label; +} + +function escapeAttr(value: string): string { + return value + .replace(/&/g, '&') + .replace(/"/g, '"') + .replace(/>(); + +/** Green β€œCopied!” then restore the idle label after COPY_FEEDBACK_MS. */ +export function showCopySuccess(button: HTMLButtonElement): void { + paintCopyButton(button, true); + const previous = copyResetTimers.get(button); + if (previous !== undefined) { + clearTimeout(previous); + } + copyResetTimers.set(button, setTimeout(() => { + paintCopyButton(button, false); + copyResetTimers.delete(button); + }, COPY_FEEDBACK_MS)); +} + +/** Idle chrome used by docs EC and explorer MCP / Try it copy controls. */ +export function copyButtonMarkup(attrs: { + copyTarget?: string + copyValue?: string + id?: string + label?: string +} = {}): string { + const {label} = copyButtonContent(false, attrs.label); + const extra = [ + attrs.id === undefined ? '' : ` id="${escapeAttr(attrs.id)}"`, + attrs.copyTarget === undefined ? '' : ` data-copy-target="${escapeAttr(attrs.copyTarget)}"`, + attrs.copyValue === undefined ? '' : ` data-copy="${escapeAttr(attrs.copyValue)}"`, + attrs.label === undefined ? '' : ` data-cs-copy-idle-label="${escapeAttr(attrs.label)}"`, + ].join(''); + return ( + `` + ); +} + +function createKindMark(kind: string): HTMLElement { + const badge = document.createElement('span'); + badge.className = 'cs-code-kind'; + badge.dataset.kind = kind; + badge.textContent = kind; + badge.setAttribute('aria-hidden', 'true'); + return badge; +} + +function ensureDots(header: HTMLElement): void { + let dots = header.querySelector(':scope > .cs-code-dots'); + if (!(dots instanceof HTMLElement)) { + dots = document.createElement('span'); + dots.className = 'cs-code-dots'; + dots.setAttribute('aria-hidden', 'true'); + header.prepend(dots); + } + // One element + CSS mask (shared by terminal + editor frames) β€” no child spans + dots.replaceChildren(); +} + +/** Park the EC copy control in the chrome row; icon + β€œCopy” β†’ green check + β€œCopied!” for COPY_FEEDBACK_MS. */ +function ensureCopyInHeader(frame: HTMLElement, header: HTMLElement): void { + const copy = frame.querySelector(':scope > .copy') ?? header.querySelector(':scope > .copy'); + if (!(copy instanceof HTMLElement)) { + return; + } + if (copy.parentElement !== header) { + header.append(copy); + } + + const button = copy.querySelector(':scope > button'); + if (!(button instanceof HTMLButtonElement) || button.dataset.csCopyWired === '1') { + return; + } + + button.dataset.csCopyWired = '1'; + button.classList.add('cs-copy-btn'); + button.type = 'button'; + paintCopyButton(button, false); + + button.addEventListener('click', (event) => { + // Own the clipboard path so we only show β€œCopied!” after a real success. + event.preventDefault(); + event.stopPropagation(); + + const text = ecDataCodeToText(button.dataset.code ?? ''); + void tryCopyText(text).then((ok) => { + if (ok) { + showCopySuccess(button); + } + }); + }); +} + +function buildTitleNodes(text: string): Node[] { + const kind = kindFromTitle(text); + const nodes: Node[] = []; + + if (kind) { + nodes.push(createKindMark(kind)); + } + + const name = document.createElement('span'); + name.className = 'cs-code-name'; + + if (!isFilePathTitle(text)) { + const label = document.createElement('span'); + label.className = 'cs-code-file'; + label.textContent = text; + name.append(label); + nodes.push(name); + return nodes; + } + + const slash = text.lastIndexOf('/'); + const basename = slash === -1 ? text : text.slice(slash + 1); + + if (slash !== -1) { + const dir = document.createElement('span'); + dir.className = 'cs-code-path'; + dir.textContent = text.slice(0, slash + 1); + name.append(dir); + } + + const file = document.createElement('span'); + file.className = 'cs-code-file'; + file.textContent = basename; + name.append(file); + nodes.push(name); + + return nodes; +} + +function titleSourceText(title: HTMLElement): string { + const path = title.querySelector('.cs-code-path')?.textContent ?? ''; + const file = title.querySelector('.cs-code-file')?.textContent ?? ''; + if (path || file) { + return `${path}${file}`; + } + return (title.textContent ?? '').trim(); +} + +function enhanceTitle(frame: HTMLElement): void { + const header = frame.querySelector(':scope > .header'); + const title = frame.querySelector(':scope > .header .title'); + if (!(header instanceof HTMLElement) || !(title instanceof HTMLElement)) { + return; + } + + ensureDots(header); + ensureCopyInHeader(frame, header); + + if (frame.classList.contains('is-terminal')) { + title.dataset.csEnhanced = '1'; + return; + } + + if (title.dataset.csEnhanced === '1' && title.querySelector('.cs-code-name')) { + return; + } + + const text = titleSourceText(title); + if (!text) { + title.dataset.csEnhanced = '1'; + return; + } + + title.replaceChildren(...buildTitleNodes(text)); + title.dataset.csEnhanced = '1'; +} + +export function enhanceCodeBlockTitles(root: ParentNode = document): void { + const frames = Array.from( + root.querySelectorAll('.expressive-code .frame.has-title, .expressive-code .frame.is-terminal'), + ); + for (const frame of frames) { + if (frame instanceof HTMLElement) { + enhanceTitle(frame); + } + } +} diff --git a/src/components/docsSearchQuery.spec.ts b/src/components/docsSearchQuery.spec.ts new file mode 100644 index 0000000..2370f4c --- /dev/null +++ b/src/components/docsSearchQuery.spec.ts @@ -0,0 +1,16 @@ +import {test} from 'kizu'; +import {DOCS_SEARCH_MIN_QUERY_LENGTH, DOCS_SEARCH_PAGE_SIZE, isDocsSearchQueryReady} from './docsSearchQuery.js'; + +test('isDocsSearchQueryReady requires at least DOCS_SEARCH_MIN_QUERY_LENGTH characters', (assert) => { + + assert.equal(DOCS_SEARCH_MIN_QUERY_LENGTH, 3); + assert.equal(isDocsSearchQueryReady('ab'), false); + assert.equal(isDocsSearchQueryReady('abc'), true); + +}); + +test('DOCS_SEARCH_PAGE_SIZE matches Pagefind UI pagination', (assert) => { + + assert.equal(DOCS_SEARCH_PAGE_SIZE, 25); + +}); diff --git a/src/components/docsSearchQuery.ts b/src/components/docsSearchQuery.ts new file mode 100644 index 0000000..489c9c2 --- /dev/null +++ b/src/components/docsSearchQuery.ts @@ -0,0 +1,9 @@ +/** Docs search does not query Pagefind until the trimmed query is at least this long. */ +export const DOCS_SEARCH_MIN_QUERY_LENGTH = 3; + +/** Pagefind UI shows this many hits per page; β€œLoad more” fetches the next page. */ +export const DOCS_SEARCH_PAGE_SIZE = 25; + +export function isDocsSearchQueryReady(query: string): boolean { + return query.trim().length >= DOCS_SEARCH_MIN_QUERY_LENGTH; +} diff --git a/src/components/isUsefulPagefindMatch.spec.ts b/src/components/isUsefulPagefindMatch.spec.ts new file mode 100644 index 0000000..284a338 --- /dev/null +++ b/src/components/isUsefulPagefindMatch.spec.ts @@ -0,0 +1,57 @@ +import {test} from 'kizu'; +import {isUsefulMark, isUsefulPagefindMatch} from './isUsefulPagefindMatch.js'; + +test('rejects inverted short-prefix garbage (adsf β†’ a, asdfasdf β†’ as)', (assert) => { + assert.equal(isUsefulMark('adsf', 'a'), false); + assert.equal(isUsefulMark('asdfasdf', 'as'), false); + assert.equal(isUsefulMark('xyzz', 'x'), false); + + assert.equal( + isUsefulPagefindMatch('adsf', { + excerpt: 'Fetch from a running server.', + }), + false, + ); + assert.equal( + isUsefulPagefindMatch('asdfasdf', { + excerpt: 'same routes object as your RPC server', + }), + false, + ); +}); + +test('keeps real prefix/extension matches and exact terms', (assert) => { + assert.equal(isUsefulMark('a', 'a'), true, 'intentional single-letter search'); + assert.equal(isUsefulMark('route', 'route'), true); + assert.equal(isUsefulMark('route', 'routes'), true); + assert.equal(isUsefulMark('err', 'error'), true); + assert.equal(isUsefulMark('mcp', 'MCP'), true); + assert.equal( + isUsefulMark('adsf', 'host'), + false, + 'unrelated similar-length marks are not kept', + ); + + assert.equal( + isUsefulPagefindMatch('mcp', { + excerpt: 'Enable MCP tools on your API', + meta: {title: 'MCP'}, + }), + true, + ); + assert.equal( + isUsefulPagefindMatch('route', { + excerpt: 'Define a route with input and output', + sub_results: [{excerpt: 'Multiple routes share schemas'}], + }), + true, + ); + assert.equal( + isUsefulPagefindMatch('openapi', { + excerpt: 'Fetch from a running server', + meta: {title: 'OpenAPI'}, + }), + true, + 'title still counts when excerpt marks are weak', + ); +}); diff --git a/src/components/isUsefulPagefindMatch.ts b/src/components/isUsefulPagefindMatch.ts new file mode 100644 index 0000000..1786cc6 --- /dev/null +++ b/src/components/isUsefulPagefindMatch.ts @@ -0,0 +1,56 @@ +/** + * Pagefind matches indexed terms that are prefixes of the query, so typing + * "adsf" returns every page that contains the word "a". Reject those weak + * inverted-prefix hits while keeping normal prefix/extension matches + * ("route" β†’ "routes", "err" β†’ "error"). + */ + +function normalize(text: string): string { + return text.replace(/<[^>]+>/g, '').trim().toLowerCase(); +} + +function extractMarks(html: string): string[] { + return [...html.matchAll(/([\s\S]*?)<\/mark>/gi)].map((match) => normalize(match[1] ?? '')); +} + +export function isUsefulMark(query: string, mark: string): boolean { + const q = normalize(query); + const m = normalize(mark); + if (!q || !m) return false; + if (m === q) return true; + // Matched word extends the query: "route" β†’ "routes" + if (m.startsWith(q)) return true; + // Indexed term is a prefix of the query: only keep if substantial + if (q.startsWith(m)) { + const minLen = Math.min(3, q.length); + return m.length >= minLen && m.length >= Math.ceil(q.length * 0.5); + } + return false; +} + +export type PagefindMatchData = { + excerpt?: string; + meta?: {title?: string}; + sub_results?: Array<{excerpt?: string; title?: string}>; +}; + +export function isUsefulPagefindMatch(query: string, data: PagefindMatchData): boolean { + const q = normalize(query); + if (!q) return false; + + const htmlBits: string[] = []; + if (data.excerpt) htmlBits.push(data.excerpt); + for (const sub of data.sub_results ?? []) { + if (sub.excerpt) htmlBits.push(sub.excerpt); + } + + const marks = htmlBits.flatMap(extractMarks); + if (marks.some((mark) => isUsefulMark(q, mark))) return true; + + const plainBits = [ + ...htmlBits.map(normalize), + normalize(data.meta?.title ?? ''), + ...(data.sub_results ?? []).map((sub) => normalize(sub.title ?? '')), + ]; + return plainBits.some((text) => text.includes(q)); +} diff --git a/src/components/pagefindMount.spec.ts b/src/components/pagefindMount.spec.ts new file mode 100644 index 0000000..8f0130e --- /dev/null +++ b/src/components/pagefindMount.spec.ts @@ -0,0 +1,30 @@ +import {test} from 'kizu'; +import {bindPagefindMount, type PagefindMountEl} from './pagefindMount.js'; + +function hostWith(mount: PagefindMountEl | null): {querySelector(selector: string): PagefindMountEl | null} { + return { + querySelector(selector: string): PagefindMountEl | null { + return selector === '[data-cs-pagefind-root]' ? mount : null; + }, + }; +} + +test('bindPagefindMount gives each site-search its own Pagefind selector', (assert) => { + const header = {id: 'starlight__search'}; + const mobile = {id: 'starlight__search'}; + + assert.equal( + bindPagefindMount(hostWith(header), 'starlight__search-1'), + '#starlight__search-1', + ); + assert.equal( + bindPagefindMount(hostWith(mobile), 'starlight__search-2'), + '#starlight__search-2', + ); + assert.equal(header.id, 'starlight__search-1'); + assert.equal(mobile.id, 'starlight__search-2'); +}); + +test('bindPagefindMount returns null when this instance has no mount', (assert) => { + assert.equal(bindPagefindMount(hostWith(null), 'starlight__search-1'), null); +}); diff --git a/src/components/pagefindMount.ts b/src/components/pagefindMount.ts new file mode 100644 index 0000000..eff597a --- /dev/null +++ b/src/components/pagefindMount.ts @@ -0,0 +1,18 @@ +export type PagefindMountEl = { + id: string; +}; + +export type PagefindMountHost = { + querySelector(selector: string): PagefindMountEl | null; +}; + +/** + * PagefindUI takes a document-level CSS selector. Header + mobile + * both used `#starlight__search`, so both UIs mounted into the first dialog. + */ +export function bindPagefindMount(host: PagefindMountHost, id: string): string | null { + const mount = host.querySelector('[data-cs-pagefind-root]'); + if (!mount) return null; + mount.id = id; + return `#${id}`; +} diff --git a/src/components/searchClearRefocus.spec.ts b/src/components/searchClearRefocus.spec.ts new file mode 100644 index 0000000..cf904ca --- /dev/null +++ b/src/components/searchClearRefocus.spec.ts @@ -0,0 +1,71 @@ +import {test} from 'kizu'; +import {bindSearchClearRefocus} from './searchClearRefocus.js'; + +function stubRoot(input: StubInput, clear: StubClear): Element { + return { + querySelector(selector: string): Element | null { + if (selector === '.pagefind-ui__search-input') return input as unknown as Element; + if (selector === '.pagefind-ui__search-clear') return clear as unknown as Element; + return null; + }, + } as unknown as Element; +} + +type StubInput = { + focusCalls: number; + focus(): void; +}; + +type StubClear = { + dataset: Record; + listeners: Array<() => void>; + addEventListener(_type: string, listener: () => void): void; + click(): void; +}; + +function stubInput(): StubInput { + return { + focusCalls: 0, + focus(): void { + this.focusCalls += 1; + }, + }; +} + +function stubClear(): StubClear { + const clear: StubClear = { + dataset: {}, + listeners: [], + addEventListener(_type, listener) { + this.listeners.push(listener); + }, + click() { + for (const listener of this.listeners) listener(); + }, + }; + return clear; +} + +test('bindSearchClearRefocus is idempotent on the clear button', (assert) => { + const input = stubInput(); + const clear = stubClear(); + const root = stubRoot(input, clear); + + bindSearchClearRefocus(root); + bindSearchClearRefocus(root); + + assert.equal(clear.listeners.length, 1); + + clear.click(); + + return new Promise((resolve, reject) => { + queueMicrotask(() => { + try { + assert.equal(input.focusCalls >= 1, true); + resolve(); + } catch (err) { + reject(err); + } + }); + }); +}); diff --git a/src/components/searchClearRefocus.ts b/src/components/searchClearRefocus.ts new file mode 100644 index 0000000..0b4524b --- /dev/null +++ b/src/components/searchClearRefocus.ts @@ -0,0 +1,29 @@ +/** Pagefind clears the query and blurs the input β€” put focus back for keyboard flow. */ +function scheduleNextFrame(callback: () => void): void { + if (typeof requestAnimationFrame === 'function') { + requestAnimationFrame(callback); + return; + } + setTimeout(callback, 0); +} + +function scheduleSearchInputFocus(input: HTMLInputElement, focus: (el: HTMLInputElement) => void): void { + queueMicrotask(() => { + focus(input); + scheduleNextFrame(() => focus(input)); + }); +} + +export function bindSearchClearRefocus( + root: Element, + focus: (input: HTMLInputElement) => void = (input) => input.focus(), +): void { + const clear = root.querySelector('.pagefind-ui__search-clear'); + const input = root.querySelector('.pagefind-ui__search-input'); + if (!clear || !input || clear.dataset.csSearchClearBound === '1') return; + + clear.dataset.csSearchClearBound = '1'; + clear.addEventListener('click', () => { + scheduleSearchInputFocus(input, focus); + }); +} diff --git a/src/components/searchDialog.spec.ts b/src/components/searchDialog.spec.ts new file mode 100644 index 0000000..cb23c5e --- /dev/null +++ b/src/components/searchDialog.spec.ts @@ -0,0 +1,84 @@ +import {test} from 'kizu'; +import { + isEditableSearchTarget, + openPrimaryDocsSearch, + shouldCloseOnOutsideClick, + shouldCloseSearchOnEscape, + shouldHandleGlobalSlash, +} from './searchDialog.js'; + +test('shouldHandleGlobalSlash opens only when idle and not typing in a field', (assert) => { + const base = { + key: '/', + metaKey: false, + ctrlKey: false, + altKey: false, + shiftKey: false, + anySearchDialogOpen: false, + targetIsEditable: false, + }; + assert.equal(shouldHandleGlobalSlash(base), true); + assert.equal(shouldHandleGlobalSlash({...base, anySearchDialogOpen: true}), false); + assert.equal(shouldHandleGlobalSlash({...base, targetIsEditable: true}), false); + assert.equal(shouldHandleGlobalSlash({...base, key: 'Escape'}), false); + assert.equal(shouldHandleGlobalSlash({...base, metaKey: true}), false); +}); + +test('isEditableSearchTarget covers inputs, textareas, selects, and contenteditable', (assert) => { + assert.equal(isEditableSearchTarget({tagName: 'INPUT', isContentEditable: false}), true); + assert.equal(isEditableSearchTarget({tagName: 'TEXTAREA', isContentEditable: false}), true); + assert.equal(isEditableSearchTarget({tagName: 'SELECT', isContentEditable: false}), true); + assert.equal(isEditableSearchTarget({tagName: 'DIV', isContentEditable: true}), true); + assert.equal(isEditableSearchTarget({tagName: 'BODY', isContentEditable: false}), false); +}); + +test('shouldCloseSearchOnEscape closes the whole dialog on first Escape', (assert) => { + assert.equal(shouldCloseSearchOnEscape('Escape'), true); + assert.equal(shouldCloseSearchOnEscape('Esc'), false); + assert.equal(shouldCloseSearchOnEscape('/'), false); +}); + +test('shouldCloseOnOutsideClick is true for backdrop / outside the frame, and for links', (assert) => { + assert.equal( + shouldCloseOnOutsideClick({ + targetIsLink: false, + targetInDocument: true, + targetInDialogFrame: false, + }), + true, + 'click on backdrop closes', + ); + assert.equal( + shouldCloseOnOutsideClick({ + targetIsLink: true, + targetInDocument: true, + targetInDialogFrame: true, + }), + true, + 'result link click closes', + ); + assert.equal( + shouldCloseOnOutsideClick({ + targetIsLink: false, + targetInDocument: true, + targetInDialogFrame: true, + }), + false, + 'click inside the dialog stays open', + ); +}); + +test('openPrimaryDocsSearch clicks only the header search button', (assert) => { + const clicks: string[] = []; + const headerBtn = {click: (): void => { clicks.push('header'); }}; + const mobileBtn = {click: (): void => { clicks.push('mobile'); }}; + const root = { + querySelector(selector: string): {click(): void} | null { + if (selector === '[data-cs-docs-search-primary] [data-open-modal]') return headerBtn; + if (selector === '[data-open-modal]') return mobileBtn; + return null; + }, + }; + assert.equal(openPrimaryDocsSearch(root), true); + assert.equal(clicks, ['header']); +}); diff --git a/src/components/searchDialog.ts b/src/components/searchDialog.ts new file mode 100644 index 0000000..7e4c13e --- /dev/null +++ b/src/components/searchDialog.ts @@ -0,0 +1,54 @@ +export type SlashKeyEvent = { + key: string; + metaKey: boolean; + ctrlKey: boolean; + altKey: boolean; + shiftKey: false | boolean; + anySearchDialogOpen: boolean; + targetIsEditable: boolean; +}; + +export type EditableTarget = { + tagName: string; + isContentEditable: boolean; +}; + +export function isEditableSearchTarget(el: EditableTarget): boolean { + const tag = el.tagName; + return tag === 'INPUT' || tag === 'TEXTAREA' || tag === 'SELECT' || el.isContentEditable; +} + +/** `/` opens search. Ignore when a search dialog is already open or the user is typing. */ +export function shouldHandleGlobalSlash(event: SlashKeyEvent): boolean { + if (event.key !== '/') return false; + if (event.metaKey || event.ctrlKey || event.altKey || event.shiftKey) return false; + if (event.anySearchDialogOpen) return false; + if (event.targetIsEditable) return false; + return true; +} + +/** First Escape closes the dialog β€” do not let Pagefind only clear the query. */ +export function shouldCloseSearchOnEscape(key: string): boolean { + return key === 'Escape'; +} + +export function shouldCloseOnOutsideClick(opts: { + targetIsLink: boolean; + targetInDocument: boolean; + targetInDialogFrame: boolean; +}): boolean { + if (opts.targetIsLink) return true; + return opts.targetInDocument && !opts.targetInDialogFrame; +} + +export type SearchOpenRoot = { + querySelector(selector: string): {click(): void} | null; +}; + +/** Mobile drawer trigger opens the header dialog β€” never a second Pagefind UI. */ +export function openPrimaryDocsSearch(root: SearchOpenRoot): boolean { + const btn = root.querySelector('[data-cs-docs-search-primary] [data-open-modal]'); + if (!btn) return false; + btn.click(); + return true; +} diff --git a/src/components/searchIdleState.spec.ts b/src/components/searchIdleState.spec.ts new file mode 100644 index 0000000..c5960a4 --- /dev/null +++ b/src/components/searchIdleState.spec.ts @@ -0,0 +1,29 @@ +import {test} from 'kizu'; +import {isSearchIdle} from './searchIdleState.js'; + +test('idle when search is ready and query is empty or shorter than 3 characters', (assert) => { + assert.equal(isSearchIdle(true, true, ''), true); + assert.equal(isSearchIdle(true, true, ' '), true); + assert.equal( + isSearchIdle(true, true, 'te'), + true, + 'queries under 3 characters stay on idle chips', + ); + assert.equal( + isSearchIdle(true, false, 'te'), + true, + 'short queries stay idle even if Pagefind left the drawer open', + ); + assert.equal( + isSearchIdle(true, true, 'mcp'), + false, + 'typing during debounce is not idle', + ); + assert.equal(isSearchIdle(true, false, ''), false); + assert.equal(isSearchIdle(true, false, 'mcp'), false); + assert.equal( + isSearchIdle(false, true, ''), + false, + 'Pagefind not ready yet β€” show loading, not idle chips', + ); +}); diff --git a/src/components/searchIdleState.ts b/src/components/searchIdleState.ts new file mode 100644 index 0000000..1408b74 --- /dev/null +++ b/src/components/searchIdleState.ts @@ -0,0 +1,24 @@ +/** + * Pagefind keeps the results drawer hidden during debounce even after the + * input has text. Treat that as β€œtyping”, not idle β€” otherwise suggestion + * chips stay visible under an active query. + * + * Also require searchReady so the idle panel never replaces the loading + * skeleton before Pagefind mounts. Queries shorter than 3 characters stay + * idle β€” Pagefind does not search until then. + */ +import {DOCS_SEARCH_MIN_QUERY_LENGTH} from './docsSearchQuery.js'; + +export function isSearchIdle( + searchReady: boolean, + drawerHidden: boolean, + query: string, +): boolean { + if (!searchReady) return false; + + const trimmed = query.trim(); + if (trimmed.length === 0) return drawerHidden; + if (trimmed.length < DOCS_SEARCH_MIN_QUERY_LENGTH) return true; + + return false; +} diff --git a/src/components/searchKeyboardNav.spec.ts b/src/components/searchKeyboardNav.spec.ts new file mode 100644 index 0000000..1ae8b40 --- /dev/null +++ b/src/components/searchKeyboardNav.spec.ts @@ -0,0 +1,337 @@ +import {test} from 'kizu'; +import { + activateSearchNavItem, + applySelection, + collectResultNavItems, + collectResultRows, + handleSearchKey, + markAwaitingFreshResults, + nextIndex, + resetSearchNavSession, + resolveKeyboardSelection, + resultLinkForRow, + scrollSearchSelectionIntoView, + shouldHandleSearchListKeyboard, + type KeyboardNavState, +} from './searchKeyboardNav.js'; + +test('resetSearchNavSession clears highlight and scroll for a new dialog open', (assert) => { + const selected = stubEl(); + selected.setAttribute('data-cs-search-selected', 'true'); + const scroller = {scrollTop: 480}; + const root = { + querySelector(selector: string): {scrollTop: number} | null { + return selector === '.pagefind-ui__results-area' ? scroller : null; + }, + querySelectorAll(selector: string): Element[] { + return selector === '[data-cs-search-selected]' ? [selected] : []; + }, + } as unknown as ParentNode; + + const next = resetSearchNavSession(root); + assert.equal(next.selectedIndex, -1); + assert.equal(next.lastFingerprint, ''); + assert.equal(next.awaitingFreshResults, false); + assert.equal(next.userMovedResults, false); + assert.equal(selected.getAttribute('data-cs-search-selected'), null); + assert.equal(scroller.scrollTop, 0); +}); + +test('nextIndex clamps without wrapping', (assert) => { + assert.equal(nextIndex(0, -1, 3), 0); + assert.equal(nextIndex(2, 1, 3), 2); + assert.equal(nextIndex(1, 1, 3), 2); + assert.equal(nextIndex(1, -1, 3), 0); + assert.equal(nextIndex(0, 1, 0), -1, 'empty list stays unselected'); +}); + +test('nextIndex from -1 enters at first or last', (assert) => { + assert.equal(nextIndex(-1, 1, 4), 0); + assert.equal(nextIndex(-1, -1, 4), 3); +}); + +test('applySelection sets data-cs-search-selected on one item', (assert) => { + const items = [stubEl(), stubEl(), stubEl()]; + applySelection(items, 1); + assert.equal(items[0]?.getAttribute('data-cs-search-selected'), null); + assert.equal(items[1]?.getAttribute('data-cs-search-selected'), 'true'); + assert.equal(items[2]?.getAttribute('data-cs-search-selected'), null); + + applySelection(items, -1); + assert.equal(items[1]?.getAttribute('data-cs-search-selected'), null); +}); + +test('handleSearchKey moves selection with arrows and activates on Enter', (assert) => { + assert.equal(handleSearchKey('ArrowDown', -1, 3), { + preventDefault: true, + selectedIndex: 0, + activate: false, + }); + assert.equal(handleSearchKey('ArrowUp', -1, 3), { + preventDefault: true, + selectedIndex: 2, + activate: false, + }); + assert.equal(handleSearchKey('ArrowDown', 0, 3), { + preventDefault: true, + selectedIndex: 1, + activate: false, + }); + assert.equal(handleSearchKey('Enter', 1, 3), { + preventDefault: true, + selectedIndex: 1, + activate: true, + }); + assert.equal(handleSearchKey('Enter', -1, 3), { + preventDefault: false, + selectedIndex: -1, + activate: false, + }); + assert.equal(handleSearchKey('a', 1, 3), { + preventDefault: false, + selectedIndex: 1, + activate: false, + }); +}); + +test('shouldHandleSearchListKeyboard only when the search input is focused', (assert) => { + const input = {tagName: 'INPUT'} as Element; + const clear = {tagName: 'BUTTON'} as Element; + assert.equal(shouldHandleSearchListKeyboard(input, input), true); + assert.equal(shouldHandleSearchListKeyboard(clear, input), false); + assert.equal(shouldHandleSearchListKeyboard(null, input), false); + assert.equal(shouldHandleSearchListKeyboard(input, null), false); +}); + +test('awaiting stays blocked until fingerprint changes or search cycle completes', (assert) => { + const afterType = markAwaitingFreshResults({ + selectedIndex: 0, + lastFingerprint: 'results:a', + awaitingFreshResults: false, + sawSearchingWhileAwaiting: false, + }); + assert.equal(afterType.selectedIndex, -1); + assert.equal(afterType.awaitingFreshResults, true); + + const stillStale = resolveKeyboardSelection(afterType, 'results', 'results:a', 2, false); + assert.equal(stillStale.selectedIndex, -1); + assert.equal(stillStale.awaitingFreshResults, false, 'live hits settle even without a searching phase'); + + const awaitingNoHits = markAwaitingFreshResults({ + selectedIndex: 0, + lastFingerprint: 'results:a', + awaitingFreshResults: false, + sawSearchingWhileAwaiting: false, + }); + const stillLoading = resolveKeyboardSelection(awaitingNoHits, 'results', 'results:a', 0, false); + assert.equal(stillLoading.awaitingFreshResults, true, 'still awaiting when live rows are not ready'); + + const searching = resolveKeyboardSelection(stillLoading, 'results', 'results:a', 2, true); + assert.equal(searching.awaitingFreshResults, true); + assert.equal(searching.sawSearchingWhileAwaiting, true); + assert.equal(searching.selectedIndex, -1); + + const sameHits = resolveKeyboardSelection(searching, 'results', 'results:a', 2, false); + assert.equal(sameHits.awaitingFreshResults, false); + assert.equal(sameHits.selectedIndex, -1); + + const afterType2 = markAwaitingFreshResults(sameHits); + const fresh = resolveKeyboardSelection(afterType2, 'results', 'results:b', 2, false); + assert.equal(fresh.selectedIndex, -1); + assert.equal(fresh.awaitingFreshResults, false); +}); + +test('fast same-hit search clears awaiting when live rows return', (assert) => { + const awaiting = markAwaitingFreshResults({ + selectedIndex: 1, + lastFingerprint: 'results:a', + awaitingFreshResults: false, + sawSearchingWhileAwaiting: false, + userMovedResults: true, + }); + const settled = resolveKeyboardSelection(awaiting, 'results', 'results:a', 4, false); + assert.equal(settled.awaitingFreshResults, false); + assert.equal(settled.selectedIndex, -1); + assert.equal(handleSearchKey('ArrowDown', settled.selectedIndex, 4).selectedIndex, 0); +}); +test('fresh results stay unselected until arrow keys move selection', (assert) => { + const state = markAwaitingFreshResults({ + selectedIndex: -1, + lastFingerprint: '', + awaitingFreshResults: false, + sawSearchingWhileAwaiting: false, + }); + const resolved = resolveKeyboardSelection(state, 'results', 'results:x', 5, false); + assert.equal(resolved.selectedIndex, -1); + assert.equal(handleSearchKey('ArrowDown', resolved.selectedIndex, 5).selectedIndex, 0); +}); + +test('hydration fingerprint changes keep the current selection', (assert) => { + const state: KeyboardNavState = { + selectedIndex: 1, + lastFingerprint: 'results:a', + awaitingFreshResults: false, + sawSearchingWhileAwaiting: false, + userMovedResults: true, + }; + const hydrated = resolveKeyboardSelection(state, 'results', 'results:a\0b', 2, false); + assert.equal(hydrated.selectedIndex, 1); + assert.equal(hydrated.awaitingFreshResults, false); +}); + +test('scrollSearchSelectionIntoView resets drawer scroll for the first row', (assert) => { + const scroller = {scrollTop: 240}; + const root = { + querySelector(selector: string): {scrollTop: number} | null { + return selector === '.pagefind-ui__results-area' ? scroller : null; + }, + }; + const item = { + scrollIntoView(): void { + assert.fail('first row should reset drawer scroll instead of nearest'); + }, + }; + + scrollSearchSelectionIntoView(root, 0, item as unknown as HTMLElement, 3); + assert.equal(scroller.scrollTop, 0); +}); + +test('scrollSearchSelectionIntoView uses end alignment for the last item', (assert) => { + const scroller = {scrollTop: 0}; + const root = { + querySelector(): {scrollTop: number} { + return scroller; + }, + }; + let block: ScrollLogicalPosition | undefined; + const item = { + scrollIntoView(options?: ScrollIntoViewOptions): void { + block = options?.block; + }, + }; + + scrollSearchSelectionIntoView(root, 2, item as unknown as HTMLElement, 3); + assert.equal(block, 'end'); +}); + +test('scrollSearchSelectionIntoView uses nearest for middle rows', (assert) => { + const scroller = {scrollTop: 0}; + const root = { + querySelector(): {scrollTop: number} { + return scroller; + }, + }; + let scrolled = false; + const item = { + scrollIntoView(options?: ScrollIntoViewOptions): void { + scrolled = true; + assert.equal(options?.block, 'nearest'); + }, + }; + + scrollSearchSelectionIntoView(root, 2, item as unknown as HTMLElement, 5); + assert.equal(scrolled, true); + assert.equal(scroller.scrollTop, 0); +}); + +test('collectResultNavItems appends load more after result rows', (assert) => { + const loadMore = { + classList: {contains: (c: string): boolean => c === 'pagefind-ui__button'}, + closest: (): null => null, + click(): void {}, + }; + const root = { + querySelector(selector: string): typeof loadMore | null { + if (selector === '.pagefind-ui__button') return loadMore; + return null; + }, + querySelectorAll(selector: string): HTMLElement[] { + if (selector.includes('pagefind-ui__result-title')) { + return [stubRow('a'), stubRow('b')] as unknown as HTMLElement[]; + } + return []; + }, + }; + const items = collectResultNavItems(root as unknown as ParentNode); + assert.equal(items.length, 3); + assert.equal(items[2], loadMore); +}); + +test('handleSearchKey reaches load more after the last result row', (assert) => { + assert.equal(handleSearchKey('ArrowDown', 1, 3), { + preventDefault: true, + selectedIndex: 2, + activate: false, + }); +}); + +test('activateSearchNavItem clicks load more', (assert) => { + let clicked = false; + const button = { + classList: {contains: (c: string): boolean => c === 'pagefind-ui__button'}, + click(): void { + clicked = true; + }, + }; + activateSearchNavItem(button as unknown as HTMLElement); + assert.equal(clicked, true); +}); + +function stubRow(href: string): { + querySelector(selector: string): {href: string} | null; +} { + return { + querySelector(selector: string): {href: string} | null { + return selector === 'a.pagefind-ui__result-link' ? {href} : null; + }, + }; +} + +function stubEl(): { + setAttribute(name: string, value: string): void; + removeAttribute(name: string): void; + getAttribute(name: string): string | null; +} { + const attrs = new Map(); + return { + setAttribute(name: string, value: string): void { + attrs.set(name, value); + }, + removeAttribute(name: string): void { + attrs.delete(name); + }, + getAttribute(name: string): string | null { + return attrs.has(name) ? (attrs.get(name) ?? null) : null; + }, + }; +} + +test('collectResultRows skips rows inside stale snapshot hosts', (assert) => { + const live = stubEl(); + live.className = 'pagefind-ui__result-title'; + const liveLink = {getAttribute: () => '/live'} as HTMLAnchorElement; + live.querySelector = (selector: string): HTMLAnchorElement | null => + selector === 'a.pagefind-ui__result-link' ? liveLink : null; + + const staleRow = stubEl(); + staleRow.className = 'pagefind-ui__result-nested'; + const staleLink = {getAttribute: () => '/stale'} as HTMLAnchorElement; + staleRow.querySelector = (selector: string): HTMLAnchorElement | null => + selector === 'a.pagefind-ui__result-link' ? staleLink : null; + staleRow.closest = (selector: string): Element | null => + selector === '[data-cs-search-stale]' ? ({} as Element) : null; + + live.closest = (): null => null; + + const root = { + querySelectorAll(selector: string): HTMLElement[] { + if (selector.includes('pagefind-ui__result-title')) return [live]; + if (selector.includes('pagefind-ui__result-nested')) return [staleRow]; + return []; + }, + } as unknown as ParentNode; + + const rows = collectResultRows(root); + assert.equal(rows.length, 1); + assert.equal(resultLinkForRow(rows[0]!)?.getAttribute('href'), '/live'); +}); diff --git a/src/components/searchKeyboardNav.ts b/src/components/searchKeyboardNav.ts new file mode 100644 index 0000000..7ffdbe8 --- /dev/null +++ b/src/components/searchKeyboardNav.ts @@ -0,0 +1,318 @@ +/** Attribute used for keyboard-selected search rows / idle chips. */ +const SEARCH_SELECTED_ATTR = 'data-cs-search-selected'; + +export const INITIAL_KEYBOARD_NAV_STATE: KeyboardNavState = { + selectedIndex: -1, + lastFingerprint: '', + awaitingFreshResults: false, + sawSearchingWhileAwaiting: false, + userMovedResults: false, +}; + +export type SelectableItem = { + setAttribute(name: string, value: string): void; + removeAttribute(name: string): void; +}; + +export type SearchKeyResult = { + preventDefault: boolean; + selectedIndex: number; + activate: boolean; +}; + +/** + * Clamp selection within `[0, length)`. From `-1`, ↓ β†’ first, ↑ β†’ last. + * Empty list always returns `-1`. + */ +export function nextIndex(current: number, delta: number, length: number): number { + if (length <= 0) return -1; + if (current < 0) return delta > 0 ? 0 : length - 1; + const next = current + delta; + if (next < 0) return 0; + if (next >= length) return length - 1; + return next; +} + +/** Mark exactly one item as selected (or none when index is out of range). */ +export function applySelection(items: SelectableItem[], index: number): void { + for (let i = 0; i < items.length; i++) { + const item = items[i]; + if (!item) continue; + if (i === index) item.setAttribute(SEARCH_SELECTED_ATTR, 'true'); + else item.removeAttribute(SEARCH_SELECTED_ATTR); + } +} + +export function handleSearchKey( + key: string, + selectedIndex: number, + itemCount: number, +): SearchKeyResult { + if (key === 'ArrowDown') { + return { + preventDefault: itemCount > 0, + selectedIndex: nextIndex(selectedIndex, 1, itemCount), + activate: false, + }; + } + if (key === 'ArrowUp') { + return { + preventDefault: itemCount > 0, + selectedIndex: nextIndex(selectedIndex, -1, itemCount), + activate: false, + }; + } + if (key === 'Enter') { + const canActivate = selectedIndex >= 0 && selectedIndex < itemCount; + return { + preventDefault: canActivate, + selectedIndex, + activate: canActivate, + }; + } + return {preventDefault: false, selectedIndex, activate: false}; +} + +/** Result-list keys apply only while the query field is focused β€” not on clear / other controls. */ +export function shouldHandleSearchListKeyboard( + activeElement: Element | null, + searchInput: Element | null, +): boolean { + return Boolean(searchInput && activeElement === searchInput); +} + +/** Visible Pagefind β€œload more” control, when present. */ +export function collectLoadMoreButton(root: ParentNode): HTMLElement | null { + const button = root.querySelector('.pagefind-ui__button'); + if (!button || button.closest('.pagefind-ui__hidden')) return null; + return button; +} + +/** Result rows plus load-more when Pagefind shows it. */ +export function collectResultNavItems(root: ParentNode): HTMLElement[] { + const rows = collectResultRows(root); + const loadMore = collectLoadMoreButton(root); + return loadMore ? [...rows, loadMore] : rows; +} + +export function isLoadMoreButton(item: HTMLElement): boolean { + return item.classList.contains('pagefind-ui__button'); +} + +export function activateSearchNavItem(item: HTMLElement): void { + if (isLoadMoreButton(item)) { + item.click(); + return; + } + resultLinkForRow(item)?.click(); +} + +/** Page title / nested rows that already have a navigable link (skip placeholders). */ +export function collectResultRows(root: ParentNode): HTMLElement[] { + return Array.from( + root.querySelectorAll( + '.pagefind-ui__result-title:not(:where(.pagefind-ui__result-nested *)), .pagefind-ui__result-nested', + ), + ).filter((row) => resultLinkForRow(row) !== null && !row.closest?.('[data-cs-search-stale]')); +} + +/** Idle suggestion chips. */ +export function collectSuggestionChips(root: ParentNode): HTMLElement[] { + return Array.from(root.querySelectorAll('[data-cs-search-suggestion]')); +} + +export function resultLinkForRow(row: HTMLElement): HTMLAnchorElement | null { + return row.querySelector('a.pagefind-ui__result-link'); +} + +/** Scroll container for Pagefind hits (results-area when present, else drawer). */ +export function findSearchResultsScroller(root: ParentNode): HTMLElement | null { + return ( + root.querySelector('.pagefind-ui__results-area') ?? + root.querySelector('.pagefind-ui__drawer') + ); +} + +/** + * Keep keyboard selection visible. Index 0 scrolls the drawer flush to the top so + * the match count and first row are fully shown β€” `nearest` leaves a stale offset. + */ +export function scrollSearchSelectionIntoView( + root: ParentNode, + selectedIndex: number, + item: HTMLElement | undefined, + itemCount: number, +): void { + if (selectedIndex === 0) { + const scroller = findSearchResultsScroller(root); + if (scroller) { + scroller.scrollTop = 0; + return; + } + } + if (item && selectedIndex === itemCount - 1) { + item.scrollIntoView({block: 'end'}); + return; + } + item?.scrollIntoView({block: 'nearest'}); +} + +/** Strip keyboard highlight from a prior modal session. */ +export function clearSearchSelection(root: ParentNode): void { + for (const el of Array.from(root.querySelectorAll(`[${SEARCH_SELECTED_ATTR}]`))) { + el.removeAttribute(SEARCH_SELECTED_ATTR); + } +} + +/** Pagefind keeps results mounted while the dialog is closed β€” reset scroll position. */ +export function resetSearchResultsScroll(root: ParentNode): void { + const scroller = findSearchResultsScroller(root); + if (scroller) scroller.scrollTop = 0; +} + +/** Fresh keyboard-nav state when the search dialog closes or reopens. */ +export function resetSearchNavSession(root: ParentNode): KeyboardNavState { + clearSearchSelection(root); + resetSearchResultsScroll(root); + return {...INITIAL_KEYBOARD_NAV_STATE}; +} + +export type KeyboardNavMode = 'idle' | 'results' | 'other'; + +export type KeyboardNavState = { + selectedIndex: number; + lastFingerprint: string; + /** True after the query changes until fresh results are accepted. */ + awaitingFreshResults: boolean; + /** True if Pagefind showed a searching state while awaiting. */ + sawSearchingWhileAwaiting: boolean; + /** True after ↓/↑ in the results list β€” blocks auto-highlight on fresh hits. */ + userMovedResults: boolean; +}; + +export function markAwaitingFreshResults(state: KeyboardNavState): KeyboardNavState { + return { + ...state, + awaitingFreshResults: true, + selectedIndex: -1, + sawSearchingWhileAwaiting: false, + userMovedResults: false, + }; +} + +/** + * Updates selection when the dialog mode / result list fingerprint changes. + * While awaiting, selection stays cleared until either the href fingerprint changes + * or a searchingβ†’idle cycle completes (same hits after an edit). + */ +export function resolveKeyboardSelection( + state: KeyboardNavState, + mode: KeyboardNavMode, + fingerprint: string, + itemCount: number, + isSearching: boolean, +): KeyboardNavState { + if (mode === 'other') { + return { + selectedIndex: -1, + lastFingerprint: fingerprint, + awaitingFreshResults: false, + sawSearchingWhileAwaiting: false, + userMovedResults: false, + }; + } + + if (mode === 'idle') { + if (fingerprint !== state.lastFingerprint) { + return { + selectedIndex: -1, + lastFingerprint: fingerprint, + awaitingFreshResults: false, + sawSearchingWhileAwaiting: false, + userMovedResults: false, + }; + } + return { + ...state, + awaitingFreshResults: false, + sawSearchingWhileAwaiting: false, + userMovedResults: false, + selectedIndex: clampIndex(state.selectedIndex, itemCount), + }; + } + + const sawSearching = + state.sawSearchingWhileAwaiting || (state.awaitingFreshResults && isSearching); + + const clearResultsNav = { + selectedIndex: -1, + userMovedResults: false, + }; + + // results (including in-flight querying, which still uses this mode) + if (fingerprint !== state.lastFingerprint) { + if (state.awaitingFreshResults || !state.userMovedResults) { + return { + ...clearResultsNav, + lastFingerprint: fingerprint, + awaitingFreshResults: false, + sawSearchingWhileAwaiting: false, + }; + } + return { + selectedIndex: clampIndex(state.selectedIndex, itemCount), + lastFingerprint: fingerprint, + awaitingFreshResults: false, + sawSearchingWhileAwaiting: false, + userMovedResults: true, + }; + } + + if (state.awaitingFreshResults) { + // Fast searches may never show a searching state β€” accept once live hits are back. + if (!isSearching && itemCount > 0) { + return { + ...clearResultsNav, + lastFingerprint: fingerprint, + awaitingFreshResults: false, + sawSearchingWhileAwaiting: false, + }; + } + + // Same hits after a visible searching cycle (query tweak, identical results). + if (sawSearching && !isSearching) { + return { + ...clearResultsNav, + lastFingerprint: fingerprint, + awaitingFreshResults: false, + sawSearchingWhileAwaiting: false, + }; + } + return { + ...state, + ...clearResultsNav, + sawSearchingWhileAwaiting: sawSearching, + }; + } + + if (!state.userMovedResults) { + return { + ...state, + selectedIndex: -1, + sawSearchingWhileAwaiting: false, + }; + } + + return { + ...state, + selectedIndex: clampIndex(state.selectedIndex, itemCount), + sawSearchingWhileAwaiting: false, + }; +} + +function clampIndex(selectedIndex: number, itemCount: number): number { + if (itemCount <= 0) return -1; + if (selectedIndex < 0) return selectedIndex; + if (selectedIndex >= itemCount) return itemCount - 1; + return selectedIndex; +} diff --git a/src/components/searchPendingDelay.spec.ts b/src/components/searchPendingDelay.spec.ts new file mode 100644 index 0000000..1f6f483 --- /dev/null +++ b/src/components/searchPendingDelay.spec.ts @@ -0,0 +1,34 @@ +import {test} from 'kizu'; +import { + INITIAL_SEARCH_PENDING_DELAY_STATE, + resolveSearchPendingDelayState, + shouldShowSearchPending, +} from './searchPendingDelay.js'; + +test('shouldShowSearchPending only while searching with no settled results', (assert) => { + assert.equal(shouldShowSearchPending('searching', false, false), true); + assert.equal(shouldShowSearchPending('searching', false, false, false), true); + assert.equal(shouldShowSearchPending('querying', false, false), false); + assert.equal(shouldShowSearchPending('searching', true, false), false); + assert.equal(shouldShowSearchPending('searching', false, true), false); + assert.equal(shouldShowSearchPending('results', false, false), false); + assert.equal(shouldShowSearchPending('searching', false, false, true), false); +}); + +test('resolveSearchPendingDelayState hides immediately when search settles', (assert) => { + const armed = resolveSearchPendingDelayState(INITIAL_SEARCH_PENDING_DELAY_STATE, true, false); + assert.equal(armed.armed, true); + assert.equal(armed.visible, false); + + const visible = resolveSearchPendingDelayState(armed, true, true); + assert.equal(visible.visible, true); + + const hidden = resolveSearchPendingDelayState(visible, false, false); + assert.equal(hidden, INITIAL_SEARCH_PENDING_DELAY_STATE); +}); + +test('resolveSearchPendingDelayState stays visible once shown until cleared', (assert) => { + const visible = {armed: false, visible: true}; + const still = resolveSearchPendingDelayState(visible, true, false); + assert.equal(still.visible, true); +}); diff --git a/src/components/searchPendingDelay.ts b/src/components/searchPendingDelay.ts new file mode 100644 index 0000000..ab4ce46 --- /dev/null +++ b/src/components/searchPendingDelay.ts @@ -0,0 +1,41 @@ +/** Wait before showing the skeleton so fast searches do not flash placeholder chrome. */ +export const SEARCH_PENDING_SHOW_DELAY_MS = 180; + +/** Skeleton while Pagefind is searching and the drawer has no hits yet. */ +export function shouldShowSearchPending( + phase: string, + hasResults: boolean, + zeroResults: boolean, + holdingStale = false, +): boolean { + if (hasResults || zeroResults || holdingStale) return false; + return phase === 'searching'; +} + +export type SearchPendingDelayState = { + armed: boolean; + visible: boolean; +}; + +export const INITIAL_SEARCH_PENDING_DELAY_STATE: SearchPendingDelayState = { + armed: false, + visible: false, +}; + +/** Pure delay gate β€” `delayElapsed` is true once the timer fires. */ +export function resolveSearchPendingDelayState( + state: SearchPendingDelayState, + wantShow: boolean, + delayElapsed: boolean, +): SearchPendingDelayState { + if (!wantShow) { + return INITIAL_SEARCH_PENDING_DELAY_STATE; + } + if (state.visible) { + return state; + } + if (delayElapsed) { + return {armed: false, visible: true}; + } + return {armed: true, visible: false}; +} diff --git a/src/components/searchStaleResults.spec.ts b/src/components/searchStaleResults.spec.ts new file mode 100644 index 0000000..4bfc843 --- /dev/null +++ b/src/components/searchStaleResults.spec.ts @@ -0,0 +1,62 @@ +import {test} from 'kizu'; +import { + beginStaleResultsHold, + INITIAL_STALE_RESULTS_STATE, + resolveStaleResultsHold, + shouldDiscardStaleResults, + shouldShowStaleResults, +} from './searchStaleResults.js'; + +test('shouldShowStaleResults only while holding a snapshot and live results are gone', (assert) => { + assert.equal(shouldShowStaleResults(true, false, true, true, false, false), true); + assert.equal(shouldShowStaleResults(true, false, false, true, false, false), false); + assert.equal(shouldShowStaleResults(true, true, true, true, false, false), false); + assert.equal(shouldShowStaleResults(false, false, true, true, false, false), false); + assert.equal(shouldShowStaleResults(true, false, true, false, false, false), false); + assert.equal(shouldShowStaleResults(true, false, true, true, true, false), false); + assert.equal(shouldShowStaleResults(true, false, true, true, false, true), false); +}); + +test('shouldDiscardStaleResults when the field resets or search settles empty', (assert) => { + assert.equal(shouldDiscardStaleResults(false, false, true), false); + assert.equal(shouldDiscardStaleResults(true, false, true), true); + assert.equal(shouldDiscardStaleResults(false, true, true), true); + assert.equal(shouldDiscardStaleResults(false, false, false), true); +}); + +test('beginStaleResultsHold records the live fingerprint', (assert) => { + const empty = beginStaleResultsHold(INITIAL_STALE_RESULTS_STATE, ''); + assert.equal(empty.holding, false); + + const held = beginStaleResultsHold(INITIAL_STALE_RESULTS_STATE, 'a\0b'); + assert.equal(held.holding, true); + assert.equal(held.capturedFingerprint, 'a\0b'); + assert.equal(held.sawSearchingWhileHolding, false); +}); + +test('resolveStaleResultsHold keeps the snapshot until fresh hits land', (assert) => { + const holding = beginStaleResultsHold(INITIAL_STALE_RESULTS_STATE, 'a\0b'); + + const stillLive = resolveStaleResultsHold(holding, true, 'a\0b', false, false); + assert.equal(stillLive.holding, true); + assert.equal(stillLive.capturedFingerprint, 'a\0b'); + + const clearedLive = resolveStaleResultsHold(holding, false, '', false, false); + assert.equal(clearedLive.holding, true); + assert.equal(clearedLive.capturedFingerprint, 'a\0b'); + + const replaced = resolveStaleResultsHold(holding, true, 'c', false, false); + assert.equal(replaced.holding, false); + + const settled = resolveStaleResultsHold( + {...holding, sawSearchingWhileHolding: true}, + true, + 'a\0b', + false, + false, + ); + assert.equal(settled.holding, false); + + const discarded = resolveStaleResultsHold(holding, false, '', false, true); + assert.equal(discarded.holding, false); +}); diff --git a/src/components/searchStaleResults.ts b/src/components/searchStaleResults.ts new file mode 100644 index 0000000..a6f10c4 --- /dev/null +++ b/src/components/searchStaleResults.ts @@ -0,0 +1,126 @@ +export type StaleResultsState = { + /** Snapshot kept until Pagefind replaces the live list. */ + holding: boolean; + /** Live-result href fingerprint when the snapshot was taken. */ + capturedFingerprint: string; + /** True once Pagefind shows a searching state while holding. */ + sawSearchingWhileHolding: boolean; +}; + +export const INITIAL_STALE_RESULTS_STATE: StaleResultsState = { + holding: false, + capturedFingerprint: '', + sawSearchingWhileHolding: false, +}; + +/** Show the last snapshot while a new query is in flight and Pagefind cleared the live list. */ +export function shouldShowStaleResults( + hasStaleSnapshot: boolean, + hasLiveResults: boolean, + holding: boolean, + queryReady: boolean, + idle: boolean, + zeroResults: boolean, +): boolean { + if (!hasStaleSnapshot || !holding || hasLiveResults || !queryReady || idle || zeroResults) { + return false; + } + return true; +} + +export function liveResultsFingerprint(root: Element): string { + const rows = root.querySelectorAll('.pagefind-ui__results > .pagefind-ui__result'); + return Array.from(rows) + .map((row) => row.querySelector('a.pagefind-ui__result-link')?.getAttribute('href') ?? '') + .join('\0'); +} + +/** Drop the snapshot when the field resets or a completed search settles. */ +export function shouldDiscardStaleResults( + idle: boolean, + zeroResults: boolean, + queryReady: boolean, +): boolean { + return idle || zeroResults || !queryReady; +} + +export function beginStaleResultsHold( + state: StaleResultsState, + liveFingerprint: string, +): StaleResultsState { + if (!liveFingerprint) return state; + return { + holding: true, + capturedFingerprint: liveFingerprint, + sawSearchingWhileHolding: false, + }; +} + +export function resolveStaleResultsHold( + state: StaleResultsState, + hasLiveResults: boolean, + liveFingerprint: string, + isSearching: boolean, + discard: boolean, +): StaleResultsState { + if (!state.holding || discard) return INITIAL_STALE_RESULTS_STATE; + + const sawSearching = + state.sawSearchingWhileHolding || (state.holding && isSearching); + + if (!hasLiveResults) { + return {...state, sawSearchingWhileHolding: sawSearching}; + } + + if (liveFingerprint !== state.capturedFingerprint) return INITIAL_STALE_RESULTS_STATE; + + if (sawSearching && !isSearching) return INITIAL_STALE_RESULTS_STATE; + + return {...state, sawSearchingWhileHolding: sawSearching}; +} + +/** Mount outside Pagefind’s Svelte-managed results block so loading toggles do not drop it. */ +export function ensureStaleResultsHost(root: Element): HTMLElement | null { + const drawer = root.querySelector('.pagefind-ui__drawer'); + if (!drawer) return null; + + const existing = drawer.querySelector('[data-cs-search-stale]'); + if (existing) return existing; + + const host = document.createElement('div'); + host.dataset.csSearchStale = ''; + host.hidden = true; + drawer.insertBefore(host, drawer.firstChild); + return host; +} + +export function captureStaleResults(root: Element): boolean { + const results = root.querySelector('.pagefind-ui__results'); + if (!results?.querySelector('.pagefind-ui__result')) return false; + + const host = ensureStaleResultsHost(root); + if (!host) return false; + + host.replaceChildren(results.cloneNode(true)); + return true; +} + +export function clearStaleResults(root: Element): void { + const host = root.querySelector('[data-cs-search-stale]'); + if (!host) return; + host.replaceChildren(); + host.hidden = true; +} + +export function syncStaleResults(root: Element, show: boolean, discard: boolean): void { + const host = root.querySelector('[data-cs-search-stale]'); + if (!host) return; + + if (discard) { + host.replaceChildren(); + host.hidden = true; + return; + } + + host.hidden = !show || host.childElementCount === 0; +} diff --git a/src/components/site-footer.css b/src/components/site-footer.css new file mode 100644 index 0000000..06958b4 --- /dev/null +++ b/src/components/site-footer.css @@ -0,0 +1,52 @@ +.site-footer { + display: flex; + flex-wrap: wrap; + align-items: center; + justify-content: space-between; + gap: 0.65rem 1.5rem; + margin-top: 2.75rem; + padding-top: 1.15rem; + border-top: 1px solid var(--cs-line, var(--sl-color-hairline)); +} + +.site-footer__icon, +.site-footer__icon svg { + display: block; + width: 1rem; + height: 1rem; + flex-shrink: 0; +} + +.site-footer__nav { + display: flex; + flex-wrap: wrap; + align-items: center; + gap: 0.35rem 1.15rem; +} + +.site-footer__nav a { + display: inline-flex; + align-items: center; + gap: 0.35rem; + font-size: 0.9375rem; + color: var(--cs-muted, var(--sl-color-gray-3)); + text-decoration: none; +} + +.site-footer__nav a:hover { + color: var(--docs-primary-bg); +} + +.pagination-links + .site-footer { + margin-top: 1.75rem; +} + +html:has(.splash-page) .site-footer, +html:has(.not-found-page) .site-footer { + margin-top: 3.25rem; + padding-top: 1.35rem; +} + +html:has(.splash-page) .site-footer { + border-top: none; +} diff --git a/src/components/wrapPagefindSearch.spec.ts b/src/components/wrapPagefindSearch.spec.ts new file mode 100644 index 0000000..f664380 --- /dev/null +++ b/src/components/wrapPagefindSearch.spec.ts @@ -0,0 +1,49 @@ +import {test} from 'kizu'; +import {wrapPagefindSearch} from './wrapPagefindSearch.js'; + +test('wrapPagefindSearch drops inverted short-prefix hits', async (assert) => { + const rawSearch = async (_term: string): Promise<{ + results: Array<{data: () => Promise<{excerpt?: string; meta?: {title?: string}}>}>; + }> => ({ + results: [ + { + data: async (): Promise<{excerpt: string}> => ({ + excerpt: 'Fetch from a running server.', + }), + }, + { + data: async (): Promise<{excerpt: string; meta: {title: string}}> => ({ + excerpt: 'Enable MCP tools', + meta: {title: 'MCP'}, + }), + }, + ], + }); + + const search = wrapPagefindSearch(rawSearch); + const garbage = await search('adsf'); + assert.equal(garbage.results.length, 0); + + const useful = await search('mcp'); + assert.equal(useful.results.length, 1); + assert.equal((await useful.results[0]!.data()).meta?.title, 'MCP'); +}); + +test('wrapPagefindSearch skips Pagefind until query is at least 3 characters', async (assert) => { + let calls = 0; + const rawSearch = async (): Promise<{results: []}> => { + calls += 1; + return {results: []}; + }; + + const search = wrapPagefindSearch(rawSearch); + + assert.equal((await search('')).results.length, 0); + assert.equal(calls, 0, 'empty query does not hit Pagefind'); + + assert.equal((await search('te')).results.length, 0); + assert.equal(calls, 0, 'two-character query does not hit Pagefind'); + + assert.equal((await search('tes')).results.length, 0); + assert.equal(calls, 1, 'three-character query runs Pagefind'); +}); diff --git a/src/components/wrapPagefindSearch.ts b/src/components/wrapPagefindSearch.ts new file mode 100644 index 0000000..b69cea7 --- /dev/null +++ b/src/components/wrapPagefindSearch.ts @@ -0,0 +1,43 @@ +import {isUsefulPagefindMatch, type PagefindMatchData} from './isUsefulPagefindMatch.js'; +import {isDocsSearchQueryReady} from './docsSearchQuery.js'; + +type PagefindSearchResult = { + data: () => Promise; +}; + +type PagefindSearchResponse = { + results: PagefindSearchResult[]; + [key: string]: unknown; +}; + +type PagefindSearchFn = ( + term: string, + options?: object, +) => Promise; + +/** + * Returns a new search function that drops inverted short-prefix hits. + * Do not assign over ESM `search` exports β€” module namespaces are immutable. + */ +export function wrapPagefindSearch(rawSearch: PagefindSearchFn): PagefindSearchFn { + return async (term, options) => { + const query = term?.trim() ?? ''; + if (!isDocsSearchQueryReady(query)) { + return {results: []}; + } + + try { + const res = await rawSearch(term, options); + const judged = await Promise.all( + res.results.map(async (result) => { + const data = await result.data(); + return isUsefulPagefindMatch(query, data) ? result : null; + }), + ); + return {...res, results: judged.filter((result): result is PagefindSearchResult => result !== null)}; + } catch (err) { + console.error('busker docs search failed', err); + return {results: []}; + } + }; +} diff --git a/src/content.config.ts b/src/content.config.ts new file mode 100644 index 0000000..2542513 --- /dev/null +++ b/src/content.config.ts @@ -0,0 +1,9 @@ +import {defineCollection} from 'astro:content'; +import {i18nLoader} from '@astrojs/starlight/loaders'; +import {docsSchema, i18nSchema} from '@astrojs/starlight/schema'; +import {githubFriendlyDocsLoader} from './content/github-friendly-docs-loader'; + +export const collections = { + docs: defineCollection({loader: githubFriendlyDocsLoader(), schema: docsSchema()}), + i18n: defineCollection({loader: i18nLoader(), schema: i18nSchema()}), +}; diff --git a/src/content/docs/api-reference.md b/src/content/docs/api-reference.md new file mode 100644 index 0000000..13817b2 --- /dev/null +++ b/src/content/docs/api-reference.md @@ -0,0 +1,106 @@ +# API reference + +```typescript +import {busk} from 'busker'; + +const show = busk(root, routine); +``` + +## `busk(root, routine)` + +Puts on a show inside `root`, an `HTMLElement`. Returns a [`Busker`](#busker). Starts by itself once the root is on screen. + +## `Routine` + +| Field | Type | Default | What it does | +|---|---|---|---| +| `scene` | `string` | none | Scene shown at the top of every loop. | +| `start` | `[number, number]` | `[0.5, 0.5]` | Where the cursor rests, as a fraction of the root's size. | +| `steps` | [`Step[]`](#step) | none | A click-driven routine. The loop length comes from it. | +| `routes` | [`Route[]`](#route) | none | What a click — the cursor's or a visitor's — does. | +| `duration` | `number` | from `steps` | Loop length in ms. Required if there are no `steps`. | +| `moves` | [`Move[]`](#move) | none | Hand-timed cursor glides. | +| `toggles` | [`Toggle[]`](#toggle) | none | Classes held for a slice of the loop. | +| `typing` | [`Typing[]`](#typing) | none | Text that types itself. | +| `countdowns` | [`Countdown[]`](#countdown) | none | `m:ss` clocks. | +| `visibility` | `number` | `1` | How much of the root must be on screen to run, as a fraction. | +| `freezeAt` | `number` | `0` | Frame to hold under `prefers-reduced-motion`. | + +## `Step` + +One beat of a click-driven routine. Either a press: + +| Field | Type | Default | +|---|---|---| +| `click` | `string` | — | +| `wait` | `number` | `0` | +| `moveFor` | `number` | `600` | +| `dwell` | `number` | `250` | + +…or a drift, which never clicks: + +| Field | Type | Default | +|---|---|---| +| `to` | `string \| [number, number]` | — | +| `wait` | `number` | `0` | +| `moveFor` | `number` | `600` | + +Beats run back to back: a step sets off `wait` after the last one finished. + +## `Route` + +| Field | Type | What it does | +|---|---|---| +| `click` | `string` | Selector of the clickable element. | +| `scene` | `string` | `data-scene` to show when it is clicked. | + +Every route target also gets `is-interactive`, which is what makes it look clickable. + +## `Move` + +A hand-timed glide. See [Hand-timed routines](./timeline.md#moves). + +| Field | Type | What it does | +|---|---|---| +| `to` | `string \| [number, number]` | Where to glide. | +| `from` | `number` | When it sets off. | +| `until` | `number` | When it arrives. | +| `press` | `number` | Optional. Animates a press. Does **not** click. | + +## `Toggle` + +| Field | Type | What it does | +|---|---|---| +| `target` | `string` | Selector of the element. | +| `class` | `string` | Class held while the loop is inside `[from, until)`. | +| `from` | `number` | | +| `until` | `number` | | + +## `Typing` + +| Field | Type | What it does | +|---|---|---| +| `target` | `string` | Selector. Busker writes its `textContent`. | +| `text` | `string` | | +| `from` | `number` | Typing starts. | +| `until` | `number` | The whole string is on screen. | +| `clearAt` | `number` | Optional. Wipes it. | + +## `Countdown` + +| Field | Type | What it does | +|---|---|---| +| `target` | `string` | Selector. Busker writes its `textContent`. | +| `seconds` | `number` | Value at the top of the loop. Stops at zero. | + +## `Busker` + +| Member | What it does | +|---|---| +| `duration` | Loop length in ms, derived from `steps` if you did not give one. | +| `play()` | Start or resume. A no-op once a visitor has taken over. | +| `pause()` | Hold where it is. | +| `stepAside()` | Hand the mock to the visitor: stop for good, hide the cursor. | +| `destroy()` | Stop everything and remove every class, listener, and observer busker added. | + +← [Styling](./styling.md) · Next: [Development](./development.md) diff --git a/src/content/docs/development.md b/src/content/docs/development.md new file mode 100644 index 0000000..bfbfde7 --- /dev/null +++ b/src/content/docs/development.md @@ -0,0 +1,40 @@ +# Development + +```bash +git clone https://github.com/logfoxai/busker.git +cd busker +npm ci +npm run validate +``` + +## Scripts + +| Script | What it does | +|---|---| +| `npm run build` | `tsc` β†’ `dist/` | +| `npm test` | [kizu](https://github.com/mhweiner/kizu) specs under c8 coverage | +| `npm run lint` | `eslint --fix` | +| `npm run astro:dev` | The docs site at `localhost:4321` | +| `npm run astro:build` | The docs site β†’ `docs-site/` | +| `npm run validate` | Everything CI runs, in the same order. Run it before you push. | + +## Layout + +| Path | What is in it | +|---|---| +| `src/timeline.ts` | The pure part: laying a routine out on a timeline, easing, interpolation, text. | +| `src/busk.ts` | The DOM part: the loop, clicks, observers, and classes. | +| `src/types.ts` | The public shape of a routine. | +| `busker.css` | Optional styling, shipped as-is. | +| `src/content/docs/` | These guides. Same files serve GitHub and the site. | +| `assets/` | Brand art. Also the docs site's `publicDir`. | + +The split is deliberate: everything that can be tested without a DOM lives in `timeline.ts` and is tested exhaustively. `busk.ts` is tested through [happy-dom](https://github.com/capricorn86/happy-dom) with a hand-driven clock, for the behaviours that only exist in a browser — a scripted click not being mistaken for a visitor's, a beat firing exactly once per pass, `destroy()` leaving nothing behind. + +## Docs + +Guides are plain markdown with a `# Title` and no frontmatter, so they read correctly on GitHub and in the site from the same file. Relative `./page.md` links are rewritten to site slugs at build time. + +## Releases + +Merges to `main` release through [autorel](https://github.com/mhweiner/autorel): the PR title's conventional-commit type decides the version bump, and the docs site publishes in the same run. Use `feat:` and `fix:` for anything that ships, `docs:` for guides. diff --git a/src/content/docs/getting-started.md b/src/content/docs/getting-started.md new file mode 100644 index 0000000..beef149 --- /dev/null +++ b/src/content/docs/getting-started.md @@ -0,0 +1,73 @@ +# Getting started + +## 1. Install + +```bash +npm i busker +``` + +Busker has no dependencies and runs in the browser. It ships types, and works with any framework or none — it only ever touches the element you hand it. + +## 2. Write the mock + +A mock is ordinary markup. Busker needs three things from it, all `data-` attributes so your class names stay yours: + +```html title="index.html" +
+ + +
+

Nothing is on fire.

+
+ +
+

Two things are on fire.

+
+ + +
+``` + +`[data-scene]` marks each page of the mock, `[data-nav-item]` marks the nav, and `[data-cursor]` is the pointer busker moves. [Markup](./markup.md) has the full contract. + +## 3. Put on a show + +```typescript title="main.ts" +import {busk} from 'busker'; +import 'busker/busker.css'; + +const root = document.querySelector('.app'); + +if (root) { + busk(root, { + scene: 'home', + steps: [ + {click: '[data-nav-item="alerts"]', wait: 1200}, + {click: '[data-nav-item="home"]', wait: 2000}, + ], + routes: [ + {click: '[data-nav-item="home"]', scene: 'home'}, + {click: '[data-nav-item="alerts"]', scene: 'alerts'}, + ], + }); +} +``` + +That is the whole thing. The cursor glides to the Alerts button, presses it, and the mock changes — because busker really clicks it. There is no separate timeline saying "and at 1.8s, switch to the alerts scene", so there is nothing to fall out of sync when you change a duration. + +## 4. Let people play with it + +Every selector in `routes` is wired for the visitor too. Click anything the show can click and busker steps aside: the loop stops, the cursor disappears, and the mock is yours. Click something dead and it flashes what *is* clickable. + +You get that for free — see [When a visitor takes over](./taking-over.md). + +## What busker does not do + +- **It does not record or replay.** There is no capture step and no video. The mock is your markup, and the show is a few lines of config. +- **It does not fake the clicks.** `el.click()` is a real click through your real handlers, so the mock behaves the same whether the cursor pressed the button or a person did. +- **It does not run off screen.** The loop only runs while the mock is on screen and the tab is in front. + +Next: [Markup](./markup.md) · [Click-driven routines](./routines.md) diff --git a/src/content/docs/markup.md b/src/content/docs/markup.md new file mode 100644 index 0000000..8199ce5 --- /dev/null +++ b/src/content/docs/markup.md @@ -0,0 +1,57 @@ +# Markup + +Busker reads your mock through five `data-` attributes and writes back a handful of classes. Nothing else is assumed: no wrapper components, no required class names, no shadow DOM. + +## What busker reads + +| Attribute | On | What it means | +|---|---|---| +| `data-cursor` | one element | The pointer. Busker positions it and shows the press. | +| `data-scene="home"` | a page of the mock | One is shown at a time. Named in `scene` and in `routes`. | +| `data-nav="home"` | the same scene element | Which nav item is lit while this scene is up. | +| `data-nav-item="home"` | a nav button or link | Gets `is-active` when a scene claims it. | + +Anything else — rows, buttons, modals, inputs — is just your markup, targeted by whatever selector you like. + +## What busker writes + +| Class | On | When | +|---|---|---| +| `busker` | the root | For as long as busker is running. Everything in `busker.css` hangs off it. | +| `is-active` | a scene, a nav item | While that scene is the one on screen. | +| `is-interactive` | every `routes` target | Always. It is what makes clickable things look clickable. | +| `is-hover` | the current target | From the moment the cursor lands until it sets off again. | +| `is-pressing` | the cursor | For 200ms as it presses. | +| `is-ringing` | the cursor | For 500ms — the ripple outlives the press so the click reads. | +| `is-hint` | every `routes` target | For 1.5s after a visitor clicks something dead. | +| `is-aside` | the root | Once a visitor has taken over. | + +Busker removes all of them on `destroy()`. + +## The root element + +The root is the element you pass to `busk()`. Two things follow from that: + +- **The cursor is positioned against it.** `busker.css` sets `position: relative` on `.busker` for you. If you are writing your own styles, the root needs to be a positioning context. +- **Visibility is measured on it.** The show runs while the root is on screen, which means the root should be the visible frame of the mock, not a wrapper that is taller than the viewport. See [`visibility`](./api-reference.md#routine) if you want to loosen that. + +## Scenes + +A scene is one page of the mock. Busker shows one at a time by putting `is-active` on it, and `busker.css` handles the display: + +```css +.busker [data-scene] { display: none; } +.busker [data-scene].is-active { display: block; } +``` + +Override those two rules if you want scenes to cross-fade, slide, or stack. + +Scenes never change on a timer. They change because something was clicked — by the cursor or by a visitor — and a [route](./routines.md#routes) said so. That is the whole point: there is one cause, so there is nothing to synchronise. + +## The cursor + +`[data-cursor]` can be any element. `busker.css` styles it as the soft dot busker's own demos use, sized by `--busker-cursor-size`. To draw your own pointer instead, put an `` in there and skip the cursor rules — see [Styling](./styling.md). + +The cursor is hidden until `busk()` runs, so it never flashes on a page whose JavaScript has not loaded, and it stays hidden under `prefers-reduced-motion`. + +← [Getting started](./getting-started.md) · Next: [Click-driven routines](./routines.md) diff --git a/src/content/docs/routines.md b/src/content/docs/routines.md new file mode 100644 index 0000000..c74efe8 --- /dev/null +++ b/src/content/docs/routines.md @@ -0,0 +1,78 @@ +# Click-driven routines + +A routine is a list of places the cursor goes. At each stop it presses the element for real, so the mock changes through the handlers you already wrote. You never say *when* a page should change, only *what* gets clicked — the change is caused, not timed. + +```typescript +busk(root, { + scene: 'home', + start: [0.55, 0.25], + steps: [ + {click: '[data-nav-item="alerts"]', wait: 900, moveFor: 550}, + {click: '[data-row="p0"]', wait: 1500, moveFor: 700}, + {click: '[data-close]', wait: 2600}, + {to: [0.55, 0.25], wait: 900, moveFor: 900}, + ], + routes: [ + {click: '[data-nav-item="alerts"]', scene: 'alerts'}, + {click: '[data-row="p0"]', scene: 'case'}, + ], +}); +``` + +## Steps + +Each step is one beat, and they run back to back. The loop is as long as the beats add up to — there is no `duration` to keep in step. + +A step either presses something: + +| Field | Default | What it does | +|---|---|---| +| `click` | — | Selector of the element to press. Really clicked. | +| `wait` | `0` | Pause before the cursor sets off. This is reading time for whatever the last press opened. | +| `moveFor` | `600` | How long the glide takes. | +| `dwell` | `250` | How long the cursor hovers on the target before pressing. | + +…or drifts somewhere without pressing: + +| Field | Default | What it does | +|---|---|---| +| `to` | — | A selector, or `[x, y]` as a fraction of the root's size. | +| `wait` | `0` | Pause before setting off. | +| `moveFor` | `600` | How long the glide takes. | + +A drift is how you get the cursor back out of the way before the loop starts over, or park it somewhere neutral while something animates on its own. + +## Timing a routine + +Time goes into `wait`, not into the glide. `wait` is how long a visitor gets to look at what just appeared; `moveFor` is only the travel. A step that opens something dense wants a long `wait` on the *next* step, not a slow glide on this one. + +A press is a stroke rather than an instant: the cursor goes down on the target, and the real click fires as it lifts. That is what stops a button from vanishing underneath its own click when it is the thing that changes the scene. + +Because the beats are relative, you can drop a step into the middle of a routine and nothing after it needs touching. + +## Routes + +A route says what a click does: + +```typescript +routes: [ + {click: '[data-nav-item="alerts"]', scene: 'alerts'}, +] +``` + +Routes do two jobs, which is the reason they are one list: + +1. **They switch scenes** — for the cursor's clicks and for a visitor's, identically. +2. **They mark what is clickable.** Every route target gets `is-interactive`, which is what gives it a pointer cursor. So the things that look clickable are exactly the things that are, with no CSS list to maintain alongside. + +Anything your own handlers do — opening a modal, filtering a table, toggling a row — needs no route at all. Busker clicked the element; your code took it from there. Routes are only for the part busker owns, which is which scene is up. + +## Where the cursor starts + +`start` is where the cursor rests before the first beat and after a drift, as a fraction of the root: `[0.55, 0.25]` is a bit right of centre, a quarter of the way down. It defaults to the middle. + +## Reading positions + +Targets are resolved from the DOM on every frame, so a routine keeps working when the mock reflows, when the container resizes, and at every breakpoint. There are no coordinates to re-measure after a design change — if the button moved, the cursor moves with it. + +← [Markup](./markup.md) · Next: [Hand-timed routines](./timeline.md) diff --git a/src/content/docs/styling.md b/src/content/docs/styling.md new file mode 100644 index 0000000..26618ab --- /dev/null +++ b/src/content/docs/styling.md @@ -0,0 +1,85 @@ +# Styling + +`busker.css` is optional. It styles the cursor, the ripple, the pointer affordance, the miss hint, and scene switching — about 100 lines, all scoped to `.busker`. + +```typescript +import 'busker/busker.css'; +``` + +Or copy it into your own stylesheet and edit it. There is nothing clever in there. + +## Custom properties + +Set these on the root, or anywhere above it: + +| Property | Default | What it is | +|---|---|---| +| `--busker-accent` | `#7c3aed` | The press colour and the ripple. Set this to your brand colour. | +| `--busker-cursor-size` | `0.95rem` | Diameter of the dot. | +| `--busker-cursor-fill` | `rgb(0 0 0 / 0.42)` | The dot at rest. | +| `--busker-cursor-edge` | `#fff` | The ring around the dot that keeps it visible on dark UI. | + +```css +.my-mock { + --busker-accent: #e5484d; + --busker-cursor-size: 1.1rem; +} +``` + +## Drawing your own pointer + +The default cursor is a soft dot rather than an arrow, because an arrow on a screenshot of an app reads as *your* mouse and people try to move it. If you want an arrow anyway, put one in the cursor element and drop the dot styling: + +```html + +``` + +```css +.busker [data-cursor] { + background: none; + border: 0; + box-shadow: none; + height: auto; + width: auto; +} +``` + +Busker only sets `left`, `top`, and the `is-*` classes. Everything else is yours. + +## Scenes + +Two rules handle scene switching: + +```css +.busker [data-scene] { display: none; } +.busker [data-scene].is-active { display: block; } +``` + +Override them for a cross-fade — but keep the inactive scene out of the accessibility tree and out of layout, or the mock will be twice as tall as it looks: + +```css +.busker [data-scene] { + display: grid; + grid-area: 1 / 1; + opacity: 0; + pointer-events: none; + transition: opacity 0.2s ease; + visibility: hidden; +} + +.busker [data-scene].is-active { + opacity: 1; + pointer-events: auto; + visibility: visible; +} +``` + +## Two lengths are in both places + +`is-ringing` lasts 500ms and `is-hint` lasts 1.5s. Those numbers are in `busker.css` as animation durations and in the JavaScript as the moment it takes the class off again. Change one, change the other. + +## Reduced motion + +`busker.css` turns off the cursor transition and the hint animation under `prefers-reduced-motion: reduce`. The loop itself is already off — that is handled in JavaScript, not CSS, so a frozen mock costs nothing. + +← [When a visitor takes over](./taking-over.md) · Next: [API reference](./api-reference.md) diff --git a/src/content/docs/taking-over.md b/src/content/docs/taking-over.md new file mode 100644 index 0000000..40e9345 --- /dev/null +++ b/src/content/docs/taking-over.md @@ -0,0 +1,39 @@ +# When a visitor takes over + +A busker plays to whoever is passing. When someone actually walks up, they stop playing and let them have a go. Busker works the same way: the first real click ends the show and hands the mock to the visitor. + +## What happens on a click + +1. If the click hit a [route](./routines.md#routes) target, that scene comes up — the same code path the cursor's own clicks use. +2. The loop stops for good and the root gets `is-aside`, which hides the cursor. +3. If the click hit nothing clickable, every route target gets `is-hint` for 1.5s, so they can see what is live. + +From then on the mock is an ordinary bit of interactive markup. Busker is not going to grab the pointer back mid-thought, which is the whole reason it stops for good rather than resuming after a pause. + +## Its own clicks do not count + +Busker's presses go through `el.click()` — real clicks, real handlers, real routes. It knows which ones are its own and does not mistake them for a visitor. You do not have to filter anything in your handlers. + +`event.isTrusted` will not tell you the difference either, by the way: it is `false` for anything scripted, including test-runner and devtools clicks. Busker tracks its own presses directly instead. + +## Handing over on purpose + +`stepAside()` does the same thing from your code — for a "try it yourself" button, or when a visitor focuses something inside the mock: + +```typescript +const show = busk(root, routine); + +document.querySelector('#try-it')?.addEventListener('click', () => show.stepAside()); +``` + +## Stopping and starting + +`play()` and `pause()` are there if you need them, but you usually do not: busker already pauses when the mock scrolls off screen or the tab goes to the background, and resumes when it comes back. That is one `IntersectionObserver` and one `visibilitychange` listener, both cleaned up by `destroy()`. + +`destroy()` puts everything back — listeners, observers, and every class busker added. Call it when the component unmounts. + +## Reduced motion + +Under `prefers-reduced-motion: reduce` there is no loop, no cursor, and no observer. Busker renders the [`freezeAt`](./timeline.md#freezing-for-reduced-motion) frame once and leaves it there. Routes are still wired, so the mock stays clickable — it just never moves on its own. + +← [Hand-timed routines](./timeline.md) · Next: [Styling](./styling.md) diff --git a/src/content/docs/timeline.md b/src/content/docs/timeline.md new file mode 100644 index 0000000..01991dc --- /dev/null +++ b/src/content/docs/timeline.md @@ -0,0 +1,86 @@ +# Hand-timed routines + +Some of a demo is not caused by clicking. Logs stream in, a notification arrives, a countdown runs out, an assistant types a reply. Nothing pressed a button; it just happens. For that, give busker a `duration` and lay the events out on it. + +```typescript +busk(root, { + duration: 20_000, + toggles: [ + {target: '[data-toast]', class: 'is-open', from: 3000, until: 11_000}, + ], + typing: [ + {target: '[data-input]', text: 'why did checkout fail?', from: 4000, until: 6200, clearAt: 7000}, + ], + countdowns: [ + {target: '[data-clock]', seconds: 90}, + ], + moves: [ + {to: '[data-send]', from: 6400, until: 7000, press: 7100}, + ], +}); +``` + +Use whichever pieces you need; they are all optional. + +## Which mode to use + +Pick by asking what makes the thing on screen change. + +| The mock changes because… | Use | +|---|---| +| something was clicked | [`steps`](./routines.md) | +| time passed | `duration` and the fields below | + +`steps` and `duration` are the two ways in. Give busker `steps` and the loop length comes from the routine; give it `duration` and you are timing everything yourself. A demo that is mostly clicks with one timed flourish is usually better as clicks plus a CSS animation on the element than as a hand-timed routine. + +## toggles + +Holds a class on an element for a slice of the loop. This is how a modal opens, a row highlights, or a banner slides in. + +```typescript +{target: '[data-modal]', class: 'is-open', from: 3000, until: 11_000} +``` + +The class goes on at `from` and comes off at `until`. What that looks like is up to your CSS. + +## typing + +Writes text into an element a character at a time. It starts slow and speeds up, which reads more like a person than a constant rate does. + +```typescript +{target: '[data-input]', text: 'hello', from: 4000, until: 5200, clearAt: 6000} +``` + +The whole string is on screen at `until`. `clearAt` wipes it — the moment the message was sent. + +Busker sets `textContent`, so point it at a `` inside your fake input rather than at a real ``. + +## countdowns + +Ticks a `m:ss` clock down over the loop and stops at zero. + +```typescript +{target: '[data-clock]', seconds: 90} +``` + +## moves + +Glides the cursor on your timings instead of a routine's. + +```typescript +{to: '[data-send]', from: 6400, until: 7000, press: 7100} +``` + +`press` animates the press — the squash and the ripple. **It does not click anything.** In hand-timed mode you are already saying what changes and when, so a real click would fire it twice. If you want the click to be the cause, that is what [`steps`](./routines.md) is for. + +## Freezing for reduced motion + +Under `prefers-reduced-motion: reduce` busker renders one frame and stops, with the cursor hidden. Pick which frame with `freezeAt`: + +```typescript +busk(root, {duration: 20_000, freezeAt: 8000, /* … */}); +``` + +Choose the moment that shows the point of the demo — the modal open, the reply written. It defaults to `0`, which for most routines is an empty starting state. + +← [Click-driven routines](./routines.md) · Next: [When a visitor takes over](./taking-over.md) diff --git a/src/content/github-friendly-docs-loader.ts b/src/content/github-friendly-docs-loader.ts new file mode 100644 index 0000000..8f314b2 --- /dev/null +++ b/src/content/github-friendly-docs-loader.ts @@ -0,0 +1,37 @@ +import {readFile} from 'node:fs/promises'; +import {docsLoader} from '@astrojs/starlight/loaders'; +import type {Loader} from 'astro/loaders'; + +/** + * Starlight requires a frontmatter `title`; the guides use a GitHub-friendly + * `# Title` instead so the same file reads correctly in a checkout. Inject the + * title from the first heading before schema validation. + */ +export function githubFriendlyDocsLoader(): Loader { + const inner = docsLoader(); + + return { + name: 'github-friendly-docs-loader', + load: async (context) => { + const parseData = context.parseData.bind(context); + + context.parseData = async (props) => { + const {data, filePath} = props; + + if (!data.title && filePath?.endsWith('.md')) { + const contents = await readFile(filePath, 'utf-8'); + + if (!contents.startsWith('---')) { + const match = contents.match(/^# (.+)\r?\n/); + + if (match) return parseData({...props, data: {...data, title: match[1]}}); + } + } + + return parseData(props); + }; + + return inner.load(context); + }, + }; +} diff --git a/src/content/i18n/en.json b/src/content/i18n/en.json new file mode 100644 index 0000000..1110262 --- /dev/null +++ b/src/content/i18n/en.json @@ -0,0 +1,8 @@ +{ + "search.label": "Search docs", + "pagefind.zero_results": "No matches for β€œ[SEARCH_TERM]”", + "pagefind.one_result": "[COUNT] match for β€œ[SEARCH_TERM]”", + "pagefind.many_results": "[COUNT] matches for β€œ[SEARCH_TERM]”", + "pagefind.searching": "Searching β€œ[SEARCH_TERM]”…", + "pagefind.search_suggestion": "No matches for β€œ[SEARCH_TERM]”. Try one of these:" +} diff --git a/src/cs-pagefind/pagefind.ts b/src/cs-pagefind/pagefind.ts new file mode 100644 index 0000000..e41299f --- /dev/null +++ b/src/cs-pagefind/pagefind.ts @@ -0,0 +1,33 @@ +/** + * Shim loaded by PagefindUI instead of the real engine. + * Re-exports Pagefind APIs with a filtered `search` (see wrapPagefindSearch). + * + * Relative import keeps GitHub Pages base paths working: + * /{base}/cs-pagefind/pagefind.js β†’ /{base}/pagefind/pagefind.js + * + * At build time esbuild leaves `../pagefind/pagefind.js` external so the browser + * loads the Starlight-generated index next to this shim. + */ +import * as engine from '../pagefind/pagefind.js'; +import {wrapPagefindSearch} from '../components/wrapPagefindSearch.js'; + +let warmPromise: Promise | null = null; + +async function warmIndex(): Promise { + warmPromise ??= engine.preload(null, {}).then(() => undefined); + await warmPromise; +} + +export async function options(opts: Parameters[0]): Promise { + await engine.options(opts); + await warmIndex(); +} +export const init = engine.init; +export const destroy = engine.destroy; +export const mergeIndex = engine.mergeIndex; +export const filters = engine.filters; +export const preload = engine.preload; +export const createInstance = engine.createInstance; +export const debouncedSearch = engine.debouncedSearch; + +export const search = wrapPagefindSearch(async (term, options) => engine.search(term, options)); diff --git a/src/env.d.ts b/src/env.d.ts new file mode 100644 index 0000000..00572bb --- /dev/null +++ b/src/env.d.ts @@ -0,0 +1,78 @@ +/// + +/** + * Starlight virtual modules + route locals for docs overrides. + * Inline declarations only β€” must not import @astrojs/starlight TS sources, + * or library `tsc` (rootDir src) will typecheck node_modules and break. + */ + +type StarlightComponent = import('astro').AstroComponentFactory; + +declare module 'virtual:starlight/user-config' { + const Config: { + pagefind?: boolean | Record; + components: Record; + }; + export default Config; +} + +declare module 'virtual:starlight/project-context' { + const ProjectContext: { + root: string; + srcDir: string; + trailingSlash: import('astro').AstroConfig['trailingSlash']; + build: { + format: import('astro').AstroConfig['build']['format']; + }; + }; + export default ProjectContext; +} + +declare module 'virtual:starlight/pagefind-config' { + export const pagefindUserConfig: Record; +} + +declare module 'virtual:starlight/components/Pagination' { + const Pagination: StarlightComponent; + export default Pagination; +} +declare module 'virtual:starlight/components/LanguageSelect' { + const LanguageSelect: StarlightComponent; + export default LanguageSelect; +} +declare module 'virtual:starlight/components/MobileMenuToggle' { + const MobileMenuToggle: StarlightComponent; + export default MobileMenuToggle; +} +declare module 'virtual:starlight/components/Search' { + const Search: StarlightComponent; + export default Search; +} +declare module 'virtual:starlight/components/SiteTitle' { + const SiteTitle: StarlightComponent; + export default SiteTitle; +} +declare module 'virtual:starlight/components/SocialIcons' { + const SocialIcons: StarlightComponent; + export default SocialIcons; +} +declare module 'virtual:starlight/components/ThemeSelect' { + const ThemeSelect: StarlightComponent; + export default ThemeSelect; +} + +declare namespace App { + interface Locals { + starlightRoute: { + pagination: { + prev?: {href: string; label: string}; + next?: {href: string; label: string}; + }; + hasSidebar: boolean; + siteTitle: string; + siteTitleHref: string; + head: Array<{tag: string; attrs?: Record; content?: string}>; + entry: {data: {title: string}}; + }; + } +} diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..4d3fda7 --- /dev/null +++ b/src/index.ts @@ -0,0 +1,12 @@ +export {busk} from './busk'; +export type { + Busker, + Countdown, + Move, + Point, + Route, + Routine, + Step, + Toggle, + Typing, +} from './types'; diff --git a/src/integrations/assertAstroDevFree.mjs b/src/integrations/assertAstroDevFree.mjs new file mode 100644 index 0000000..f767c2d --- /dev/null +++ b/src/integrations/assertAstroDevFree.mjs @@ -0,0 +1,61 @@ +import net from 'node:net'; + +/** Starlight default β€” documented in development.md as http://127.0.0.1:4321 */ +export const ASTRO_DEV_PORT = 4321; + +/** Astro increments when 4321 is taken β€” scan the usual fallback range. */ +export const ASTRO_DEV_PORT_MAX = 4330; + +export function portInUse(port) { + return new Promise((resolve) => { + const socket = net.connect({port, host: '127.0.0.1'}); + socket.once('connect', () => { + socket.destroy(); + resolve(true); + }); + socket.once('error', () => { + resolve(false); + }); + }); +} + +export async function astroDevPortsInUse( + from = ASTRO_DEV_PORT, + to = ASTRO_DEV_PORT_MAX, +) { + const busy = []; + + for (let port = from; port <= to; port++) { + if (await portInUse(port)) { + busy.push(port); + } + } + + return busy; +} + +function astroDevBusyMessage(ports) { + const primary = ports[0]; + const also = ports.length > 1 ? ` (also ${ports.slice(1).join(', ')})` : ''; + + return ( + `Astro dev appears to be running on http://127.0.0.1:${primary}${also}. ` + + 'Stop it before starting another or running astro:build β€” wiping .astro while dev is up breaks Starlight sidebar slugs.' + ); +} + +export async function assertAstroDevFree(port) { + if (port !== undefined) { + if (await portInUse(port)) { + throw new Error(astroDevBusyMessage([port])); + } + + return; + } + + const busy = await astroDevPortsInUse(); + + if (busy.length > 0) { + throw new Error(astroDevBusyMessage(busy)); + } +} diff --git a/src/integrations/assertAstroDevFree.spec.ts b/src/integrations/assertAstroDevFree.spec.ts new file mode 100644 index 0000000..6314bef --- /dev/null +++ b/src/integrations/assertAstroDevFree.spec.ts @@ -0,0 +1,117 @@ +import {createServer} from 'node:net'; +import {readFileSync} from 'node:fs'; +import path from 'node:path'; +import {fileURLToPath} from 'node:url'; +import {test} from 'kizu'; +import {ASTRO_DEV_PORT, ASTRO_DEV_PORT_MAX, assertAstroDevFree, astroDevPortsInUse, portInUse} from './assertAstroDevFree.mjs'; + +const root = path.join(path.dirname(fileURLToPath(import.meta.url)), '../..'); + +function listen(port = 0): Promise<{port: number; close: () => Promise}> { + return new Promise((resolve, reject) => { + const server = createServer(); + server.once('error', reject); + server.listen(port, '127.0.0.1', () => { + const addr = server.address(); + if (!addr || typeof addr === 'string') { + server.close(); + reject(new Error('expected TCP address')); + return; + } + resolve({ + port: addr.port, + close: () => new Promise((done, fail) => { + server.close((err) => (err ? fail(err) : done())); + }), + }); + }); + }); +} + +test('portInUse is true while a socket is listening', async (assert) => { + const server = await listen(); + try { + assert.equal(await portInUse(server.port), true); + } finally { + await server.close(); + } +}); + +test('portInUse is false after the socket closes', async (assert) => { + const server = await listen(); + await server.close(); + assert.equal(await portInUse(server.port), false); +}); + +test('assertAstroDevFree throws when the port is taken', async (assert) => { + const server = await listen(); + try { + let message = ''; + try { + await assertAstroDevFree(server.port); + } catch (err) { + message = err instanceof Error ? err.message : String(err); + } + assert.equal(message.includes(String(server.port)), true); + assert.equal(message.includes('Astro dev appears to be running'), true); + } finally { + await server.close(); + } +}); + +test('assertAstroDevFree resolves when the port is free', async (assert) => { + const server = await listen(); + const {port} = server; + await server.close(); + await assertAstroDevFree(port); + assert.equal(true, true); +}); + +test('astroDevPortsInUse finds listeners in the dev port range', async (assert) => { + const server = await listen(); + try { + assert.equal(await astroDevPortsInUse(server.port, server.port), [server.port]); + } finally { + await server.close(); + } +}); + +test('assertAstroDevFree scans the Astro fallback port range by default', async (assert) => { + let server: {port: number; close: () => Promise} | undefined; + + for (let port = ASTRO_DEV_PORT_MAX; port >= ASTRO_DEV_PORT; port--) { + try { + server = await listen(port); + break; + } catch { + // try the next port in Astro's fallback range + } + } + + assert.equal(typeof server, 'object'); + + try { + let message = ''; + try { + await assertAstroDevFree(); + } catch (err) { + message = err instanceof Error ? err.message : String(err); + } + assert.equal(message.includes(String(server?.port)), true); + assert.equal(message.includes('sidebar slugs'), true); + } finally { + await server?.close(); + } +}); + +test('astro:dev wipes compiler caches after the port check; astro:build does not', (assert) => { + const pkg = JSON.parse(readFileSync(path.join(root, 'package.json'), 'utf8')); + const devPrefix = 'node scripts/assert-astro-dev-free.mjs && node scripts/clean-astro-cache.mjs &&'; + const buildPrefix = 'node scripts/assert-astro-dev-free.mjs &&'; + + assert.equal(pkg.scripts['astro:dev'].startsWith(devPrefix), true); + assert.equal(pkg.scripts['astro:build'].startsWith(buildPrefix), true); + assert.equal(pkg.scripts['astro:build'].includes('clean-astro-cache.mjs'), false); + assert.equal(ASTRO_DEV_PORT, 4321); + assert.equal(ASTRO_DEV_PORT_MAX, 4330); +}); diff --git a/src/integrations/assertAstroDevFree.ts b/src/integrations/assertAstroDevFree.ts new file mode 100644 index 0000000..d1540c4 --- /dev/null +++ b/src/integrations/assertAstroDevFree.ts @@ -0,0 +1,61 @@ +import net from 'node:net'; + +/** Starlight default β€” documented in development.md as http://127.0.0.1:4321 */ +export const ASTRO_DEV_PORT = 4321; + +/** Astro increments when 4321 is taken β€” scan the usual fallback range. */ +export const ASTRO_DEV_PORT_MAX = 4330; + +export function portInUse(port: number): Promise { + return new Promise((resolve) => { + const socket = net.connect({port, host: '127.0.0.1'}); + socket.once('connect', () => { + socket.destroy(); + resolve(true); + }); + socket.once('error', () => { + resolve(false); + }); + }); +} + +export async function astroDevPortsInUse( + from: number = ASTRO_DEV_PORT, + to: number = ASTRO_DEV_PORT_MAX, +): Promise { + const busy: number[] = []; + + for (let port = from; port <= to; port++) { + if (await portInUse(port)) { + busy.push(port); + } + } + + return busy; +} + +function astroDevBusyMessage(ports: number[]): string { + const primary = ports[0]; + const also = ports.length > 1 ? ` (also ${ports.slice(1).join(', ')})` : ''; + + return ( + `Astro dev appears to be running on http://127.0.0.1:${primary}${also}. ` + + 'Stop it before starting another or running astro:build β€” wiping .astro while dev is up breaks Starlight sidebar slugs.' + ); +} + +export async function assertAstroDevFree(port?: number): Promise { + if (port !== undefined) { + if (await portInUse(port)) { + throw new Error(astroDevBusyMessage([port])); + } + + return; + } + + const busy = await astroDevPortsInUse(); + + if (busy.length > 0) { + throw new Error(astroDevBusyMessage(busy)); + } +} diff --git a/src/integrations/cleanAstroCache.mjs b/src/integrations/cleanAstroCache.mjs new file mode 100644 index 0000000..c6f3dbc --- /dev/null +++ b/src/integrations/cleanAstroCache.mjs @@ -0,0 +1,15 @@ +import fs from 'node:fs'; +import path from 'node:path'; + +/** Compiler / prebundle dirs that survive `astro dev` restarts and go stale. */ +export const ASTRO_DEV_CACHE_DIRS = ['.astro', path.join('node_modules', '.vite')]; + +export function cleanAstroCache(root) { + for (const rel of ASTRO_DEV_CACHE_DIRS) { + if (rel.includes('..') || path.isAbsolute(rel)) { + throw new Error(`refusing to delete unsafe cache path: ${rel}`); + } + + fs.rmSync(path.join(root, rel), {recursive: true, force: true}); + } +} diff --git a/src/integrations/cleanAstroCache.spec.ts b/src/integrations/cleanAstroCache.spec.ts new file mode 100644 index 0000000..9207972 --- /dev/null +++ b/src/integrations/cleanAstroCache.spec.ts @@ -0,0 +1,62 @@ +import {test} from 'kizu'; +import {existsSync, mkdirSync, readFileSync, writeFileSync} from 'node:fs'; +import os from 'node:os'; +import path from 'node:path'; +import {fileURLToPath} from 'node:url'; +import {ASTRO_DEV_CACHE_DIRS, cleanAstroCache} from './cleanAstroCache.mjs'; + +const root = path.join(path.dirname(fileURLToPath(import.meta.url)), '../..'); + +test('cleanAstroCache removes .astro and Vite prebundle dirs', (assert) => { + + const tmp = path.join(os.tmpdir(), `busker-astro-cache-${Date.now()}`); + + for (const rel of ASTRO_DEV_CACHE_DIRS) { + + const dir = path.join(tmp, rel); + + mkdirSync(dir, {recursive: true}); + writeFileSync(path.join(dir, 'stale.json'), '{"stale":true}'); + + } + + cleanAstroCache(tmp); + + for (const rel of ASTRO_DEV_CACHE_DIRS) { + + assert.equal(existsSync(path.join(tmp, rel)), false); + + } + +}); + +test('cleanAstroCache is a no-op when cache dirs are missing', (assert) => { + + const tmp = path.join(os.tmpdir(), `busker-astro-cache-empty-${Date.now()}`); + + mkdirSync(tmp, {recursive: true}); + cleanAstroCache(tmp); + assert.equal(existsSync(tmp), true); + +}); + +test('astro:dev wipes compiler caches after the port check; astro:build does not', (assert) => { + + const pkg = JSON.parse(readFileSync(path.join(root, 'package.json'), 'utf8')); + const devPrefix = 'node scripts/assert-astro-dev-free.mjs && node scripts/clean-astro-cache.mjs &&'; + const buildPrefix = 'node scripts/assert-astro-dev-free.mjs &&'; + + assert.equal(pkg.scripts['astro:dev'].startsWith(devPrefix), true); + assert.equal(pkg.scripts['astro:build'].startsWith(buildPrefix), true); + assert.equal(pkg.scripts['astro:build'].includes('clean-astro-cache.mjs'), false); + +}); + +test('astro dev server sends Cache-Control: no-store', (assert) => { + + const astro = readFileSync(path.join(root, 'astro.config.mjs'), 'utf8'); + + assert.equal(astro.includes("'Cache-Control': 'no-store'"), true); + assert.equal(astro.includes('server:'), true); + +}); diff --git a/src/integrations/cleanAstroCache.ts b/src/integrations/cleanAstroCache.ts new file mode 100644 index 0000000..a508e9b --- /dev/null +++ b/src/integrations/cleanAstroCache.ts @@ -0,0 +1,15 @@ +import fs from 'node:fs'; +import path from 'node:path'; + +/** Compiler / prebundle dirs that survive `astro dev` restarts and go stale. */ +export const ASTRO_DEV_CACHE_DIRS = ['.astro', path.join('node_modules', '.vite')]; + +export function cleanAstroCache(root: string): void { + for (const rel of ASTRO_DEV_CACHE_DIRS) { + if (rel.includes('..') || path.isAbsolute(rel)) { + throw new Error(`refusing to delete unsafe cache path: ${rel}`); + } + + fs.rmSync(path.join(root, rel), {recursive: true, force: true}); + } +} diff --git a/src/integrations/devPagefind.mjs b/src/integrations/devPagefind.mjs new file mode 100644 index 0000000..c32b6aa --- /dev/null +++ b/src/integrations/devPagefind.mjs @@ -0,0 +1,108 @@ +import fs from 'node:fs'; +import path from 'node:path'; +import {fileURLToPath} from 'node:url'; + +const repoRoot = path.join(path.dirname(fileURLToPath(import.meta.url)), '../..'); +const docsSiteDir = path.join(repoRoot, 'docs-site'); + +const PAGEFIND_BUCKETS = { + pagefind: path.join(docsSiteDir, 'pagefind'), + 'cs-pagefind': path.join(docsSiteDir, 'cs-pagefind'), +}; + +const SEARCH_INDEX_MARKERS = [ + path.join('docs-site', 'pagefind', 'pagefind.js'), + path.join('docs-site', 'cs-pagefind', 'pagefind.js'), +]; + +const STATIC_TYPES = { + '.js': 'text/javascript; charset=utf-8', + '.css': 'text/css; charset=utf-8', + '.json': 'application/json; charset=utf-8', + '.wasm': 'application/wasm', + '.pf_fragment': 'application/octet-stream', + '.pf_index': 'application/octet-stream', + '.pf_meta': 'application/json; charset=utf-8', +}; + +/** + * Map a dev-server request to a built Pagefind asset under docs-site/. + * @param {string} pathname decoded URL pathname (no query) + * @returns {{ bucket: 'pagefind' | 'cs-pagefind', rel: string } | null} + */ +export function resolvePagefindDevAsset(pathname) { + const clean = pathname.split('?')[0]; + + if (clean.startsWith('/pagefind/')) { + const rel = clean.slice('/pagefind/'.length); + if (!rel || rel.includes('..') || path.isAbsolute(rel)) return null; + return {bucket: 'pagefind', rel}; + } + + if (clean.startsWith('/cs-pagefind/')) { + const rel = clean.slice('/cs-pagefind/'.length); + if (!rel || rel.includes('..') || path.isAbsolute(rel)) return null; + return {bucket: 'cs-pagefind', rel}; + } + + return null; +} + +export function pagefindIndexReady(root = repoRoot) { + return SEARCH_INDEX_MARKERS.every((rel) => fs.existsSync(path.join(root, rel))); +} + +function sendPagefindFile(bucket, rel, res) { + const root = PAGEFIND_BUCKETS[bucket]; + const file = path.join(root, rel); + + if (!fs.existsSync(file) || !fs.statSync(file).isFile()) { + res.statusCode = 404; + res.setHeader('Content-Type', 'text/plain; charset=utf-8'); + res.end('Not Found'); + return; + } + + const type = STATIC_TYPES[path.extname(file)] ?? 'application/octet-stream'; + res.statusCode = 200; + res.setHeader('Content-Type', type); + res.setHeader('Cache-Control', 'no-store'); + fs.createReadStream(file).pipe(res); +} + +/** + * Vite plugin: serve the last astro:build Pagefind index during astro dev. + * Search-modal CSS/JS hot-reloads via Vite; refresh the index with `npm run astro:build:pagefind` while dev keeps running. + */ +export function devPagefindPlugin() { + return { + name: 'callspec-dev-pagefind', + configureServer(server) { + const logger = server.config.logger; + + if (!pagefindIndexReady()) { + logger.warn( + 'Docs search index missing β€” run `npm run astro:build` once, then search works in dev with HMR.', + ); + } + + return () => { + server.middlewares.stack.unshift({ + route: '', + handle(req, res, next) { + const url = req.url ?? '/'; + const pathname = decodeURI(new URL(url, 'http://localhost').pathname); + const asset = resolvePagefindDevAsset(pathname); + + if (asset) { + sendPagefindFile(asset.bucket, asset.rel, res); + return; + } + + next(); + }, + }); + }; + }, + }; +} diff --git a/src/integrations/devPagefind.spec.ts b/src/integrations/devPagefind.spec.ts new file mode 100644 index 0000000..6b08a95 --- /dev/null +++ b/src/integrations/devPagefind.spec.ts @@ -0,0 +1,35 @@ +import fs from 'node:fs'; +import os from 'node:os'; +import path from 'node:path'; +import {test} from 'kizu'; +import {pagefindIndexReady, resolvePagefindDevAsset} from './devPagefind.mjs'; + +test('resolvePagefindDevAsset maps built index URLs', (assert) => { + const engine = resolvePagefindDevAsset('/pagefind/pagefind.js'); + assert.equal(engine?.bucket, 'pagefind'); + assert.equal(engine?.rel, 'pagefind.js'); + + const shim = resolvePagefindDevAsset('/cs-pagefind/pagefind.js'); + assert.equal(shim?.bucket, 'cs-pagefind'); + assert.equal(shim?.rel, 'pagefind.js'); + + assert.equal(resolvePagefindDevAsset('/pagefind/wasm.unknown/pagefind_bg.wasm')?.rel, 'wasm.unknown/pagefind_bg.wasm'); + assert.equal(resolvePagefindDevAsset('/pagefind/../secret'), null); + assert.equal(resolvePagefindDevAsset('/other'), null); +}); + +test('pagefindIndexReady requires engine + shim', (assert) => { + const root = fs.mkdtempSync(path.join(os.tmpdir(), 'busker-pagefind-')); + const pagefindDir = path.join(root, 'docs-site', 'pagefind'); + const shimDir = path.join(root, 'docs-site', 'cs-pagefind'); + fs.mkdirSync(pagefindDir, {recursive: true}); + fs.mkdirSync(shimDir, {recursive: true}); + + assert.equal(pagefindIndexReady(root), false); + + fs.writeFileSync(path.join(pagefindDir, 'pagefind.js'), 'engine'); + assert.equal(pagefindIndexReady(root), false); + + fs.writeFileSync(path.join(shimDir, 'pagefind.js'), 'shim'); + assert.equal(pagefindIndexReady(root), true); +}); diff --git a/src/integrations/pagefindShim.mjs b/src/integrations/pagefindShim.mjs new file mode 100644 index 0000000..e30d807 --- /dev/null +++ b/src/integrations/pagefindShim.mjs @@ -0,0 +1,23 @@ +import {spawnSync} from 'node:child_process'; +import path from 'node:path'; +import {fileURLToPath} from 'node:url'; + +const root = path.join(path.dirname(fileURLToPath(import.meta.url)), '../..'); + +/** Starlight writes /pagefind/ during astro build; the cs-pagefind shim must follow. */ +export function pagefindShimIntegration() { + return { + name: 'callspec-pagefind-shim', + hooks: { + 'astro:build:done': () => { + const result = spawnSync(process.execPath, ['scripts/write-cs-pagefind-shim.mjs'], { + cwd: root, + stdio: 'inherit', + }); + if (result.status !== 0) { + throw new Error('write-cs-pagefind-shim failed'); + } + }, + }, + }; +} diff --git a/src/integrations/rehype-wrap-tables.mjs b/src/integrations/rehype-wrap-tables.mjs new file mode 100644 index 0000000..d647bab --- /dev/null +++ b/src/integrations/rehype-wrap-tables.mjs @@ -0,0 +1,32 @@ +import {visit} from 'unist-util-visit'; + +function classList(node) { + const value = node?.properties?.className; + if (Array.isArray(value)) return value; + if (typeof value === 'string') return value.split(/\s+/).filter(Boolean); + return []; +} + +/** + * Wrap every content `` in `.cs-table-scroll` so wide grids can + * scroll horizontally without crushing columns (`display: table` preserved). + */ +export function rehypeWrapTables() { + return (tree) => { + visit(tree, 'element', (node, index, parent) => { + if (node.tagName !== 'table' || parent == null || index == null) { + return; + } + if (parent.tagName === 'div' && classList(parent).includes('cs-table-scroll')) { + return; + } + + parent.children[index] = { + type: 'element', + tagName: 'div', + properties: {className: ['cs-table-scroll']}, + children: [node], + }; + }); + }; +} diff --git a/src/integrations/rehype-wrap-tables.ts b/src/integrations/rehype-wrap-tables.ts new file mode 100644 index 0000000..ce9346b --- /dev/null +++ b/src/integrations/rehype-wrap-tables.ts @@ -0,0 +1,35 @@ +import type {Element, Root} from 'hast'; +import type {Plugin} from 'unified'; +import {visit} from 'unist-util-visit'; + +function classList(node: Element | undefined): string[] { + const value = node?.properties?.className; + if (Array.isArray(value)) return value.filter((entry): entry is string => typeof entry === 'string'); + if (typeof value === 'string') return value.split(/\s+/).filter(Boolean); + return []; +} + +/** + * Wrap every content `
` in `.cs-table-scroll` so wide grids can + * scroll horizontally without crushing columns (`display: table` preserved). + */ +export function rehypeWrapTables(): Plugin<[undefined], Root> { + return (tree) => { + visit(tree, 'element', (node, index, parent) => { + if (node.tagName !== 'table' || parent == null || index == null) { + return; + } + const parentElement = parent as Element; + if (parentElement.tagName === 'div' && classList(parentElement).includes('cs-table-scroll')) { + return; + } + + parentElement.children[index] = { + type: 'element', + tagName: 'div', + properties: {className: ['cs-table-scroll']}, + children: [node], + }; + }); + }; +} diff --git a/src/integrations/remark-drop-title.mjs b/src/integrations/remark-drop-title.mjs new file mode 100644 index 0000000..5a8d83a --- /dev/null +++ b/src/integrations/remark-drop-title.mjs @@ -0,0 +1,18 @@ +import path from 'node:path'; + +const DOCS_SEGMENT = `${path.sep}src${path.sep}content${path.sep}docs${path.sep}`; + +/** + * Guides open with a GitHub-friendly `# Title` and no frontmatter. Starlight + * already prints that title above the content, so drop the heading from the + * body β€” otherwise every page says its name twice. + */ +export function remarkDropTitle() { + return (tree, file) => { + if (!file.path?.includes(DOCS_SEGMENT)) return; + + const first = tree.children[0]; + + if (first?.type === 'heading' && first.depth === 1) tree.children.shift(); + }; +} diff --git a/src/integrations/remark-starlight-md-links.mjs b/src/integrations/remark-starlight-md-links.mjs new file mode 100644 index 0000000..db06670 --- /dev/null +++ b/src/integrations/remark-starlight-md-links.mjs @@ -0,0 +1,53 @@ +import path from 'node:path'; + +const DOCS_SEGMENT = `${path.sep}src${path.sep}content${path.sep}docs${path.sep}`; + +/** + * Rewrite GitHub-friendly `./page.md` links to Starlight slugs (`/page/`) at build time. + * Source files keep `.md` extensions for GitHub browsing. + */ +function toStarlightSlug(filePath, url) { + const hashIndex = url.indexOf('#'); + const pathPart = hashIndex === -1 ? url : url.slice(0, hashIndex); + const hash = hashIndex === -1 ? '' : url.slice(hashIndex); + + if (!pathPart.endsWith('.md')) return null; + if (/^[a-z][a-z0-9+.-]*:/i.test(pathPart)) return null; + + const docsIndex = filePath.indexOf(DOCS_SEGMENT); + if (docsIndex === -1) return null; + + const docsRoot = filePath.slice(0, docsIndex + DOCS_SEGMENT.length); + const sourceDir = path.dirname(filePath); + const targetPath = path.resolve(sourceDir, pathPart); + + if (!targetPath.startsWith(docsRoot)) return null; + + const slug = path + .relative(docsRoot, targetPath) + .replace(/\.md$/, '') + .split(path.sep) + .join('/'); + + return `/${slug}/${hash}`; +} + +function visitLinks(node, visitor) { + if (!node || typeof node !== 'object') return; + if (node.type === 'link' && typeof node.url === 'string') visitor(node); + if (Array.isArray(node.children)) { + for (const child of node.children) visitLinks(child, visitor); + } +} + +/** @returns {import('unified').Plugin} */ +export function remarkStarlightMdLinks() { + return (tree, file) => { + if (!file.path?.includes(DOCS_SEGMENT)) return; + + visitLinks(tree, (link) => { + const rewritten = toStarlightSlug(file.path, link.url); + if (rewritten) link.url = rewritten; + }); + }; +} diff --git a/src/integrations/remark-starlight-md-links.ts b/src/integrations/remark-starlight-md-links.ts new file mode 100644 index 0000000..d335242 --- /dev/null +++ b/src/integrations/remark-starlight-md-links.ts @@ -0,0 +1,59 @@ +import path from 'node:path'; +import type {Link, Root} from 'mdast'; +import type {Plugin} from 'unified'; +import type {VFile} from 'vfile'; + +const DOCS_SEGMENT = `${path.sep}src${path.sep}content${path.sep}docs${path.sep}`; + +/** + * Rewrite GitHub-friendly `./page.md` links to Starlight slugs (`/page/`) at build time. + * Source files keep `.md` extensions for GitHub browsing. + */ +function toStarlightSlug(filePath: string, url: string): string | null { + const hashIndex = url.indexOf('#'); + const pathPart = hashIndex === -1 ? url : url.slice(0, hashIndex); + const hash = hashIndex === -1 ? '' : url.slice(hashIndex); + + if (!pathPart.endsWith('.md')) return null; + if (/^[a-z][a-z0-9+.-]*:/i.test(pathPart)) return null; + + const docsIndex = filePath.indexOf(DOCS_SEGMENT); + if (docsIndex === -1) return null; + + const docsRoot = filePath.slice(0, docsIndex + DOCS_SEGMENT.length); + const sourceDir = path.dirname(filePath); + const targetPath = path.resolve(sourceDir, pathPart); + + if (!targetPath.startsWith(docsRoot)) return null; + + const slug = path + .relative(docsRoot, targetPath) + .replace(/\.md$/, '') + .split(path.sep) + .join('/'); + + return `/${slug}/${hash}`; +} + +function visitLinks(node: unknown, visitor: (link: Link) => void): void { + if (!node || typeof node !== 'object') return; + const tree = node as {type?: string; url?: string; children?: unknown[]}; + if (tree.type === 'link' && typeof tree.url === 'string') { + visitor(tree as Link); + } + if (Array.isArray(tree.children)) { + for (const child of tree.children) visitLinks(child, visitor); + } +} + +export function remarkStarlightMdLinks(): Plugin<[undefined], Root> { + return (tree, file) => { + const vfile = file as VFile; + if (!vfile.path?.includes(DOCS_SEGMENT)) return; + + visitLinks(tree, (link) => { + const rewritten = toStarlightSlug(vfile.path!, link.url); + if (rewritten) link.url = rewritten; + }); + }; +} diff --git a/src/overrides/Footer.astro b/src/overrides/Footer.astro new file mode 100644 index 0000000..acad8ce --- /dev/null +++ b/src/overrides/Footer.astro @@ -0,0 +1,24 @@ +--- +/** + * Prev/next on guide pages, plus a site footer on splash, 404, and docs. + */ +import Pagination from 'virtual:starlight/components/Pagination'; +import SiteFooter from '../components/SiteFooter.astro'; + +const {pagination} = Astro.locals.starlightRoute; +const hasPaging = Boolean(pagination.prev || pagination.next); +--- + +
+ {hasPaging && } + +
+ + diff --git a/src/overrides/Head.astro b/src/overrides/Head.astro new file mode 100644 index 0000000..2840369 --- /dev/null +++ b/src/overrides/Head.astro @@ -0,0 +1,25 @@ +--- +import CodeBlockTitles from '../components/CodeBlockTitles.astro'; +import SearchModalPolish from '../components/SearchModalPolish.astro'; + +const {head} = Astro.locals.starlightRoute; +--- + +{head.map(({tag: Tag, attrs, content}) => )} + + + + + diff --git a/src/overrides/Header.astro b/src/overrides/Header.astro new file mode 100644 index 0000000..a31c3c4 --- /dev/null +++ b/src/overrides/Header.astro @@ -0,0 +1,162 @@ +--- +/** + * Header β€” on guide pages below 50rem: logo + Menu only. + * Search + theme live in the mobile drawer (MobileMenuFooter). + */ +import config from 'virtual:starlight/user-config'; + +import LanguageSelect from 'virtual:starlight/components/LanguageSelect'; +import MobileMenuToggle from 'virtual:starlight/components/MobileMenuToggle'; +import Search from 'virtual:starlight/components/Search'; +import SiteTitle from 'virtual:starlight/components/SiteTitle'; +import SocialIcons from 'virtual:starlight/components/SocialIcons'; +import ThemeSelect from 'virtual:starlight/components/ThemeSelect'; + +const {hasSidebar} = Astro.locals.starlightRoute; + +const shouldRenderSearch = + config.pagefind || config.components.Search !== '@astrojs/starlight/components/Search.astro'; +--- + +
+
+ +
+ { + shouldRenderSearch && ( + + ) + } +
+ + + + {hasSidebar && } +
+
+ + diff --git a/src/overrides/Hero.astro b/src/overrides/Hero.astro new file mode 100644 index 0000000..365b7bb --- /dev/null +++ b/src/overrides/Hero.astro @@ -0,0 +1,7 @@ +--- +/** + * Starlight only uses the single-panel splash layout when hero frontmatter is set. + * Real heroes live on src/pages/index.astro and src/pages/404.astro β€” this + * suppresses Starlight's default Hero. + */ +--- diff --git a/src/overrides/MobileMenuFooter.astro b/src/overrides/MobileMenuFooter.astro new file mode 100644 index 0000000..0831f1a --- /dev/null +++ b/src/overrides/MobileMenuFooter.astro @@ -0,0 +1,131 @@ +--- +/** + * Mobile drawer chrome β€” search trigger (opens the header dialog) + theme. + * Do not mount a second Search component: two site-search elements both handle `/` + * and stack two dialogs, so focus, Esc, and outside-click all fail. + */ +import {Icon} from '@astrojs/starlight/components'; +import config from 'virtual:starlight/user-config'; +import LanguageSelect from 'virtual:starlight/components/LanguageSelect'; +import SocialIcons from 'virtual:starlight/components/SocialIcons'; +import ThemeSelect from 'virtual:starlight/components/ThemeSelect'; + +const shouldRenderSearch = + config.pagefind || config.components.Search !== '@astrojs/starlight/components/Search.astro'; +--- + +
+ { + shouldRenderSearch && ( + + ) + } +
+ +
+ Theme + +
+ +
+
+ + diff --git a/src/overrides/MobileMenuToggle.astro b/src/overrides/MobileMenuToggle.astro new file mode 100644 index 0000000..ab9a63a --- /dev/null +++ b/src/overrides/MobileMenuToggle.astro @@ -0,0 +1,236 @@ +--- +/** + * In-header Menu chip β€” equal bars + β€œMenu”/β€œClose” label. + * Animates open ↔ close. + */ +const label = Astro.locals.t('menuButton.accessibleLabel'); +--- + + + + + + + + + + diff --git a/src/overrides/PageFrame.astro b/src/overrides/PageFrame.astro new file mode 100644 index 0000000..3cbb1d1 --- /dev/null +++ b/src/overrides/PageFrame.astro @@ -0,0 +1,167 @@ +--- +/** + * Page frame β€” menu control lives in the header (see Header.astro). + * Sidebar open state uses body[data-mobile-menu-expanded] instead of a + * sibling selector on the old fixed toggle. + */ +const {hasSidebar} = Astro.locals.starlightRoute; +--- + +
+
+ { + hasSidebar && ( + + ) + } +
+
+ +{/* + Starlight persists sidebar scroll, but restore skips falsy scroll and save is + only on visibilitychange. We patch that lightly β€” save only on sidebar-link + click / pagehide (no per-frame sessionStorage writes; those felt sluggish). +*/} + + + + + diff --git a/src/overrides/PageFrame.spec.ts b/src/overrides/PageFrame.spec.ts new file mode 100644 index 0000000..99859fe --- /dev/null +++ b/src/overrides/PageFrame.spec.ts @@ -0,0 +1,22 @@ +import {readFileSync} from 'node:fs'; +import path from 'node:path'; +import {fileURLToPath} from 'node:url'; +import {test} from 'kizu'; + +const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../..'); +const pageFrame = readFileSync(path.join(root, 'src/overrides/PageFrame.astro'), 'utf8'); + +test('PageFrame: persist sidebar scroll before nav (Starlight gap)', (assert) => { + + // Starlight only saves on visibilitychange and skips restore when scroll is 0 + // (`if (!window._starlightScrollRestore)`). We harden both sides. + assert.equal(pageFrame.includes("const KEY = 'sl-sidebar-state'"), true); + assert.equal(pageFrame.includes('sessionStorage.getItem(KEY)'), true); + assert.equal(pageFrame.includes('pagehide'), true); + assert.equal(pageFrame.includes('#starlight__sidebar a[href]'), true); + assert.equal(pageFrame.includes("typeof state.scroll !== 'number'"), true); + assert.equal(pageFrame.includes("(min-width: 50rem)"), true); + // Per-scroll sessionStorage writes made docs feel sluggish β€” keep save on click/pagehide only. + assert.equal(pageFrame.includes("addEventListener('scroll'"), false); + +}); diff --git a/src/overrides/PageTitle.astro b/src/overrides/PageTitle.astro new file mode 100644 index 0000000..66b7946 --- /dev/null +++ b/src/overrides/PageTitle.astro @@ -0,0 +1,25 @@ +--- +const PAGE_TITLE_ID = '_top'; +const {entry} = Astro.locals.starlightRoute; +--- + +{ + entry.data.template !== 'splash' && ( +

{entry.data.title}

+ ) +} + + diff --git a/src/overrides/Search.astro b/src/overrides/Search.astro new file mode 100644 index 0000000..af9dd50 --- /dev/null +++ b/src/overrides/Search.astro @@ -0,0 +1,611 @@ +--- +/** + * busker Search override (from Starlight Search.astro): + * - Eager Pagefind load (no idle-callback deferral) + * - Loading placeholder until the UI mounts + */ +import {Icon} from '@astrojs/starlight/components'; +import project from 'virtual:starlight/project-context'; + +const pagefindTranslations = { + placeholder: Astro.locals.t('search.label'), + ...Object.fromEntries( + Object.entries(Astro.locals.t.all()) + .filter(([key]) => key.startsWith('pagefind.')) + .map(([key, value]) => [key.replace('pagefind.', ''), value]) + ), +}; + +const dataAttributes: DOMStringMap = { 'data-translations': JSON.stringify(pagefindTranslations) }; +if (project.trailingSlash === 'never') dataAttributes['data-strip-trailing-slash'] = ''; +--- + + + + + +
+ { + /* TODO: Make the layout of this button flexible to accommodate different word lengths. Currently hard-coded for English: β€œCancel” */ + } + +
+
+ + +
+
+
+ + + + + + diff --git a/src/overrides/ThemeSelect.astro b/src/overrides/ThemeSelect.astro new file mode 100644 index 0000000..dea0048 --- /dev/null +++ b/src/overrides/ThemeSelect.astro @@ -0,0 +1,242 @@ +--- +/** + * Click-to-toggle theme switch β€” sun/moon inside the track. + * Default preference is system; UI is light ↔ dark only (no middle). + * Thumb position follows html[data-theme] so it stays in sync with ThemeProvider. + */ +import {Icon} from '@astrojs/starlight/components'; +--- + + + + + + + + + + diff --git a/src/pagefind/pagefind.js b/src/pagefind/pagefind.js new file mode 100644 index 0000000..6adacc3 --- /dev/null +++ b/src/pagefind/pagefind.js @@ -0,0 +1,30 @@ +/** + * Dev/type stub only. The real Pagefind engine is written to docs-site/pagefind/ + * during `astro build`. The cs-pagefind shim imports that built file at runtime. + */ +export async function options() {} +export async function init() {} +export async function destroy() {} +export async function mergeIndex() {} +export async function filters() { + return {}; +} +export async function preload() {} +export async function search() { + return {results: []}; +} +export async function debouncedSearch() { + return {results: []}; +} +export function createInstance() { + return { + options, + init, + destroy, + mergeIndex, + filters, + preload, + search, + debouncedSearch, + }; +} diff --git a/src/pages/404.astro b/src/pages/404.astro new file mode 100644 index 0000000..c72a519 --- /dev/null +++ b/src/pages/404.astro @@ -0,0 +1,16 @@ +--- +import StarlightPage from '@astrojs/starlight/components/StarlightPage.astro'; +import NotFoundPage from '../components/NotFoundPage.astro'; +--- + + + + diff --git a/src/pages/index.astro b/src/pages/index.astro new file mode 100644 index 0000000..223640a --- /dev/null +++ b/src/pages/index.astro @@ -0,0 +1,629 @@ +--- +import StarlightPage from '@astrojs/starlight/components/StarlightPage.astro'; +import {Code, Icon} from '@astrojs/starlight/components'; +import '../../busker.css'; +import '../styles/splash.css'; + +const routineSource = `busk(root, { + scene: 'beans', + start: [0.55, 0.3], + steps: [ + {click: '[data-cta]', wait: 900}, + {click: '[data-plan="duo"]', wait: 1500}, + {click: '[data-checkout]', wait: 2200}, + {click: '[data-close]', wait: 2600}, + {click: '[data-nav-item="plans"]', wait: 900}, + {click: '[data-plan="solo"]', wait: 1400}, + {to: [0.55, 0.3], wait: 900}, + ], + routes: [ + {click: '[data-nav-item="beans"]', scene: 'beans'}, + {click: '[data-nav-item="plans"]', scene: 'plans'}, + {click: '[data-nav-item="roasters"]', scene: 'roasters'}, + {click: '[data-cta]', scene: 'plans'}, + {click: '[data-plan="solo"]', scene: 'checkout'}, + {click: '[data-plan="duo"]', scene: 'checkout'}, + {click: '[data-plan="family"]', scene: 'checkout'}, + {click: '[data-checkout]', scene: 'checkout'}, + {click: '[data-close]', scene: 'beans'}, + ], +});`; +--- + + +
+
+

Scripted cursor demos that really click.

+

+ The easy way to build an interactive product mock. Write the + screens in plain HTML, then link a click to the screen it opens — the same + move as wiring frames in a Figma prototype, except this one ships on your page. + Busker plays it back with a cursor that glides and really clicks, so the mock + changes through its own handlers. Click it yourself and busker steps aside. +

+

+ Get started + GitHub +

+

Zero dependencies · ~4kB · MIT

+
+ +
+
+
+
+ + +
+
+
+

Fresh roasts,
to your door.

+

+ Single-origin beans, roasted the morning they ship. No + warehouse, no staleness. +

+ +
+
+ +
+

Pick your pace

+
+ + + +
+
+ +
+

You're almost there

+
+ Duo + $32/mo +
+ +
+
+ You're in. +

+ First batch ships Monday. We'll email your tracking link. +

+ +
+
+
+ +
+

The people behind the beans

+
+
MartaOaxaca, MX
+
YusufYirgacheffe, ET
+
LenaHuila, CO
+
+
+
+
+ +
+ +

+ + Click around and explore +

+
+ +
+

That demo, in full

+

+ Six beats and three routes. The scenes are not timed — they change because the + cursor pressed a button, exactly as they do when you press it. +

+ +
+
+
+ + + + diff --git a/src/styles/docs-shared.css b/src/styles/docs-shared.css new file mode 100644 index 0000000..2b3f3de --- /dev/null +++ b/src/styles/docs-shared.css @@ -0,0 +1,448 @@ +/* + * Shared docs chrome tokens β€” Astro/Starlight guides + Vite callspec-ui. + * Splash stays separate (src/components/splash.css). Keep this file the single source of truth + * for page/sidebar/nav/code colors and header metrics. + * + * :root defaults to light (avoids dark FOUC before theme JS). Dark is + * html[data-theme='dark'] / [data-theme='dark'] only. + */ + +/* Mobile-first html scale β€” both surfaces inherit rem from this. */ +html { + font-size: 0.875rem; + /* Same reserved gutter on splash + guides so the header never shifts. */ + scrollbar-gutter: stable; +} + +@media (min-width: 40em) { + html { + font-size: 0.9375rem; + } +} + +@media (min-width: 50em) { + html { + font-size: 1rem; + } +} + +/* + * Wide type bumps β€” start after MacBook Neo (~1204–1408 CSS px). + * 72rem (1152px) was firing on Neo and scaled the whole UI up. + * Desktop β€œOn this page” TOC also waits until 90rem (starlight-custom.css). + */ +@media (min-width: 90rem) { + html { + font-size: 1.03125rem; + } +} + +@media (min-width: 110rem) { + html { + font-size: 1.0625rem; + } +} + +@media (min-width: 140rem) { + html { + font-size: 1.075rem; + } +} + +:root, +::backdrop, +html[data-theme='light'], +[data-theme='light'], +[data-theme='light'] ::backdrop { + color-scheme: light; + + --docs-bg: hsl(228, 24%, 97%); + --docs-surface: #ffffff; + /* Light chrome β€” white top bar + sidebar (content stays soft grey). */ + --docs-sidebar: #ffffff; + --docs-nav: #ffffff; + --docs-border: hsl(228, 15%, 85%); + --docs-border-strong: hsl(228, 13%, 75%); + --docs-text: hsl(228, 25%, 12%); + --docs-text-secondary: hsl(228, 10%, 40%); + --docs-text-tertiary: hsl(228, 8%, 52%); + + /* Primary fill β€” sidebar selected, links, TOC, splash accent, footer hover. */ + --docs-primary-bg: hsl(262, 46%, 44%); + --docs-primary-fg: #f8fafc; + --docs-primary-hover-bg: var(--docs-primary-bg); + --docs-primary-hover-fg: var(--docs-primary-fg); + + --docs-accent: var(--docs-primary-bg); + --docs-accent-soft: color-mix(in srgb, var(--docs-primary-bg) 14%, var(--docs-bg)); + --docs-link: var(--docs-primary-bg); + /* Callspec hex mark β€” ink (host --accent must not override). */ + --docs-callspec-mark: #000; + /* Mark ↔ label gap β€” explorer contract chips + docs nav lockup. */ + --docs-mark-label-gap: 0.18rem; + /* Neutral inset β€” slightly cooler/darker than page chrome so blocks read as panels. */ + --docs-code-bg: hsl(228, 18%, 91%); + --docs-code-border: var(--docs-border); + --docs-mcp: #9333ea; + --docs-radius: 0.75rem; + --docs-code-radius: 0.5rem; + --docs-header-height: 3.5rem; + --docs-header-pad-x: 1.25rem; + --docs-search-height: 2.25rem; + --docs-search-max-width: 22rem; + /* Track header brand edge; wide screens bump both via --docs-header-pad-x. */ + --docs-sidebar-pad-x: var(--docs-header-pad-x); + /* Inline pad inside Home / group / route rows (docs + explorer). */ + --docs-sidebar-link-pad-inline: 0.7rem; + /* Nested route/page rows under a category (docs + explorer). */ + --docs-sidebar-subitem-line-height: 1.65; + /* Sidebar link/category hover β€” pure black in light. */ + --docs-sidebar-hover: #000; + --docs-header-bg: #ffffff; + + --docs-table-bg: hsl(0, 0%, 100%); + --docs-table-row-hover: hsl(228, 14%, 90%); + --docs-pagination-hover: color-mix(in srgb, var(--docs-primary-bg) 34%, var(--docs-code-bg)); + --docs-pagination-hover-fg: var(--docs-text); + + --docs-elevated: #ffffff; + /* + * Search modal stack β€” field inset darker than result cards (modal β†’ field β†’ cards). + */ + --docs-search-field: hsl(228, 14%, 90%); + --docs-search-item: var(--docs-code-bg); + --docs-search-hover: color-mix(in srgb, var(--docs-primary-bg) 34%, var(--docs-search-item)); + --docs-search-hover-fg: var(--docs-text); + --docs-search-modal-shadow: + 0 6px 16px hsl(228 25% 25% / 0.1), + 0 20px 56px hsl(228 30% 25% / 0.16), + 0 36px 88px hsl(228 30% 30% / 0.22); + + /* Flat CTA depth β€” primary: black; light secondary: softer grey from fill. */ + --docs-btn-shadow: 2px 2px 0 0 #000; + --docs-btn-shadow-hover: 2px 2px 0 0 #000; + --docs-btn-shadow-active: 1px 1px 0 0 #000; + --docs-btn-light-fill: hsl(228 14% 84%); + --docs-btn-light-fill-hover: hsl(228 12% 78%); +} + +html[data-theme='dark'], +[data-theme='dark'], +[data-theme='dark'] ::backdrop { + color-scheme: dark; + + --docs-bg: hsl(228, 22%, 6%); + --docs-surface: hsl(228, 18%, 9%); + --docs-sidebar: hsl(228, 16%, 11%); + /* Same plane as sidebar β€” top bar shouldn’t read as a different grey. */ + --docs-nav: var(--docs-sidebar); + --docs-border: hsl(228, 12%, 22%); + --docs-border-strong: hsl(228, 10%, 32%); + --docs-text: hsl(228, 20%, 98%); + --docs-text-secondary: hsl(228, 12%, 68%); + --docs-text-tertiary: hsl(228, 10%, 52%); + --docs-sidebar-hover: var(--docs-text); + + /* Rich iris β€” same 262Β° axis as light, higher chroma, mid tone for dark UI. */ + --docs-primary-bg: hsl(262, 52%, 62%); + --docs-primary-fg: hsl(262, 28%, 8%); + --docs-primary-hover-bg: var(--docs-primary-bg); + --docs-primary-hover-fg: var(--docs-primary-fg); + + --docs-accent: var(--docs-primary-bg); + --docs-accent-soft: color-mix(in oklab, var(--docs-primary-bg) 26%, var(--docs-surface)); + --docs-link: var(--docs-primary-bg); + /* Callspec hex mark β€” assets/mark-dark.svg */ + --docs-callspec-mark: #fff; + --docs-code-bg: hsl(228, 14%, 15%); + --docs-code-border: color-mix(in srgb, var(--docs-code-bg) 88%, white); + --docs-mcp: hsl(262, 58%, 72%); + --docs-header-bg: var(--docs-sidebar); + + --docs-table-bg: hsl(228, 16%, 10%); + --docs-table-row-hover: hsl(228, 14%, 13%); + --docs-pagination-hover: color-mix(in srgb, var(--docs-primary-bg) 48%, var(--docs-table-bg)); + --docs-pagination-hover-fg: var(--docs-text); + + /* + * Search modal stack β€” neutral grey planes; primary tint only on hover/selection. + */ + --docs-elevated: hsl(228, 11%, 15%); + --docs-search-inner: hsl(228, 8%, 20%); + --docs-search-field: color-mix(in srgb, var(--docs-search-inner) 93%, white); + --docs-search-item: var(--docs-search-inner); + --docs-search-hover: color-mix(in srgb, var(--docs-primary-bg) 48%, var(--docs-search-item)); + --docs-search-hover-fg: var(--docs-text); + --docs-search-modal-shadow: + 0 6px 18px hsl(228 40% 2% / 0.42), + 0 22px 52px hsl(228 40% 2% / 0.48), + 0 44px 112px hsl(228 40% 2% / 0.62); + + --docs-btn-dark-fill: hsl(228 12% 32%); + --docs-btn-dark-fill-hover: hsl(228 12% 34%); + --docs-btn-dark-highlight: inset 0 2px 0 0 hsl(0 0% 100% / 0.14); + --docs-btn-dark-highlight-hover: inset 0 2px 0 0 hsl(0 0% 100% / 0.2); + --docs-btn-dark-highlight-active: inset 0 1px 0 0 hsl(0 0% 100% / 0.1); +} + +/* Roomier L/R chrome β€” past MacBook Neo widths (see html type scale above). */ +@media (min-width: 90rem) { + /* Match token selectors β€” `:root` alone loses to `html[data-theme]`. */ + :root, + html[data-theme='light'], + html[data-theme='dark'], + [data-theme='light'], + [data-theme='dark'] { + --docs-header-pad-x: 1.5rem; + } +} + +/* Aliases β€” Starlight / cs-* + Vite callspec-ui */ +:root, +::backdrop, +html[data-theme='dark'], +html[data-theme='light'], +[data-theme='dark'], +[data-theme='light'] { + --cs-radius: var(--docs-radius); + --cs-ink: var(--docs-text); + --cs-muted: var(--docs-text-secondary); + --cs-line: var(--docs-border); + --cs-surface: var(--docs-surface); + --cs-mcp: var(--docs-mcp); + --cs-code-bg: var(--docs-code-bg); + --cs-code-border: var(--docs-code-border); + --cs-cyan: var(--docs-primary-bg); + --cs-link: var(--docs-primary-bg); + --cs-primary-bg: var(--docs-primary-bg); + --cs-primary-fg: var(--docs-primary-fg); + --cs-primary-hover-bg: var(--docs-primary-hover-bg); + --cs-primary-hover-fg: var(--docs-primary-hover-fg); + --cs-table-bg: var(--docs-table-bg); + --cs-table-row-hover: var(--docs-table-row-hover); + --cs-pagination-hover-bg: var(--docs-pagination-hover); + --cs-pagination-hover-fg: var(--docs-pagination-hover-fg); + --cs-search-modal-bg: var(--docs-elevated); + --cs-search-modal-shadow: var(--docs-search-modal-shadow); + --cs-search-field-bg: var(--docs-search-field); + --cs-search-item-bg: var(--docs-search-item); + --cs-search-hover-bg: var(--docs-search-hover); + --cs-search-hover-fg: var(--docs-search-hover-fg); + --cs-search-field-fg: var(--docs-text); + --cs-search-result-fg: var(--docs-text); + --cs-search-card-radius: 0.75rem; + --cs-btn-shadow: var(--docs-btn-shadow); + --cs-btn-shadow-hover: var(--docs-btn-shadow-hover); + --cs-btn-shadow-active: var(--docs-btn-shadow-active); + --cs-btn-light-fill: var(--docs-btn-light-fill); + --cs-btn-light-shadow-color: color-mix(in srgb, var(--cs-btn-light-fill) 70%, hsl(228 14% 38%)); + --cs-btn-light-shadow: 2px 2px 0 0 var(--cs-btn-light-shadow-color); + --cs-btn-light-shadow-hover: 2px 2px 0 0 color-mix(in srgb, var(--cs-btn-light-shadow-color) 88%, hsl(228 14% 28%)); + --cs-btn-light-shadow-active: 1px 1px 0 0 var(--cs-btn-light-shadow-color); + --cs-btn-dark-highlight: var(--docs-btn-dark-highlight); + --cs-btn-dark-highlight-hover: var(--docs-btn-dark-highlight-hover); + --cs-btn-dark-highlight-active: var(--docs-btn-dark-highlight-active); + + --bg: var(--docs-bg); + --surface: var(--docs-surface); + --sidebar-bg: var(--docs-sidebar); + --border: var(--docs-border); + --border-strong: var(--docs-border-strong); + --text: var(--docs-text); + --text-secondary: var(--docs-text-secondary); + --text-tertiary: var(--docs-text-tertiary); + --accent: var(--docs-accent); + /* Derive from --accent so brand theme overrides (inline --accent) tint soft too. */ + --accent-soft: color-mix(in srgb, var(--accent) 16%, var(--surface)); + --hljs-bg: var(--docs-code-bg); + --radius: var(--docs-radius); + --header-height: var(--docs-header-height); + --header-pad-x: var(--docs-header-pad-x); + --sidebar-link-pad-inline: var(--docs-sidebar-link-pad-inline); + --sidebar-subitem-line-height: var(--docs-sidebar-subitem-line-height); + --sidebar-hover: var(--docs-sidebar-hover); + --nav-active-bg: var(--docs-primary-bg); + --nav-active-fg: var(--docs-primary-fg); + --callspec-mark: var(--docs-callspec-mark); + --mark-label-gap: var(--docs-mark-label-gap); + --font-heading: 'Inter Variable', ui-sans-serif, system-ui, sans-serif; + --font-lockup: 'Inter Variable', ui-sans-serif, system-ui, sans-serif; + --font-mono: 'JetBrainsMono Nerd Font Mono', ui-monospace, 'SF Mono', Menlo, Monaco, Consolas, monospace; +} + +/* Shared lockup β€” docs header/footer + explorer powered-by. */ +.cs-lockup { + --cs-mark: var(--docs-callspec-mark); + --cs-word: var(--docs-text); + --cs-lockup-mark-size: 1.8rem; + --cs-lockup-word-size: 1.3rem; + display: inline-flex; + align-items: center; + gap: var(--docs-mark-label-gap); + text-decoration: none; + line-height: 1; +} + +.cs-lockup__mark { + display: block; + flex: none; + width: var(--cs-lockup-mark-size); + height: var(--cs-lockup-mark-size); + color: var(--cs-mark); + transition: color 0.25s ease; +} + +.cs-hex { + fill: currentColor; +} + +.cs-eq { + transform-box: fill-box; + transform-origin: left center; +} + +/* Explorer overlay marks β€” eq bars paint over hex; match the page behind. */ +.cs-lockup[data-holes='overlay'] .cs-eq { + fill: var(--bg); +} + +.cs-lockup__word { + font-family: var(--font-lockup); + font-size: var(--cs-lockup-word-size); + font-weight: 600; + letter-spacing: -0.02em; + color: var(--cs-word); + transition: color 0.25s ease; + display: flex; + align-items: center; + height: var(--cs-lockup-mark-size); + line-height: 1; + transform: translateY(-0.06em); +} + +@media (hover: hover) { + .cs-lockup:hover .cs-eq--top { + animation: cs-eq-type 0.34s cubic-bezier(0.22, 0.7, 0.24, 1) both; + } + + .cs-lockup:hover .cs-eq--bottom { + animation: cs-eq-type 0.34s cubic-bezier(0.22, 0.7, 0.24, 1) 0.09s both; + } +} + +@keyframes cs-eq-type { + 0% { + transform: scaleX(0); + } + + 100% { + transform: scaleX(1); + } +} + +.cs-lockup:focus-visible { + outline: 2px solid var(--cs-mark); + outline-offset: 3px; + border-radius: 6px; +} + +@media (prefers-reduced-motion: reduce) { + .cs-lockup:hover .cs-eq--top, + .cs-lockup:hover .cs-eq--bottom { + animation: none; + } +} + +/* Shared frosted header treatment (guides + explorer). */ +.docs-frosted-header { + background: var(--docs-header-bg); + backdrop-filter: blur(14px) saturate(1.25); + -webkit-backdrop-filter: blur(14px) saturate(1.25); +} + +/* Theme toggle crossfade (document.startViewTransition in ThemeSelect / theme.ts). */ +@media (prefers-reduced-motion: no-preference) { + ::view-transition-old(root), + ::view-transition-new(root) { + animation-duration: 0.2s; + animation-timing-function: ease; + } +} + +/* Docs code chrome + explorer copy controls β€” icon + Copy / green Copied! */ +.cs-copy-btn { + display: inline-flex; + align-items: center; + justify-content: center; + gap: 0.35rem; + flex: 0 0 auto; + width: auto; + min-width: max-content; + height: auto; + margin: 0; + padding: 0.28rem 0.55rem; + border: 0; + border-radius: 0.3rem; + background: transparent; + box-shadow: none; + outline: none; + color: color-mix(in srgb, var(--cs-ink) 72%, transparent); + font-family: inherit; + font-size: 0.75rem; + font-weight: 500; + letter-spacing: 0.01em; + line-height: 1.2; + white-space: nowrap; + cursor: pointer; + transition: background 120ms ease, color 120ms ease; +} + +.cs-copy-btn .cs-copy-icon { + display: inline-flex; + align-items: center; + justify-content: center; + flex-shrink: 0; + width: 0.9rem; + height: 0.9rem; + overflow: visible; +} + +.cs-copy-btn .cs-copy-glyph { + display: block; + width: 100%; + height: 100%; + overflow: visible; +} + +.cs-copy-btn:hover { + color: var(--cs-ink); + background: color-mix(in srgb, var(--cs-ink) 8%, transparent); +} + +html[data-theme='light'] .cs-copy-btn, +[data-theme='light'] .cs-copy-btn { + color: hsl(228, 14%, 30%); +} + +html[data-theme='light'] .cs-copy-btn:hover, +[data-theme='light'] .cs-copy-btn:hover { + color: var(--cs-ink); + background: color-mix(in srgb, var(--cs-ink) 6%, transparent); +} + +.cs-copy-btn.cs-copied, +.cs-copy-btn.cs-copied:hover, +.cs-copy-btn.cs-copied:focus, +.cs-copy-btn.cs-copied:active, +html[data-theme='light'] .cs-copy-btn.cs-copied, +html[data-theme='light'] .cs-copy-btn.cs-copied:hover, +html[data-theme='light'] .cs-copy-btn.cs-copied:focus, +html[data-theme='light'] .cs-copy-btn.cs-copied:active, +[data-theme='light'] .cs-copy-btn.cs-copied, +[data-theme='light'] .cs-copy-btn.cs-copied:hover, +[data-theme='light'] .cs-copy-btn.cs-copied:focus, +[data-theme='light'] .cs-copy-btn.cs-copied:active { + color: #000; + background: #7bee7b; + box-shadow: none; +} diff --git a/src/styles/docsShared.spec.ts b/src/styles/docsShared.spec.ts new file mode 100644 index 0000000..84433d0 --- /dev/null +++ b/src/styles/docsShared.spec.ts @@ -0,0 +1,49 @@ +import {existsSync, readFileSync} from 'node:fs'; +import path from 'node:path'; +import {fileURLToPath} from 'node:url'; +import {test} from 'kizu'; + +const root = path.join(path.dirname(fileURLToPath(import.meta.url)), '../..'); + +test('guide site footer is a Starlight override with GitHub and MIT', (assert) => { + const astro = readFileSync(path.join(root, 'astro.config.mjs'), 'utf8'); + const footer = readFileSync(path.join(root, 'src/overrides/Footer.astro'), 'utf8'); + const site = readFileSync(path.join(root, 'src/components/SiteFooter.astro'), 'utf8'); + + assert.equal(astro.includes("Footer: './src/overrides/Footer.astro'"), true); + assert.equal(footer.includes('SiteFooter'), true); + assert.equal(site.includes('github.com/logfoxai/busker'), true); + assert.equal(site.includes('MIT'), true); + assert.equal(site.includes('discord.gg/'), false); +}); + +test('custom pages are Astro, not collection MDX', (assert) => { + const astro = readFileSync(path.join(root, 'astro.config.mjs'), 'utf8'); + + assert.equal(existsSync(path.join(root, 'src/pages/index.astro')), true); + assert.equal(existsSync(path.join(root, 'src/pages/404.astro')), true); + assert.equal(existsSync(path.join(root, 'src/content/docs/index.mdx')), false); + assert.equal(existsSync(path.join(root, 'src/content/docs/404.mdx')), false); + assert.equal(astro.includes('disable404Route: true'), true); +}); + +test('docs highlight aliases the primary fill token', (assert) => { + const shared = readFileSync(path.join(root, 'src/styles/docs-shared.css'), 'utf8'); + const starlight = readFileSync(path.join(root, 'src/styles/starlight-custom.css'), 'utf8'); + + assert.equal(shared.includes('--docs-link: var(--docs-primary-bg)'), true); + assert.equal(shared.includes('--cs-cyan: var(--docs-primary-bg)'), true); + assert.equal(shared.includes('--cs-link: var(--docs-primary-bg)'), true); + assert.equal(starlight.includes('--sl-color-text-accent: var(--docs-primary-bg)'), true); + assert.equal(starlight.includes('--sl-color-accent-high: var(--docs-primary-bg)'), true); + assert.equal(shared.includes('--docs-primary-hover-bg: var(--docs-primary-bg)'), true); +}); + +test('lockup mark svg is block-level so explorer matches docs alignment', (assert) => { + const shared = readFileSync(path.join(root, 'src/styles/docs-shared.css'), 'utf8'); + + assert.equal(shared.includes('.cs-lockup__mark {\n display: block;'), true); + assert.equal(shared.includes('.cs-lockup__mark svg'), false); + assert.equal(shared.includes('--cs-lockup-word-size: 1.3rem'), true); + assert.equal(shared.includes(".cs-lockup[data-holes='overlay'] .cs-eq"), true); +}); diff --git a/src/styles/fonts.css b/src/styles/fonts.css new file mode 100644 index 0000000..9922d63 --- /dev/null +++ b/src/styles/fonts.css @@ -0,0 +1,40 @@ +/* + * Self-hosted fonts (@fontsource). Latin only. + * UI + headings + lockup: Inter. Code: JetBrainsMono Nerd Font Mono. + */ + +@font-face { + font-family: 'Inter Variable'; + font-style: normal; + font-display: optional; + font-weight: 100 900; + src: + local('Inter Variable'), + local('Inter'), + url('/fonts/inter-latin-wght-normal.woff2') format('woff2-variations'); + unicode-range: + U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, + U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; +} + +@font-face { + font-family: 'JetBrainsMono Nerd Font Mono'; + font-style: normal; + font-display: swap; + font-weight: 400; + src: url('/fonts/jetbrains-mono-nerd-mono-400.woff2') format('woff2'); + unicode-range: + U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, + U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; +} + +@font-face { + font-family: 'JetBrainsMono Nerd Font Mono'; + font-style: normal; + font-display: swap; + font-weight: 600; + src: url('/fonts/jetbrains-mono-nerd-mono-600.woff2') format('woff2'); + unicode-range: + U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, + U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; +} diff --git a/src/styles/not-found.css b/src/styles/not-found.css new file mode 100644 index 0000000..8b56a37 --- /dev/null +++ b/src/styles/not-found.css @@ -0,0 +1,204 @@ +/* + * Dedicated 404 page β€” not the splash homepage. + * Scoped with .not-found-page (src/pages/404.astro body). + */ + +html:not([data-has-sidebar]):has(.not-found-page) { + --sl-content-width: min(44rem, calc(100vw - 2rem)); + --sl-content-pad-x: clamp(1.25rem, 3.5vw, 2.75rem); + + &[data-theme='dark'] { + --sl-color-bg: #07080c; + --sl-color-black: #07080c; + --sl-color-bg-nav: #07080c; + --sl-color-white: #f5f6fa; + --sl-color-gray-2: #a8acb8; + --sl-color-gray-3: #7a7f8c; + --sl-color-gray-5: #2a2d38; + --sl-color-text: var(--sl-color-gray-2); + --cs-ink: #f4f6fb; + --cs-muted: #9aa3b5; + --cs-line: #2a2f3c; + color-scheme: dark; + + &, + & body { + background: + radial-gradient(ellipse 70% 40% at 50% 0%, hsl(210 60% 35% / 0.2), transparent 58%), + linear-gradient(180deg, #0a0c12 0%, #07080c 100%); + background-attachment: scroll; + } + } + + &[data-theme='light'] { + --sl-color-bg: #f4f5f8; + --sl-color-black: #f4f5f8; + --sl-color-bg-nav: #f4f5f8; + --sl-color-white: #12141a; + --sl-color-gray-2: #3a3f4d; + --sl-color-gray-3: #5c6170; + --sl-color-gray-5: #d5d8e2; + --sl-color-text: var(--sl-color-gray-2); + --cs-ink: #10131a; + --cs-muted: #5a6170; + --cs-line: #dde1ea; + color-scheme: light; + + &, + & body { + background: + radial-gradient(ellipse 70% 40% at 50% 0%, hsl(198 60% 55% / 0.1), transparent 58%), + #f3f5f9; + background-attachment: scroll; + } + } + + header.header { + background-color: color-mix(in srgb, var(--sl-color-bg) 72%, transparent); + border-bottom: none; + backdrop-filter: blur(14px) saturate(1.25); + -webkit-backdrop-filter: blur(14px) saturate(1.25); + box-shadow: none; + } + + .content-panel .sl-container { + width: min(100%, var(--sl-content-width)); + max-width: var(--sl-content-width); + margin-inline: auto; + } + + .page, + .content-panel { + background: transparent; + } + + .content-panel:first-of-type { + padding-block: 0 4rem; + padding-inline: var(--sl-content-pad-x); + } + + .sl-container > .sl-markdown-content { + margin-top: 0; + padding-top: 0; + border-top: none; + } +} + +.not-found-hero { + display: flex; + flex-direction: column; + align-items: center; + text-align: center; + gap: clamp(1rem, 2vw, 1.35rem); + min-height: min(52vh, 28rem); + justify-content: center; + padding-block: clamp(4rem, 12vw, 7rem) clamp(3rem, 8vw, 5rem); + position: relative; + z-index: 1; + + .not-found-hero__code { + margin: 0; + font-family: var(--cs-font-heading); + font-size: clamp(3.5rem, 10vw, 5.5rem); + font-weight: 600; + letter-spacing: -0.06em; + line-height: 0.95; + color: var(--cs-cyan); + animation: not-found-fade-up 0.55s ease both; + } + + .not-found-hero__headline { + margin: 0; + max-width: min(22ch, 94vw); + font-family: var(--cs-font-heading); + font-size: clamp(1.85rem, 4vw, 2.6rem); + font-weight: 600; + letter-spacing: -0.04em; + line-height: 1.12; + text-wrap: balance; + color: var(--cs-ink); + animation: not-found-fade-up 0.6s ease 0.05s both; + } + + .not-found-hero__lead { + margin: 0.15rem auto 0; + max-width: 32rem; + font-family: var(--sl-font); + font-size: clamp(0.98rem, 1.45vw, 1.08rem); + font-weight: 450; + line-height: 1.5; + text-wrap: pretty; + color: var(--cs-muted); + animation: not-found-fade-up 0.6s ease 0.1s both; + } + + .not-found-hero__actions { + display: flex; + flex-wrap: wrap; + justify-content: center; + align-items: center; + gap: 0.85rem; + margin-block: 0.75rem 0; + animation: not-found-fade-up 0.6s ease 0.18s both; + + .sl-link-button { + font-family: var(--cs-font-heading); + font-size: 0.95rem; + letter-spacing: -0.01em; + padding: 0.6rem 1.2rem; + border-radius: 0.5rem; + min-height: 2.45rem; + border: none; + transition: background-color 0.15s ease, color 0.15s ease, box-shadow 0.15s ease; + + svg { + transition: transform 0.15s ease; + } + + &.sl-link-button--forward:hover svg { + transform: translateX(0.2rem); + } + + &.primary { + font-weight: 600; + background: var(--cs-primary-bg); + color: var(--cs-primary-fg); + + &:hover { + background: var(--cs-primary-hover-bg); + color: var(--cs-primary-hover-fg); + transform: none; + } + } + + &.secondary { + font-weight: 500; + } + } + } +} + +@media (prefers-reduced-motion: reduce) { + .not-found-hero__code, + .not-found-hero__headline, + .not-found-hero__lead, + .not-found-hero__actions { + animation: none; + } + + .not-found-hero__actions .sl-link-button--forward:hover svg { + transform: none; + } +} + +@keyframes not-found-fade-up { + from { + opacity: 0; + transform: translateY(0.65rem); + } + + to { + opacity: 1; + transform: translateY(0); + } +} diff --git a/src/styles/splash.css b/src/styles/splash.css new file mode 100644 index 0000000..a625204 --- /dev/null +++ b/src/styles/splash.css @@ -0,0 +1,22 @@ +/* + * Splash homepage container β€” matches callspec's splash page chrome. + * Scoped to the splash page (no sidebar) so docs pages are untouched. + * Without this, Starlight's defaults (narrow left-aligned sl-container) shift the splash off-centre. + */ + +html:not([data-has-sidebar]):has(.splash) { + --sl-content-width: min(80rem, calc(100vw - 2rem)); + --sl-content-pad-x: clamp(1.25rem, 3.5vw, 2.75rem); + + /* Centre the content column on wide viewports. Starlight's default sl-container + has no auto margins on the sidebar-less splash, so it sticks left without this. */ + .content-panel .sl-container { + width: min(100%, var(--sl-content-width)); + max-width: var(--sl-content-width); + margin-inline: auto; + } + + .content-panel:first-of-type { + padding-inline: var(--sl-content-pad-x); + } +} diff --git a/src/styles/starlight-custom.css b/src/styles/starlight-custom.css new file mode 100644 index 0000000..ddb654e --- /dev/null +++ b/src/styles/starlight-custom.css @@ -0,0 +1,2678 @@ +/* + * Callspec docs theme + * Guide pages and splash home are scoped separately so splash never bleeds into guides. + * Splash homepage styles live in src/components/splash.css. + * Shared page/sidebar/nav/code tokens: docs-shared.css (also used by Vite UI). + */ + +/* ─── Global tokens (unlayered so they beat Starlight props) ──────────────── */ + +:root, +::backdrop { + --sl-font: 'Inter Variable', ui-sans-serif, system-ui, sans-serif; + --sl-font-mono: var(--font-mono); + --sl-color-accent-low: color-mix(in srgb, var(--docs-primary-bg) 22%, var(--docs-bg)); + --sl-color-accent: var(--docs-primary-bg); + --sl-color-accent-high: var(--docs-primary-bg); + --sl-color-text-accent: var(--docs-primary-bg); + --sl-color-bg-accent: var(--docs-primary-bg); + --sl-color-text-invert: hsl(0, 0%, 100%); + --sl-color-bg-inline-code: var(--cs-inline-code-bg); + + /* Align Starlight chrome with shared docs palette (not default hue-224 greys). */ + --sl-color-bg: var(--docs-bg); + --sl-color-bg-nav: var(--docs-nav); + --sl-color-bg-sidebar: var(--docs-sidebar); + /* content-panel / sidebar hairlines β€” Starlight gray-6 is ~94% and vanishes in light. */ + --sl-color-hairline: var(--docs-border); + --sl-color-hairline-light: var(--docs-border); + --sl-color-hairline-shade: var(--docs-border); + + /* --cs-primary-* / --cs-table-bg from docs-shared.css */ + --cs-font-heading: var(--font-heading); + + --cs-inline-code-bg: hsl(228, 12%, 16%); + --cs-inline-code-border: transparent; + --cs-inline-code-fg: hsl(228, 10%, 78%); + + --cs-mcp-bg: color-mix(in srgb, var(--cs-mcp) 14%, var(--docs-surface)); + --cs-mcp-border: color-mix(in srgb, var(--cs-mcp) 38%, var(--docs-border)); + + --cs-table-head-bg: hsl(228, 14%, 14%); + --cs-table-row-alt: hsl(228, 14%, 11.5%); + --cs-table-code-bg: hsl(228, 12%, 16%); +} + +:root[data-theme='light'], +[data-theme='light'] ::backdrop { + --sl-color-accent-low: color-mix(in srgb, var(--docs-primary-bg) 16%, var(--docs-bg)); + --sl-color-accent: var(--docs-primary-bg); + --sl-color-accent-high: var(--docs-primary-bg); + --sl-color-text-accent: var(--docs-primary-bg); + --sl-color-bg-accent: var(--docs-primary-bg); + --sl-color-text-invert: hsl(0, 0%, 100%); + --sl-color-bg-inline-code: var(--cs-inline-code-bg); + + --sl-color-bg: var(--docs-bg); + --sl-color-bg-nav: var(--docs-nav); + --sl-color-bg-sidebar: var(--docs-sidebar); + --sl-color-hairline: var(--docs-border); + --sl-color-hairline-light: var(--docs-border); + --sl-color-hairline-shade: var(--docs-border); + + --cs-inline-code-bg: hsl(228, 16%, 92%); + --cs-inline-code-border: transparent; + --cs-inline-code-fg: hsl(228, 16%, 34%); + + --cs-mcp-bg: color-mix(in srgb, var(--cs-mcp) 10%, var(--docs-surface)); + --cs-mcp-border: color-mix(in srgb, var(--cs-mcp) 24%, var(--docs-border)); + + --cs-table-head-bg: hsl(228, 22%, 96%); + --cs-table-row-alt: hsl(228, 24%, 98.5%); + --cs-table-code-bg: hsl(228, 17%, 91%); +} + +/* Primary CTAs β€” shared across splash hero, docs pages, and search idle chips */ +.sl-link-button.primary { + background: var(--cs-primary-bg); + border: none; + color: var(--cs-primary-fg); +} + +.sl-link-button.primary:hover { + background: var(--cs-primary-hover-bg); + color: var(--cs-primary-hover-fg); +} + +.sl-link-button.secondary { + border: none; +} + +html[data-theme='light'] .sl-link-button.primary, +[data-theme='light'] .sl-link-button.primary { + box-shadow: var(--cs-btn-shadow); +} + +html[data-theme='light'] .sl-link-button.primary:hover, +[data-theme='light'] .sl-link-button.primary:hover { + box-shadow: var(--cs-btn-shadow-hover); +} + +html[data-theme='light'] .sl-link-button.primary:active, +[data-theme='light'] .sl-link-button.primary:active { + box-shadow: var(--cs-btn-shadow-active); +} + +html[data-theme='light'] .sl-link-button.secondary, +[data-theme='light'] .sl-link-button.secondary { + --cs-btn-light-fill: var(--docs-btn-light-fill); + background: var(--cs-btn-light-fill); + color: var(--cs-ink); + box-shadow: var(--cs-btn-light-shadow); +} + +html[data-theme='light'] .sl-link-button.secondary:hover, +[data-theme='light'] .sl-link-button.secondary:hover { + --cs-btn-light-fill: var(--docs-btn-light-fill-hover); + background: var(--cs-btn-light-fill); + box-shadow: var(--cs-btn-light-shadow-hover); +} + +html[data-theme='light'] .sl-link-button.secondary:active, +[data-theme='light'] .sl-link-button.secondary:active { + box-shadow: var(--cs-btn-light-shadow-active); +} + +html[data-theme='dark'] .sl-link-button.primary, +[data-theme='dark'] .sl-link-button.primary { + --cs-btn-fill: var(--cs-primary-bg); + box-shadow: var(--cs-btn-dark-highlight); +} + +html[data-theme='dark'] .sl-link-button.primary:hover, +[data-theme='dark'] .sl-link-button.primary:hover { + --cs-btn-fill: var(--cs-primary-hover-bg); + box-shadow: var(--cs-btn-dark-highlight-hover); +} + +html[data-theme='dark'] .sl-link-button.primary:active, +[data-theme='dark'] .sl-link-button.primary:active { + box-shadow: var(--cs-btn-dark-highlight-active); +} + +html[data-theme='dark'] .sl-link-button.secondary, +[data-theme='dark'] .sl-link-button.secondary { + --cs-btn-fill: var(--docs-btn-dark-fill); + background: var(--cs-btn-fill); + color: var(--cs-ink); + box-shadow: var(--cs-btn-dark-highlight); +} + +html[data-theme='dark'] .sl-link-button.secondary:hover, +[data-theme='dark'] .sl-link-button.secondary:hover { + --cs-btn-fill: var(--docs-btn-dark-fill-hover); + background: var(--cs-btn-fill); + box-shadow: var(--cs-btn-dark-highlight-hover); +} + +html[data-theme='dark'] .sl-link-button.secondary:active, +[data-theme='dark'] .sl-link-button.secondary:active { + box-shadow: var(--cs-btn-dark-highlight-active); +} + +/* ─── Guide pages: readable type + real breathing room ────────────────────── */ + +html[data-has-sidebar] { + /* Keep Starlight pad/width math intact β€” oversized content pad-x collapses article gutters */ + --sl-sidebar-pad-x: var(--docs-sidebar-pad-x); + --sl-content-width: 52rem; + /* Match explorer `--content-pad-x` (1.25 β†’ 1.75 at 90rem). */ + --sl-content-pad-x: 1.25rem; + --sl-content-gap-y: 1.75rem; + + /* + * Page title β€” slightly above explorer route titles; still far below + * Starlight’s 50em 5xl billboard. + */ + --sl-text-h1: 1.4rem; + --sl-text-h2: 1.25rem; + --sl-text-h3: 1.1rem; + --sl-text-h4: 1rem; + --sl-line-height-headings: 1.25; +} + +@media (min-width: 50em) { + html[data-has-sidebar] { + /* Keep compact title β€” do not inherit Starlight’s 5xl billboard. */ + --sl-text-h1: 1.4rem; + --sl-text-h2: 1.35rem; + --sl-text-h3: 1.15rem; + } +} + +@media (min-width: 90rem) { + html[data-has-sidebar] { + --sl-content-pad-x: 1.75rem; + --sl-text-h1: 1.625rem; + } +} + +/* Unlayered β€” Starlight’s layered content styles were winning and collapsing rhythm */ +html[data-has-sidebar] .sl-markdown-content { + font-size: 0.975rem; + line-height: 1.7; +} + +html[data-has-sidebar] .sl-markdown-content .sl-heading-wrapper.level-h2 { + margin-top: 2.75rem; + margin-bottom: 0.85rem; + font-weight: 600; + letter-spacing: -0.015em; +} + +html[data-has-sidebar] .sl-markdown-content .sl-heading-wrapper.level-h3 { + margin-top: 2rem; + margin-bottom: 0.65rem; +} + +/* Headings β€” same Plex Sans stack as body/UI (weight contrast only) */ +html[data-has-sidebar] .content-panel:first-of-type h1, +html[data-has-sidebar] .sl-markdown-content .sl-heading-wrapper :is(h1, h2, h3, h4), +.splash-hero__headline, +.splash-how__title, +.splash-features__title, +.splash-demo__title, +.splash-sdk__title, +.splash-agents__title { + font-family: var(--cs-font-heading); +} + +/* Belt-and-suspenders: heading font must not inherit into chrome or code */ +html[data-has-sidebar] :is( + #starlight__sidebar, + header.header, + .pagination-links, + starlight-toc nav, + .sl-markdown-content :is(p, li, td, th, dd, dt, blockquote, figcaption) + ) { + font-family: var(--sl-font); +} + +html[data-has-sidebar] :is(code, pre, kbd, samp, .expressive-code, .sl-badge), +html[data-has-sidebar] .sl-markdown-content .sl-heading-wrapper :is(h1, h2, h3, h4) :is(code, kbd) { + font-family: var(--sl-font-mono); +} + +html[data-has-sidebar] .sl-markdown-content > .sl-heading-wrapper.level-h1:first-child { + display: none; +} + +html[data-has-sidebar] .sl-markdown-content > .sl-heading-wrapper:first-child { + margin-top: 0; +} + +/* Body pad is the gap under the title hairline β€” do not rely on first-child margins + (.md pages keep a hidden H1 so the lead

is not :first-child; MDX often is). */ +html[data-has-sidebar] .sl-markdown-content > :first-child, +html[data-has-sidebar] .sl-markdown-content > .sl-heading-wrapper.level-h1:first-child + * { + margin-top: 0; +} + +html[data-has-sidebar] .sl-markdown-content .sl-heading-wrapper + .expressive-code, +html[data-has-sidebar] .sl-markdown-content .sl-heading-wrapper + pre { + margin-top: 0.85rem; +} + +html[data-has-sidebar] .sl-markdown-content .expressive-code + p, +html[data-has-sidebar] .sl-markdown-content pre + p { + margin-top: 1.15rem; +} + +html[data-has-sidebar] .sl-markdown-content p + .expressive-code, +html[data-has-sidebar] .sl-markdown-content p + pre { + margin-top: 1rem; +} + +/* Inline code β€” accent pill chips (no filter) */ +html[data-has-sidebar] .sl-markdown-content + code:not(:where(.not-content *, pre *, .expressive-code *, .frame *)), +html[data-has-sidebar] .sl-markdown-content .starlight-aside + code:not(:where(pre *, .expressive-code *, .frame *)) { + display: inline-block; + max-width: 100%; + font-family: var(--sl-font-mono); + font-size: 0.84em; + font-weight: 400; + letter-spacing: -0.01em; + line-height: 1.4; + color: var(--cs-inline-code-fg); + background-color: var(--cs-inline-code-bg); + border: 0; + border-radius: 0.35rem; + padding: 0.18em 0.5em; + margin-block: -0.08em; + margin-inline: 0.08em; + vertical-align: baseline; + -webkit-box-decoration-break: clone; + box-decoration-break: clone; +} + +/* Headings with backtick names β€” mono title, not inline pill chips */ +html[data-has-sidebar] .sl-markdown-content .sl-heading-wrapper :is(h1, h2, h3, h4, h5, h6) + code:not(:where(pre *, .expressive-code *, .frame *)) { + display: inline; + margin: 0; + padding: 0; + border: 0; + border-radius: 0; + background: none; + font-family: var(--sl-font-mono); + font-size: inherit; + font-weight: inherit; + letter-spacing: inherit; + line-height: inherit; + color: inherit; + vertical-align: baseline; + -webkit-box-decoration-break: initial; + box-decoration-break: initial; +} + +/* Tables β€” real

layout (auto columns), optional scroll wrapper */ +html[data-has-sidebar] .sl-markdown-content .cs-table-scroll { + width: 100%; + margin-block: 1.25rem 1.5rem; + overflow-x: auto; + overflow-y: hidden; + border: 1px solid var(--cs-line); + border-radius: calc(var(--cs-radius) - 0.1rem); + background: var(--cs-table-bg); + -webkit-overflow-scrolling: touch; +} + +html[data-has-sidebar] .sl-markdown-content table:not(:where(.not-content *)) { + display: table; + width: 100%; + margin-block: 1.25rem 1.5rem; + border: 1px solid var(--cs-line); + border-radius: calc(var(--cs-radius) - 0.1rem); + border-collapse: separate; + border-spacing: 0; + table-layout: auto; + background: var(--cs-table-bg); +} + +html[data-has-sidebar] .sl-markdown-content .cs-table-scroll > table:not(:where(.not-content *)) { + margin-block: 0; + border: 0; + border-radius: 0; + background: transparent; +} + +/* Dense HTML tables with β€” fixed columns + room to scroll */ +html[data-has-sidebar] .sl-markdown-content .cs-table-scroll > table:has(colgroup):not(:where(.not-content *)) { + min-width: 40rem; + table-layout: fixed; +} + +/* Header/footer fills on cells so they follow the rounded frame */ +html[data-has-sidebar] .sl-markdown-content table:not(:where(.not-content *)) thead th { + background: var(--cs-table-head-bg); +} + +html[data-has-sidebar] .sl-markdown-content table:not(:where(.not-content *)) thead th:first-child { + border-top-left-radius: calc(var(--cs-radius) - 0.1rem); +} + +html[data-has-sidebar] .sl-markdown-content table:not(:where(.not-content *)) thead th:last-child { + border-top-right-radius: calc(var(--cs-radius) - 0.1rem); +} + +html[data-has-sidebar] .sl-markdown-content table:not(:where(.not-content *)) tbody tr:last-child td:first-child { + border-bottom-left-radius: calc(var(--cs-radius) - 0.1rem); +} + +html[data-has-sidebar] .sl-markdown-content table:not(:where(.not-content *)) tbody tr:last-child td:last-child { + border-bottom-right-radius: calc(var(--cs-radius) - 0.1rem); +} + +html[data-has-sidebar] .sl-markdown-content :is(th, td):not(:where(.not-content *)) { + border-bottom: 1px solid var(--cs-line); + padding: 0.55rem 0.85rem; + vertical-align: top; + line-height: 1.45; +} + +html[data-has-sidebar] .sl-markdown-content tbody tr:last-child :is(th, td):not(:where(.not-content *)) { + border-bottom: 0; +} + +html[data-has-sidebar] .sl-markdown-content :is(th:first-child, td:first-child):not(:where(.not-content *)) { + padding-inline-start: 1rem; +} + +html[data-has-sidebar] .sl-markdown-content :is(th:last-child, td:last-child):not(:where(.not-content *)) { + padding-inline-end: 1rem; +} + +html[data-has-sidebar] .sl-markdown-content th:not(:where(.not-content *)) { + color: var(--cs-muted); + font-family: var(--sl-font); + font-size: 0.75rem; + font-weight: 600; + letter-spacing: 0.06em; + text-transform: uppercase; + white-space: nowrap; +} + +html[data-has-sidebar] .sl-markdown-content tbody tr:nth-child(even):not(:where(.not-content *)) { + background: var(--cs-table-row-alt); +} + +html[data-has-sidebar] .sl-markdown-content tbody tr:hover:not(:where(.not-content *)) { + background: var(--cs-table-row-hover); +} + +/* Option tables (name | default | …) β€” first cell is */ +html[data-has-sidebar] + .sl-markdown-content + table:has(thead th:nth-child(3)) + td:first-child:has(code):not(:where(.not-content *)) { + font-family: var(--sl-font-mono); + font-size: 0.875em; + color: var(--cs-ink); + white-space: nowrap; + width: 1%; +} + +html[data-has-sidebar] + .sl-markdown-content + table:has(thead th:nth-child(3)) + tr:has(td:first-child code) + td:nth-child(2):not(:where(.not-content *)) { + color: var(--cs-muted); + font-family: var(--sl-font-mono); + font-size: 0.875em; + white-space: nowrap; + width: 1%; +} + +/* 2-column tables β€” label/token column stays compact; content column can wrap */ +html[data-has-sidebar] + .sl-markdown-content + table:not(:has(thead th:nth-child(3))) + td:first-child:not(:where(.not-content *)) { + font-weight: 500; + color: var(--cs-ink); + width: 1%; + white-space: nowrap; + vertical-align: top; +} + +html[data-has-sidebar] + .sl-markdown-content + table:not(:has(thead th:nth-child(3))) + td:last-child:not(:where(.not-content *)) { + vertical-align: top; +} + +/* Keep tokens intact; .cs-table-scroll provides overflow-x */ +html[data-has-sidebar] .sl-markdown-content table + code:not(:where(pre *, .expressive-code *, .frame *)) { + font-size: 0.9em; + padding: 0.12em 0.38em; + background-color: var(--cs-table-code-bg); + white-space: nowrap; +} + +@layer starlight.core { + html[data-has-sidebar] header.header { + background-color: var(--docs-header-bg); + backdrop-filter: blur(14px) saturate(1.25); + -webkit-backdrop-filter: blur(14px) saturate(1.25); + } +} + +/* + * Page title band β€” equal block padding so the H1 is vertically centered + * (unlayered: beats ContentPanel `padding: 1.5rem` + PageTitle margin). + * Metrics match explorer `.overview-title-panel` / `.route-page__title`. + */ +html[data-has-sidebar] .content-panel:first-of-type { + padding-block: 1rem; + padding-inline: var(--sl-content-pad-x); +} + +html[data-has-sidebar] .content-panel:first-of-type h1 { + margin: 0; + font-size: var(--sl-text-h1); + font-weight: 700; + letter-spacing: -0.02em; + line-height: 1.25; +} + +html[data-has-sidebar] .main-pane .content-panel + .content-panel { + padding-top: 1.25rem; +} + +@media (min-width: 90rem) { + html[data-has-sidebar] .content-panel:first-of-type { + padding-block: 1.25rem; + } +} + +/* + * Header chrome β€” same L/R pad + breakpoints as explorer (`--docs-header-pad-x`). + * Re-assert inside Starlight’s 50em media so layered `--sl-nav-pad-x: 1.5rem` can’t win. + */ +:root, +html[data-theme='light'], +html[data-theme='dark'] { + --sl-nav-height: var(--docs-header-height); + --sl-nav-pad-y: 0.65rem; + --sl-nav-pad-x: var(--docs-header-pad-x); + --sl-nav-gap: 1.25rem; + --sl-sidebar-pad-x: var(--docs-sidebar-pad-x); +} + +@media (min-width: 50em) { + :root, + html[data-theme='light'], + html[data-theme='dark'] { + --sl-nav-height: var(--docs-header-height); + --sl-nav-pad-x: var(--docs-header-pad-x); + } +} + +header.header { + /* Match explorer `.top-header { padding: 0 var(--header-pad-x) }`. */ + padding-inline: var(--docs-header-pad-x); + /* Never border-box a 1px line here β€” that shrinks the bar and shifts the lockup. */ + border-bottom: none; +} + +/* Hairline sits *under* the fixed bar so splash ↔ guides keep the same header box. */ +html[data-has-sidebar] header.header::after { + content: ''; + position: absolute; + inset-inline: 0; + top: 100%; + height: 1px; + background: var(--docs-border); + pointer-events: none; +} + +html[data-has-sidebar] .sidebar-pane { + inset-block-start: calc(var(--sl-nav-height) + 1px); + border-inline-end-color: var(--sl-color-hairline); +} + +html[data-has-sidebar] .main-frame { + padding-top: calc(var(--sl-nav-height) + 1px + var(--sl-mobile-toc-height)); +} + +/* + * Desktop β€œOn this page” β€” delay Starlight’s 72rem TOC flip until 90rem. + * Mobile TOC lives inside .right-sidebar-container β€” never display:none that wrapper. + */ +@media (max-width: 89.99rem) { + html[data-has-toc] { + /* Starlight zeros this at 72rem; desktop TOC is delayed to 90rem below. */ + --sl-mobile-toc-height: 3rem; + } + + html[data-has-sidebar][data-has-toc] { + --__toc-width: 0rem; + } + + html[data-has-sidebar][data-has-toc] .right-sidebar-panel { + display: none !important; + } + + html[data-has-sidebar][data-has-toc] .right-sidebar-container { + flex: 0 0 0 !important; + width: 0 !important; + min-width: 0 !important; + overflow: visible; + } + + html[data-has-sidebar][data-has-toc] .right-sidebar { + position: static; + width: auto !important; + height: auto !important; + padding-top: 0; + border: 0; + overflow: visible; + } +} + +@media (min-width: 72rem) and (max-width: 89.99rem) { + html[data-has-sidebar][data-has-toc] .lg\:sl-hidden { + display: block !important; + } +} + +html[data-has-toc] { + scroll-padding-top: calc(1.5rem + var(--sl-nav-height) + var(--sl-mobile-toc-height)); +} + +@media (min-width: 90rem) { + html[data-has-toc] { + scroll-padding-top: calc(1.5rem + var(--sl-nav-height)); + } + + html[data-has-sidebar][data-has-toc] .main-frame { + padding-top: calc(var(--sl-nav-height) + 1px); + } +} + +/* + * Guides <50rem: logo + Menu only. + * Must be unlayered β€” Header.astro rules in @layer starlight.core lose to + * Starlight’s unlayered .sl-flex { display: flex }. + */ +@media (max-width: 49.99rem) { + html[data-has-sidebar] header.header .social-icons, + html[data-has-sidebar] header.header starlight-theme-select, + html[data-has-sidebar] header.header starlight-lang-select { + display: none; + } +} + +/* Desktop: title | centered search | tools β€” equal side columns keep search + optically centered. Do NOT set display on site-search β€” Starlight uses + display:contents so the can center in the viewport top layer. */ +@media (min-width: 50rem) { + header.header .header { + display: grid; + grid-template-columns: 1fr minmax(14rem, var(--docs-search-max-width)) 1fr; + align-items: center; + column-gap: 0.75rem; + width: 100%; + } + + header.header .header > .title-wrapper { + justify-self: start; + } + + header.header .header > .header-search { + display: flex; + justify-content: center; + width: 100%; + min-width: 0; + } + + header.header .header > .header-tools { + justify-self: end; + gap: 0.75rem; + } + + site-search > button { + width: 100%; + max-width: var(--docs-search-max-width); + height: var(--docs-search-height); + border-radius: 0.55rem; + border: 1px solid var(--docs-border-strong); + background-color: var(--docs-surface); + color: var(--sl-color-gray-2); + padding-inline: 0.85rem; + justify-content: flex-start; + gap: 0.55rem; + } + + site-search > button:hover { + border-color: color-mix(in srgb, var(--docs-border-strong) 70%, var(--docs-text)); + color: var(--sl-color-white); + } + + site-search > button > kbd, + site-search > button .cs-search-kbd { + margin-inline-start: auto; + background-color: var(--sl-color-gray-5); + border: 0; + color: var(--sl-color-gray-2); + } +} + +html[data-theme='light'] site-search > button { + background-color: var(--docs-surface); + border-color: var(--docs-border-strong); + box-shadow: 0 1px 2px hsl(228 25% 40% / 0.08); +} + +html[data-theme='light'] site-search > button > kbd, +html[data-theme='light'] site-search > button .cs-search-kbd { + background-color: var(--sl-color-gray-6); + color: var(--sl-color-gray-3); +} + +/* + * Small header: search is an icon like GitHub β€” no chip outline. + * Drawer search keeps the full bordered trigger (not under header.header). + */ +@media (max-width: 49.99rem) { + header.header site-search > button { + width: 2.5rem; + height: 2.5rem; + padding: 0; + border: 0; + border-radius: 0; + background: transparent; + box-shadow: none; + justify-content: center; + gap: 0; + color: var(--sl-color-white); + } + + header.header site-search > button:hover { + border: 0; + background: transparent; + color: var(--sl-color-white); + opacity: 0.82; + } + + header.header site-search .cs-search-label, + header.header site-search .cs-search-kbd, + header.header site-search > button > kbd { + display: none; + } + + header.header site-search > button svg { + width: 1.35rem; + height: 1.35rem; + flex-shrink: 0; + } + + html[data-theme='light'] header.header site-search > button { + background: transparent; + border: 0; + box-shadow: none; + } +} + + +/* Header right icons β€” GitHub, theme toggle */ +header.header .social-icons::after { + display: none; + content: none; +} + +header.header .social-icons a { + display: grid; + place-items: center; + width: 2.5rem; + height: 2.5rem; + padding: 0; + margin: 0; + /* Neutral β€” Starlight defaults social icons to accent. */ + color: var(--sl-color-white); + transition: color 120ms ease; +} + +header.header .social-icons a[href*='github']:hover { + color: var(--sl-color-gray-3); +} + +header.header .social-icons a[href*='discord']:hover { + color: #5865f2; +} + +/* Mobile drawer β€” same social targets as header. */ +.cs-mobile-menu-tools .social-icons a { + transition: color 120ms ease; +} + +.cs-mobile-menu-tools .social-icons a[href*='github']:hover { + color: var(--sl-color-gray-3); +} + +.cs-mobile-menu-tools .social-icons a[href*='discord']:hover { + color: #5865f2; +} + +header.header .social-icons a svg, +.cs-mobile-menu-tools .social-icons a svg { + display: block; + transition: transform 140ms ease; + transform-origin: center; +} + +header.header .social-icons a svg { + width: 1.35rem; + height: 1.35rem; +} + +header.header .social-icons a:hover svg, +.cs-mobile-menu-tools .social-icons a:hover svg { + transform: scale(1.14); +} + +header.header .social-icons a[href*='discord']:hover svg, +.cs-mobile-menu-tools .social-icons a[href*='discord']:hover svg { + animation: cs-discord-shudder 480ms ease-in-out; +} + +@keyframes cs-discord-shudder { + 0%, + 100% { + transform: scale(1.14) rotate(0deg); + } + + 18% { + transform: scale(1.14) rotate(-11deg); + } + + 36% { + transform: scale(1.14) rotate(9deg); + } + + 54% { + transform: scale(1.14) rotate(-7deg); + } + + 72% { + transform: scale(1.14) rotate(5deg); + } + + 88% { + transform: scale(1.14) rotate(-2deg); + } +} + +@media (prefers-reduced-motion: reduce) { + header.header .social-icons a svg, + .cs-mobile-menu-tools .social-icons a svg { + transition: none; + } + + header.header .social-icons a:hover svg, + .cs-mobile-menu-tools .social-icons a:hover svg { + transform: none; + } + + header.header .social-icons a[href*='discord']:hover svg, + .cs-mobile-menu-tools .social-icons a[href*='discord']:hover svg { + animation: none; + } +} + +/* + * Search dialog β€” unlayered on purpose. + * Starlight sets `margin: 4rem auto auto` in @layer starlight.core, but Vite can + * register that layer before layers.css’s order statement, so starlight.reset’s + * `* { margin: 0 }` wins and the modal sticks to the top-left. + * Also kills the 15rem min-height shell that leaves a hollow void when idle / empty. + */ +site-search dialog { + border: none; + outline: none; + user-select: none; +} + +site-search dialog:focus, +site-search dialog:focus-visible { + outline: none; +} + +site-search dialog[open] { + background: var(--cs-search-modal-bg); + box-shadow: var(--cs-search-modal-shadow); +} + +site-search dialog .pagefind-ui__search-input { + user-select: text; +} + +@media (min-width: 50rem) { + site-search dialog[open] { + margin: 4rem auto auto; + width: min(92vw, 36rem); + max-width: 36rem; + border-radius: 1rem; + } + + site-search dialog[open]:not(:has(.pagefind-ui__result)) { + min-height: 0; + } + + site-search dialog[open] .dialog-frame { + padding: 0; + gap: var(--cs-search-field-gap, 0.65rem); + overflow: hidden; + min-height: 0; + } +} + +site-search dialog[open] .dialog-frame { + --cs-search-dialog-inset: 0.75rem; + --cs-search-scrollbar-size: 0.5rem; + --cs-search-input-block-size: calc(64px * var(--pagefind-ui-scale, 0.92)); + --cs-search-icon-size: calc(20px * var(--pagefind-ui-scale, 0.92)); + --cs-search-icon-inline-start: calc(20px * var(--pagefind-ui-scale, 0.92)); + --cs-search-clear-size: 1.875rem; + --cs-search-clear-icon-size: 1.25rem; + --cs-search-clear-inline-end: 0.5rem; + --cs-search-content-pad-start: calc(var(--cs-search-dialog-inset) + 0.5rem); +} + +/* Search field inset β€” flush modal shell, inset components only. */ +[data-cs-pagefind-root] .pagefind-ui__form::before { + width: var(--cs-search-icon-size); + height: var(--cs-search-icon-size); + top: calc( + var(--cs-search-dialog-inset) + + (var(--cs-search-input-block-size) - var(--cs-search-icon-size)) / 2 + ); + left: calc(var(--cs-search-dialog-inset) + var(--cs-search-icon-inline-start)); + --pagefind-ui-text: var(--cs-muted); + opacity: 1; +} + +[data-cs-pagefind-root] .pagefind-ui__search-input { + margin: var(--cs-search-dialog-inset) var(--cs-search-dialog-inset) 0; + width: auto; + height: var(--cs-search-input-block-size); + line-height: var(--cs-search-input-block-size); + box-sizing: border-box; + color: var(--cs-search-field-fg); + padding-inline-end: calc( + var(--cs-search-clear-size) + var(--cs-search-clear-inline-end) + 0.15rem + ); +} + +[data-cs-pagefind-root] .pagefind-ui__search-clear { + inset-inline-end: calc(var(--cs-search-dialog-inset) + var(--cs-search-clear-inline-end)); + top: calc( + var(--cs-search-dialog-inset) + + (var(--cs-search-input-block-size) - var(--cs-search-clear-size)) / 2 + ); + transform: none; + z-index: 2; + width: var(--cs-search-clear-size); + height: var(--cs-search-clear-size); + display: inline-flex; + align-items: center; + justify-content: center; + padding: 0; + border: 0; + border-radius: 999px; + background-color: transparent; + cursor: pointer; + font-size: 0; + line-height: 0; + color: transparent; + overflow: hidden; + transition: background-color 0.15s ease; +} + +/* + * Scroll lane: left edge aligns with the search bar; scrollbar sits in the right inset. + * Left inset lives on the drawer; right inset + gutter live on the scroller. + */ +[data-cs-pagefind-root]:has(.pagefind-ui__result) .pagefind-ui__drawer { + padding-inline-start: var(--cs-search-dialog-inset); + box-sizing: border-box; +} + +[data-cs-pagefind-root]:has(.pagefind-ui__result) .pagefind-ui__drawer .pagefind-ui__results-area, +[data-cs-pagefind-root]:has(.pagefind-ui__result) .pagefind-ui__drawer [data-cs-search-stale]:not([hidden]) { + padding-inline-start: 0; + padding-inline-end: var(--cs-search-dialog-inset); + box-sizing: border-box; +} + +[data-cs-pagefind-root]:has(.pagefind-ui__result) .pagefind-ui__drawer .pagefind-ui__results-area { + scrollbar-gutter: stable; +} + +[data-cs-pagefind-root]:has(.pagefind-ui__result) .pagefind-ui__message { + padding-inline-start: var(--cs-search-content-pad-start); + padding-inline-end: var(--cs-search-dialog-inset); + box-sizing: border-box; +} + +site-search dialog[open] .cs-search-loading { + margin: var(--cs-search-dialog-inset) var(--cs-search-dialog-inset) 0; + padding: 0; +} + +site-search dialog[open] .cs-search-idle { + margin: 0 var(--cs-search-dialog-inset) var(--cs-search-dialog-inset); + padding: 0.7rem 0 0.1rem 0.5rem; +} + +/* Idle panel (injected by SearchModalPolish.astro) */ +.cs-search-idle { + margin-top: 0; + padding: 0; + border: 0; + background: transparent; +} + +.cs-search-idle[hidden] { + display: none !important; +} + +.cs-search-idle__lead { + margin: 0; + color: var(--docs-text-secondary); + font-size: 0.875rem; + line-height: 1.5; + max-width: 28rem; +} + +.cs-search-idle__try { + display: flex; + flex-wrap: wrap; + align-items: center; + gap: 0.4rem; + margin: 0.85rem 0 0; +} + +.cs-search-idle__try-label { + color: var(--docs-text-secondary); + font-size: 0.8125rem; + font-weight: 500; + line-height: 1.2; +} + +.cs-search-idle__chips { + display: flex; + flex-wrap: wrap; + gap: 0.4rem; + list-style: none; + margin: 0; + padding: 0; +} + +.cs-search-idle__chips button { + appearance: none; + border: 0; + border-radius: 0.55rem; + background: var(--cs-search-item-bg); + color: var(--cs-ink); + padding: 0.35rem 0.75rem; + font: inherit; + font-size: 0.8125rem; + font-weight: 500; + line-height: 1.2; + letter-spacing: -0.01em; + cursor: pointer; + box-shadow: none; + transition: + background-color 0.15s ease, + color 0.15s ease, + transform 140ms ease; +} + +.cs-search-idle__chips button:hover, +.cs-search-idle__chips button:focus-visible, +.cs-search-idle__chips button[data-cs-search-selected='true'] { + border: 0; + background: var(--cs-search-hover-bg); + color: var(--cs-search-hover-fg); + outline: none; +} + +.cs-search-idle__chips button:active { + transform: translateY(1px); +} + +site-search dialog::backdrop { + background: hsl(228 24% 4% / 0.72); + -webkit-backdrop-filter: blur(6px) saturate(1.15); + backdrop-filter: blur(6px) saturate(1.15); +} + +/* Pagefind field + results inside the dialog */ +[data-cs-pagefind-root] { + --pagefind-ui-border-radius: 0.7rem; + --pagefind-ui-scale: 0.92; + --pagefind-ui-background: var(--cs-search-field-bg); + --pagefind-ui-text: var(--cs-search-result-fg); + --pagefind-ui-primary: var(--cs-search-result-fg); + --pagefind-ui-border-width: 0; + --pagefind-ui-border: transparent; +} + +/* + * Result sections β€” rounded group shell; per-row grey + full-width hover live in Search.astro. + */ +[data-cs-pagefind-root] .pagefind-ui__result { + border: 0; + padding: 0; +} + +[data-cs-pagefind-root] .pagefind-ui__result-inner { + align-items: stretch; + width: 100%; + gap: 0; + padding: 0; + background-color: var(--cs-search-item-bg); + border-radius: var(--cs-search-card-radius); + overflow: hidden; +} + +[data-cs-pagefind-root] .pagefind-ui__result-inner > .pagefind-ui__result-title, +[data-cs-pagefind-root] .pagefind-ui__result-inner > .pagefind-ui__result-nested { + border-radius: 0; +} + +html[data-theme='light'] [data-cs-pagefind-root] { + --pagefind-ui-background: var(--cs-search-field-bg); +} + +html[data-theme='light'] [data-cs-pagefind-root] .pagefind-ui__result-inner > .pagefind-ui__result-title, +html[data-theme='light'] [data-cs-pagefind-root] .pagefind-ui__result-nested { + background-color: var(--cs-search-item-bg); +} + +html[data-theme='light'] [data-cs-pagefind-root] .pagefind-ui__button { + background: var(--cs-search-item-bg); + border: 0; + color: var(--cs-ink); +} + +html[data-theme='light'] [data-cs-pagefind-root] .pagefind-ui__button:hover { + background: var(--cs-search-hover-bg); + border: 0; + color: var(--cs-search-hover-fg); +} + +html[data-theme='light'] + [data-cs-pagefind-root] + .pagefind-ui__result-inner + > .pagefind-ui__result-title:is(:hover, [data-cs-search-selected='true']), +html[data-theme='light'] + [data-cs-pagefind-root] + .pagefind-ui__result-nested:is(:hover, [data-cs-search-selected='true']) { + background-color: var(--cs-search-hover-bg); +} + +[data-cs-pagefind-root] .pagefind-ui__result-inner > .pagefind-ui__result-title { + display: block; + width: 100%; + box-sizing: border-box; + cursor: pointer; +} + +[data-cs-pagefind-root] .pagefind-ui__result-nested { + position: relative; + display: flex; + flex-direction: column; + align-items: stretch; + width: 100%; + min-width: 0; + align-self: stretch; + box-sizing: border-box; + cursor: pointer; + overflow: hidden; + padding-block: var(--sl-search-result-nested-pad-block-start, calc(0.375rem * var(--pagefind-ui-scale, 0.92))) + var(--sl-search-result-nested-pad-block, calc(0.5rem * var(--pagefind-ui-scale, 0.92))); + padding-inline: var(--sl-search-result-pad-inline-start) var(--sl-search-result-pad-inline-end); +} + +/* Pagefind sets nested links position:relative β€” shrinks the ::after hit target to the title line. */ +[data-cs-pagefind-root] .pagefind-ui__result-nested .pagefind-ui__result-link { + position: static; + font-size: 0.75rem; + line-height: 1.35; + color: var(--cs-search-result-fg); + text-decoration: none; +} + +[data-cs-pagefind-root] .pagefind-ui__result-nested .pagefind-ui__result-title { + display: block; + width: 100%; +} + +[data-cs-pagefind-root] .pagefind-ui__result-nested .pagefind-ui__result-link::before { + content: none; +} + +/* Page headers β€” larger type; nested hits stay compact. */ +[data-cs-pagefind-root] .pagefind-ui__result-inner > .pagefind-ui__result-title, +[data-cs-pagefind-root] .pagefind-ui__result-inner > .pagefind-ui__result-title .pagefind-ui__result-link { + font-size: 0.9375rem; + line-height: 1.35; +} + +[data-cs-pagefind-root] .pagefind-ui__result-nested .pagefind-ui__result-excerpt { + width: 100%; + min-width: 0; + box-sizing: border-box; + font-size: 0.6875rem; + font-weight: 400; + line-height: 1.45; + color: var(--docs-text-secondary); + display: -webkit-box; + /* autoprefixer strips bare -webkit-box-orient; keep line-clamp working in prod builds. */ + /* autoprefixer: ignore next */ + -webkit-box-orient: vertical; + -webkit-line-clamp: 2; + line-clamp: 2; + overflow: hidden; + max-height: calc(2 * 1.45 * 0.6875rem); + overflow-wrap: break-word; + word-break: normal; + pointer-events: none; +} + +/* Hover / keyboard focus β€” full row reads at full contrast. */ +[data-cs-pagefind-root] + .pagefind-ui__result-inner + > .pagefind-ui__result-title:is(:hover, [data-cs-search-selected='true']) + .pagefind-ui__result-link, +[data-cs-pagefind-root] + .pagefind-ui__result-nested:is(:hover, [data-cs-search-selected='true']) + .pagefind-ui__result-link, +[data-cs-pagefind-root] + .pagefind-ui__result-nested:is(:hover, [data-cs-search-selected='true']) + .pagefind-ui__result-excerpt, +[data-cs-pagefind-root] + .pagefind-ui__result-nested:is(:hover, [data-cs-search-selected='true']) + mark, +[data-cs-pagefind-root] + .pagefind-ui__result-inner + > .pagefind-ui__result-title:is(:hover, [data-cs-search-selected='true']) + mark { + color: var(--cs-search-hover-fg); +} + +[data-cs-pagefind-root] + .pagefind-ui__result-inner + > .pagefind-ui__result-title:is(:hover, [data-cs-search-selected='true'])::before { + background: var(--cs-search-hover-fg); +} + +[data-cs-pagefind-root] + .pagefind-ui__result-nested:is(:hover, [data-cs-search-selected='true'])::before { + background: var(--cs-search-hover-fg); +} + +html[data-theme='light'] + [data-cs-pagefind-root] + .pagefind-ui__result-inner + > .pagefind-ui__result-title:is(:hover, [data-cs-search-selected='true']) + .pagefind-ui__result-link, +html[data-theme='light'] + [data-cs-pagefind-root] + .pagefind-ui__result-nested:is(:hover, [data-cs-search-selected='true']) + .pagefind-ui__result-link, +html[data-theme='light'] + [data-cs-pagefind-root] + .pagefind-ui__result-nested:is(:hover, [data-cs-search-selected='true']) + .pagefind-ui__result-excerpt, +html[data-theme='light'] + [data-cs-pagefind-root] + .pagefind-ui__result-nested:is(:hover, [data-cs-search-selected='true']) + mark, +html[data-theme='light'] + [data-cs-pagefind-root] + .pagefind-ui__result-inner + > .pagefind-ui__result-title:is(:hover, [data-cs-search-selected='true']) + mark { + color: var(--cs-search-hover-fg); +} + +html[data-theme='light'] + [data-cs-pagefind-root] + .pagefind-ui__result-inner + > .pagefind-ui__result-title:is(:hover, [data-cs-search-selected='true'])::before, +html[data-theme='light'] + [data-cs-pagefind-root] + .pagefind-ui__result-nested:is(:hover, [data-cs-search-selected='true'])::before { + background: var(--cs-search-hover-fg); +} + +[data-cs-pagefind-root] .pagefind-ui__result-inner > .pagefind-ui__result-title .pagefind-ui__result-link { + color: var(--cs-search-result-fg); + text-decoration: none; +} + +[data-cs-pagefind-root] .pagefind-ui__result-inner > .pagefind-ui__result-title, +[data-cs-pagefind-root] .pagefind-ui__result-nested { + background-color: var(--cs-search-item-bg); +} + +[data-cs-pagefind-root] .pagefind-ui__result-inner > .pagefind-ui__result-title:is(:hover, [data-cs-search-selected='true']), +[data-cs-pagefind-root] .pagefind-ui__result-nested:is(:hover, [data-cs-search-selected='true']) { + background-color: var(--cs-search-hover-bg); +} + +[data-cs-pagefind-root] .pagefind-ui__search-input { + font-weight: 500; + letter-spacing: -0.01em; + border: 0; + background-color: var(--cs-search-field-bg); + box-shadow: none; +} + +[data-cs-pagefind-root] .pagefind-ui__search-input::placeholder { + opacity: 0.45; + font-weight: 400; +} + +[data-cs-pagefind-root] .pagefind-ui__search-input:is(:focus, :focus-visible) { + outline: none; + border: 0; + background-color: var(--cs-search-field-bg); + box-shadow: none; +} + +[data-cs-pagefind-root] .pagefind-ui__search-clear:hover { + background-color: var(--sl-color-gray-5); +} + +[data-cs-pagefind-root] .pagefind-ui__search-clear:is(:focus, :focus-visible, :active) { + outline: none; + background-color: var(--sl-color-accent); +} + +[data-cs-pagefind-root] .pagefind-ui__search-clear::before { + content: ''; + width: var(--cs-search-clear-icon-size); + height: var(--cs-search-clear-icon-size); + flex-shrink: 0; + -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='m13.41 12 6.3-6.29a1 1 0 1 0-1.42-1.42L12 10.59l-6.29-6.3a1 1 0 0 0-1.42 1.42l6.3 6.29-6.3 6.29a1 1 0 0 0 .33 1.64 1 1 0 0 0 1.09-.22l6.29-6.3 6.29 6.3a1 1 0 0 0 1.64-.33 1 1 0 0 0-.22-1.09L13.41 12Z'/%3E%3C/svg%3E") + center / contain no-repeat; + mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='m13.41 12 6.3-6.29a1 1 0 1 0-1.42-1.42L12 10.59l-6.29-6.3a1 1 0 0 0-1.42 1.42l6.3 6.29-6.3 6.29a1 1 0 0 0 .33 1.64 1 1 0 0 0 1.09-.22l6.29-6.3 6.29 6.3a1 1 0 0 0 1.64-.33 1 1 0 0 0-.22-1.09L13.41 12Z'/%3E%3C/svg%3E") + center / contain no-repeat; + background-color: var(--sl-color-gray-2); + transition: background-color 0.15s ease; +} + +[data-cs-pagefind-root] .pagefind-ui__search-clear:hover:not(:focus):not(:focus-visible):not(:active)::before { + background-color: var(--sl-color-gray-1); +} + +[data-cs-pagefind-root] .pagefind-ui__search-clear:is(:focus, :focus-visible, :active)::before { + background-color: var(--sl-color-black); +} + +site-search dialog[open] .search-container { + display: flex; + flex-direction: column; + gap: var(--cs-search-field-gap, 0.65rem); +} + +/* Pagefind nests the drawer inside the form β€” do not shrink-wrap the form. */ +[data-cs-pagefind-root] .pagefind-ui, +[data-cs-pagefind-root] .pagefind-ui__form { + display: flex; + flex-direction: column; + min-height: 0; +} + +[data-cs-pagefind-root] .pagefind-ui__form { + flex: 1 1 auto; + gap: var(--cs-search-field-gap, 0.65rem); +} + +[data-cs-pagefind-root] .pagefind-ui__search-input, +[data-cs-pagefind-root] .pagefind-ui__search-clear { + flex-shrink: 0; +} + +[data-cs-pagefind-root] .pagefind-ui__results-area { + margin-top: 0; +} + +[data-cs-pagefind-root][data-cs-search-short-query] .pagefind-ui__drawer { + display: none !important; +} + +[data-cs-pagefind-root]:has(.pagefind-ui__result) .pagefind-ui__drawer { + display: flex; + flex-direction: column; + min-height: 0; + max-height: min(28rem, calc(100dvh - 16rem)); + overflow: hidden; + overscroll-behavior: contain; + box-sizing: border-box; + border-top-left-radius: 0.75rem; + border-top-right-radius: 0; +} + +[data-cs-pagefind-root]:has(.pagefind-ui__result) .pagefind-ui__drawer .pagefind-ui__results-area { + flex: 1 1 auto; + min-height: 0; + overflow-x: hidden; + overflow-y: auto; + overscroll-behavior: contain; + padding-bottom: 1.25rem; + scroll-padding-bottom: 1.25rem; + box-sizing: border-box; + border-top-left-radius: 0.75rem; + border-top-right-radius: 0; +} + +[data-cs-pagefind-root]:has(.pagefind-ui__result) .pagefind-ui__drawer [data-cs-search-stale]:not([hidden]) { + flex-shrink: 0; + border-top-left-radius: 0.75rem; + border-top-right-radius: 0; + overflow: hidden; +} + +[data-cs-pagefind-root]:has(.pagefind-ui__result) .pagefind-ui__drawer [data-cs-search-stale]:not([hidden]) + .pagefind-ui__results-area { + border-top-left-radius: 0; + border-top-right-radius: 0; +} + +[data-cs-pagefind-root] .pagefind-ui__drawer { + scrollbar-width: thin; + scrollbar-color: color-mix(in srgb, var(--sl-color-gray-4) 75%, transparent) transparent; +} + +[data-cs-pagefind-root]:has(.pagefind-ui__result) .pagefind-ui__drawer .pagefind-ui__results-area { + scrollbar-width: thin; + scrollbar-color: color-mix(in srgb, var(--sl-color-gray-4) 75%, transparent) transparent; +} + +[data-cs-pagefind-root]:has(.pagefind-ui__result) .pagefind-ui__drawer .pagefind-ui__results-area::-webkit-scrollbar { + width: 0.5rem; +} + +[data-cs-pagefind-root]:has(.pagefind-ui__result) .pagefind-ui__drawer .pagefind-ui__results-area::-webkit-scrollbar-thumb { + background: color-mix(in srgb, var(--sl-color-gray-4) 70%, transparent); + border-radius: 999px; + border: 2px solid transparent; + background-clip: padding-box; +} + +[data-cs-pagefind-root]:has(.pagefind-ui__result) .pagefind-ui__drawer .pagefind-ui__results-area::-webkit-scrollbar-track { + margin-block: 0.15rem; + background: transparent; +} + +[data-cs-pagefind-root] .pagefind-ui__drawer::-webkit-scrollbar { + width: 0.5rem; +} + +[data-cs-pagefind-root] .pagefind-ui__drawer::-webkit-scrollbar-thumb { + background: color-mix(in srgb, var(--sl-color-gray-4) 70%, transparent); + border-radius: 999px; + border: 2px solid transparent; + background-clip: padding-box; +} + +[data-cs-pagefind-root] .pagefind-ui__drawer::-webkit-scrollbar-track { + margin-block: 0.15rem; + background: transparent; +} + +/* Results-only polish (scrollbar lane, top radii). */ +@media (max-width: 49.99rem) { + site-search dialog[open] { + display: flex; + flex-direction: column; + overflow: hidden; + } + + site-search dialog[open] .dialog-frame, + site-search dialog[open] .search-container { + display: flex; + flex-direction: column; + min-height: 0; + flex: 1 1 auto; + } + + /* + * Idle chips live after the Pagefind root. Growing the root on idle + * shoves that card to the bottom of the full-screen dialog. + */ + site-search dialog[open]:has(.pagefind-ui__result) [data-cs-pagefind-root], + site-search dialog[open]:has(.pagefind-ui__result) [data-cs-pagefind-root] .pagefind-ui { + display: flex; + flex-direction: column; + min-height: 0; + flex: 1 1 auto; + } + + site-search dialog[open]:has(.pagefind-ui__result) [data-cs-pagefind-root] .pagefind-ui__form, + site-search dialog[open]:has(.pagefind-ui__result) [data-cs-pagefind-root] .pagefind-ui__drawer { + flex: 1 1 auto; + min-height: 0; + max-height: none; + overflow: hidden; + } + + site-search dialog[open]:has(.pagefind-ui__result) [data-cs-pagefind-root] .pagefind-ui__drawer .pagefind-ui__results-area { + overflow-y: auto; + } +} + +site-search dialog[open]:has(.pagefind-ui__result) [data-cs-pagefind-root] .pagefind-ui__drawer .pagefind-ui__results-area { + overflow-y: auto; +} + +[data-cs-pagefind-root] .pagefind-ui__button[data-cs-search-selected='true'] { + border: 0; + background: var(--cs-search-hover-bg); + color: var(--cs-search-hover-fg); +} + +/* + * Starlight reset (`* { margin: 0 }`) beats layered Pagefind/Search margins. + * Unlayered so result cards keep a gap under their rounded bottoms, and so the + * load-more button isn’t flush with the last result. + */ +[data-cs-pagefind-root] .pagefind-ui__button { + margin-top: 0.85rem; + margin-bottom: 0; + height: auto; + min-height: 2.35rem; + padding: 0.55rem 1.1rem; + border: 0; + border-radius: 0.55rem; + background: var(--cs-search-item-bg); + color: var(--cs-ink); + font-family: var(--sl-font); + font-size: 0.8125rem; + font-weight: 500; + letter-spacing: -0.01em; + box-shadow: none; + transition: + background-color 0.15s ease, + color 0.15s ease; +} + +[data-cs-pagefind-root] .pagefind-ui__button:hover { + border: 0; + background: var(--cs-search-hover-bg); + color: var(--cs-search-hover-fg); +} + +[data-cs-pagefind-root] .pagefind-ui__results > * + * { + margin-top: 0.65rem; +} + +[data-cs-search-stale][hidden] { + display: none !important; +} + +[data-cs-search-stale]:not([hidden]) { + margin-bottom: 0; +} + +[data-cs-pagefind-root]:has([data-cs-search-stale]:not([hidden])) .pagefind-ui__message { + display: none !important; +} + +[data-cs-pagefind-root] .pagefind-ui__message { + font-size: 0.8125rem; + font-weight: 500; + letter-spacing: 0.01em; + color: var(--cs-muted); + height: auto; + min-height: 0; + padding: 0.35rem 0 0.75rem; + box-sizing: border-box; +} + +/* Idle drawer content (searching / no matches) β€” align with the inset search field. */ +[data-cs-pagefind-root]:not(:has(.pagefind-ui__results > .pagefind-ui__result)):not( + :has(.cs-search-pending:not([hidden])) + ) + .pagefind-ui__drawer:not(.pagefind-ui__hidden) + .pagefind-ui__results-area { + margin-top: 0; + padding-inline-start: var(--cs-search-content-pad-start); + padding-inline-end: var(--cs-search-dialog-inset); + padding-bottom: var(--cs-search-dialog-inset); + box-sizing: border-box; +} + +/* Skeleton uses the same scroll lane as live result cards. */ +[data-cs-pagefind-root]:has(.cs-search-pending:not([hidden])) .pagefind-ui__drawer:not(.pagefind-ui__hidden) { + padding-inline-start: var(--cs-search-dialog-inset); + box-sizing: border-box; +} + +[data-cs-pagefind-root]:has(.cs-search-pending:not([hidden])) + .pagefind-ui__drawer:not(.pagefind-ui__hidden) + .pagefind-ui__results-area { + margin-top: 0; + padding-inline-start: 0; + padding-inline-end: var(--cs-search-dialog-inset); + padding-bottom: var(--cs-search-dialog-inset); + box-sizing: border-box; +} + +/* Zero-results β€” flat stack; left edge comes from results-area inset only */ +[data-cs-pagefind-root]:has(.pagefind-ui__results:not(:has(.pagefind-ui__result))) .pagefind-ui__message { + display: block; + margin: 0; + padding: 0.35rem 0 0; + border: 0; + background: transparent; + color: var(--cs-ink); + font-size: 0.9375rem; + font-weight: 600; + letter-spacing: -0.015em; +} + +[data-cs-pagefind-root] .pagefind-ui__results:not(:has(.pagefind-ui__result)) { + margin: 0; + padding: 0; + min-height: 0; + list-style: none; +} + +.cs-search-empty-help { + margin: 0.5rem 0 0; + padding: 0; + color: var(--cs-muted); + font-size: 0.8125rem; + line-height: 1.45; +} + +.cs-search-empty-help[hidden] { + display: none !important; +} + +.cs-search-empty-help a { + color: var(--sl-color-text-accent); + text-decoration: underline; + text-underline-offset: 0.15em; +} + +/* In-flight query skeleton β€” mirrors Pagefind result cards for a seamless swap-in. */ +[data-cs-pagefind-root]:has(.cs-search-pending:not([hidden])) .pagefind-ui__message { + display: none !important; +} + +.cs-search-pending { + display: grid; + gap: 0; + width: 100%; + margin: 0; + padding: 0; + box-sizing: border-box; +} + +.cs-search-pending__result + .cs-search-pending__result { + margin-top: 0.65rem; +} + +.cs-search-pending:not([hidden]) { + animation: cs-search-pending-in 220ms ease forwards; +} + +@keyframes cs-search-pending-in { + from { + opacity: 0; + } + + to { + opacity: 1; + } +} + +@media (prefers-reduced-motion: reduce) { + .cs-search-pending:not([hidden]) { + animation: none; + } +} + +.cs-search-pending[hidden] { + display: none !important; +} + +.cs-search-pending__inner { + display: grid; + gap: 0; + width: 100%; + overflow: hidden; + border-radius: var(--cs-search-card-radius); +} + +.cs-search-pending__title, +.cs-search-pending__nested { + position: relative; + width: 100%; + box-sizing: border-box; + background: var(--cs-search-item-bg); + border-radius: 0; +} + +.cs-search-pending__title { + padding: var(--sl-search-result-pad-block, calc(0.9375rem * var(--pagefind-ui-scale, 0.92))) + var(--sl-search-result-pad-inline-end, calc(1.25rem * var(--pagefind-ui-scale, 0.92))); + padding-inline-start: var(--sl-search-result-pad-inline-start, calc(3.75rem * var(--pagefind-ui-scale, 0.92))); +} + +.cs-search-pending__title::before { + content: ''; + position: absolute; + inset-block: 0; + inset-inline-start: var(--sl-search-page-icon-inline-start, calc(0.9375rem * var(--pagefind-ui-scale, 0.92))); + width: var(--sl-search-page-icon-size, calc(1.875rem * var(--pagefind-ui-scale, 0.92))); + background: var(--sl-color-gray-3); + -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='currentColor' viewBox='0 0 24 24'%3E%3Cpath d='M9 10h1a1 1 0 1 0 0-2H9a1 1 0 0 0 0 2Zm0 2a1 1 0 0 0 0 2h6a1 1 0 0 0 0-2H9Zm11-3V8l-6-6a1 1 0 0 0-1 0H7a3 3 0 0 0-3 3v14a3 3 0 0 0 3 3h10a3 3 0 0 0 3-3V9Zm-6-4 3 3h-2a1 1 0 0 1-1-1V5Zm4 14a1 1 0 0 1-1 1H7a1 1 0 0 1-1-1V5a1 1 0 0 1 1-1h5v3a3 3 0 0 0 3 3h3v9Zm-3-3H9a1 1 0 0 0 0 2h6a1 1 0 0 0 0-2Z'/%3E%3C/svg%3E") + center no-repeat; + mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='currentColor' viewBox='0 0 24 24'%3E%3Cpath d='M9 10h1a1 1 0 1 0 0-2H9a1 1 0 0 0 0 2Zm0 2a1 1 0 0 0 0 2h6a1 1 0 0 0 0-2H9Zm11-3V8l-6-6a1 1 0 0 0-1 0H7a3 3 0 0 0-3 3v14a3 3 0 0 0 3 3h10a3 3 0 0 0 3-3V9Zm-6-4 3 3h-2a1 1 0 0 1-1-1V5Zm4 14a1 1 0 0 1-1 1H7a1 1 0 0 1-1-1V5a1 1 0 0 1 1-1h5v3a3 3 0 0 0 3 3h3v9Zm-3-3H9a1 1 0 0 0 0 2h6a1 1 0 0 0 0-2Z'/%3E%3C/svg%3E") + center no-repeat; + opacity: 0.55; +} + +.cs-search-pending__nested { + display: grid; + gap: 0.28rem; + padding-block: var(--sl-search-result-nested-pad-block-start, calc(0.375rem * var(--pagefind-ui-scale, 0.92))) + var(--sl-search-result-nested-pad-block, calc(0.5rem * var(--pagefind-ui-scale, 0.92))); + padding-inline: var(--sl-search-result-pad-inline-start, calc(3.75rem * var(--pagefind-ui-scale, 0.92))) + var(--sl-search-result-pad-inline-end, calc(1.25rem * var(--pagefind-ui-scale, 0.92))); +} + +.cs-search-pending__nested::before { + content: ''; + position: absolute; + inset-block: 0; + inset-inline-start: var(--sl-search-tree-diagram-inline-start, calc(0.625rem * var(--pagefind-ui-scale, 0.92))); + width: var(--sl-search-tree-diagram-size, calc(2.5rem * var(--pagefind-ui-scale, 0.92))); + background: var(--sl-color-gray-4); + -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' stroke='currentColor' stroke-linecap='round' viewBox='0 0 16 1000' preserveAspectRatio='xMinYMin slice'%3E%3Cpath d='M8 0v1000m6-988H8'/%3E%3C/svg%3E") + 0% 0% / 100% no-repeat; + mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' stroke='currentColor' stroke-linecap='round' viewBox='0 0 16 1000' preserveAspectRatio='xMinYMin slice'%3E%3Cpath d='M8 0v1000m6-988H8'/%3E%3C/svg%3E") + 0% 0% / 100% no-repeat; + opacity: 0.55; +} + +.cs-search-pending__nested--last::before { + -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' stroke='currentColor' stroke-linecap='round' stroke-linejoin='round' viewBox='0 0 16 16'%3E%3Cpath d='M8 0v8m6 0H8'/%3E%3C/svg%3E"); + mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' stroke='currentColor' stroke-linecap='round' stroke-linejoin='round' viewBox='0 0 16 16'%3E%3Cpath d='M8 0v8m6 0H8'/%3E%3C/svg%3E"); +} + +.cs-search-pending__bar { + display: block; + width: 100%; + max-width: 100%; + border-radius: 999px; + background: + linear-gradient( + 110deg, + color-mix(in srgb, var(--sl-color-gray-5) 28%, transparent) 0%, + color-mix(in srgb, var(--sl-color-gray-4) 55%, transparent) 45%, + color-mix(in srgb, var(--sl-color-gray-5) 28%, transparent) 90% + ); + background-size: 200% 100%; + animation: cs-search-shimmer 1.15s ease-in-out infinite; +} + +.cs-search-pending__bar--title { + height: calc(0.9375rem * 1.35 * 0.72); +} + +.cs-search-pending__bar--hit { + height: calc(0.75rem * 1.35 * 0.72); +} + +.cs-search-pending__bar--excerpt { + height: calc(0.6875rem * 1.45 * 0.68); + opacity: 0.72; +} + +.cs-search-pending__bar--w44 { + width: 44%; +} + +.cs-search-pending__bar--w48 { + width: 48%; +} + +.cs-search-pending__bar--w52 { + width: 52%; +} + +.cs-search-pending__bar--w56 { + width: 56%; +} + +.cs-search-pending__bar--w68 { + width: 68%; +} + +.cs-search-pending__bar--w72 { + width: 72%; +} + +.cs-search-pending__bar--w78 { + width: 78%; +} + +.cs-search-pending__bar--w84 { + width: 84%; +} + +.cs-search-pending__nested .cs-search-pending__bar--excerpt + .cs-search-pending__bar--excerpt { + margin-top: 0.1rem; +} + +@media (prefers-reduced-motion: reduce) { + .cs-search-pending__bar { + animation: none; + background: color-mix(in srgb, var(--sl-color-gray-5) 45%, transparent); + } +} + +html[data-theme='light'] .cs-search-pending__bar { + background: + linear-gradient( + 110deg, + color-mix(in srgb, var(--sl-color-gray-5) 35%, hsl(0 0% 100%)) 0%, + color-mix(in srgb, var(--sl-color-gray-4) 18%, hsl(0 0% 100%)) 45%, + color-mix(in srgb, var(--sl-color-gray-5) 35%, hsl(0 0% 100%)) 90% + ); + background-size: 200% 100%; +} + +html[data-theme='light'] .cs-search-pending__title::before, +html[data-theme='light'] .cs-search-pending__nested::before { + opacity: 0.45; +} + +.cs-search-empty-help a:hover { + color: var(--cs-cyan); +} + +[data-cs-pagefind-root] mark { + color: var(--sl-color-text-accent); + background: transparent; + font-weight: 600; +} + +/* Cold-open loading placeholder (Search.astro override) */ +.cs-search-loading { + display: grid; + gap: 0.85rem; + padding: 0.1rem 0 0.25rem; +} + +.cs-search-loading__field { + height: calc(64px * 0.92); + border-radius: 0.7rem; + border: 1px solid var(--sl-color-gray-5); + background: + linear-gradient( + 110deg, + color-mix(in srgb, var(--sl-color-black) 40%, transparent) 0%, + color-mix(in srgb, var(--sl-color-gray-5) 55%, transparent) 45%, + color-mix(in srgb, var(--sl-color-black) 40%, transparent) 90% + ); + background-size: 200% 100%; + animation: cs-search-shimmer 1.1s ease-in-out infinite; +} + +.cs-search-loading__label { + margin: 0; + color: var(--cs-muted); + font-size: 0.875rem; + font-weight: 500; + letter-spacing: -0.01em; +} + +.cs-search-loading--error .cs-search-loading__field { + animation: none; + opacity: 0.45; +} + +.cs-search-loading--error .cs-search-loading__label { + color: var(--sl-color-orange-high, var(--cs-ink)); +} + +@keyframes cs-search-shimmer { + 0% { + background-position: 100% 0; + } + 100% { + background-position: -100% 0; + } +} + +@media (prefers-reduced-motion: reduce) { + .cs-search-loading__field { + animation: none; + background: color-mix(in srgb, var(--sl-color-black) 45%, var(--sl-color-gray-5)); + } +} + +html[data-theme='light'] .cs-search-loading__field { + background: + linear-gradient( + 110deg, + #f3f4f8 0%, + #ffffff 45%, + #f3f4f8 90% + ); + background-size: 200% 100%; +} + +html[data-theme='light'] site-search dialog[open] { + background: var(--cs-search-modal-bg); +} + +html[data-theme='light'] site-search dialog::backdrop { + background: hsl(228 20% 20% / 0.35); +} + +html[data-theme='light'] .cs-search-idle__chips button { + background: var(--cs-search-item-bg); + color: var(--cs-ink); +} + +html[data-theme='light'] .cs-search-idle__chips button:hover, +html[data-theme='light'] .cs-search-idle__chips button:focus-visible, +html[data-theme='light'] .cs-search-idle__chips button[data-cs-search-selected='true'] { + background: var(--cs-search-hover-bg); + color: var(--cs-search-hover-fg); +} + +html[data-theme='light'] [data-cs-pagefind-root]:has(.pagefind-ui__results:not(:has(.pagefind-ui__result))) .pagefind-ui__message { + color: var(--cs-ink); +} + +/* Unlayered β€” beat Starlight’s current-page grey fill. */ +html[data-has-sidebar] #starlight__sidebar a[aria-current='page'] { + color: var(--docs-primary-fg); + background-color: var(--docs-primary-bg); + border-radius: 0.45rem; +} + +@layer starlight.components { + /* Sidebar: readable labels + clear active state */ + + html[data-has-sidebar] #starlight__sidebar a { + border-radius: 0.45rem; + } + + html[data-has-sidebar] #starlight__sidebar summary { + color: var(--sl-color-gray-1); + font-family: var(--sl-font); + font-weight: 600; + } +} + +/* Unlayered β€” beat any heading-font leakage into sidebar group labels */ +html[data-has-sidebar] #starlight__sidebar summary { + font-family: var(--sl-font); +} + +/* + * TOC β€” aligned links, calm spacing (unlayered; beats Starlight PageSidebar layers). + */ +html[data-has-sidebar] .right-sidebar-panel { + padding-block: 1.35rem 2rem; +} + +html[data-has-sidebar] starlight-toc h2 { + margin: 0 0 0.9rem; + padding-inline: 0.65rem; + font-family: var(--sl-font); + font-size: 0.75rem; + font-weight: 600; + letter-spacing: 0.06em; + text-transform: uppercase; + line-height: 1.3; + color: var(--sl-color-gray-3); +} + +html[data-has-sidebar] starlight-toc :is(ul, li) { + margin: 0; + padding: 0; + list-style: none; +} + +html[data-has-sidebar] starlight-toc a { + display: block; + padding: 0.4rem 0.5rem 0.4rem 0.65rem; + border-left: 2px solid transparent; + border-radius: 0 0.3rem 0.3rem 0; + font-family: var(--sl-font); + font-size: 0.8125rem; + font-weight: 400; + line-height: 1.45; + color: var(--sl-color-gray-2); + text-decoration: none; + overflow-wrap: anywhere; +} + +html[data-has-sidebar] starlight-toc ul ul a { + padding-left: 1.15rem; + font-size: 0.78rem; + color: var(--sl-color-gray-3); +} + +html[data-has-sidebar] starlight-toc a:hover { + color: var(--sl-color-gray-1); +} + +html[data-has-sidebar] starlight-toc a[aria-current='true'] { + color: var(--sl-color-text-accent); + border-left-color: var(--sl-color-accent); + background: color-mix(in srgb, var(--sl-color-accent) 12%, transparent); +} + +/* + * Code blocks β€” match ui-components Code: + * flat panel, no border, 0.5rem radius, --color-bg-dark1 panels (#191a20 / #e4e1ef). + * Avoid CSS filter on blocks β€” filter forces a compositor layer per block and janks scroll/nav. + */ +.expressive-code { + box-sizing: border-box; + width: 100%; + min-width: 0; + max-width: 100%; + font-size: 0.9rem; + contain: layout style; + --ec-codeFontFml: var(--sl-font-mono); + --ec-codeFontSize: 0.9rem; + --ec-codeLineHt: var(--sl-line-height); + --ec-codePadBlk: 0.6rem; + --ec-codePadInl: 0.75rem; + --ec-codeBg: var(--cs-code-bg); + --ec-brdRad: 0.5rem; + /* Length token β€” unitless 0 breaks EC copy-button calc(var(--ec-brdWd) + …) */ + --ec-brdWd: 0px; +} + +.expressive-code .frame { + margin: 0 !important; + padding: 0 !important; + border: 0 !important; + box-shadow: none !important; + background: transparent !important; + width: 100%; + min-width: 0; + max-width: 100%; + /* Keep copy button clear of padding edge */ + --button-spacing: 0.5rem !important; +} + +/* Hide empty editor chrome; keep file titles + terminal bars + copy */ +.expressive-code .frame:not(.has-title):not(.is-terminal) > .header { + display: none !important; +} + +/* + * Minimal window chrome (splash-style dots) + active file tab. + * Kill EC’s editor-tab / terminal-dot defaults β€” we draw our own. + * Dark: border matches code bg; frame shell transparent so the titlebar + * reads as open to the page. Light: filled panel + stronger edge. + */ +.expressive-code .frame.has-title, +.expressive-code .frame.has-title:not(.is-terminal), +.expressive-code .frame.is-terminal { + --cs-code-border: var(--docs-code-border); + --code-background: var(--cs-code-bg) !important; + /* Keep copy control inside the chrome bar (EC’s titled default pushes it into the code) */ + --button-spacing: 0.38rem !important; + position: relative; + background: transparent !important; + border: 1px solid var(--cs-code-border) !important; + border-radius: 0.55rem !important; + overflow: hidden; + box-shadow: none !important; + outline: none !important; +} + +html[data-theme='light'] .expressive-code .frame.has-title, +html[data-theme='light'] .expressive-code .frame.is-terminal { + --cs-code-border: var(--docs-code-border); + background: var(--cs-code-bg) !important; +} + +.expressive-code .frame.has-title > .header, +.expressive-code .frame.is-terminal > .header { + display: flex !important; + flex-direction: row !important; + justify-content: flex-start !important; + align-items: center !important; + gap: 0.55rem !important; + position: relative !important; + z-index: 1; + margin: 0 !important; + padding: 0.35rem 0.45rem 0.35rem 0.7rem !important; + border: 0 !important; + border-bottom: 1px solid var(--cs-code-border) !important; + border-radius: 0 !important; + color: inherit !important; + background: transparent !important; + box-shadow: none !important; + min-height: 2.25rem !important; + font-weight: inherit !important; + letter-spacing: normal !important; +} + +/* Light: code titlebar matches the frame border (--docs-code-border), not the site nav. */ +html[data-theme='light'] .expressive-code .frame.has-title > .header, +html[data-theme='light'] .expressive-code .frame.is-terminal > .header { + background: var(--docs-code-border) !important; +} + +/* Kill EC editor/terminal header overlays β€” we own the chrome */ +.expressive-code .frame.has-title > .header::before, +.expressive-code .frame.has-title > .header::after, +.expressive-code .frame.is-terminal > .header::before, +.expressive-code .frame.is-terminal > .header::after { + content: none !important; + display: none !important; + background: none !important; + -webkit-mask-image: none !important; + mask-image: none !important; + border: 0 !important; + opacity: 0 !important; + width: 0 !important; + height: 0 !important; +} + +/* + * Window dots β€” one mask, same on bash + TS. + * (EC’s terminal/editor frames used different chrome; this replaces both.) + */ +.expressive-code .cs-code-dots { + --cs-dots: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 40 10'%3E%3Ccircle cx='5' cy='5' r='4' fill='black'/%3E%3Ccircle cx='20' cy='5' r='4' fill='black'/%3E%3Ccircle cx='35' cy='5' r='4' fill='black'/%3E%3C/svg%3E"); + display: block; + flex: 0 0 auto; + align-self: center; + width: 1.85rem; + height: 0.45rem; + margin: 0; + background: hsl(228 10% 48%); + -webkit-mask: var(--cs-dots) center / contain no-repeat; + mask: var(--cs-dots) center / contain no-repeat; +} + +html[data-theme='light'] .expressive-code .cs-code-dots { + background: hsl(228 12% 42%); +} + +/* Active tab */ +.expressive-code .frame.has-title .title, +.expressive-code .frame.is-terminal .title { + position: static !important; + display: inline-flex !important; + align-items: center !important; + align-self: center !important; + gap: 0.35rem; + flex: 0 1 auto; + min-width: 0; + max-width: min(100%, 36rem); + margin: 0 !important; + padding: 0.28rem 0.6rem !important; + border: 0 !important; + border-radius: 0.35rem !important; + background: transparent !important; + box-shadow: none !important; + font-family: var(--sl-font-mono, ui-monospace, monospace) !important; + font-size: 0.74rem !important; + font-weight: 500 !important; + letter-spacing: 0.01em; + line-height: 1.25 !important; + color: var(--cs-muted) !important; + white-space: nowrap !important; + overflow: hidden !important; + text-overflow: ellipsis !important; +} + +.expressive-code .frame.is-terminal .title { + color: color-mix(in srgb, var(--cs-cyan) 75%, var(--ec-codeFg, #d4d8e4)) !important; +} + +html[data-theme='light'] .expressive-code .frame.has-title .title { + color: hsl(228, 14%, 30%) !important; +} + +html[data-theme='light'] .expressive-code .frame.is-terminal .title { + color: var(--docs-primary-bg) !important; +} + +.expressive-code .frame.has-title .title::before, +.expressive-code .frame.is-terminal .title::before, +.expressive-code .frame.has-title .title::after, +.expressive-code .frame.is-terminal .title::after { + content: none !important; + display: none !important; +} + +/* Path + filename share one flex item β€” no gap after "/" */ +.expressive-code .cs-code-name { + display: inline; + min-width: 0; + white-space: nowrap; +} + +/* Compact lang chip β€” same shape for TS/JS/JSON/…; color via data-kind */ +.expressive-code .frame.has-title .cs-code-kind { + flex: 0 0 auto; + display: inline-flex; + align-items: center; + justify-content: center; + box-sizing: border-box; + min-width: 1.15rem; + height: 1rem; + padding: 0 0.22rem; + border-radius: 0.18rem; + font-family: var(--sl-font-mono, ui-monospace, monospace); + font-size: 0.56rem; + font-weight: 700; + letter-spacing: 0.02em; + line-height: 1; + color: #fff; + background: #3178c6; /* TS / TSX default */ +} + +.expressive-code .frame.has-title .cs-code-kind[data-kind='JS'], +.expressive-code .frame.has-title .cs-code-kind[data-kind='JSX'] { + background: #c4a000; + color: #111; +} + +.expressive-code .frame.has-title .cs-code-kind[data-kind='JSON'] { + background: #8a8a2a; + color: #f5f5d0; +} + +.expressive-code .frame.has-title .cs-code-kind[data-kind='MD'] { + background: #555b6e; +} + +.expressive-code .frame.has-title .cs-code-kind[data-kind='CSS'] { + background: #2b6cb0; +} + +.expressive-code .frame.has-title .cs-code-kind[data-kind='PY'] { + background: #3776ab; +} + +.expressive-code .frame.has-title .cs-code-kind[data-kind='GO'] { + background: #007d9c; +} + +.expressive-code .frame.has-title .cs-code-kind[data-kind='RS'] { + background: #b7410e; +} + +.expressive-code .frame.has-title .cs-code-kind[data-kind='YML'] { + background: #6b7280; +} + +.expressive-code .cs-code-path { + color: color-mix(in srgb, var(--cs-muted) 65%, transparent); + font-weight: 400; +} + +html[data-theme='light'] .expressive-code .cs-code-path { + color: hsl(228, 10%, 42%); +} + +.expressive-code .cs-code-file { + color: var(--ec-codeFg, #d4d8e4); + font-weight: 600; +} + +html[data-theme='light'] .expressive-code .cs-code-file { + color: var(--cs-ink); +} + +.expressive-code .frame.has-title > pre, +.expressive-code .frame.is-terminal > pre { + border-radius: 0 !important; + /* Code body keeps panel fill; frame/header stay transparent in dark mode */ + background: var(--cs-code-bg) !important; +} + +/* + * Copy control: idle icon + β€œCopy”; copied green pill + check + β€œCopied!” for 1.5s. + * Kill EC’s floating tooltip β€” we own the feedback UI. + */ +.expressive-code .frame.has-title > .header > .copy, +.expressive-code .frame.is-terminal > .header > .copy { + position: static !important; + display: flex !important; + align-items: center !important; + justify-content: center !important; + align-self: center !important; + flex: 0 0 auto !important; + margin: 0 0 0 auto !important; + padding: 0 !important; + inset: auto !important; + z-index: 3; +} + +.expressive-code .frame.has-title > .header > .copy > [aria-live], +.expressive-code .frame.is-terminal > .header > .copy > [aria-live] { + display: none !important; +} + +.expressive-code .frame.has-title .copy button.cs-copy-btn, +.expressive-code .frame.is-terminal .copy button.cs-copy-btn, +.expressive-code .frame.has-title .copy button.cs-copy-btn:not(:hover), +.expressive-code .frame.is-terminal .copy button.cs-copy-btn:not(:hover), +.expressive-code .frame.has-title:not(:hover) .copy button.cs-copy-btn, +.expressive-code .frame.is-terminal:not(:hover) .copy button.cs-copy-btn { + opacity: 1 !important; + position: relative !important; + display: inline-flex !important; + align-items: center !important; + justify-content: center !important; + gap: 0.35rem !important; + flex: 0 0 auto !important; + width: auto !important; + min-width: max-content !important; + height: auto !important; + margin: 0 !important; + padding: 0.28rem 0.55rem !important; + border: 0 !important; + border-radius: 0.3rem !important; + background: transparent !important; + box-shadow: none !important; + outline: none !important; + color: color-mix(in srgb, var(--ec-codeFg, #d4d8e4) 72%, transparent) !important; + font-size: 0.75rem !important; + font-weight: 500 !important; + letter-spacing: 0.01em !important; + line-height: 1.2 !important; + white-space: nowrap !important; + overflow: visible !important; + cursor: pointer; + transition: background 120ms ease, color 120ms ease; +} + +.expressive-code .frame.has-title .copy button.cs-copy-btn > div, +.expressive-code .frame.is-terminal .copy button.cs-copy-btn > div { + display: none !important; +} + +.expressive-code .frame.has-title .copy button.cs-copy-btn::before, +.expressive-code .frame.is-terminal .copy button.cs-copy-btn::before, +.expressive-code .frame.has-title .copy button.cs-copy-btn::after, +.expressive-code .frame.is-terminal .copy button.cs-copy-btn::after { + content: none !important; + display: none !important; + background: none !important; + -webkit-mask-image: none !important; + mask-image: none !important; + width: 0 !important; + height: 0 !important; + margin: 0 !important; + padding: 0 !important; + border: 0 !important; + opacity: 0 !important; +} + +.expressive-code .frame.has-title .copy button.cs-copy-btn .cs-copy-icon, +.expressive-code .frame.is-terminal .copy button.cs-copy-btn .cs-copy-icon { + display: inline-flex; + align-items: center; + justify-content: center; + flex-shrink: 0; + width: 0.9rem; + height: 0.9rem; + overflow: visible; +} + +.expressive-code .frame.has-title .copy button.cs-copy-btn .cs-copy-glyph, +.expressive-code .frame.is-terminal .copy button.cs-copy-btn .cs-copy-glyph { + display: block; + width: 100%; + height: 100%; + overflow: visible; +} + +.expressive-code .frame.has-title .copy button.cs-copy-btn:hover, +.expressive-code .frame.is-terminal .copy button.cs-copy-btn:hover { + color: var(--ec-codeFg, #d4d8e4) !important; + background: color-mix(in srgb, var(--ec-codeFg, #d4d8e4) 8%, transparent) !important; +} + +html[data-theme='light'] .expressive-code .frame.has-title .copy button.cs-copy-btn, +html[data-theme='light'] .expressive-code .frame.is-terminal .copy button.cs-copy-btn, +html[data-theme='light'] .expressive-code .frame.has-title .copy button.cs-copy-btn:not(:hover), +html[data-theme='light'] .expressive-code .frame.is-terminal .copy button.cs-copy-btn:not(:hover), +html[data-theme='light'] .expressive-code .frame.has-title:not(:hover) .copy button.cs-copy-btn, +html[data-theme='light'] .expressive-code .frame.is-terminal:not(:hover) .copy button.cs-copy-btn { + /* Avoid translucent mixes β€” they read as washed-out on light panels */ + color: hsl(228, 14%, 30%) !important; +} + +html[data-theme='light'] .expressive-code .frame.has-title .copy button.cs-copy-btn:hover, +html[data-theme='light'] .expressive-code .frame.is-terminal .copy button.cs-copy-btn:hover { + color: var(--cs-ink) !important; + background: color-mix(in srgb, var(--cs-ink) 6%, transparent) !important; +} + +/* Success green wins over hover when copied */ +.expressive-code .frame.has-title .copy button.cs-copy-btn.cs-copied, +.expressive-code .frame.is-terminal .copy button.cs-copy-btn.cs-copied, +.expressive-code .frame.has-title .copy button.cs-copy-btn.cs-copied:hover, +.expressive-code .frame.is-terminal .copy button.cs-copy-btn.cs-copied:hover, +.expressive-code .frame.has-title .copy button.cs-copy-btn.cs-copied:focus, +.expressive-code .frame.is-terminal .copy button.cs-copy-btn.cs-copied:focus, +.expressive-code .frame.has-title .copy button.cs-copy-btn.cs-copied:active, +.expressive-code .frame.is-terminal .copy button.cs-copy-btn.cs-copied:active, +html[data-theme='light'] .expressive-code .frame.has-title .copy button.cs-copy-btn.cs-copied, +html[data-theme='light'] .expressive-code .frame.is-terminal .copy button.cs-copy-btn.cs-copied, +html[data-theme='light'] .expressive-code .frame.has-title .copy button.cs-copy-btn.cs-copied:hover, +html[data-theme='light'] .expressive-code .frame.is-terminal .copy button.cs-copy-btn.cs-copied:hover, +html[data-theme='light'] .expressive-code .frame.has-title .copy button.cs-copy-btn.cs-copied:focus, +html[data-theme='light'] .expressive-code .frame.is-terminal .copy button.cs-copy-btn.cs-copied:focus, +html[data-theme='light'] .expressive-code .frame.has-title .copy button.cs-copy-btn.cs-copied:active, +html[data-theme='light'] .expressive-code .frame.is-terminal .copy button.cs-copy-btn.cs-copied:active { + color: #000 !important; + background: #7bee7b !important; + box-shadow: none !important; +} + +.expressive-code pre { + margin: 0 !important; + padding: 0.6rem 0.75rem !important; + border: 0 !important; + border-radius: 0.5rem !important; + box-shadow: none !important; + overflow-x: auto !important; + overflow-y: hidden !important; + background: var(--cs-code-bg) !important; + box-sizing: border-box; + width: 100%; + min-width: 0; + max-width: 100%; +} + +.expressive-code pre code { + display: block; + min-width: min-content; + color: var(--ec-codeFg); +} + +html[data-theme='dark'] .expressive-code { + --ec-codeFg: #d4d8e4; +} + +html[data-theme='light'] .expressive-code { + --ec-codeFg: #24292e; +} + +.expressive-code .ec-line { + display: grid; + grid-template-columns: auto 1fr; +} + +.expressive-code .ec-line .code { + padding-inline: var(--ec-codePadInl, 0.75rem); + white-space: pre; +} + +/* + * Guide layout β€” full-width main column, article centered from 72rem. + * Right TOC column joins at 90rem (see TOC delay rules above). + */ +@media (min-width: 72rem) { + html[data-has-sidebar][data-has-toc] .lg\:sl-flex { + width: 100%; + } + + html[data-has-sidebar][data-has-toc] .main-pane { + flex: 1 1 auto; + width: auto !important; + min-width: 0; + max-width: none; + } + + html[data-has-sidebar] .main-pane .content-panel .sl-container { + margin-inline: auto; + } +} + +@media (min-width: 90rem) { + html[data-has-sidebar][data-has-toc] .right-sidebar-container { + flex: 0 0 var(--sl-sidebar-width); + width: var(--sl-sidebar-width) !important; + overflow: hidden; + } + + html[data-has-sidebar][data-has-toc] .right-sidebar { + position: fixed; + inset-inline: auto 0; + width: var(--sl-sidebar-width); + height: 100vh; + padding-top: var(--sl-nav-height); + border-inline-start: 1px solid var(--sl-color-hairline); + overflow-y: auto; + } + + html[data-has-sidebar] .right-sidebar-panel .sl-container { + max-width: none; + width: 100%; + } +} + +/* Prev/next page navigation */ +html[data-has-sidebar] .pagination-links { + margin-top: 3.25rem; + padding-top: 2rem; + border-top: 1px solid var(--cs-line); + gap: 0.85rem; + /* Match explorer β€” Starlight’s 18rem auto-fit stacks in the article column. */ + grid-template-columns: repeat(2, minmax(0, 1fr)); +} + +html[data-has-sidebar] .pagination-links > :only-child { + grid-column: 1 / -1; +} + +html[data-has-sidebar] .pagination-links a { + align-items: center; + gap: 0.85rem; + border: none; + border-radius: calc(var(--cs-radius) - 0.15rem); + padding: 1rem 1.15rem; + box-shadow: none; + background: var(--cs-table-bg); + color: var(--cs-muted); + transition: + background 0.15s ease, + color 0.15s ease; +} + +html[data-has-sidebar] .pagination-links a:hover { + background: var(--cs-pagination-hover-bg); + color: var(--cs-muted); +} + +html[data-theme='light'][data-has-sidebar] .pagination-links a { + background: var(--docs-code-bg); +} + +html[data-theme='light'][data-has-sidebar] .pagination-links a:hover { + background: var(--cs-pagination-hover-bg); +} + +html[data-has-sidebar] .pagination-links a > span { + font-size: 0.66rem; + font-weight: 600; + letter-spacing: 0.08em; + line-height: 1.35; + text-transform: uppercase; + transition: color 0.15s ease; +} + +html[data-has-sidebar] .pagination-links .link-title { + color: var(--cs-ink); + font-family: var(--cs-font-heading); + font-size: 0.95rem; + font-weight: 600; + letter-spacing: -0.015em; + line-height: 1.3; + text-transform: none; + transition: color 0.15s ease; +} + +html[data-has-sidebar] .pagination-links svg { + width: 1.15rem; + height: 1.15rem; + color: var(--cs-muted); + opacity: 0.9; + transition: + color 0.15s ease, + opacity 0.15s ease, + transform 0.15s ease; +} + +html[data-has-sidebar] .pagination-links a:hover svg { + color: var(--sl-color-accent); + opacity: 1; +} + +html[data-has-sidebar] .pagination-links a[rel='next']:hover svg { + transform: translateX(0.2rem); +} + +html[data-has-sidebar] .pagination-links a[rel='prev']:hover svg { + transform: translateX(-0.2rem); +} + +@media (prefers-reduced-motion: reduce) { + html[data-has-sidebar] .pagination-links a:hover svg { + transform: none; + } +} + +/* + * Match explorer `.sidebar-nav`: pull content in by link pad so β€œHome” text + * aligns with brand/search; caret flush on the right of group rows. + */ +html[data-has-sidebar] .sidebar-content { + padding: 0.65rem calc(var(--sl-sidebar-pad-x) - var(--docs-sidebar-link-pad-inline)) 2.5rem; +} + +html[data-has-sidebar] #starlight__sidebar .top-level > li + li { + margin-top: 0.45rem; +} + +html[data-has-sidebar] #starlight__sidebar a { + padding-block: 0.35em; + padding-inline: var(--docs-sidebar-link-pad-inline); + /* Nested + top-level links: grey; hover black (beats SidebarSublist .large). */ + color: var(--cs-muted); +} + +/* Nested pages under a category β€” roomier line box (matches explorer). */ +html[data-has-sidebar] #starlight__sidebar ul ul a { + line-height: var(--docs-sidebar-subitem-line-height); +} + +html[data-has-sidebar] #starlight__sidebar a .large { + color: inherit; + font-weight: 600; +} + +html[data-has-sidebar] #starlight__sidebar a:hover, +html[data-has-sidebar] #starlight__sidebar a:focus-visible { + color: var(--docs-sidebar-hover); +} + +html[data-has-sidebar] #starlight__sidebar summary:hover { + color: var(--docs-sidebar-hover); +} + +html[data-has-sidebar] #starlight__sidebar a[aria-current='page'], +html[data-has-sidebar] #starlight__sidebar a[aria-current='page']:hover, +html[data-has-sidebar] #starlight__sidebar a[aria-current='page']:focus-visible { + color: var(--docs-primary-fg); + background-color: var(--docs-primary-bg); +} + +html[data-has-sidebar] #starlight__sidebar summary { + padding-block: 0.35em; + /* Left pad for label; caret flush to the row’s right edge. */ + padding-inline: var(--docs-sidebar-link-pad-inline) 0; + color: var(--cs-ink); +} + +html[data-has-sidebar] #starlight__sidebar summary .large { + color: inherit; +} + +/* Caret stays muted until hover (don’t inherit category ink). */ +html[data-has-sidebar] #starlight__sidebar summary .caret { + color: var(--docs-text-tertiary); +} + +html[data-has-sidebar] #starlight__sidebar summary:hover .caret { + color: var(--docs-sidebar-hover); +} + +/* Drop Starlight nested left hairline (SidebarSublist `ul ul li`). */ +html[data-has-sidebar] #starlight__sidebar ul ul li { + border-inline-start: none; +} + +/* Demo UI screenshot β€” inverted by docs/splash theme for contrast. */ +.demo-screenshot { + margin: 1.25rem 0; +} + +.demo-screenshot__link { + display: block; +} + +.demo-screenshot__img { + display: block; + width: 100%; + height: auto; + border-radius: 0.5rem; + border: 1px solid var(--docs-border); +} + +.demo-screenshot__img--for-dark { + display: none; +} + +html[data-theme='dark'] .demo-screenshot__img--for-light { + display: none; +} + +html[data-theme='dark'] .demo-screenshot__img--for-dark { + display: block; +} + diff --git a/src/timeline.spec.ts b/src/timeline.spec.ts new file mode 100644 index 0000000..709cb2b --- /dev/null +++ b/src/timeline.spec.ts @@ -0,0 +1,134 @@ +import {test} from 'kizu'; +import { + compile, + countdownText, + easeInOutCubic, + isOn, + moveIndexAt, + positionAt, + typedText, +} from './timeline'; + +test('compile: beats run back to back, so nothing has to be timed by hand', (assert) => { + + const {moves, duration} = compile([ + {click: '#a', wait: 100, moveFor: 200, dwell: 50}, + {click: '#b', wait: 300, moveFor: 400, dwell: 60}, + ]); + + // A beat ends when its click lands, PRESS_MS after the cursor goes down. + assert.equal(moves[0], {to: '#a', from: 100, until: 300, press: 350}); + assert.equal(moves[1], {to: '#b', from: 850, until: 1250, press: 1310}); + assert.equal(duration, 1810); + +}); + +test('compile: a step with no wait sets off the moment the last one finished', (assert) => { + + const {moves} = compile([ + {click: '#a', moveFor: 100, dwell: 10}, + {click: '#b', moveFor: 100, dwell: 10}, + ]); + + assert.equal(moves[0].press, 110); + assert.equal(moves[1].from, 310); + +}); + +test('compile: a loop that ends on a press runs on until the ring has read', (assert) => { + + const {duration} = compile([{click: '#a', moveFor: 100, dwell: 0}]); + + // Press at 100, click at 300, ring done at 600. Ending on the click would + // wipe it β€” and reset the loop before the click had a frame to fire. + assert.equal(duration, 600); + +}); + +test('compile: a drift has nothing to press', (assert) => { + + const {moves, duration} = compile([{to: [0.5, 0.5], wait: 100, moveFor: 200}]); + + assert.equal(moves[0].press, undefined); + assert.equal(duration, 300); + +}); + +test('easeInOutCubic: still at both ends, halfway at halfway', (assert) => { + + assert.equal(easeInOutCubic(0), 0); + assert.equal(easeInOutCubic(0.5), 0.5); + assert.equal(easeInOutCubic(1), 1); + +}); + +test('moveIndexAt: nothing before the first beat, then the latest one to have started', (assert) => { + + const moves = [ + {to: '#a', from: 100, until: 200}, + {to: '#b', from: 300, until: 400}, + ]; + + assert.equal(moveIndexAt(moves, 50), -1); + assert.equal(moveIndexAt(moves, 150), 0); + assert.equal(moveIndexAt(moves, 250), 0); + assert.equal(moveIndexAt(moves, 1000), 1); + +}); + +test('positionAt: parks on the target once it has arrived', (assert) => { + + const move = {to: '#a', from: 0, until: 100}; + + assert.equal(positionAt([0, 0], [10, 20], move, 100), [10, 20]); + assert.equal(positionAt([0, 0], [10, 20], null, 100), [10, 20]); + +}); + +test('positionAt: halfway through the glide is halfway there', (assert) => { + + assert.equal(positionAt([0, 0], [10, 20], {to: '#a', from: 0, until: 100}, 50), [5, 10]); + +}); + +test('isOn: holds from the start, lets go at the end', (assert) => { + + const toggle = {target: '#a', class: 'is-open', from: 100, until: 200}; + + assert.equal(isOn(toggle, 99), false); + assert.equal(isOn(toggle, 100), true); + assert.equal(isOn(toggle, 199), true); + assert.equal(isOn(toggle, 200), false); + +}); + +test('typedText: nothing, then some of it, then all of it', (assert) => { + + const typing = {target: '#a', text: 'hello', from: 0, until: 100}; + + assert.equal(typedText(typing, -1), ''); + assert.equal(typedText(typing, 50), 'h'); + assert.equal(typedText(typing, 100), 'hello'); + assert.equal(typedText(typing, 5000), 'hello'); + +}); + +test('typedText: clearAt wipes it, e.g. the message was sent', (assert) => { + + const typing = {target: '#a', text: 'hello', from: 0, until: 100, clearAt: 200}; + + assert.equal(typedText(typing, 199), 'hello'); + assert.equal(typedText(typing, 200), ''); + +}); + +test('countdownText: m:ss, zero padded, and it stops at zero', (assert) => { + + const countdown = {target: '#a', seconds: 125}; + + assert.equal(countdownText(countdown, 0), '2:05'); + assert.equal(countdownText(countdown, 60_000), '1:05'); + assert.equal(countdownText(countdown, 120_000), '0:05'); + assert.equal(countdownText(countdown, 999_000), '0:00'); + +}); diff --git a/src/timeline.ts b/src/timeline.ts new file mode 100644 index 0000000..cc77152 --- /dev/null +++ b/src/timeline.ts @@ -0,0 +1,80 @@ +import type {Countdown, Move, Point, Step, Toggle, Typing} from './types'; + +/** How long a glide takes when a step does not say. */ +const DEFAULT_MOVE_MS = 600; +/** How long the cursor hovers before pressing when a step does not say. */ +const DEFAULT_DWELL_MS = 250; +/** How long the cursor stays squashed after a press. */ +export const PRESS_MS = 200; +/** How long the ripple ring lingers. Outlives the press so the click reads. */ +export const RING_MS = 500; + +export function easeInOutCubic(t: number): number { + return t < 0.5 ? 4 * t * t * t : 1 - Math.pow(-2 * t + 2, 3) / 2; +} + +/** + * Lay a routine out on a timeline, back to back. A step's press and the page + * change it causes are one event, so there is nothing to keep in sync by hand. + */ +export function compile(steps: Step[]): {moves: Move[]; duration: number} { + let t = 0; + + const moves = steps.map((step): Move => { + const presses = 'click' in step; + const from = t + (step.wait ?? 0); + const until = from + (step.moveFor ?? DEFAULT_MOVE_MS); + const press = presses ? until + (step.dwell ?? DEFAULT_DWELL_MS) : undefined; + + // A beat runs to the click, which lands as the press lifts β€” not to the + // press itself. Otherwise the next beat starts mid-stroke. + t = press === undefined ? until : press + PRESS_MS; + + return {to: presses ? step.click : step.to, from, until, press}; + }); + + const last = moves[moves.length - 1]; + + // A press needs its ring to finish before the loop wipes the scene. Between + // beats the next one's wait covers that; the last beat has no next one, and + // ending on the click would reset the loop before the click could fire. + const duration = last?.press === undefined ? t : Math.max(t, last.press + RING_MS); + + return {moves, duration}; +} + +/** Index of the move the cursor is on at `t`, or -1 before the first one starts. */ +export function moveIndexAt(moves: Move[], t: number): number { + return moves.findLastIndex((move) => t >= move.from); +} + +/** Where the cursor sits at `t`: mid-glide between `from` and `to`, or parked on `to`. */ +export function positionAt(from: Point, to: Point, move: Move | null, t: number): Point { + if (!move || t >= move.until) return to; + + const p = easeInOutCubic((t - move.from) / (move.until - move.from)); + + return [from[0] + (to[0] - from[0]) * p, from[1] + (to[1] - from[1]) * p]; +} + +export function isOn(toggle: Toggle, t: number): boolean { + return t >= toggle.from && t < toggle.until; +} + +/** How much of the string has been written at `t`. Starts slow, speeds up. */ +export function typedText(typing: Typing, t: number): string { + if (t < typing.from) return ''; + if (typing.clearAt !== undefined && t >= typing.clearAt) return ''; + if (t >= typing.until) return typing.text; + + const p = (t - typing.from) / (typing.until - typing.from); + + return typing.text.slice(0, Math.floor(p * p * typing.text.length)); +} + +/** The clock at `t`, as `m:ss`. Stops at zero rather than going negative. */ +export function countdownText(countdown: Countdown, t: number): string { + const left = Math.max(0, countdown.seconds - Math.floor(t / 1000)); + + return `${Math.floor(left / 60)}:${String(left % 60).padStart(2, '0')}`; +} diff --git a/src/types.ts b/src/types.ts new file mode 100644 index 0000000..30db2a2 --- /dev/null +++ b/src/types.ts @@ -0,0 +1,123 @@ +/** A spot in the root element, as a fraction of its size: `[0.5, 0.5]` is the middle. */ +export type Point = [number, number]; + +/** + * One beat of a routine. Either press something, or drift somewhere without + * pressing. Both are laid out back to back, so a routine reads top to bottom + * in the order a visitor would see it. + */ +export type Step = + | { + /** Selector of the element to press. It really gets clicked. */ + click: string; + /** Pause before the cursor sets off β€” time to read whatever just opened. */ + wait?: number; + /** How long the glide takes. Default 600. */ + moveFor?: number; + /** How long the cursor hovers before pressing. Default 250. */ + dwell?: number; + } + | { + /** Where to glide, with no press at the end. */ + to: string | Point; + wait?: number; + moveFor?: number; + }; + +/** A click on `click` shows scene `scene`. */ +export interface Route { + /** Selector of the clickable element. */ + click: string; + /** `data-scene` value to show. */ + scene: string; +} + +/** A cursor glide on a hand-set timeline. */ +export interface Move { + /** Where to glide. */ + to: string | Point; + /** When the glide starts. */ + from: number; + /** When the cursor arrives. */ + until: number; + /** + * Optional moment the cursor goes down. In a click-driven show the real + * click lands as the stroke lifts, a press later. Timeline moves never + * really click, so for them this is the animation and nothing more. + */ + press?: number; +} + +/** A class held on an element for a slice of the loop. */ +export interface Toggle { + /** Selector of the element. */ + target: string; + /** Class held while the loop is inside `[from, until)`. */ + class: string; + from: number; + until: number; +} + +/** Text that types itself out. */ +export interface Typing { + /** Selector of the element whose text content is written. */ + target: string; + text: string; + /** Typing starts; the whole string is shown at `until`. */ + from: number; + until: number; + /** Optional moment the text is wiped, e.g. the message was sent. */ + clearAt?: number; +} + +/** A `m:ss` clock ticking down over the loop. */ +export interface Countdown { + /** Selector of the element whose text content is written. */ + target: string; + /** Value at the top of the loop. */ + seconds: number; +} + +/** + * A routine. Give it `steps` for a click-driven show, or `duration` plus any of + * `moves` / `toggles` / `typing` / `countdowns` for a hand-timed one. + */ +export interface Routine { + /** Scene shown at the top of every loop. */ + scene?: string; + /** Where the cursor rests before the first beat. Default `[0.5, 0.5]`. */ + start?: Point; + /** Click-driven show. The loop length is derived from these. */ + steps?: Step[]; + /** How clicks β€” the cursor's and the visitor's β€” change the scene. */ + routes?: Route[]; + /** Loop length in ms. Required unless `steps` is given. */ + duration?: number; + moves?: Move[]; + toggles?: Toggle[]; + typing?: Typing[]; + countdowns?: Countdown[]; + /** + * How much of the root must be on screen for the show to run, as a fraction + * of its size. Default 1 β€” the whole thing. + */ + visibility?: number; + /** Frame to hold under `prefers-reduced-motion`. Default 0. */ + freezeAt?: number; +} + +export interface Busker { + /** Loop length in ms. */ + readonly duration: number; + /** Start (or resume) the show. */ + play(): void; + /** Hold the show where it is. */ + pause(): void; + /** + * Hand the mock over: stop the show for good and hide the cursor so the + * visitor can click around. Happens by itself when they click. + */ + stepAside(): void; + /** Stop everything and put the DOM back the way it was found. */ + destroy(): void; +} diff --git a/tsconfig.astro.json b/tsconfig.astro.json new file mode 100644 index 0000000..20f8c4e --- /dev/null +++ b/tsconfig.astro.json @@ -0,0 +1,13 @@ +{ + "extends": "astro/tsconfigs/strict", + "include": [ + "src/env.d.ts", + ".astro/types.d.ts", + "src/content.config.ts", + "src/components/**/*.astro", + "src/components/**/*.ts", + "src/overrides/**/*.astro", + "src/pages/**/*.astro" + ], + "exclude": ["dist", "node_modules", "docs-site"] +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..ef29141 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,30 @@ +{ + "compilerOptions": { + "target": "ES2023", + "moduleResolution": "node16", + "module": "Node16", + "declaration": true, + "lib": ["ES2023", "DOM"], + "outDir": "dist", + "rootDir": "src", + "strict": true, + "noImplicitAny": true, + "esModuleInterop": true, + "skipLibCheck": true + }, + "include": ["src/**/*.ts"], + "exclude": [ + "node_modules", + "dist", + "src/components/**", + "src/overrides/**", + "src/pages/**", + "src/cs-pagefind/**", + "src/content/**", + "src/content.config.ts", + "src/integrations/**", + "src/styles/**", + "src/**/*.spec.ts", + "src/env.d.ts" + ] +}