From 12292415ab8b8c091e91e0234941395debf71e5f Mon Sep 17 00:00:00 2001 From: Ryan Malloy Date: Mon, 16 Feb 2026 03:12:41 -0700 Subject: [PATCH] Starlight docs site for pg_orbit v0.2.0 34 MDX pages covering all 57 functions across 7 domains: satellites (SGP4/SDP4), planets (VSOP87), Moon (ELP2000-82B), 19 planetary moons (L1.2/TASS17/GUST86/MarsSat), stars, comets, Jupiter radio bursts, and Lambert transfers. Site structure: - Getting Started: overview, installation, 5-query quick start - Guides: 8 domain-specific walkthroughs with workflow translation - Workflow Translation: side-by-side comparisons with Skyfield, JPL Horizons, GMAT, Radio Jupiter Pro, plus SQL patterns - Reference: all types, functions, operators, body IDs, constants - Architecture: Hamilton's principles, constant chain of custody, observation pipeline, theory-to-code mapping, thread safety - Performance: verified benchmarks with reproduction methodology Stack: Astro 5.17 + Starlight 0.37.6, KaTeX math, Mermaid diagrams, Pagefind search, Caddy production Docker image. --- .gitignore | 5 + docs/Dockerfile | 61 + docs/astro.config.mjs | 121 + docs/package-lock.json | 9509 +++++++++++++++++ docs/package.json | 27 + docs/public/favicon.svg | 6 + docs/src/assets/pg-orbit-logo.svg | 12 + docs/src/content.config.ts | 6 + .../constant-chain-of-custody.mdx | 143 + .../docs/architecture/design-principles.mdx | 181 + .../architecture/memory-thread-safety.mdx | 186 + .../architecture/observation-pipeline.mdx | 191 + .../docs/architecture/sgp4-integration.mdx | 218 + .../docs/architecture/theory-to-code.mdx | 161 + .../docs/getting-started/installation.mdx | 116 + .../docs/getting-started/quick-start.mdx | 115 + .../docs/getting-started/what-is-pg-orbit.mdx | 63 + .../content/docs/guides/comets-asteroids.mdx | 269 + .../docs/guides/conjunction-screening.mdx | 290 + .../guides/interplanetary-trajectories.mdx | 271 + .../docs/guides/jupiter-radio-bursts.mdx | 279 + .../docs/guides/observing-solar-system.mdx | 260 + .../content/docs/guides/planetary-moons.mdx | 282 + .../src/content/docs/guides/star-catalogs.mdx | 256 + .../docs/guides/tracking-satellites.mdx | 324 + docs/src/content/docs/index.mdx | 67 + .../content/docs/performance/benchmarks.mdx | 268 + docs/src/content/docs/reference/body-ids.mdx | 170 + .../docs/reference/constants-accuracy.mdx | 198 + .../docs/reference/functions-moons.mdx | 256 + .../docs/reference/functions-radio.mdx | 193 + .../docs/reference/functions-satellite.mdx | 529 + .../docs/reference/functions-solar-system.mdx | 228 + .../docs/reference/functions-stars-comets.mdx | 257 + .../docs/reference/functions-transfers.mdx | 152 + .../content/docs/reference/operators-gist.mdx | 190 + docs/src/content/docs/reference/types.mdx | 269 + docs/src/content/docs/workflow/from-gmat.mdx | 281 + .../docs/workflow/from-jpl-horizons.mdx | 339 + .../docs/workflow/from-radio-jupiter-pro.mdx | 275 + .../content/docs/workflow/from-skyfield.mdx | 310 + .../content/docs/workflow/sql-advantage.mdx | 473 + docs/src/styles/custom.css | 129 + docs/src/styles/katex-fixes.css | 48 + docs/tsconfig.json | 3 + 45 files changed, 17987 insertions(+) create mode 100644 docs/Dockerfile create mode 100644 docs/astro.config.mjs create mode 100644 docs/package-lock.json create mode 100644 docs/package.json create mode 100644 docs/public/favicon.svg create mode 100644 docs/src/assets/pg-orbit-logo.svg create mode 100644 docs/src/content.config.ts create mode 100644 docs/src/content/docs/architecture/constant-chain-of-custody.mdx create mode 100644 docs/src/content/docs/architecture/design-principles.mdx create mode 100644 docs/src/content/docs/architecture/memory-thread-safety.mdx create mode 100644 docs/src/content/docs/architecture/observation-pipeline.mdx create mode 100644 docs/src/content/docs/architecture/sgp4-integration.mdx create mode 100644 docs/src/content/docs/architecture/theory-to-code.mdx create mode 100644 docs/src/content/docs/getting-started/installation.mdx create mode 100644 docs/src/content/docs/getting-started/quick-start.mdx create mode 100644 docs/src/content/docs/getting-started/what-is-pg-orbit.mdx create mode 100644 docs/src/content/docs/guides/comets-asteroids.mdx create mode 100644 docs/src/content/docs/guides/conjunction-screening.mdx create mode 100644 docs/src/content/docs/guides/interplanetary-trajectories.mdx create mode 100644 docs/src/content/docs/guides/jupiter-radio-bursts.mdx create mode 100644 docs/src/content/docs/guides/observing-solar-system.mdx create mode 100644 docs/src/content/docs/guides/planetary-moons.mdx create mode 100644 docs/src/content/docs/guides/star-catalogs.mdx create mode 100644 docs/src/content/docs/guides/tracking-satellites.mdx create mode 100644 docs/src/content/docs/index.mdx create mode 100644 docs/src/content/docs/performance/benchmarks.mdx create mode 100644 docs/src/content/docs/reference/body-ids.mdx create mode 100644 docs/src/content/docs/reference/constants-accuracy.mdx create mode 100644 docs/src/content/docs/reference/functions-moons.mdx create mode 100644 docs/src/content/docs/reference/functions-radio.mdx create mode 100644 docs/src/content/docs/reference/functions-satellite.mdx create mode 100644 docs/src/content/docs/reference/functions-solar-system.mdx create mode 100644 docs/src/content/docs/reference/functions-stars-comets.mdx create mode 100644 docs/src/content/docs/reference/functions-transfers.mdx create mode 100644 docs/src/content/docs/reference/operators-gist.mdx create mode 100644 docs/src/content/docs/reference/types.mdx create mode 100644 docs/src/content/docs/workflow/from-gmat.mdx create mode 100644 docs/src/content/docs/workflow/from-jpl-horizons.mdx create mode 100644 docs/src/content/docs/workflow/from-radio-jupiter-pro.mdx create mode 100644 docs/src/content/docs/workflow/from-skyfield.mdx create mode 100644 docs/src/content/docs/workflow/sql-advantage.mdx create mode 100644 docs/src/styles/custom.css create mode 100644 docs/src/styles/katex-fixes.css create mode 100644 docs/tsconfig.json diff --git a/.gitignore b/.gitignore index 5335aab..6171108 100644 --- a/.gitignore +++ b/.gitignore @@ -17,3 +17,8 @@ log/ *~ .vscode/ .idea/ + +# Docs site +docs/node_modules/ +docs/dist/ +docs/.astro/ diff --git a/docs/Dockerfile b/docs/Dockerfile new file mode 100644 index 0000000..476fd73 --- /dev/null +++ b/docs/Dockerfile @@ -0,0 +1,61 @@ +ARG NODE_VERSION=22 + +# ── Stage 1: Build ────────────────────────────────────────────── +FROM node:${NODE_VERSION}-slim AS build + +WORKDIR /app + +# Install dependencies first (cache layer) +COPY package.json package-lock.json ./ +RUN --mount=type=cache,target=/root/.npm \ + npm ci + +# Copy source and build +COPY . . +ENV ASTRO_TELEMETRY_DISABLED=1 +RUN npm run build + +# ── Stage 2: Production ──────────────────────────────────────── +FROM caddy:2-alpine AS production + +COPY --from=build /app/dist /srv +COPY <<'CADDYFILE' /etc/caddy/Caddyfile +:3000 { + root * /srv + file_server + try_files {path} {path}/ /404.html + encode gzip + + header { + X-Content-Type-Options nosniff + X-Frame-Options DENY + Referrer-Policy strict-origin-when-cross-origin + } + + header /docs/_astro/* { + Cache-Control "public, max-age=31536000, immutable" + } +} +CADDYFILE + +EXPOSE 3000 + +HEALTHCHECK --interval=30s --timeout=3s --start-period=5s \ + CMD wget -qO- http://127.0.0.1:3000/ || exit 1 + +# ── Stage 3: Development ─────────────────────────────────────── +FROM node:${NODE_VERSION}-slim AS development + +WORKDIR /app + +COPY package.json package-lock.json ./ +RUN --mount=type=cache,target=/root/.npm \ + npm ci + +COPY . . + +ENV ASTRO_TELEMETRY_DISABLED=1 + +EXPOSE 3000 + +CMD ["npm", "run", "dev"] diff --git a/docs/astro.config.mjs b/docs/astro.config.mjs new file mode 100644 index 0000000..39820b9 --- /dev/null +++ b/docs/astro.config.mjs @@ -0,0 +1,121 @@ +import { defineConfig } from "astro/config"; +import starlight from "@astrojs/starlight"; +import tailwindcss from "@tailwindcss/vite"; +import remarkMath from "remark-math"; +import rehypeKatex from "rehype-katex"; +import mermaid from "astro-mermaid"; + +export default defineConfig({ + site: "https://pg-orbit.supported.systems", + integrations: [ + mermaid(), + starlight({ + title: "pg_orbit", + description: + "Solar system computation for PostgreSQL", + favicon: "/favicon.svg", + logo: { + src: "./src/assets/pg-orbit-logo.svg", + replacesTitle: true, + }, + social: [ + { + icon: "github", + label: "Gitea", + href: "https://git.supported.systems/warehack.ing/pg_orbit", + }, + ], + customCss: [ + "./src/styles/custom.css", + "./src/styles/katex-fixes.css", + "katex/dist/katex.min.css", + ], + head: [ + { + tag: "meta", + attrs: { + name: "theme-color", + content: "#0a0e17", + }, + }, + ], + sidebar: [ + { + label: "Getting Started", + items: [ + { label: "What is pg_orbit?", slug: "getting-started/what-is-pg-orbit" }, + { label: "Installation", slug: "getting-started/installation" }, + { label: "Quick Start", slug: "getting-started/quick-start" }, + ], + }, + { + label: "Guides", + items: [ + { label: "Tracking Satellites", slug: "guides/tracking-satellites" }, + { label: "Observing the Solar System", slug: "guides/observing-solar-system" }, + { label: "Planetary Moon Tracking", slug: "guides/planetary-moons" }, + { label: "Star Catalogs in SQL", slug: "guides/star-catalogs" }, + { label: "Comet & Asteroid Tracking", slug: "guides/comets-asteroids" }, + { label: "Jupiter Radio Burst Prediction", slug: "guides/jupiter-radio-bursts" }, + { label: "Interplanetary Trajectories", slug: "guides/interplanetary-trajectories" }, + { label: "Conjunction Screening", slug: "guides/conjunction-screening" }, + ], + }, + { + label: "Workflow Translation", + items: [ + { label: "From Skyfield to SQL", slug: "workflow/from-skyfield" }, + { label: "From JPL Horizons to SQL", slug: "workflow/from-jpl-horizons" }, + { label: "From GMAT to SQL", slug: "workflow/from-gmat" }, + { label: "From Radio Jupiter Pro to SQL", slug: "workflow/from-radio-jupiter-pro" }, + { label: "The SQL Advantage", slug: "workflow/sql-advantage" }, + ], + }, + { + label: "Reference", + items: [ + { label: "Types", slug: "reference/types" }, + { label: "Functions: Satellite", slug: "reference/functions-satellite" }, + { label: "Functions: Solar System", slug: "reference/functions-solar-system" }, + { label: "Functions: Moons", slug: "reference/functions-moons" }, + { label: "Functions: Stars & Comets", slug: "reference/functions-stars-comets" }, + { label: "Functions: Radio", slug: "reference/functions-radio" }, + { label: "Functions: Transfers", slug: "reference/functions-transfers" }, + { label: "Operators & GiST Index", slug: "reference/operators-gist" }, + { label: "Body ID Reference", slug: "reference/body-ids" }, + { label: "Constants & Accuracy", slug: "reference/constants-accuracy" }, + ], + }, + { + label: "Architecture", + items: [ + { label: "Design Principles", slug: "architecture/design-principles" }, + { label: "Constant Chain of Custody", slug: "architecture/constant-chain-of-custody" }, + { label: "Observation Pipeline", slug: "architecture/observation-pipeline" }, + { label: "Theory-to-Code Mapping", slug: "architecture/theory-to-code" }, + { label: "Memory & Thread Safety", slug: "architecture/memory-thread-safety" }, + { label: "SGP4 Integration", slug: "architecture/sgp4-integration" }, + ], + }, + { + label: "Performance", + items: [ + { label: "Benchmarks", slug: "performance/benchmarks" }, + ], + }, + ], + }), + ], + + markdown: { + remarkPlugins: [remarkMath], + rehypePlugins: [rehypeKatex], + }, + + vite: { + plugins: [tailwindcss()], + }, + + telemetry: false, + devToolbar: { enabled: false }, +}); diff --git a/docs/package-lock.json b/docs/package-lock.json new file mode 100644 index 0000000..ad74e9d --- /dev/null +++ b/docs/package-lock.json @@ -0,0 +1,9509 @@ +{ + "name": "pg-orbit-docs", + "version": "2026.02.16", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "pg-orbit-docs", + "version": "2026.02.16", + "dependencies": { + "@astrojs/starlight": "^0.37.6", + "@fontsource/inter": "^5.0.0", + "@fontsource/jetbrains-mono": "^5.0.0", + "astro": "^5.17.2", + "astro-mermaid": "^1.3.1", + "katex": "^0.16.28", + "rehype-katex": "^7.0.1", + "remark-math": "^6.0.0", + "sharp": "^0.33.0" + }, + "devDependencies": { + "@tailwindcss/vite": "^4.0.0", + "tailwindcss": "^4.0.0", + "typescript": "^5.7.0" + } + }, + "node_modules/@antfu/install-pkg": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@antfu/install-pkg/-/install-pkg-1.1.0.tgz", + "integrity": "sha512-MGQsmw10ZyI+EJo45CdSER4zEb+p31LpDAFp2Z3gkSd1yqVZGi0Ebx++YTEMonJy4oChEMLsxZ64j8FH6sSqtQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "package-manager-detector": "^1.3.0", + "tinyexec": "^1.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@astrojs/compiler": { + "version": "2.13.1", + "resolved": "https://registry.npmjs.org/@astrojs/compiler/-/compiler-2.13.1.tgz", + "integrity": "sha512-f3FN83d2G/v32ipNClRKgYv30onQlMZX1vCeZMjPsMMPl1mDpmbl0+N5BYo4S/ofzqJyS5hvwacEo0CCVDn/Qg==", + "license": "MIT" + }, + "node_modules/@astrojs/internal-helpers": { + "version": "0.7.5", + "resolved": "https://registry.npmjs.org/@astrojs/internal-helpers/-/internal-helpers-0.7.5.tgz", + "integrity": "sha512-vreGnYSSKhAjFJCWAwe/CNhONvoc5lokxtRoZims+0wa3KbHBdPHSSthJsKxPd8d/aic6lWKpRTYGY/hsgK6EA==", + "license": "MIT" + }, + "node_modules/@astrojs/markdown-remark": { + "version": "6.3.10", + "resolved": "https://registry.npmjs.org/@astrojs/markdown-remark/-/markdown-remark-6.3.10.tgz", + "integrity": "sha512-kk4HeYR6AcnzC4QV8iSlOfh+N8TZ3MEStxPyenyCtemqn8IpEATBFMTJcfrNW32dgpt6MY3oCkMM/Tv3/I4G3A==", + "license": "MIT", + "dependencies": { + "@astrojs/internal-helpers": "0.7.5", + "@astrojs/prism": "3.3.0", + "github-slugger": "^2.0.0", + "hast-util-from-html": "^2.0.3", + "hast-util-to-text": "^4.0.2", + "import-meta-resolve": "^4.2.0", + "js-yaml": "^4.1.1", + "mdast-util-definitions": "^6.0.0", + "rehype-raw": "^7.0.0", + "rehype-stringify": "^10.0.1", + "remark-gfm": "^4.0.1", + "remark-parse": "^11.0.0", + "remark-rehype": "^11.1.2", + "remark-smartypants": "^3.0.2", + "shiki": "^3.19.0", + "smol-toml": "^1.5.2", + "unified": "^11.0.5", + "unist-util-remove-position": "^5.0.0", + "unist-util-visit": "^5.0.0", + "unist-util-visit-parents": "^6.0.2", + "vfile": "^6.0.3" + } + }, + "node_modules/@astrojs/mdx": { + "version": "4.3.13", + "resolved": "https://registry.npmjs.org/@astrojs/mdx/-/mdx-4.3.13.tgz", + "integrity": "sha512-IHDHVKz0JfKBy3//52JSiyWv089b7GVSChIXLrlUOoTLWowG3wr2/8hkaEgEyd/vysvNQvGk+QhysXpJW5ve6Q==", + "license": "MIT", + "dependencies": { + "@astrojs/markdown-remark": "6.3.10", + "@mdx-js/mdx": "^3.1.1", + "acorn": "^8.15.0", + "es-module-lexer": "^1.7.0", + "estree-util-visit": "^2.0.0", + "hast-util-to-html": "^9.0.5", + "piccolore": "^0.1.3", + "rehype-raw": "^7.0.0", + "remark-gfm": "^4.0.1", + "remark-smartypants": "^3.0.2", + "source-map": "^0.7.6", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.3" + }, + "engines": { + "node": "18.20.8 || ^20.3.0 || >=22.0.0" + }, + "peerDependencies": { + "astro": "^5.0.0" + } + }, + "node_modules/@astrojs/prism": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/@astrojs/prism/-/prism-3.3.0.tgz", + "integrity": "sha512-q8VwfU/fDZNoDOf+r7jUnMC2//H2l0TuQ6FkGJL8vD8nw/q5KiL3DS1KKBI3QhI9UQhpJ5dc7AtqfbXWuOgLCQ==", + "license": "MIT", + "dependencies": { + "prismjs": "^1.30.0" + }, + "engines": { + "node": "18.20.8 || ^20.3.0 || >=22.0.0" + } + }, + "node_modules/@astrojs/sitemap": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/@astrojs/sitemap/-/sitemap-3.7.0.tgz", + "integrity": "sha512-+qxjUrz6Jcgh+D5VE1gKUJTA3pSthuPHe6Ao5JCxok794Lewx8hBFaWHtOnN0ntb2lfOf7gvOi9TefUswQ/ZVA==", + "license": "MIT", + "dependencies": { + "sitemap": "^8.0.2", + "stream-replace-string": "^2.0.0", + "zod": "^3.25.76" + } + }, + "node_modules/@astrojs/starlight": { + "version": "0.37.6", + "resolved": "https://registry.npmjs.org/@astrojs/starlight/-/starlight-0.37.6.tgz", + "integrity": "sha512-wQrKwH431q+8FsLBnNQeG+R36TMtEGxTQ2AuiVpcx9APcazvL3n7wVW8mMmYyxX0POjTnxlcWPkdMGR3Yj1L+w==", + "license": "MIT", + "dependencies": { + "@astrojs/markdown-remark": "^6.3.1", + "@astrojs/mdx": "^4.2.3", + "@astrojs/sitemap": "^3.3.0", + "@pagefind/default-ui": "^1.3.0", + "@types/hast": "^3.0.4", + "@types/js-yaml": "^4.0.9", + "@types/mdast": "^4.0.4", + "astro-expressive-code": "^0.41.1", + "bcp-47": "^2.1.0", + "hast-util-from-html": "^2.0.1", + "hast-util-select": "^6.0.2", + "hast-util-to-string": "^3.0.0", + "hastscript": "^9.0.0", + "i18next": "^23.11.5", + "js-yaml": "^4.1.0", + "klona": "^2.0.6", + "magic-string": "^0.30.17", + "mdast-util-directive": "^3.0.0", + "mdast-util-to-markdown": "^2.1.0", + "mdast-util-to-string": "^4.0.0", + "pagefind": "^1.3.0", + "rehype": "^13.0.1", + "rehype-format": "^5.0.0", + "remark-directive": "^3.0.0", + "ultrahtml": "^1.6.0", + "unified": "^11.0.5", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.2" + }, + "peerDependencies": { + "astro": "^5.5.0" + } + }, + "node_modules/@astrojs/telemetry": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/@astrojs/telemetry/-/telemetry-3.3.0.tgz", + "integrity": "sha512-UFBgfeldP06qu6khs/yY+q1cDAaArM2/7AEIqQ9Cuvf7B1hNLq0xDrZkct+QoIGyjq56y8IaE2I3CTvG99mlhQ==", + "license": "MIT", + "dependencies": { + "ci-info": "^4.2.0", + "debug": "^4.4.0", + "dlv": "^1.1.3", + "dset": "^3.1.4", + "is-docker": "^3.0.0", + "is-wsl": "^3.1.0", + "which-pm-runs": "^1.1.0" + }, + "engines": { + "node": "18.20.8 || ^20.3.0 || >=22.0.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.0.tgz", + "integrity": "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.0" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.6.tgz", + "integrity": "sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", + "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@braintree/sanitize-url": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/@braintree/sanitize-url/-/sanitize-url-7.1.2.tgz", + "integrity": "sha512-jigsZK+sMF/cuiB7sERuo9V7N9jx+dhmHHnQyDSVdpZwVutaBu7WvNYqMDLSgFgfB30n452TP3vjDAvFC973mA==", + "license": "MIT", + "peer": true + }, + "node_modules/@capsizecss/unpack": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@capsizecss/unpack/-/unpack-4.0.0.tgz", + "integrity": "sha512-VERIM64vtTP1C4mxQ5thVT9fK0apjPFobqybMtA1UdUujWka24ERHbRHFGmpbbhp73MhV+KSsHQH9C6uOTdEQA==", + "license": "MIT", + "dependencies": { + "fontkitten": "^1.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@chevrotain/cst-dts-gen": { + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/@chevrotain/cst-dts-gen/-/cst-dts-gen-11.0.3.tgz", + "integrity": "sha512-BvIKpRLeS/8UbfxXxgC33xOumsacaeCKAjAeLyOn7Pcp95HiRbrpl14S+9vaZLolnbssPIUuiUd8IvgkRyt6NQ==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@chevrotain/gast": "11.0.3", + "@chevrotain/types": "11.0.3", + "lodash-es": "4.17.21" + } + }, + "node_modules/@chevrotain/cst-dts-gen/node_modules/lodash-es": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", + "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==", + "license": "MIT", + "peer": true + }, + "node_modules/@chevrotain/gast": { + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/@chevrotain/gast/-/gast-11.0.3.tgz", + "integrity": "sha512-+qNfcoNk70PyS/uxmj3li5NiECO+2YKZZQMbmjTqRI3Qchu8Hig/Q9vgkHpI3alNjr7M+a2St5pw5w5F6NL5/Q==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@chevrotain/types": "11.0.3", + "lodash-es": "4.17.21" + } + }, + "node_modules/@chevrotain/gast/node_modules/lodash-es": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", + "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==", + "license": "MIT", + "peer": true + }, + "node_modules/@chevrotain/regexp-to-ast": { + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/@chevrotain/regexp-to-ast/-/regexp-to-ast-11.0.3.tgz", + "integrity": "sha512-1fMHaBZxLFvWI067AVbGJav1eRY7N8DDvYCTwGBiE/ytKBgP8azTdgyrKyWZ9Mfh09eHWb5PgTSO8wi7U824RA==", + "license": "Apache-2.0", + "peer": true + }, + "node_modules/@chevrotain/types": { + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/@chevrotain/types/-/types-11.0.3.tgz", + "integrity": "sha512-gsiM3G8b58kZC2HaWR50gu6Y1440cHiJ+i3JUvcp/35JchYejb2+5MVeJK0iKThYpAa/P2PYFV4hoi44HD+aHQ==", + "license": "Apache-2.0", + "peer": true + }, + "node_modules/@chevrotain/utils": { + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/@chevrotain/utils/-/utils-11.0.3.tgz", + "integrity": "sha512-YslZMgtJUyuMbZ+aKvfF3x1f5liK4mWNxghFRv7jqRR9C3R3fAOGTTKvxXDa2Y1s9zSbcpuO0cAxDYsc9SrXoQ==", + "license": "Apache-2.0", + "peer": true + }, + "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==", + "license": "MIT", + "engines": { + "node": ">=14" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.8.1.tgz", + "integrity": "sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg==", + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.3.tgz", + "integrity": "sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.3.tgz", + "integrity": "sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.3.tgz", + "integrity": "sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.3.tgz", + "integrity": "sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.3.tgz", + "integrity": "sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.3.tgz", + "integrity": "sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.3.tgz", + "integrity": "sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.3.tgz", + "integrity": "sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.3.tgz", + "integrity": "sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.3.tgz", + "integrity": "sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.3.tgz", + "integrity": "sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.3.tgz", + "integrity": "sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA==", + "cpu": [ + "loong64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.3.tgz", + "integrity": "sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw==", + "cpu": [ + "mips64el" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.3.tgz", + "integrity": "sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.3.tgz", + "integrity": "sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.3.tgz", + "integrity": "sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw==", + "cpu": [ + "s390x" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.3.tgz", + "integrity": "sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.3.tgz", + "integrity": "sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.3.tgz", + "integrity": "sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.3.tgz", + "integrity": "sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.3.tgz", + "integrity": "sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.3.tgz", + "integrity": "sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.3.tgz", + "integrity": "sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.3.tgz", + "integrity": "sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.3.tgz", + "integrity": "sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.3.tgz", + "integrity": "sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@expressive-code/core": { + "version": "0.41.6", + "resolved": "https://registry.npmjs.org/@expressive-code/core/-/core-0.41.6.tgz", + "integrity": "sha512-FvJQP+hG0jWi/FLBSmvHInDqWR7jNANp9PUDjdMqSshHb0y7sxx3vHuoOr6SgXjWw+MGLqorZyPQ0aAlHEok6g==", + "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.41.6", + "resolved": "https://registry.npmjs.org/@expressive-code/plugin-frames/-/plugin-frames-0.41.6.tgz", + "integrity": "sha512-d+hkSYXIQot6fmYnOmWAM+7TNWRv/dhfjMsNq+mIZz8Tb4mPHOcgcfZeEM5dV9TDL0ioQNvtcqQNuzA1sRPjxg==", + "license": "MIT", + "dependencies": { + "@expressive-code/core": "^0.41.6" + } + }, + "node_modules/@expressive-code/plugin-shiki": { + "version": "0.41.6", + "resolved": "https://registry.npmjs.org/@expressive-code/plugin-shiki/-/plugin-shiki-0.41.6.tgz", + "integrity": "sha512-Y6zmKBmsIUtWTzdefqlzm/h9Zz0Rc4gNdt2GTIH7fhHH2I9+lDYCa27BDwuBhjqcos6uK81Aca9dLUC4wzN+ng==", + "license": "MIT", + "dependencies": { + "@expressive-code/core": "^0.41.6", + "shiki": "^3.2.2" + } + }, + "node_modules/@expressive-code/plugin-text-markers": { + "version": "0.41.6", + "resolved": "https://registry.npmjs.org/@expressive-code/plugin-text-markers/-/plugin-text-markers-0.41.6.tgz", + "integrity": "sha512-PBFa1wGyYzRExMDzBmAWC6/kdfG1oLn4pLpBeTfIRrALPjcGA/59HP3e7q9J0Smk4pC7U+lWkA2LHR8FYV8U7Q==", + "license": "MIT", + "dependencies": { + "@expressive-code/core": "^0.41.6" + } + }, + "node_modules/@fontsource/inter": { + "version": "5.2.8", + "resolved": "https://registry.npmjs.org/@fontsource/inter/-/inter-5.2.8.tgz", + "integrity": "sha512-P6r5WnJoKiNVV+zvW2xM13gNdFhAEpQ9dQJHt3naLvfg+LkF2ldgSLiF4T41lf1SQCM9QmkqPTn4TH568IRagg==", + "license": "OFL-1.1", + "funding": { + "url": "https://github.com/sponsors/ayuhito" + } + }, + "node_modules/@fontsource/jetbrains-mono": { + "version": "5.2.8", + "resolved": "https://registry.npmjs.org/@fontsource/jetbrains-mono/-/jetbrains-mono-5.2.8.tgz", + "integrity": "sha512-6w8/SG4kqvIMu7xd7wt6x3idn1Qux3p9N62s6G3rfldOUYHpWcc2FKrqf+Vo44jRvqWj2oAtTHrZXEP23oSKwQ==", + "license": "OFL-1.1", + "funding": { + "url": "https://github.com/sponsors/ayuhito" + } + }, + "node_modules/@iconify/types": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@iconify/types/-/types-2.0.0.tgz", + "integrity": "sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==", + "license": "MIT", + "peer": true + }, + "node_modules/@iconify/utils": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@iconify/utils/-/utils-3.1.0.tgz", + "integrity": "sha512-Zlzem1ZXhI1iHeeERabLNzBHdOa4VhQbqAcOQaMKuTuyZCpwKbC2R4Dd0Zo3g9EAc+Y4fiarO8HIHRAth7+skw==", + "license": "MIT", + "peer": true, + "dependencies": { + "@antfu/install-pkg": "^1.1.0", + "@iconify/types": "^2.0.0", + "mlly": "^1.8.0" + } + }, + "node_modules/@img/colour": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.0.0.tgz", + "integrity": "sha512-A5P/LfWGFSl6nsckYtjw9da+19jB8hkJ6ACTGcDfEJ0aE+l2n2El7dsVM7UVHZQ9s2lmYMWlrS21YLy2IR1LUw==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@img/sharp-darwin-arm64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.33.5.tgz", + "integrity": "sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-arm64": "1.0.4" + } + }, + "node_modules/@img/sharp-darwin-x64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.33.5.tgz", + "integrity": "sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-x64": "1.0.4" + } + }, + "node_modules/@img/sharp-libvips-darwin-arm64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.0.4.tgz", + "integrity": "sha512-XblONe153h0O2zuFfTAbQYAX2JhYmDHeWikp1LM9Hul9gVPjFY427k6dFEcOL72O01QxQsWi761svJ/ev9xEDg==", + "cpu": [ + "arm64" + ], + "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.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.0.4.tgz", + "integrity": "sha512-xnGR8YuZYfJGmWPvmlunFaWJsb9T/AO2ykoP3Fz/0X5XV2aoYBPkX6xqCQvUTKKiLddarLaxpzNe+b1hjeWHAQ==", + "cpu": [ + "x64" + ], + "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.0.5", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.0.5.tgz", + "integrity": "sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g==", + "cpu": [ + "arm" + ], + "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.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.0.4.tgz", + "integrity": "sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-ppc64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.2.4.tgz", + "integrity": "sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==", + "cpu": [ + "ppc64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-riscv64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.2.4.tgz", + "integrity": "sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==", + "cpu": [ + "riscv64" + ], + "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.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.0.4.tgz", + "integrity": "sha512-u7Wz6ntiSSgGSGcjZ55im6uvTrOxSIS8/dgoVMoiGE9I6JAfU50yH5BoDlYA1tcuGS7g/QNtetJnxA6QEsCVTA==", + "cpu": [ + "s390x" + ], + "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.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.0.4.tgz", + "integrity": "sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw==", + "cpu": [ + "x64" + ], + "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.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.0.4.tgz", + "integrity": "sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA==", + "cpu": [ + "arm64" + ], + "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.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.0.4.tgz", + "integrity": "sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-linux-arm": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.33.5.tgz", + "integrity": "sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ==", + "cpu": [ + "arm" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm": "1.0.5" + } + }, + "node_modules/@img/sharp-linux-arm64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.33.5.tgz", + "integrity": "sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm64": "1.0.4" + } + }, + "node_modules/@img/sharp-linux-ppc64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.34.5.tgz", + "integrity": "sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==", + "cpu": [ + "ppc64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-ppc64": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-riscv64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.34.5.tgz", + "integrity": "sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==", + "cpu": [ + "riscv64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-riscv64": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-s390x": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.33.5.tgz", + "integrity": "sha512-y/5PCd+mP4CA/sPDKl2961b+C9d+vPAveS33s6Z3zfASk2j5upL6fXVPZi7ztePZ5CuH+1kW8JtvxgbuXHRa4Q==", + "cpu": [ + "s390x" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-s390x": "1.0.4" + } + }, + "node_modules/@img/sharp-linux-x64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.33.5.tgz", + "integrity": "sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-x64": "1.0.4" + } + }, + "node_modules/@img/sharp-linuxmusl-arm64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.33.5.tgz", + "integrity": "sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-arm64": "1.0.4" + } + }, + "node_modules/@img/sharp-linuxmusl-x64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.33.5.tgz", + "integrity": "sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-x64": "1.0.4" + } + }, + "node_modules/@img/sharp-wasm32": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.33.5.tgz", + "integrity": "sha512-ykUW4LVGaMcU9lu9thv85CbRMAwfeadCJHRsg2GmeRa/cJxsVY9Rbd57JcMxBkKHag5U/x7TSBpScF4U8ElVzg==", + "cpu": [ + "wasm32" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", + "optional": true, + "dependencies": { + "@emnapi/runtime": "^1.2.0" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.34.5.tgz", + "integrity": "sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-ia32": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.33.5.tgz", + "integrity": "sha512-T36PblLaTwuVJ/zw/LaH0PdZkRz5rd3SmMHX8GSmR7vtNSP5Z6bQkExdSK7xGWyxLw4sUknBuugTelgw2faBbQ==", + "cpu": [ + "ia32" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-x64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.33.5.tgz", + "integrity": "sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "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.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "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==", + "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/@mermaid-js/parser": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/@mermaid-js/parser/-/parser-0.6.3.tgz", + "integrity": "sha512-lnjOhe7zyHjc+If7yT4zoedx2vo4sHaTmtkl1+or8BRTnCtDmcTpAjpzDSfCZrshM5bCoz0GyidzadJAH1xobA==", + "license": "MIT", + "peer": true, + "dependencies": { + "langium": "3.3.1" + } + }, + "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==", + "license": "MIT" + }, + "node_modules/@pagefind/darwin-arm64": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@pagefind/darwin-arm64/-/darwin-arm64-1.4.0.tgz", + "integrity": "sha512-2vMqkbv3lbx1Awea90gTaBsvpzgRs7MuSgKDxW0m9oV1GPZCZbZBJg/qL83GIUEN2BFlY46dtUZi54pwH+/pTQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@pagefind/darwin-x64": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@pagefind/darwin-x64/-/darwin-x64-1.4.0.tgz", + "integrity": "sha512-e7JPIS6L9/cJfow+/IAqknsGqEPjJnVXGjpGm25bnq+NPdoD3c/7fAwr1OXkG4Ocjx6ZGSCijXEV4ryMcH2E3A==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@pagefind/default-ui": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@pagefind/default-ui/-/default-ui-1.4.0.tgz", + "integrity": "sha512-wie82VWn3cnGEdIjh4YwNESyS1G6vRHwL6cNjy9CFgNnWW/PGRjsLq300xjVH5sfPFK3iK36UxvIBymtQIEiSQ==", + "license": "MIT" + }, + "node_modules/@pagefind/freebsd-x64": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@pagefind/freebsd-x64/-/freebsd-x64-1.4.0.tgz", + "integrity": "sha512-WcJVypXSZ+9HpiqZjFXMUobfFfZZ6NzIYtkhQ9eOhZrQpeY5uQFqNWLCk7w9RkMUwBv1HAMDW3YJQl/8OqsV0Q==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@pagefind/linux-arm64": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@pagefind/linux-arm64/-/linux-arm64-1.4.0.tgz", + "integrity": "sha512-PIt8dkqt4W06KGmQjONw7EZbhDF+uXI7i0XtRLN1vjCUxM9vGPdtJc2mUyVPevjomrGz5M86M8bqTr6cgDp1Uw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@pagefind/linux-x64": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@pagefind/linux-x64/-/linux-x64-1.4.0.tgz", + "integrity": "sha512-z4oddcWwQ0UHrTHR8psLnVlz6USGJ/eOlDPTDYZ4cI8TK8PgwRUPQZp9D2iJPNIPcS6Qx/E4TebjuGJOyK8Mmg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@pagefind/windows-x64": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@pagefind/windows-x64/-/windows-x64-1.4.0.tgz", + "integrity": "sha512-NkT+YAdgS2FPCn8mIA9bQhiBs+xmniMGq1LFPDhcFn0+2yIUEiIG06t7bsZlhdjknEQRTSdT7YitP6fC5qwP0g==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/pluginutils": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.3.0.tgz", + "integrity": "sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "estree-walker": "^2.0.2", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/pluginutils/node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.57.1.tgz", + "integrity": "sha512-A6ehUVSiSaaliTxai040ZpZ2zTevHYbvu/lDoeAteHI8QnaosIzm4qwtezfRg1jOYaUmnzLX1AOD6Z+UJjtifg==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.57.1.tgz", + "integrity": "sha512-dQaAddCY9YgkFHZcFNS/606Exo8vcLHwArFZ7vxXq4rigo2bb494/xKMMwRRQW6ug7Js6yXmBZhSBRuBvCCQ3w==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.57.1.tgz", + "integrity": "sha512-crNPrwJOrRxagUYeMn/DZwqN88SDmwaJ8Cvi/TN1HnWBU7GwknckyosC2gd0IqYRsHDEnXf328o9/HC6OkPgOg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.57.1.tgz", + "integrity": "sha512-Ji8g8ChVbKrhFtig5QBV7iMaJrGtpHelkB3lsaKzadFBe58gmjfGXAOfI5FV0lYMH8wiqsxKQ1C9B0YTRXVy4w==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.57.1.tgz", + "integrity": "sha512-R+/WwhsjmwodAcz65guCGFRkMb4gKWTcIeLy60JJQbXrJ97BOXHxnkPFrP+YwFlaS0m+uWJTstrUA9o+UchFug==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.57.1.tgz", + "integrity": "sha512-IEQTCHeiTOnAUC3IDQdzRAGj3jOAYNr9kBguI7MQAAZK3caezRrg0GxAb6Hchg4lxdZEI5Oq3iov/w/hnFWY9Q==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.57.1.tgz", + "integrity": "sha512-F8sWbhZ7tyuEfsmOxwc2giKDQzN3+kuBLPwwZGyVkLlKGdV1nvnNwYD0fKQ8+XS6hp9nY7B+ZeK01EBUE7aHaw==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.57.1.tgz", + "integrity": "sha512-rGfNUfn0GIeXtBP1wL5MnzSj98+PZe/AXaGBCRmT0ts80lU5CATYGxXukeTX39XBKsxzFpEeK+Mrp9faXOlmrw==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.57.1.tgz", + "integrity": "sha512-MMtej3YHWeg/0klK2Qodf3yrNzz6CGjo2UntLvk2RSPlhzgLvYEB3frRvbEF2wRKh1Z2fDIg9KRPe1fawv7C+g==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.57.1.tgz", + "integrity": "sha512-1a/qhaaOXhqXGpMFMET9VqwZakkljWHLmZOX48R0I/YLbhdxr1m4gtG1Hq7++VhVUmf+L3sTAf9op4JlhQ5u1Q==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.57.1.tgz", + "integrity": "sha512-QWO6RQTZ/cqYtJMtxhkRkidoNGXc7ERPbZN7dVW5SdURuLeVU7lwKMpo18XdcmpWYd0qsP1bwKPf7DNSUinhvA==", + "cpu": [ + "loong64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.57.1.tgz", + "integrity": "sha512-xpObYIf+8gprgWaPP32xiN5RVTi/s5FCR+XMXSKmhfoJjrpRAjCuuqQXyxUa/eJTdAE6eJ+KDKaoEqjZQxh3Gw==", + "cpu": [ + "loong64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.57.1.tgz", + "integrity": "sha512-4BrCgrpZo4hvzMDKRqEaW1zeecScDCR+2nZ86ATLhAoJ5FQ+lbHVD3ttKe74/c7tNT9c6F2viwB3ufwp01Oh2w==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.57.1.tgz", + "integrity": "sha512-NOlUuzesGauESAyEYFSe3QTUguL+lvrN1HtwEEsU2rOwdUDeTMJdO5dUYl/2hKf9jWydJrO9OL/XSSf65R5+Xw==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.57.1.tgz", + "integrity": "sha512-ptA88htVp0AwUUqhVghwDIKlvJMD/fmL/wrQj99PRHFRAG6Z5nbWoWG4o81Nt9FT+IuqUQi+L31ZKAFeJ5Is+A==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.57.1.tgz", + "integrity": "sha512-S51t7aMMTNdmAMPpBg7OOsTdn4tySRQvklmL3RpDRyknk87+Sp3xaumlatU+ppQ+5raY7sSTcC2beGgvhENfuw==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.57.1.tgz", + "integrity": "sha512-Bl00OFnVFkL82FHbEqy3k5CUCKH6OEJL54KCyx2oqsmZnFTR8IoNqBF+mjQVcRCT5sB6yOvK8A37LNm/kPJiZg==", + "cpu": [ + "s390x" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.57.1.tgz", + "integrity": "sha512-ABca4ceT4N+Tv/GtotnWAeXZUZuM/9AQyCyKYyKnpk4yoA7QIAuBt6Hkgpw8kActYlew2mvckXkvx0FfoInnLg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.57.1.tgz", + "integrity": "sha512-HFps0JeGtuOR2convgRRkHCekD7j+gdAuXM+/i6kGzQtFhlCtQkpwtNzkNj6QhCDp7DRJ7+qC/1Vg2jt5iSOFw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.57.1.tgz", + "integrity": "sha512-H+hXEv9gdVQuDTgnqD+SQffoWoc0Of59AStSzTEj/feWTBAnSfSD3+Dql1ZruJQxmykT/JVY0dE8Ka7z0DH1hw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.57.1.tgz", + "integrity": "sha512-4wYoDpNg6o/oPximyc/NG+mYUejZrCU2q+2w6YZqrAs2UcNUChIZXjtafAiiZSUc7On8v5NyNj34Kzj/Ltk6dQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.57.1.tgz", + "integrity": "sha512-O54mtsV/6LW3P8qdTcamQmuC990HDfR71lo44oZMZlXU4tzLrbvTii87Ni9opq60ds0YzuAlEr/GNwuNluZyMQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.57.1.tgz", + "integrity": "sha512-P3dLS+IerxCT/7D2q2FYcRdWRl22dNbrbBEtxdWhXrfIMPP9lQhb5h4Du04mdl5Woq05jVCDPCMF7Ub0NAjIew==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.57.1.tgz", + "integrity": "sha512-VMBH2eOOaKGtIJYleXsi2B8CPVADrh+TyNxJ4mWPnKfLB/DBUmzW+5m1xUrcwWoMfSLagIRpjUFeW5CO5hyciQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.57.1.tgz", + "integrity": "sha512-mxRFDdHIWRxg3UfIIAwCm6NzvxG0jDX/wBN6KsQFTvKFqqg9vTrWUE68qEjHt19A5wwx5X5aUi2zuZT7YR0jrA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@shikijs/core": { + "version": "3.22.0", + "resolved": "https://registry.npmjs.org/@shikijs/core/-/core-3.22.0.tgz", + "integrity": "sha512-iAlTtSDDbJiRpvgL5ugKEATDtHdUVkqgHDm/gbD2ZS9c88mx7G1zSYjjOxp5Qa0eaW0MAQosFRmJSk354PRoQA==", + "license": "MIT", + "dependencies": { + "@shikijs/types": "3.22.0", + "@shikijs/vscode-textmate": "^10.0.2", + "@types/hast": "^3.0.4", + "hast-util-to-html": "^9.0.5" + } + }, + "node_modules/@shikijs/engine-javascript": { + "version": "3.22.0", + "resolved": "https://registry.npmjs.org/@shikijs/engine-javascript/-/engine-javascript-3.22.0.tgz", + "integrity": "sha512-jdKhfgW9CRtj3Tor0L7+yPwdG3CgP7W+ZEqSsojrMzCjD1e0IxIbwUMDDpYlVBlC08TACg4puwFGkZfLS+56Tw==", + "license": "MIT", + "dependencies": { + "@shikijs/types": "3.22.0", + "@shikijs/vscode-textmate": "^10.0.2", + "oniguruma-to-es": "^4.3.4" + } + }, + "node_modules/@shikijs/engine-oniguruma": { + "version": "3.22.0", + "resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-3.22.0.tgz", + "integrity": "sha512-DyXsOG0vGtNtl7ygvabHd7Mt5EY8gCNqR9Y7Lpbbd/PbJvgWrqaKzH1JW6H6qFkuUa8aCxoiYVv8/YfFljiQxA==", + "license": "MIT", + "dependencies": { + "@shikijs/types": "3.22.0", + "@shikijs/vscode-textmate": "^10.0.2" + } + }, + "node_modules/@shikijs/langs": { + "version": "3.22.0", + "resolved": "https://registry.npmjs.org/@shikijs/langs/-/langs-3.22.0.tgz", + "integrity": "sha512-x/42TfhWmp6H00T6uwVrdTJGKgNdFbrEdhaDwSR5fd5zhQ1Q46bHq9EO61SCEWJR0HY7z2HNDMaBZp8JRmKiIA==", + "license": "MIT", + "dependencies": { + "@shikijs/types": "3.22.0" + } + }, + "node_modules/@shikijs/themes": { + "version": "3.22.0", + "resolved": "https://registry.npmjs.org/@shikijs/themes/-/themes-3.22.0.tgz", + "integrity": "sha512-o+tlOKqsr6FE4+mYJG08tfCFDS+3CG20HbldXeVoyP+cYSUxDhrFf3GPjE60U55iOkkjbpY2uC3It/eeja35/g==", + "license": "MIT", + "dependencies": { + "@shikijs/types": "3.22.0" + } + }, + "node_modules/@shikijs/types": { + "version": "3.22.0", + "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-3.22.0.tgz", + "integrity": "sha512-491iAekgKDBFE67z70Ok5a8KBMsQ2IJwOWw3us/7ffQkIBCyOQfm/aNwVMBUriP02QshIfgHCBSIYAl3u2eWjg==", + "license": "MIT", + "dependencies": { + "@shikijs/vscode-textmate": "^10.0.2", + "@types/hast": "^3.0.4" + } + }, + "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==", + "license": "MIT" + }, + "node_modules/@tailwindcss/node": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.18.tgz", + "integrity": "sha512-DoR7U1P7iYhw16qJ49fgXUlry1t4CpXeErJHnQ44JgTSKMaZUdf17cfn5mHchfJ4KRBZRFA/Coo+MUF5+gOaCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/remapping": "^2.3.4", + "enhanced-resolve": "^5.18.3", + "jiti": "^2.6.1", + "lightningcss": "1.30.2", + "magic-string": "^0.30.21", + "source-map-js": "^1.2.1", + "tailwindcss": "4.1.18" + } + }, + "node_modules/@tailwindcss/oxide": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.1.18.tgz", + "integrity": "sha512-EgCR5tTS5bUSKQgzeMClT6iCY3ToqE1y+ZB0AKldj809QXk1Y+3jB0upOYZrn9aGIzPtUsP7sX4QQ4XtjBB95A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10" + }, + "optionalDependencies": { + "@tailwindcss/oxide-android-arm64": "4.1.18", + "@tailwindcss/oxide-darwin-arm64": "4.1.18", + "@tailwindcss/oxide-darwin-x64": "4.1.18", + "@tailwindcss/oxide-freebsd-x64": "4.1.18", + "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.18", + "@tailwindcss/oxide-linux-arm64-gnu": "4.1.18", + "@tailwindcss/oxide-linux-arm64-musl": "4.1.18", + "@tailwindcss/oxide-linux-x64-gnu": "4.1.18", + "@tailwindcss/oxide-linux-x64-musl": "4.1.18", + "@tailwindcss/oxide-wasm32-wasi": "4.1.18", + "@tailwindcss/oxide-win32-arm64-msvc": "4.1.18", + "@tailwindcss/oxide-win32-x64-msvc": "4.1.18" + } + }, + "node_modules/@tailwindcss/oxide-android-arm64": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.1.18.tgz", + "integrity": "sha512-dJHz7+Ugr9U/diKJA0W6N/6/cjI+ZTAoxPf9Iz9BFRF2GzEX8IvXxFIi/dZBloVJX/MZGvRuFA9rqwdiIEZQ0Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-darwin-arm64": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.1.18.tgz", + "integrity": "sha512-Gc2q4Qhs660bhjyBSKgq6BYvwDz4G+BuyJ5H1xfhmDR3D8HnHCmT/BSkvSL0vQLy/nkMLY20PQ2OoYMO15Jd0A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-darwin-x64": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.1.18.tgz", + "integrity": "sha512-FL5oxr2xQsFrc3X9o1fjHKBYBMD1QZNyc1Xzw/h5Qu4XnEBi3dZn96HcHm41c/euGV+GRiXFfh2hUCyKi/e+yw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-freebsd-x64": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.1.18.tgz", + "integrity": "sha512-Fj+RHgu5bDodmV1dM9yAxlfJwkkWvLiRjbhuO2LEtwtlYlBgiAT4x/j5wQr1tC3SANAgD+0YcmWVrj8R9trVMA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.1.18.tgz", + "integrity": "sha512-Fp+Wzk/Ws4dZn+LV2Nqx3IilnhH51YZoRaYHQsVq3RQvEl+71VGKFpkfHrLM/Li+kt5c0DJe/bHXK1eHgDmdiA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.1.18.tgz", + "integrity": "sha512-S0n3jboLysNbh55Vrt7pk9wgpyTTPD0fdQeh7wQfMqLPM/Hrxi+dVsLsPrycQjGKEQk85Kgbx+6+QnYNiHalnw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-musl": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.1.18.tgz", + "integrity": "sha512-1px92582HkPQlaaCkdRcio71p8bc8i/ap5807tPRDK/uw953cauQBT8c5tVGkOwrHMfc2Yh6UuxaH4vtTjGvHg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-gnu": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.1.18.tgz", + "integrity": "sha512-v3gyT0ivkfBLoZGF9LyHmts0Isc8jHZyVcbzio6Wpzifg/+5ZJpDiRiUhDLkcr7f/r38SWNe7ucxmGW3j3Kb/g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-musl": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.1.18.tgz", + "integrity": "sha512-bhJ2y2OQNlcRwwgOAGMY0xTFStt4/wyU6pvI6LSuZpRgKQwxTec0/3Scu91O8ir7qCR3AuepQKLU/kX99FouqQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.1.18.tgz", + "integrity": "sha512-LffYTvPjODiP6PT16oNeUQJzNVyJl1cjIebq/rWWBF+3eDst5JGEFSc5cWxyRCJ0Mxl+KyIkqRxk1XPEs9x8TA==", + "bundleDependencies": [ + "@napi-rs/wasm-runtime", + "@emnapi/core", + "@emnapi/runtime", + "@tybys/wasm-util", + "@emnapi/wasi-threads", + "tslib" + ], + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.7.1", + "@emnapi/runtime": "^1.7.1", + "@emnapi/wasi-threads": "^1.1.0", + "@napi-rs/wasm-runtime": "^1.1.0", + "@tybys/wasm-util": "^0.10.1", + "tslib": "^2.4.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.18.tgz", + "integrity": "sha512-HjSA7mr9HmC8fu6bdsZvZ+dhjyGCLdotjVOgLA2vEqxEBZaQo9YTX4kwgEvPCpRh8o4uWc4J/wEoFzhEmjvPbA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-win32-x64-msvc": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.1.18.tgz", + "integrity": "sha512-bJWbyYpUlqamC8dpR7pfjA0I7vdF6t5VpUGMWRkXVE3AXgIZjYUYAK7II1GNaxR8J1SSrSrppRar8G++JekE3Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/vite": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.1.18.tgz", + "integrity": "sha512-jVA+/UpKL1vRLg6Hkao5jldawNmRo7mQYrZtNHMIVpLfLhDml5nMRUo/8MwoX2vNXvnaXNNMedrMfMugAVX1nA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@tailwindcss/node": "4.1.18", + "@tailwindcss/oxide": "4.1.18", + "tailwindcss": "4.1.18" + }, + "peerDependencies": { + "vite": "^5.2.0 || ^6 || ^7" + } + }, + "node_modules/@types/d3": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@types/d3/-/d3-7.4.3.tgz", + "integrity": "sha512-lZXZ9ckh5R8uiFVt8ogUNf+pIrK4EsWrx2Np75WvF/eTpJ0FMHNhjXk8CKEx/+gpHbNQyJWehbFaTvqmHWB3ww==", + "license": "MIT", + "peer": true, + "dependencies": { + "@types/d3-array": "*", + "@types/d3-axis": "*", + "@types/d3-brush": "*", + "@types/d3-chord": "*", + "@types/d3-color": "*", + "@types/d3-contour": "*", + "@types/d3-delaunay": "*", + "@types/d3-dispatch": "*", + "@types/d3-drag": "*", + "@types/d3-dsv": "*", + "@types/d3-ease": "*", + "@types/d3-fetch": "*", + "@types/d3-force": "*", + "@types/d3-format": "*", + "@types/d3-geo": "*", + "@types/d3-hierarchy": "*", + "@types/d3-interpolate": "*", + "@types/d3-path": "*", + "@types/d3-polygon": "*", + "@types/d3-quadtree": "*", + "@types/d3-random": "*", + "@types/d3-scale": "*", + "@types/d3-scale-chromatic": "*", + "@types/d3-selection": "*", + "@types/d3-shape": "*", + "@types/d3-time": "*", + "@types/d3-time-format": "*", + "@types/d3-timer": "*", + "@types/d3-transition": "*", + "@types/d3-zoom": "*" + } + }, + "node_modules/@types/d3-array": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.2.tgz", + "integrity": "sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==", + "license": "MIT", + "peer": true + }, + "node_modules/@types/d3-axis": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-axis/-/d3-axis-3.0.6.tgz", + "integrity": "sha512-pYeijfZuBd87T0hGn0FO1vQ/cgLk6E1ALJjfkC0oJ8cbwkZl3TpgS8bVBLZN+2jjGgg38epgxb2zmoGtSfvgMw==", + "license": "MIT", + "peer": true, + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-brush": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-brush/-/d3-brush-3.0.6.tgz", + "integrity": "sha512-nH60IZNNxEcrh6L1ZSMNA28rj27ut/2ZmI3r96Zd+1jrZD++zD3LsMIjWlvg4AYrHn/Pqz4CF3veCxGjtbqt7A==", + "license": "MIT", + "peer": true, + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-chord": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-chord/-/d3-chord-3.0.6.tgz", + "integrity": "sha512-LFYWWd8nwfwEmTZG9PfQxd17HbNPksHBiJHaKuY1XeqscXacsS2tyoo6OdRsjf+NQYeB6XrNL3a25E3gH69lcg==", + "license": "MIT", + "peer": true + }, + "node_modules/@types/d3-color": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz", + "integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==", + "license": "MIT", + "peer": true + }, + "node_modules/@types/d3-contour": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-contour/-/d3-contour-3.0.6.tgz", + "integrity": "sha512-BjzLgXGnCWjUSYGfH1cpdo41/hgdWETu4YxpezoztawmqsvCeep+8QGfiY6YbDvfgHz/DkjeIkkZVJavB4a3rg==", + "license": "MIT", + "peer": true, + "dependencies": { + "@types/d3-array": "*", + "@types/geojson": "*" + } + }, + "node_modules/@types/d3-delaunay": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-delaunay/-/d3-delaunay-6.0.4.tgz", + "integrity": "sha512-ZMaSKu4THYCU6sV64Lhg6qjf1orxBthaC161plr5KuPHo3CNm8DTHiLw/5Eq2b6TsNP0W0iJrUOFscY6Q450Hw==", + "license": "MIT", + "peer": true + }, + "node_modules/@types/d3-dispatch": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@types/d3-dispatch/-/d3-dispatch-3.0.7.tgz", + "integrity": "sha512-5o9OIAdKkhN1QItV2oqaE5KMIiXAvDWBDPrD85e58Qlz1c1kI/J0NcqbEG88CoTwJrYe7ntUCVfeUl2UJKbWgA==", + "license": "MIT", + "peer": true + }, + "node_modules/@types/d3-drag": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@types/d3-drag/-/d3-drag-3.0.7.tgz", + "integrity": "sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-dsv": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@types/d3-dsv/-/d3-dsv-3.0.7.tgz", + "integrity": "sha512-n6QBF9/+XASqcKK6waudgL0pf/S5XHPPI8APyMLLUHd8NqouBGLsU8MgtO7NINGtPBtk9Kko/W4ea0oAspwh9g==", + "license": "MIT", + "peer": true + }, + "node_modules/@types/d3-ease": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.2.tgz", + "integrity": "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==", + "license": "MIT", + "peer": true + }, + "node_modules/@types/d3-fetch": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@types/d3-fetch/-/d3-fetch-3.0.7.tgz", + "integrity": "sha512-fTAfNmxSb9SOWNB9IoG5c8Hg6R+AzUHDRlsXsDZsNp6sxAEOP0tkP3gKkNSO/qmHPoBFTxNrjDprVHDQDvo5aA==", + "license": "MIT", + "peer": true, + "dependencies": { + "@types/d3-dsv": "*" + } + }, + "node_modules/@types/d3-force": { + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/@types/d3-force/-/d3-force-3.0.10.tgz", + "integrity": "sha512-ZYeSaCF3p73RdOKcjj+swRlZfnYpK1EbaDiYICEEp5Q6sUiqFaFQ9qgoshp5CzIyyb/yD09kD9o2zEltCexlgw==", + "license": "MIT", + "peer": true + }, + "node_modules/@types/d3-format": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-format/-/d3-format-3.0.4.tgz", + "integrity": "sha512-fALi2aI6shfg7vM5KiR1wNJnZ7r6UuggVqtDA+xiEdPZQwy/trcQaHnwShLuLdta2rTymCNpxYTiMZX/e09F4g==", + "license": "MIT", + "peer": true + }, + "node_modules/@types/d3-geo": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@types/d3-geo/-/d3-geo-3.1.0.tgz", + "integrity": "sha512-856sckF0oP/diXtS4jNsiQw/UuK5fQG8l/a9VVLeSouf1/PPbBE1i1W852zVwKwYCBkFJJB7nCFTbk6UMEXBOQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "@types/geojson": "*" + } + }, + "node_modules/@types/d3-hierarchy": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/@types/d3-hierarchy/-/d3-hierarchy-3.1.7.tgz", + "integrity": "sha512-tJFtNoYBtRtkNysX1Xq4sxtjK8YgoWUNpIiUee0/jHGRwqvzYxkq0hGVbbOGSz+JgFxxRu4K8nb3YpG3CMARtg==", + "license": "MIT", + "peer": true + }, + "node_modules/@types/d3-interpolate": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz", + "integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==", + "license": "MIT", + "peer": true, + "dependencies": { + "@types/d3-color": "*" + } + }, + "node_modules/@types/d3-path": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.1.1.tgz", + "integrity": "sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==", + "license": "MIT", + "peer": true + }, + "node_modules/@types/d3-polygon": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-polygon/-/d3-polygon-3.0.2.tgz", + "integrity": "sha512-ZuWOtMaHCkN9xoeEMr1ubW2nGWsp4nIql+OPQRstu4ypeZ+zk3YKqQT0CXVe/PYqrKpZAi+J9mTs05TKwjXSRA==", + "license": "MIT", + "peer": true + }, + "node_modules/@types/d3-quadtree": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-quadtree/-/d3-quadtree-3.0.6.tgz", + "integrity": "sha512-oUzyO1/Zm6rsxKRHA1vH0NEDG58HrT5icx/azi9MF1TWdtttWl0UIUsjEQBBh+SIkrpd21ZjEv7ptxWys1ncsg==", + "license": "MIT", + "peer": true + }, + "node_modules/@types/d3-random": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/d3-random/-/d3-random-3.0.3.tgz", + "integrity": "sha512-Imagg1vJ3y76Y2ea0871wpabqp613+8/r0mCLEBfdtqC7xMSfj9idOnmBYyMoULfHePJyxMAw3nWhJxzc+LFwQ==", + "license": "MIT", + "peer": true + }, + "node_modules/@types/d3-scale": { + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.9.tgz", + "integrity": "sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==", + "license": "MIT", + "peer": true, + "dependencies": { + "@types/d3-time": "*" + } + }, + "node_modules/@types/d3-scale-chromatic": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@types/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz", + "integrity": "sha512-iWMJgwkK7yTRmWqRB5plb1kadXyQ5Sj8V/zYlFGMUBbIPKQScw+Dku9cAAMgJG+z5GYDoMjWGLVOvjghDEFnKQ==", + "license": "MIT", + "peer": true + }, + "node_modules/@types/d3-selection": { + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/@types/d3-selection/-/d3-selection-3.0.11.tgz", + "integrity": "sha512-bhAXu23DJWsrI45xafYpkQ4NtcKMwWnAC/vKrd2l+nxMFuvOT3XMYTIj2opv8vq8AO5Yh7Qac/nSeP/3zjTK0w==", + "license": "MIT", + "peer": true + }, + "node_modules/@types/d3-shape": { + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.8.tgz", + "integrity": "sha512-lae0iWfcDeR7qt7rA88BNiqdvPS5pFVPpo5OfjElwNaT2yyekbM0C9vK+yqBqEmHr6lDkRnYNoTBYlAgJa7a4w==", + "license": "MIT", + "peer": true, + "dependencies": { + "@types/d3-path": "*" + } + }, + "node_modules/@types/d3-time": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.4.tgz", + "integrity": "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==", + "license": "MIT", + "peer": true + }, + "node_modules/@types/d3-time-format": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@types/d3-time-format/-/d3-time-format-4.0.3.tgz", + "integrity": "sha512-5xg9rC+wWL8kdDj153qZcsJ0FWiFt0J5RB6LYUNZjwSnesfblqrI/bJ1wBdJ8OQfncgbJG5+2F+qfqnqyzYxyg==", + "license": "MIT", + "peer": true + }, + "node_modules/@types/d3-timer": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.2.tgz", + "integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==", + "license": "MIT", + "peer": true + }, + "node_modules/@types/d3-transition": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/@types/d3-transition/-/d3-transition-3.0.9.tgz", + "integrity": "sha512-uZS5shfxzO3rGlu0cC3bjmMFKsXv+SmZZcgp0KD22ts4uGXp5EVYGzu/0YdwZeKmddhcAccYtREJKkPfXkZuCg==", + "license": "MIT", + "peer": true, + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-zoom": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@types/d3-zoom/-/d3-zoom-3.0.8.tgz", + "integrity": "sha512-iqMC4/YlFCSlO8+2Ii1GGGliCAY4XdeG748w5vQUbevlbDu0zSjH/+jojorQVBK/se0j6DUFNPBGSqD3YWYnDw==", + "license": "MIT", + "peer": true, + "dependencies": { + "@types/d3-interpolate": "*", + "@types/d3-selection": "*" + } + }, + "node_modules/@types/debug": { + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz", + "integrity": "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==", + "license": "MIT", + "dependencies": { + "@types/ms": "*" + } + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "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==", + "license": "MIT", + "dependencies": { + "@types/estree": "*" + } + }, + "node_modules/@types/geojson": { + "version": "7946.0.16", + "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.16.tgz", + "integrity": "sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==", + "license": "MIT", + "peer": true + }, + "node_modules/@types/hast": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", + "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "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==", + "license": "MIT" + }, + "node_modules/@types/katex": { + "version": "0.16.8", + "resolved": "https://registry.npmjs.org/@types/katex/-/katex-0.16.8.tgz", + "integrity": "sha512-trgaNyfU+Xh2Tc+ABIb44a5AYUpicB3uwirOioeOkNPPbmgRNtcWyDeeFRzjPZENO9Vq8gvVqfhaaXWLlevVwg==", + "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==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/@types/mdx": { + "version": "2.0.13", + "resolved": "https://registry.npmjs.org/@types/mdx/-/mdx-2.0.13.tgz", + "integrity": "sha512-+OWZQfAYyio6YkJb3HLxDrvnx6SWWDbC0zVPfBRzUk0/nqoDyf6dNxQi3eArPe8rJ473nobTMQ/8Zk+LxJ+Yuw==", + "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==", + "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==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/@types/node": { + "version": "25.2.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.2.3.tgz", + "integrity": "sha512-m0jEgYlYz+mDJZ2+F4v8D1AyQb+QzsNqRuI7xg1VQX/KlKS0qT9r1Mo16yo5F/MtifXFgaofIFsdFMox2SxIbQ==", + "license": "MIT", + "dependencies": { + "undici-types": "~7.16.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==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/trusted-types": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", + "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", + "license": "MIT", + "optional": true, + "peer": true + }, + "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==", + "license": "MIT" + }, + "node_modules/@ungap/structured-clone": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", + "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", + "license": "ISC" + }, + "node_modules/acorn": { + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "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==", + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ansi-align": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.1.tgz", + "integrity": "sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==", + "license": "ISC", + "dependencies": { + "string-width": "^4.1.0" + } + }, + "node_modules/ansi-align/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==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-align/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, + "node_modules/ansi-align/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-align/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==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "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==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "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==", + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/anymatch/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "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==", + "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==", + "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==", + "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==", + "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==", + "license": "MIT", + "bin": { + "astring": "bin/astring" + } + }, + "node_modules/astro": { + "version": "5.17.2", + "resolved": "https://registry.npmjs.org/astro/-/astro-5.17.2.tgz", + "integrity": "sha512-7jnMqGo53hOQNwo1N/wqeOvUp8wwW/p+DeerSjSkHNx8L/1mhy6P7rVo7EhdmF8DpKqw0tl/B5Fx1WcIzg1ysA==", + "license": "MIT", + "dependencies": { + "@astrojs/compiler": "^2.13.0", + "@astrojs/internal-helpers": "0.7.5", + "@astrojs/markdown-remark": "6.3.10", + "@astrojs/telemetry": "3.3.0", + "@capsizecss/unpack": "^4.0.0", + "@oslojs/encoding": "^1.1.0", + "@rollup/pluginutils": "^5.3.0", + "acorn": "^8.15.0", + "aria-query": "^5.3.2", + "axobject-query": "^4.1.0", + "boxen": "8.0.1", + "ci-info": "^4.3.1", + "clsx": "^2.1.1", + "common-ancestor-path": "^1.0.1", + "cookie": "^1.1.1", + "cssesc": "^3.0.0", + "debug": "^4.4.3", + "deterministic-object-hash": "^2.0.2", + "devalue": "^5.6.2", + "diff": "^8.0.3", + "dlv": "^1.1.3", + "dset": "^3.1.4", + "es-module-lexer": "^1.7.0", + "esbuild": "^0.27.0", + "estree-walker": "^3.0.3", + "flattie": "^1.1.1", + "fontace": "~0.4.0", + "github-slugger": "^2.0.0", + "html-escaper": "3.0.3", + "http-cache-semantics": "^4.2.0", + "import-meta-resolve": "^4.2.0", + "js-yaml": "^4.1.1", + "magic-string": "^0.30.21", + "magicast": "^0.5.1", + "mrmime": "^2.0.1", + "neotraverse": "^0.6.18", + "p-limit": "^6.2.0", + "p-queue": "^8.1.1", + "package-manager-detector": "^1.6.0", + "piccolore": "^0.1.3", + "picomatch": "^4.0.3", + "prompts": "^2.4.2", + "rehype": "^13.0.2", + "semver": "^7.7.3", + "shiki": "^3.21.0", + "smol-toml": "^1.6.0", + "svgo": "^4.0.0", + "tinyexec": "^1.0.2", + "tinyglobby": "^0.2.15", + "tsconfck": "^3.1.6", + "ultrahtml": "^1.6.0", + "unifont": "~0.7.3", + "unist-util-visit": "^5.0.0", + "unstorage": "^1.17.4", + "vfile": "^6.0.3", + "vite": "^6.4.1", + "vitefu": "^1.1.1", + "xxhash-wasm": "^1.1.0", + "yargs-parser": "^21.1.1", + "yocto-spinner": "^0.2.3", + "zod": "^3.25.76", + "zod-to-json-schema": "^3.25.1", + "zod-to-ts": "^1.2.0" + }, + "bin": { + "astro": "astro.js" + }, + "engines": { + "node": "18.20.8 || ^20.3.0 || >=22.0.0", + "npm": ">=9.6.5", + "pnpm": ">=7.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/astrodotbuild" + }, + "optionalDependencies": { + "sharp": "^0.34.0" + } + }, + "node_modules/astro-expressive-code": { + "version": "0.41.6", + "resolved": "https://registry.npmjs.org/astro-expressive-code/-/astro-expressive-code-0.41.6.tgz", + "integrity": "sha512-l47tb1uhmVIebHUkw+HEPtU/av0G4O8Q34g2cbkPvC7/e9ZhANcjUUciKt9Hp6gSVDdIuXBBLwJQn2LkeGMOAw==", + "license": "MIT", + "dependencies": { + "rehype-expressive-code": "^0.41.6" + }, + "peerDependencies": { + "astro": "^4.0.0-beta || ^5.0.0-beta || ^3.3.0 || ^6.0.0-beta" + } + }, + "node_modules/astro-mermaid": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/astro-mermaid/-/astro-mermaid-1.3.1.tgz", + "integrity": "sha512-1+FjwayMSZLtFd+ofdu1+v8a902nN5wmPmjY2qb8tLiO96YlL65LbskiuUcyH6q9h0CdZCrkc5FimlaHZsMJsg==", + "license": "MIT", + "dependencies": { + "import-meta-resolve": "^4.2.0", + "mdast-util-to-string": "^4.0.0", + "unist-util-visit": "^5.0.0" + }, + "peerDependencies": { + "@mermaid-js/layout-elk": "^0.2.0", + "astro": "^4.0.0 || ^5.0.0", + "mermaid": "^10.0.0 || ^11.0.0" + }, + "peerDependenciesMeta": { + "@mermaid-js/layout-elk": { + "optional": true + } + } + }, + "node_modules/astro/node_modules/@esbuild/aix-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", + "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/astro/node_modules/@esbuild/android-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", + "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/astro/node_modules/@esbuild/android-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", + "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/astro/node_modules/@esbuild/android-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", + "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/astro/node_modules/@esbuild/darwin-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", + "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/astro/node_modules/@esbuild/darwin-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", + "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/astro/node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", + "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/astro/node_modules/@esbuild/freebsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", + "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/astro/node_modules/@esbuild/linux-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", + "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/astro/node_modules/@esbuild/linux-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", + "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/astro/node_modules/@esbuild/linux-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", + "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/astro/node_modules/@esbuild/linux-loong64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", + "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", + "cpu": [ + "loong64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/astro/node_modules/@esbuild/linux-mips64el": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", + "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", + "cpu": [ + "mips64el" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/astro/node_modules/@esbuild/linux-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", + "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/astro/node_modules/@esbuild/linux-riscv64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", + "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/astro/node_modules/@esbuild/linux-s390x": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", + "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", + "cpu": [ + "s390x" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/astro/node_modules/@esbuild/linux-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", + "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/astro/node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", + "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/astro/node_modules/@esbuild/netbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", + "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/astro/node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", + "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/astro/node_modules/@esbuild/openbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", + "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/astro/node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", + "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/astro/node_modules/@esbuild/sunos-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", + "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/astro/node_modules/@esbuild/win32-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", + "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/astro/node_modules/@esbuild/win32-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", + "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/astro/node_modules/@esbuild/win32-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", + "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/astro/node_modules/@img/sharp-darwin-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.5.tgz", + "integrity": "sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-arm64": "1.2.4" + } + }, + "node_modules/astro/node_modules/@img/sharp-darwin-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.34.5.tgz", + "integrity": "sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-x64": "1.2.4" + } + }, + "node_modules/astro/node_modules/@img/sharp-libvips-darwin-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.2.4.tgz", + "integrity": "sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/astro/node_modules/@img/sharp-libvips-darwin-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.2.4.tgz", + "integrity": "sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/astro/node_modules/@img/sharp-libvips-linux-arm": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.2.4.tgz", + "integrity": "sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==", + "cpu": [ + "arm" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/astro/node_modules/@img/sharp-libvips-linux-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.2.4.tgz", + "integrity": "sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/astro/node_modules/@img/sharp-libvips-linux-s390x": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.2.4.tgz", + "integrity": "sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==", + "cpu": [ + "s390x" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/astro/node_modules/@img/sharp-libvips-linux-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.2.4.tgz", + "integrity": "sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/astro/node_modules/@img/sharp-libvips-linuxmusl-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.2.4.tgz", + "integrity": "sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/astro/node_modules/@img/sharp-libvips-linuxmusl-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.2.4.tgz", + "integrity": "sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/astro/node_modules/@img/sharp-linux-arm": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.34.5.tgz", + "integrity": "sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==", + "cpu": [ + "arm" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm": "1.2.4" + } + }, + "node_modules/astro/node_modules/@img/sharp-linux-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.34.5.tgz", + "integrity": "sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm64": "1.2.4" + } + }, + "node_modules/astro/node_modules/@img/sharp-linux-s390x": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.34.5.tgz", + "integrity": "sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==", + "cpu": [ + "s390x" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-s390x": "1.2.4" + } + }, + "node_modules/astro/node_modules/@img/sharp-linux-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.5.tgz", + "integrity": "sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-x64": "1.2.4" + } + }, + "node_modules/astro/node_modules/@img/sharp-linuxmusl-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.5.tgz", + "integrity": "sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-arm64": "1.2.4" + } + }, + "node_modules/astro/node_modules/@img/sharp-linuxmusl-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.34.5.tgz", + "integrity": "sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-x64": "1.2.4" + } + }, + "node_modules/astro/node_modules/@img/sharp-wasm32": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.34.5.tgz", + "integrity": "sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==", + "cpu": [ + "wasm32" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", + "optional": true, + "dependencies": { + "@emnapi/runtime": "^1.7.0" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/astro/node_modules/@img/sharp-win32-ia32": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.34.5.tgz", + "integrity": "sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==", + "cpu": [ + "ia32" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/astro/node_modules/@img/sharp-win32-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.34.5.tgz", + "integrity": "sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/astro/node_modules/sharp": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.5.tgz", + "integrity": "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==", + "hasInstallScript": true, + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@img/colour": "^1.0.0", + "detect-libc": "^2.1.2", + "semver": "^7.7.3" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-darwin-arm64": "0.34.5", + "@img/sharp-darwin-x64": "0.34.5", + "@img/sharp-libvips-darwin-arm64": "1.2.4", + "@img/sharp-libvips-darwin-x64": "1.2.4", + "@img/sharp-libvips-linux-arm": "1.2.4", + "@img/sharp-libvips-linux-arm64": "1.2.4", + "@img/sharp-libvips-linux-ppc64": "1.2.4", + "@img/sharp-libvips-linux-riscv64": "1.2.4", + "@img/sharp-libvips-linux-s390x": "1.2.4", + "@img/sharp-libvips-linux-x64": "1.2.4", + "@img/sharp-libvips-linuxmusl-arm64": "1.2.4", + "@img/sharp-libvips-linuxmusl-x64": "1.2.4", + "@img/sharp-linux-arm": "0.34.5", + "@img/sharp-linux-arm64": "0.34.5", + "@img/sharp-linux-ppc64": "0.34.5", + "@img/sharp-linux-riscv64": "0.34.5", + "@img/sharp-linux-s390x": "0.34.5", + "@img/sharp-linux-x64": "0.34.5", + "@img/sharp-linuxmusl-arm64": "0.34.5", + "@img/sharp-linuxmusl-x64": "0.34.5", + "@img/sharp-wasm32": "0.34.5", + "@img/sharp-win32-arm64": "0.34.5", + "@img/sharp-win32-ia32": "0.34.5", + "@img/sharp-win32-x64": "0.34.5" + } + }, + "node_modules/astro/node_modules/vite": { + "version": "6.4.1", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.1.tgz", + "integrity": "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==", + "license": "MIT", + "dependencies": { + "esbuild": "^0.25.0", + "fdir": "^6.4.4", + "picomatch": "^4.0.2", + "postcss": "^8.5.3", + "rollup": "^4.34.9", + "tinyglobby": "^0.2.13" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "jiti": ">=1.21.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/astro/node_modules/vite/node_modules/esbuild": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", + "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.12", + "@esbuild/android-arm": "0.25.12", + "@esbuild/android-arm64": "0.25.12", + "@esbuild/android-x64": "0.25.12", + "@esbuild/darwin-arm64": "0.25.12", + "@esbuild/darwin-x64": "0.25.12", + "@esbuild/freebsd-arm64": "0.25.12", + "@esbuild/freebsd-x64": "0.25.12", + "@esbuild/linux-arm": "0.25.12", + "@esbuild/linux-arm64": "0.25.12", + "@esbuild/linux-ia32": "0.25.12", + "@esbuild/linux-loong64": "0.25.12", + "@esbuild/linux-mips64el": "0.25.12", + "@esbuild/linux-ppc64": "0.25.12", + "@esbuild/linux-riscv64": "0.25.12", + "@esbuild/linux-s390x": "0.25.12", + "@esbuild/linux-x64": "0.25.12", + "@esbuild/netbsd-arm64": "0.25.12", + "@esbuild/netbsd-x64": "0.25.12", + "@esbuild/openbsd-arm64": "0.25.12", + "@esbuild/openbsd-x64": "0.25.12", + "@esbuild/openharmony-arm64": "0.25.12", + "@esbuild/sunos-x64": "0.25.12", + "@esbuild/win32-arm64": "0.25.12", + "@esbuild/win32-ia32": "0.25.12", + "@esbuild/win32-x64": "0.25.12" + } + }, + "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==", + "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==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/base-64": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/base-64/-/base-64-1.0.0.tgz", + "integrity": "sha512-kwDPIFCGx0NZHog36dj+tHiwP4QMzsZ3AgMViUBKI0+V5n4U0ufTCUMhnQ04diaRI8EX/QcPfql7zlhZ7j4zgg==", + "license": "MIT" + }, + "node_modules/bcp-47": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/bcp-47/-/bcp-47-2.1.0.tgz", + "integrity": "sha512-9IIS3UPrvIa1Ej+lVDdDwO7zLehjqsaByECw0bu2RRGP73jALm6FYbzI5gWbgHLvNdkvfXB5YrSbocZdOS0c0w==", + "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==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "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==", + "license": "ISC" + }, + "node_modules/boxen": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/boxen/-/boxen-8.0.1.tgz", + "integrity": "sha512-F3PH5k5juxom4xktynS7MoFY+NUWH5LC4CnH11YB8NPew+HLpmBLCybSAEyb2F+4pRXhuhWqFesoQd6DAyc2hw==", + "license": "MIT", + "dependencies": { + "ansi-align": "^3.0.1", + "camelcase": "^8.0.0", + "chalk": "^5.3.0", + "cli-boxes": "^3.0.0", + "string-width": "^7.2.0", + "type-fest": "^4.21.0", + "widest-line": "^5.0.0", + "wrap-ansi": "^9.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/camelcase": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-8.0.0.tgz", + "integrity": "sha512-8WB3Jcas3swSvjIeA2yvCJ+Miyz5l1ZmB6HFb9R1317dt9LCQoswg/BGrmAmkWVEszSrrg4RwmO46qIm2OEnSA==", + "license": "MIT", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ccount": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz", + "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/chalk": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "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==", + "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==", + "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==", + "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==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/chevrotain": { + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/chevrotain/-/chevrotain-11.0.3.tgz", + "integrity": "sha512-ci2iJH6LeIkvP9eJW6gpueU8cnZhv85ELY8w8WiFtNjMHA5ad6pQLaJo9mEly/9qUyCpvqX8/POVUTf18/HFdw==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@chevrotain/cst-dts-gen": "11.0.3", + "@chevrotain/gast": "11.0.3", + "@chevrotain/regexp-to-ast": "11.0.3", + "@chevrotain/types": "11.0.3", + "@chevrotain/utils": "11.0.3", + "lodash-es": "4.17.21" + } + }, + "node_modules/chevrotain-allstar": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/chevrotain-allstar/-/chevrotain-allstar-0.3.1.tgz", + "integrity": "sha512-b7g+y9A0v4mxCW1qUhf3BSVPg+/NvGErk/dOkrDaHA0nQIQGAtrOjlX//9OQtRlSCy+x9rfB5N8yC71lH1nvMw==", + "license": "MIT", + "peer": true, + "dependencies": { + "lodash-es": "^4.17.21" + }, + "peerDependencies": { + "chevrotain": "^11.0.0" + } + }, + "node_modules/chevrotain/node_modules/lodash-es": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", + "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==", + "license": "MIT", + "peer": true + }, + "node_modules/chokidar": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-5.0.0.tgz", + "integrity": "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==", + "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==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/cli-boxes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-3.0.0.tgz", + "integrity": "sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "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==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/color": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz", + "integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1", + "color-string": "^1.9.0" + }, + "engines": { + "node": ">=12.5.0" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "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==", + "license": "MIT" + }, + "node_modules/color-string": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz", + "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", + "license": "MIT", + "dependencies": { + "color-name": "^1.0.0", + "simple-swizzle": "^0.2.2" + } + }, + "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==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/commander": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", + "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "node_modules/common-ancestor-path": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/common-ancestor-path/-/common-ancestor-path-1.0.1.tgz", + "integrity": "sha512-L3sHRo1pXXEqX8VU28kfgUY+YGsk09hPqZiZmLacNib6XNTCM8ubYeT7ryXQw8asB1sKgcU5lkB7ONug08aB8w==", + "license": "ISC" + }, + "node_modules/confbox": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.8.tgz", + "integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==", + "license": "MIT", + "peer": true + }, + "node_modules/cookie": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz", + "integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/cookie-es": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cookie-es/-/cookie-es-1.2.2.tgz", + "integrity": "sha512-+W7VmiVINB+ywl1HGXJXmrqkOhpKrIiVZV6tQuV54ZyQC7MMuBt81Vc336GMLoHBq5hV/F9eXgt5Mnx0Rha5Fg==", + "license": "MIT" + }, + "node_modules/cose-base": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/cose-base/-/cose-base-1.0.3.tgz", + "integrity": "sha512-s9whTXInMSgAp/NVXVNuVxVKzGH2qck3aQlVHxDCdAEPgtMKwc4Wq6/QKhgdEdgbLSi9rBTAcPoRa6JpiG4ksg==", + "license": "MIT", + "peer": true, + "dependencies": { + "layout-base": "^1.0.0" + } + }, + "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==", + "license": "MIT", + "dependencies": { + "uncrypto": "^0.1.3" + } + }, + "node_modules/css-select": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.2.2.tgz", + "integrity": "sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==", + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0", + "css-what": "^6.1.0", + "domhandler": "^5.0.2", + "domutils": "^3.0.1", + "nth-check": "^2.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/css-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==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/mdevils" + }, + { + "type": "patreon", + "url": "https://patreon.com/mdevils" + } + ], + "license": "MIT" + }, + "node_modules/css-tree": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.1.0.tgz", + "integrity": "sha512-0eW44TGN5SQXU1mWSkKwFstI/22X2bG1nYzZTYMAWjylYURhse752YgbE4Cx46AC+bAvI+/dYTPRk1LqSUnu6w==", + "license": "MIT", + "dependencies": { + "mdn-data": "2.12.2", + "source-map-js": "^1.0.1" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" + } + }, + "node_modules/css-what": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.2.2.tgz", + "integrity": "sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==", + "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==", + "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==", + "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==", + "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==", + "license": "CC0-1.0" + }, + "node_modules/cytoscape": { + "version": "3.33.1", + "resolved": "https://registry.npmjs.org/cytoscape/-/cytoscape-3.33.1.tgz", + "integrity": "sha512-iJc4TwyANnOGR1OmWhsS9ayRS3s+XQ185FmuHObThD+5AeJCakAAbWv8KimMTt08xCCLNgneQwFp+JRJOr9qGQ==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/cytoscape-cose-bilkent": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cytoscape-cose-bilkent/-/cytoscape-cose-bilkent-4.1.0.tgz", + "integrity": "sha512-wgQlVIUJF13Quxiv5e1gstZ08rnZj2XaLHGoFMYXz7SkNfCDOOteKBE6SYRfA9WxxI/iBc3ajfDoc6hb/MRAHQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "cose-base": "^1.0.0" + }, + "peerDependencies": { + "cytoscape": "^3.2.0" + } + }, + "node_modules/cytoscape-fcose": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/cytoscape-fcose/-/cytoscape-fcose-2.2.0.tgz", + "integrity": "sha512-ki1/VuRIHFCzxWNrsshHYPs6L7TvLu3DL+TyIGEsRcvVERmxokbf5Gdk7mFxZnTdiGtnA4cfSmjZJMviqSuZrQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "cose-base": "^2.2.0" + }, + "peerDependencies": { + "cytoscape": "^3.2.0" + } + }, + "node_modules/cytoscape-fcose/node_modules/cose-base": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/cose-base/-/cose-base-2.2.0.tgz", + "integrity": "sha512-AzlgcsCbUMymkADOJtQm3wO9S3ltPfYOFD5033keQn9NJzIbtnZj+UdBJe7DYml/8TdbtHJW3j58SOnKhWY/5g==", + "license": "MIT", + "peer": true, + "dependencies": { + "layout-base": "^2.0.0" + } + }, + "node_modules/cytoscape-fcose/node_modules/layout-base": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/layout-base/-/layout-base-2.0.1.tgz", + "integrity": "sha512-dp3s92+uNI1hWIpPGH3jK2kxE2lMjdXdr+DH8ynZHpd6PUlH6x6cbuXnoMmiNumznqaNO31xu9e79F0uuZ0JFg==", + "license": "MIT", + "peer": true + }, + "node_modules/d3": { + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/d3/-/d3-7.9.0.tgz", + "integrity": "sha512-e1U46jVP+w7Iut8Jt8ri1YsPOvFpg46k+K8TpCb0P+zjCkjkPnV7WzfDJzMHy1LnA+wj5pLT1wjO901gLXeEhA==", + "license": "ISC", + "peer": true, + "dependencies": { + "d3-array": "3", + "d3-axis": "3", + "d3-brush": "3", + "d3-chord": "3", + "d3-color": "3", + "d3-contour": "4", + "d3-delaunay": "6", + "d3-dispatch": "3", + "d3-drag": "3", + "d3-dsv": "3", + "d3-ease": "3", + "d3-fetch": "3", + "d3-force": "3", + "d3-format": "3", + "d3-geo": "3", + "d3-hierarchy": "3", + "d3-interpolate": "3", + "d3-path": "3", + "d3-polygon": "3", + "d3-quadtree": "3", + "d3-random": "3", + "d3-scale": "4", + "d3-scale-chromatic": "3", + "d3-selection": "3", + "d3-shape": "3", + "d3-time": "3", + "d3-time-format": "4", + "d3-timer": "3", + "d3-transition": "3", + "d3-zoom": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-array": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz", + "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==", + "license": "ISC", + "peer": true, + "dependencies": { + "internmap": "1 - 2" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-axis": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-axis/-/d3-axis-3.0.0.tgz", + "integrity": "sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw==", + "license": "ISC", + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-brush": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-brush/-/d3-brush-3.0.0.tgz", + "integrity": "sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ==", + "license": "ISC", + "peer": true, + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-drag": "2 - 3", + "d3-interpolate": "1 - 3", + "d3-selection": "3", + "d3-transition": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-chord": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-chord/-/d3-chord-3.0.1.tgz", + "integrity": "sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g==", + "license": "ISC", + "peer": true, + "dependencies": { + "d3-path": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-color": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", + "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==", + "license": "ISC", + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-contour": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-contour/-/d3-contour-4.0.2.tgz", + "integrity": "sha512-4EzFTRIikzs47RGmdxbeUvLWtGedDUNkTcmzoeyg4sP/dvCexO47AaQL7VKy/gul85TOxw+IBgA8US2xwbToNA==", + "license": "ISC", + "peer": true, + "dependencies": { + "d3-array": "^3.2.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-delaunay": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/d3-delaunay/-/d3-delaunay-6.0.4.tgz", + "integrity": "sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A==", + "license": "ISC", + "peer": true, + "dependencies": { + "delaunator": "5" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-dispatch": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-3.0.1.tgz", + "integrity": "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==", + "license": "ISC", + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-drag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-3.0.0.tgz", + "integrity": "sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==", + "license": "ISC", + "peer": true, + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-selection": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-dsv": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-dsv/-/d3-dsv-3.0.1.tgz", + "integrity": "sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q==", + "license": "ISC", + "peer": true, + "dependencies": { + "commander": "7", + "iconv-lite": "0.6", + "rw": "1" + }, + "bin": { + "csv2json": "bin/dsv2json.js", + "csv2tsv": "bin/dsv2dsv.js", + "dsv2dsv": "bin/dsv2dsv.js", + "dsv2json": "bin/dsv2json.js", + "json2csv": "bin/json2dsv.js", + "json2dsv": "bin/json2dsv.js", + "json2tsv": "bin/json2dsv.js", + "tsv2csv": "bin/dsv2dsv.js", + "tsv2json": "bin/dsv2json.js" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-dsv/node_modules/commander": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 10" + } + }, + "node_modules/d3-ease": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz", + "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==", + "license": "BSD-3-Clause", + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-fetch": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-fetch/-/d3-fetch-3.0.1.tgz", + "integrity": "sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw==", + "license": "ISC", + "peer": true, + "dependencies": { + "d3-dsv": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-force": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-force/-/d3-force-3.0.0.tgz", + "integrity": "sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==", + "license": "ISC", + "peer": true, + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-quadtree": "1 - 3", + "d3-timer": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-format": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.2.tgz", + "integrity": "sha512-AJDdYOdnyRDV5b6ArilzCPPwc1ejkHcoyFarqlPqT7zRYjhavcT3uSrqcMvsgh2CgoPbK3RCwyHaVyxYcP2Arg==", + "license": "ISC", + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-geo": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-3.1.1.tgz", + "integrity": "sha512-637ln3gXKXOwhalDzinUgY83KzNWZRKbYubaG+fGVuc/dxO64RRljtCTnf5ecMyE1RIdtqpkVcq0IbtU2S8j2Q==", + "license": "ISC", + "peer": true, + "dependencies": { + "d3-array": "2.5.0 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-hierarchy": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/d3-hierarchy/-/d3-hierarchy-3.1.2.tgz", + "integrity": "sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA==", + "license": "ISC", + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-interpolate": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", + "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", + "license": "ISC", + "peer": true, + "dependencies": { + "d3-color": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-path": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz", + "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==", + "license": "ISC", + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-polygon": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-polygon/-/d3-polygon-3.0.1.tgz", + "integrity": "sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg==", + "license": "ISC", + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-quadtree": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-quadtree/-/d3-quadtree-3.0.1.tgz", + "integrity": "sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==", + "license": "ISC", + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-random": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-random/-/d3-random-3.0.1.tgz", + "integrity": "sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ==", + "license": "ISC", + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-sankey": { + "version": "0.12.3", + "resolved": "https://registry.npmjs.org/d3-sankey/-/d3-sankey-0.12.3.tgz", + "integrity": "sha512-nQhsBRmM19Ax5xEIPLMY9ZmJ/cDvd1BG3UVvt5h3WRxKg5zGRbvnteTyWAbzeSvlh3tW7ZEmq4VwR5mB3tutmQ==", + "license": "BSD-3-Clause", + "peer": true, + "dependencies": { + "d3-array": "1 - 2", + "d3-shape": "^1.2.0" + } + }, + "node_modules/d3-sankey/node_modules/d3-array": { + "version": "2.12.1", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-2.12.1.tgz", + "integrity": "sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ==", + "license": "BSD-3-Clause", + "peer": true, + "dependencies": { + "internmap": "^1.0.0" + } + }, + "node_modules/d3-sankey/node_modules/d3-path": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-1.0.9.tgz", + "integrity": "sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg==", + "license": "BSD-3-Clause", + "peer": true + }, + "node_modules/d3-sankey/node_modules/d3-shape": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-1.3.7.tgz", + "integrity": "sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw==", + "license": "BSD-3-Clause", + "peer": true, + "dependencies": { + "d3-path": "1" + } + }, + "node_modules/d3-sankey/node_modules/internmap": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/internmap/-/internmap-1.0.1.tgz", + "integrity": "sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw==", + "license": "ISC", + "peer": true + }, + "node_modules/d3-scale": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz", + "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==", + "license": "ISC", + "peer": true, + "dependencies": { + "d3-array": "2.10.0 - 3", + "d3-format": "1 - 3", + "d3-interpolate": "1.2.0 - 3", + "d3-time": "2.1.1 - 3", + "d3-time-format": "2 - 4" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-scale-chromatic": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz", + "integrity": "sha512-A3s5PWiZ9YCXFye1o246KoscMWqf8BsD9eRiJ3He7C9OBaxKhAd5TFCdEx/7VbKtxxTsu//1mMJFrEt572cEyQ==", + "license": "ISC", + "peer": true, + "dependencies": { + "d3-color": "1 - 3", + "d3-interpolate": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-selection": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz", + "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==", + "license": "ISC", + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-shape": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz", + "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==", + "license": "ISC", + "peer": true, + "dependencies": { + "d3-path": "^3.1.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz", + "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==", + "license": "ISC", + "peer": true, + "dependencies": { + "d3-array": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time-format": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz", + "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==", + "license": "ISC", + "peer": true, + "dependencies": { + "d3-time": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-timer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz", + "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==", + "license": "ISC", + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-transition": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-3.0.1.tgz", + "integrity": "sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==", + "license": "ISC", + "peer": true, + "dependencies": { + "d3-color": "1 - 3", + "d3-dispatch": "1 - 3", + "d3-ease": "1 - 3", + "d3-interpolate": "1 - 3", + "d3-timer": "1 - 3" + }, + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "d3-selection": "2 - 3" + } + }, + "node_modules/d3-zoom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-zoom/-/d3-zoom-3.0.0.tgz", + "integrity": "sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==", + "license": "ISC", + "peer": true, + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-drag": "2 - 3", + "d3-interpolate": "1 - 3", + "d3-selection": "2 - 3", + "d3-transition": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/dagre-d3-es": { + "version": "7.0.13", + "resolved": "https://registry.npmjs.org/dagre-d3-es/-/dagre-d3-es-7.0.13.tgz", + "integrity": "sha512-efEhnxpSuwpYOKRm/L5KbqoZmNNukHa/Flty4Wp62JRvgH2ojwVgPgdYyr4twpieZnyRDdIH7PY2mopX26+j2Q==", + "license": "MIT", + "peer": true, + "dependencies": { + "d3": "^7.9.0", + "lodash-es": "^4.17.21" + } + }, + "node_modules/dayjs": { + "version": "1.11.19", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.19.tgz", + "integrity": "sha512-t5EcLVS6QPBNqM2z8fakk/NKel+Xzshgt8FFKAn+qwlD1pzZWxh0nVCrvFK7ZDb6XucZeF9z8C7CBWTRIVApAw==", + "license": "MIT", + "peer": true + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "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==", + "license": "MIT", + "dependencies": { + "character-entities": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/defu": { + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/defu/-/defu-6.1.4.tgz", + "integrity": "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==", + "license": "MIT" + }, + "node_modules/delaunator": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/delaunator/-/delaunator-5.0.1.tgz", + "integrity": "sha512-8nvh+XBe96aCESrGOqMp/84b13H9cdKbG5P2ejQCh4d4sK9RL4371qou9drQjMhvnPmhWl5hnmqbEE0fXr9Xnw==", + "license": "ISC", + "peer": true, + "dependencies": { + "robust-predicates": "^3.0.2" + } + }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "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==", + "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==", + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/deterministic-object-hash": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/deterministic-object-hash/-/deterministic-object-hash-2.0.2.tgz", + "integrity": "sha512-KxektNH63SrbfUyDiwXqRb1rLwKt33AmMv+5Nhsw1kqZ13SJBRTgZHtGbE+hH3a1mVW1cz+4pqSWVPAtLVXTzQ==", + "license": "MIT", + "dependencies": { + "base-64": "^1.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/devalue": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/devalue/-/devalue-5.6.2.tgz", + "integrity": "sha512-nPRkjWzzDQlsejL1WVifk5rvcFi/y1onBRxjaFMjZeR9mFpqu2gmAZ9xUB9/IEanEP/vBtGeGganC/GO1fmufg==", + "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==", + "license": "MIT", + "dependencies": { + "dequal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/diff": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/diff/-/diff-8.0.3.tgz", + "integrity": "sha512-qejHi7bcSD4hQAZE0tNAawRK1ZtafHDmMTMkrrIGgSLl7hTnQHmKCeB45xAcbfTqK2zowkM3j3bHt/4b/ARbYQ==", + "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==", + "license": "MIT", + "bin": { + "direction": "cli.js" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/dlv": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", + "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", + "license": "MIT" + }, + "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==", + "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==", + "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==", + "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==", + "license": "BSD-2-Clause", + "dependencies": { + "domelementtype": "^2.3.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/dompurify": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.3.1.tgz", + "integrity": "sha512-qkdCKzLNtrgPFP1Vo+98FRzJnBRGe4ffyCea9IwHB1fyxPOeNTHpLKYGd4Uk9xvNoH0ZoOjwZxNptyMwqrId1Q==", + "license": "(MPL-2.0 OR Apache-2.0)", + "peer": true, + "optionalDependencies": { + "@types/trusted-types": "^2.0.7" + } + }, + "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==", + "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==", + "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==", + "license": "MIT" + }, + "node_modules/enhanced-resolve": { + "version": "5.19.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.19.0.tgz", + "integrity": "sha512-phv3E1Xl4tQOShqSte26C7Fl84EwUdZsyOuSSk9qtAGyyQs2s3jJzComh+Abf4g187lUUAvH+H26omrqia2aGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.3.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/entities": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/es-module-lexer": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", + "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", + "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==", + "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==", + "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.27.3", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.3.tgz", + "integrity": "sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg==", + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.27.3", + "@esbuild/android-arm": "0.27.3", + "@esbuild/android-arm64": "0.27.3", + "@esbuild/android-x64": "0.27.3", + "@esbuild/darwin-arm64": "0.27.3", + "@esbuild/darwin-x64": "0.27.3", + "@esbuild/freebsd-arm64": "0.27.3", + "@esbuild/freebsd-x64": "0.27.3", + "@esbuild/linux-arm": "0.27.3", + "@esbuild/linux-arm64": "0.27.3", + "@esbuild/linux-ia32": "0.27.3", + "@esbuild/linux-loong64": "0.27.3", + "@esbuild/linux-mips64el": "0.27.3", + "@esbuild/linux-ppc64": "0.27.3", + "@esbuild/linux-riscv64": "0.27.3", + "@esbuild/linux-s390x": "0.27.3", + "@esbuild/linux-x64": "0.27.3", + "@esbuild/netbsd-arm64": "0.27.3", + "@esbuild/netbsd-x64": "0.27.3", + "@esbuild/openbsd-arm64": "0.27.3", + "@esbuild/openbsd-x64": "0.27.3", + "@esbuild/openharmony-arm64": "0.27.3", + "@esbuild/sunos-x64": "0.27.3", + "@esbuild/win32-arm64": "0.27.3", + "@esbuild/win32-ia32": "0.27.3", + "@esbuild/win32-x64": "0.27.3" + } + }, + "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==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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==", + "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==", + "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==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/estree-util-scope": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/estree-util-scope/-/estree-util-scope-1.0.0.tgz", + "integrity": "sha512-2CAASclonf+JFWBNJPndcOpA8EMJwa0Q8LUFJEKqXLW6+qBvbFZuF5gItbQOs/umBUkjviCSDCbBwU2cXbmrhQ==", + "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==", + "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==", + "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==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/eventemitter3": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.4.tgz", + "integrity": "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==", + "license": "MIT" + }, + "node_modules/expressive-code": { + "version": "0.41.6", + "resolved": "https://registry.npmjs.org/expressive-code/-/expressive-code-0.41.6.tgz", + "integrity": "sha512-W/5+IQbrpCIM5KGLjO35wlp1NCwDOOVQb+PAvzEoGkW1xjGM807ZGfBKptNWH6UECvt6qgmLyWolCMYKh7eQmA==", + "license": "MIT", + "dependencies": { + "@expressive-code/core": "^0.41.6", + "@expressive-code/plugin-frames": "^0.41.6", + "@expressive-code/plugin-shiki": "^0.41.6", + "@expressive-code/plugin-text-markers": "^0.41.6" + } + }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "license": "MIT" + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "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==", + "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==", + "license": "MIT", + "dependencies": { + "fontkitten": "^1.0.2" + } + }, + "node_modules/fontkitten": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/fontkitten/-/fontkitten-1.0.2.tgz", + "integrity": "sha512-piJxbLnkD9Xcyi7dWJRnqszEURixe7CrF/efBfbffe2DPyabmuIuqraruY8cXTs19QoM8VJzx47BDRVNXETM7Q==", + "license": "MIT", + "dependencies": { + "tiny-inflate": "^1.0.3" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/get-east-asian-width": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.4.0.tgz", + "integrity": "sha512-QZjmEOC+IT1uk6Rx0sX22V6uHWVwbdbxf1faPqJ1QhLdGgsRGCZoyaQBm/piRdJy/D2um6hM1UP7ZEeQ4EkP+Q==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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==", + "license": "ISC" + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/h3": { + "version": "1.15.5", + "resolved": "https://registry.npmjs.org/h3/-/h3-1.15.5.tgz", + "integrity": "sha512-xEyq3rSl+dhGX2Lm0+eFQIAzlDN6Fs0EcC4f7BNUmzaRX/PTzeuM+Tr2lHB8FoXggsQIeXLj8EDVgs5ywxyxmg==", + "license": "MIT", + "dependencies": { + "cookie-es": "^1.2.2", + "crossws": "^0.3.5", + "defu": "^6.1.4", + "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/hachure-fill": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/hachure-fill/-/hachure-fill-0.5.2.tgz", + "integrity": "sha512-3GKBOn+m2LX9iq+JC1064cSFprJY4jL1jCXTcpnfER5HYE2l/4EfWSGzkPa/ZDBmYI0ZOEj5VHV/eKnPGkHuOg==", + "license": "MIT", + "peer": true + }, + "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==", + "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==", + "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-dom": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/hast-util-from-dom/-/hast-util-from-dom-5.0.1.tgz", + "integrity": "sha512-N+LqofjR2zuzTjCPzyDUdSshy4Ma6li7p/c3pA78uTwzFgENbgbUrm2ugwsOdcjI1muO+o6Dgzp9p8WHtn/39Q==", + "license": "ISC", + "dependencies": { + "@types/hast": "^3.0.0", + "hastscript": "^9.0.0", + "web-namespaces": "^2.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==", + "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-html-isomorphic": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/hast-util-from-html-isomorphic/-/hast-util-from-html-isomorphic-2.0.0.tgz", + "integrity": "sha512-zJfpXq44yff2hmE0XmwEOzdWin5xwH+QIhMLOScpX91e/NSGPsAzNCvLQDIEPyO2TXi+lBmU6hjLIhV8MwP2kw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "hast-util-from-dom": "^5.0.0", + "hast-util-from-html": "^2.0.0", + "unist-util-remove-position": "^5.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==", + "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==", + "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==", + "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==", + "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==", + "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==", + "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==", + "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==", + "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==", + "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==", + "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==", + "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==", + "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==", + "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==", + "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==", + "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==", + "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==", + "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": "3.0.3", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-3.0.3.tgz", + "integrity": "sha512-RuMffC89BOWQoY0WKGpIhn5gX3iI54O6nRA0yC124NYVtzjmFWBIiFd8M0x+ZdX0P9R4lADg1mgP8C7PxGOWuQ==", + "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==", + "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==", + "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==", + "license": "BSD-2-Clause" + }, + "node_modules/i18next": { + "version": "23.16.8", + "resolved": "https://registry.npmjs.org/i18next/-/i18next-23.16.8.tgz", + "integrity": "sha512-06r/TitrM88Mg5FdUXAKL96dJMzgqLE5dv3ryBAra4KCwD9mJ4ndOTS95ZuymIGoE+2hzfdaMak2X11/es7ZWg==", + "funding": [ + { + "type": "individual", + "url": "https://locize.com" + }, + { + "type": "individual", + "url": "https://locize.com/i18next.html" + }, + { + "type": "individual", + "url": "https://www.i18next.com/how-to/faq#i18next-is-awesome.-how-can-i-support-the-project" + } + ], + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.23.2" + } + }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "license": "MIT", + "peer": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/import-meta-resolve": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-4.2.0.tgz", + "integrity": "sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "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==", + "license": "MIT" + }, + "node_modules/internmap": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", + "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==", + "license": "ISC", + "peer": true, + "engines": { + "node": ">=12" + } + }, + "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==", + "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==", + "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==", + "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-arrayish": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.4.tgz", + "integrity": "sha512-m6UrgzFVUYawGBh1dUsWR5M2Clqic9RVXC/9f8ceNlv2IcO9j9J/z8UoCLPqtsPBFNzEpfR3xftohbfqDx8EQA==", + "license": "MIT" + }, + "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==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-docker": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz", + "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==", + "license": "MIT", + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "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==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-inside-container": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz", + "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==", + "license": "MIT", + "dependencies": { + "is-docker": "^3.0.0" + }, + "bin": { + "is-inside-container": "cli.js" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-wsl": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.1.tgz", + "integrity": "sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw==", + "license": "MIT", + "dependencies": { + "is-inside-container": "^1.0.0" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/jiti": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz", + "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==", + "devOptional": true, + "license": "MIT", + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, + "node_modules/js-yaml": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/katex": { + "version": "0.16.28", + "resolved": "https://registry.npmjs.org/katex/-/katex-0.16.28.tgz", + "integrity": "sha512-YHzO7721WbmAL6Ov1uzN/l5mY5WWWhJBSW+jq4tkfZfsxmo1hu6frS0EOswvjBUnWE6NtjEs48SFn5CQESRLZg==", + "funding": [ + "https://opencollective.com/katex", + "https://github.com/sponsors/katex" + ], + "license": "MIT", + "dependencies": { + "commander": "^8.3.0" + }, + "bin": { + "katex": "cli.js" + } + }, + "node_modules/khroma": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/khroma/-/khroma-2.1.0.tgz", + "integrity": "sha512-Ls993zuzfayK269Svk9hzpeGUKob/sIgZzyHYdjQoAdQetRKpOLj+k/QQQ/6Qi0Yz65mlROrfd+Ev+1+7dz9Kw==", + "peer": true + }, + "node_modules/kleur": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", + "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", + "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==", + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/langium": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/langium/-/langium-3.3.1.tgz", + "integrity": "sha512-QJv/h939gDpvT+9SiLVlY7tZC3xB2qK57v0J04Sh9wpMb6MP1q8gB21L3WIo8T5P1MSMg3Ep14L7KkDCFG3y4w==", + "license": "MIT", + "peer": true, + "dependencies": { + "chevrotain": "~11.0.3", + "chevrotain-allstar": "~0.3.0", + "vscode-languageserver": "~9.0.1", + "vscode-languageserver-textdocument": "~1.0.11", + "vscode-uri": "~3.0.8" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/layout-base": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/layout-base/-/layout-base-1.0.2.tgz", + "integrity": "sha512-8h2oVEZNktL4BH2JCOI90iD1yXwL6iNW7KcCKT2QZgQJR2vbqDsldCTPRU9NifTCqHZci57XvQQ15YTu+sTYPg==", + "license": "MIT", + "peer": true + }, + "node_modules/lightningcss": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.30.2.tgz", + "integrity": "sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ==", + "devOptional": 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.30.2", + "lightningcss-darwin-arm64": "1.30.2", + "lightningcss-darwin-x64": "1.30.2", + "lightningcss-freebsd-x64": "1.30.2", + "lightningcss-linux-arm-gnueabihf": "1.30.2", + "lightningcss-linux-arm64-gnu": "1.30.2", + "lightningcss-linux-arm64-musl": "1.30.2", + "lightningcss-linux-x64-gnu": "1.30.2", + "lightningcss-linux-x64-musl": "1.30.2", + "lightningcss-win32-arm64-msvc": "1.30.2", + "lightningcss-win32-x64-msvc": "1.30.2" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.30.2.tgz", + "integrity": "sha512-BH9sEdOCahSgmkVhBLeU7Hc9DWeZ1Eb6wNS6Da8igvUwAe0sqROHddIlvU06q3WyXVEOYDZ6ykBZQnjTbmo4+A==", + "cpu": [ + "arm64" + ], + "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.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.30.2.tgz", + "integrity": "sha512-ylTcDJBN3Hp21TdhRT5zBOIi73P6/W0qwvlFEk22fkdXchtNTOU4Qc37SkzV+EKYxLouZ6M4LG9NfZ1qkhhBWA==", + "cpu": [ + "arm64" + ], + "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.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.30.2.tgz", + "integrity": "sha512-oBZgKchomuDYxr7ilwLcyms6BCyLn0z8J0+ZZmfpjwg9fRVZIR5/GMXd7r9RH94iDhld3UmSjBM6nXWM2TfZTQ==", + "cpu": [ + "x64" + ], + "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.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.30.2.tgz", + "integrity": "sha512-c2bH6xTrf4BDpK8MoGG4Bd6zAMZDAXS569UxCAGcA7IKbHNMlhGQ89eRmvpIUGfKWNVdbhSbkQaWhEoMGmGslA==", + "cpu": [ + "x64" + ], + "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.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.30.2.tgz", + "integrity": "sha512-eVdpxh4wYcm0PofJIZVuYuLiqBIakQ9uFZmipf6LF/HRj5Bgm0eb3qL/mr1smyXIS1twwOxNWndd8z0E374hiA==", + "cpu": [ + "arm" + ], + "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.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.30.2.tgz", + "integrity": "sha512-UK65WJAbwIJbiBFXpxrbTNArtfuznvxAJw4Q2ZGlU8kPeDIWEX1dg3rn2veBVUylA2Ezg89ktszWbaQnxD/e3A==", + "cpu": [ + "arm64" + ], + "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.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.30.2.tgz", + "integrity": "sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA==", + "cpu": [ + "arm64" + ], + "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.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.30.2.tgz", + "integrity": "sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w==", + "cpu": [ + "x64" + ], + "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.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.30.2.tgz", + "integrity": "sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA==", + "cpu": [ + "x64" + ], + "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.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.30.2.tgz", + "integrity": "sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ==", + "cpu": [ + "arm64" + ], + "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.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.30.2.tgz", + "integrity": "sha512-5g1yc73p+iAkid5phb4oVFMB45417DkRevRbt/El/gKXJk4jid+vPFF/AXbxn05Aky8PapwzZrdJShv5C0avjw==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lodash-es": { + "version": "4.17.23", + "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.23.tgz", + "integrity": "sha512-kVI48u3PZr38HdYz98UmfPnXl2DXrpdctLrFLCd3kOx1xUkOmpFPx7gCWWM5MPkL/fD8zb+Ph0QzjGFs4+hHWg==", + "license": "MIT", + "peer": true + }, + "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==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/lru-cache": { + "version": "11.2.6", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.6.tgz", + "integrity": "sha512-ESL2CrkS/2wTPfuend7Zhkzo2u0daGJ/A2VucJOgQ/C48S/zB8MMeMHSGKYpXhIjbPxfuezITkaBH1wqv00DDQ==", + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/magicast": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.5.2.tgz", + "integrity": "sha512-E3ZJh4J3S9KfwdjZhe2afj6R9lGIN5Pher1pF39UGrXRqq/VDaGVIGN13BjHd2u8B61hArAGOnso7nBOouW3TQ==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.0", + "@babel/types": "^7.29.0", + "source-map-js": "^1.2.1" + } + }, + "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==", + "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==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/marked": { + "version": "16.4.2", + "resolved": "https://registry.npmjs.org/marked/-/marked-16.4.2.tgz", + "integrity": "sha512-TI3V8YYWvkVf3KJe1dRkpnjs68JUPyEa5vjKrp1XEEJUAOaQc+Qj+L1qWbPd0SJuAdQkFU0h73sXXqwDYxsiDA==", + "license": "MIT", + "peer": true, + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 20" + } + }, + "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==", + "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==", + "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==", + "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-from-markdown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.2.tgz", + "integrity": "sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA==", + "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==", + "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==", + "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==", + "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==", + "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==", + "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==", + "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-math": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-math/-/mdast-util-math-3.0.0.tgz", + "integrity": "sha512-Tl9GBNeG/AhJnQM221bJR2HPvLOSnLE/T9cJI9tlc6zwQk2nPk/4f0cHkOdEixQPC/j8UtKDdITswvLAy1OZ1w==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "longest-streak": "^3.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.1.0", + "unist-util-remove-position": "^5.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==", + "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==", + "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==", + "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==", + "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==", + "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==", + "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==", + "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==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdn-data": { + "version": "2.12.2", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.12.2.tgz", + "integrity": "sha512-IEn+pegP1aManZuckezWCO+XZQDplx1366JoVhTpMpBB1sPey/SbveZQUosKiKiGYjg1wH4pMlNgXbCiYgihQA==", + "license": "CC0-1.0" + }, + "node_modules/mermaid": { + "version": "11.12.2", + "resolved": "https://registry.npmjs.org/mermaid/-/mermaid-11.12.2.tgz", + "integrity": "sha512-n34QPDPEKmaeCG4WDMGy0OT6PSyxKCfy2pJgShP+Qow2KLrvWjclwbc3yXfSIf4BanqWEhQEpngWwNp/XhZt6w==", + "license": "MIT", + "peer": true, + "dependencies": { + "@braintree/sanitize-url": "^7.1.1", + "@iconify/utils": "^3.0.1", + "@mermaid-js/parser": "^0.6.3", + "@types/d3": "^7.4.3", + "cytoscape": "^3.29.3", + "cytoscape-cose-bilkent": "^4.1.0", + "cytoscape-fcose": "^2.2.0", + "d3": "^7.9.0", + "d3-sankey": "^0.12.3", + "dagre-d3-es": "7.0.13", + "dayjs": "^1.11.18", + "dompurify": "^3.2.5", + "katex": "^0.16.22", + "khroma": "^2.1.0", + "lodash-es": "^4.17.21", + "marked": "^16.2.1", + "roughjs": "^4.6.6", + "stylis": "^4.3.6", + "ts-dedent": "^2.2.0", + "uuid": "^11.1.0" + } + }, + "node_modules/micromark": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.2.tgz", + "integrity": "sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==", + "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==", + "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": "3.0.2", + "resolved": "https://registry.npmjs.org/micromark-extension-directive/-/micromark-extension-directive-3.0.2.tgz", + "integrity": "sha512-wjcXHgk+PPdmvR58Le9d7zQYWy+vKEU9Se44p2CrCDPiLr2FMyiT4Fyb5UFKFC66wGB3kPlgD7q3TnoqPS7SZA==", + "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==", + "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==", + "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==", + "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==", + "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==", + "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==", + "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==", + "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-math": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-math/-/micromark-extension-math-3.1.0.tgz", + "integrity": "sha512-lvEqd+fHjATVs+2v/8kg9i5Q0AP2k85H0WUOwpIVvUML8BapsMvh1XAogmQjOCsLpoKRCVQqEkQBB3NhVBcsOg==", + "license": "MIT", + "dependencies": { + "@types/katex": "^0.16.0", + "devlop": "^1.0.0", + "katex": "^0.16.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==", + "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==", + "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==", + "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==", + "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==", + "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==", + "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==", + "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==", + "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==", + "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==", + "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==", + "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==", + "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==", + "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==", + "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==", + "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==", + "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==", + "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==", + "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==", + "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==", + "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==", + "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==", + "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==", + "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==", + "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==", + "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==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/mlly": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.8.0.tgz", + "integrity": "sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g==", + "license": "MIT", + "peer": true, + "dependencies": { + "acorn": "^8.15.0", + "pathe": "^2.0.3", + "pkg-types": "^1.3.1", + "ufo": "^1.6.1" + } + }, + "node_modules/mrmime": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.1.tgz", + "integrity": "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==", + "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==", + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "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/neotraverse": { + "version": "0.6.18", + "resolved": "https://registry.npmjs.org/neotraverse/-/neotraverse-0.6.18.tgz", + "integrity": "sha512-Z4SmBUweYa09+o6pG+eASabEpP6QkQ70yHj351pQoEXIs8uHbaU2DWVmzBANKgflPa47A50PtB2+NgRpQvr7vA==", + "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==", + "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==", + "license": "MIT" + }, + "node_modules/node-mock-http": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/node-mock-http/-/node-mock-http-1.0.4.tgz", + "integrity": "sha512-8DY+kFsDkNXy1sJglUfuODx1/opAGJGyrTuFqEoN90oRc2Vk0ZbD4K2qmKXBBEhZQzdKHIVfEJpDU8Ak2NJEvQ==", + "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==", + "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==", + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0" + }, + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" + } + }, + "node_modules/ofetch": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/ofetch/-/ofetch-1.5.1.tgz", + "integrity": "sha512-2W4oUZlVaqAPAil6FUg/difl6YhqhUR7x2eZY4bQCko22UXg3hptq9KLQdqFClV+Wu85UX7hNtdGTngi/1BxcA==", + "license": "MIT", + "dependencies": { + "destr": "^2.0.5", + "node-fetch-native": "^1.6.7", + "ufo": "^1.6.1" + } + }, + "node_modules/ohash": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/ohash/-/ohash-2.0.11.tgz", + "integrity": "sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==", + "license": "MIT" + }, + "node_modules/oniguruma-parser": { + "version": "0.12.1", + "resolved": "https://registry.npmjs.org/oniguruma-parser/-/oniguruma-parser-0.12.1.tgz", + "integrity": "sha512-8Unqkvk1RYc6yq2WBYRj4hdnsAxVze8i7iPfQr8e4uSP3tRv0rpZcbGUDvxfQQcdwHt/e9PrMvGCsa8OqG9X3w==", + "license": "MIT" + }, + "node_modules/oniguruma-to-es": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/oniguruma-to-es/-/oniguruma-to-es-4.3.4.tgz", + "integrity": "sha512-3VhUGN3w2eYxnTzHn+ikMI+fp/96KoRSVK9/kMTcFqj1NRDh2IhQCKvYxDnWePKRXY/AqH+Fuiyb7VHSzBjHfA==", + "license": "MIT", + "dependencies": { + "oniguruma-parser": "^0.12.1", + "regex": "^6.0.1", + "regex-recursion": "^6.0.2" + } + }, + "node_modules/p-limit": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-6.2.0.tgz", + "integrity": "sha512-kuUqqHNUqoIWp/c467RI4X6mmyuojY5jGutNU0wVTmEOOfcuwLqyMVoAi9MKi2Ak+5i9+nhmrK4ufZE8069kHA==", + "license": "MIT", + "dependencies": { + "yocto-queue": "^1.1.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-queue": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/p-queue/-/p-queue-8.1.1.tgz", + "integrity": "sha512-aNZ+VfjobsWryoiPnEApGGmf5WmNsCo9xu8dfaYamG5qaLP7ClhLN6NgsFe6SwJ2UbLEBK5dv9x8Mn5+RVhMWQ==", + "license": "MIT", + "dependencies": { + "eventemitter3": "^5.0.1", + "p-timeout": "^6.1.2" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-timeout": { + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-6.1.4.tgz", + "integrity": "sha512-MyIV3ZA/PmyBN/ud8vV9XzwTrNtR4jFrObymZYnZqMmW0zA8Z17vnT0rBgFE/TlohB+YCHqXMgZzb3Csp49vqg==", + "license": "MIT", + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/package-manager-detector": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/package-manager-detector/-/package-manager-detector-1.6.0.tgz", + "integrity": "sha512-61A5ThoTiDG/C8s8UMZwSorAGwMJ0ERVGj2OjoW5pAalsNOg15+iQiPzrLJ4jhZ1HJzmC2PIHT2oEiH3R5fzNA==", + "license": "MIT" + }, + "node_modules/pagefind": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/pagefind/-/pagefind-1.4.0.tgz", + "integrity": "sha512-z2kY1mQlL4J8q5EIsQkLzQjilovKzfNVhX8De6oyE6uHpfFtyBaqUpcl/XzJC/4fjD8vBDyh1zolimIcVrCn9g==", + "license": "MIT", + "bin": { + "pagefind": "lib/runner/bin.cjs" + }, + "optionalDependencies": { + "@pagefind/darwin-arm64": "1.4.0", + "@pagefind/darwin-x64": "1.4.0", + "@pagefind/freebsd-x64": "1.4.0", + "@pagefind/linux-arm64": "1.4.0", + "@pagefind/linux-x64": "1.4.0", + "@pagefind/windows-x64": "1.4.0" + } + }, + "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==", + "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==", + "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==", + "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==", + "license": "MIT", + "dependencies": { + "entities": "^6.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/path-data-parser": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/path-data-parser/-/path-data-parser-0.1.0.tgz", + "integrity": "sha512-NOnmBpt5Y2RWbuv0LMzsayp3lVylAHLPUTut412ZA3l+C4uw4ZVkQbjShYCQ8TCpUMdPapr4YjUqLYD6v68j+w==", + "license": "MIT", + "peer": true + }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "license": "MIT", + "peer": true + }, + "node_modules/piccolore": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/piccolore/-/piccolore-0.1.3.tgz", + "integrity": "sha512-o8bTeDWjE086iwKrROaDf31K0qC/BENdm15/uH9usSC/uZjJOKb2YGiVHfLY4GhwsERiPI1jmwI2XrA7ACOxVw==", + "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==", + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pkg-types": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.3.1.tgz", + "integrity": "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "confbox": "^0.1.8", + "mlly": "^1.7.4", + "pathe": "^2.0.1" + } + }, + "node_modules/points-on-curve": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/points-on-curve/-/points-on-curve-0.2.0.tgz", + "integrity": "sha512-0mYKnYYe9ZcqMCWhUjItv/oHjvgEsfKvnUTg8sAtnHr3GVy7rGkXCb6d5cSyqrWqL4k81b9CPg3urd+T7aop3A==", + "license": "MIT", + "peer": true + }, + "node_modules/points-on-path": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/points-on-path/-/points-on-path-0.2.1.tgz", + "integrity": "sha512-25ClnWWuw7JbWZcgqY/gJ4FQWadKxGWk+3kR/7kD0tCaDtPPMj7oHu2ToLaVhfpnHrZzYby2w6tUA0eOIuUg8g==", + "license": "MIT", + "peer": true, + "dependencies": { + "path-data-parser": "0.1.0", + "points-on-curve": "0.2.0" + } + }, + "node_modules/postcss": { + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-nested": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.2.0.tgz", + "integrity": "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==", + "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.2", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", + "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/prismjs": { + "version": "1.30.0", + "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.30.0.tgz", + "integrity": "sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/prompts": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", + "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", + "license": "MIT", + "dependencies": { + "kleur": "^3.0.3", + "sisteransi": "^1.0.5" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/property-information": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-7.1.0.tgz", + "integrity": "sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/radix3": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/radix3/-/radix3-1.1.2.tgz", + "integrity": "sha512-b484I/7b8rDEdSDKckSSBA8knMpcdsXudlE/LNL639wFoHKwLbEkQFZHWEYwDC0wa0FKUcCY+GAF73Z7wxNVFA==", + "license": "MIT" + }, + "node_modules/readdirp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-5.0.0.tgz", + "integrity": "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==", + "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==", + "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==", + "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==", + "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==", + "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==", + "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==", + "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==", + "license": "MIT" + }, + "node_modules/rehype": { + "version": "13.0.2", + "resolved": "https://registry.npmjs.org/rehype/-/rehype-13.0.2.tgz", + "integrity": "sha512-j31mdaRFrwFRUIlxGeuPXXKWQxet52RBQRvCmzl5eCefn/KGbomK5GMHNMsOJf55fgo3qw5tST5neDuarDYR2A==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "rehype-parse": "^9.0.0", + "rehype-stringify": "^10.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/rehype-expressive-code": { + "version": "0.41.6", + "resolved": "https://registry.npmjs.org/rehype-expressive-code/-/rehype-expressive-code-0.41.6.tgz", + "integrity": "sha512-aBMX8kxPtjmDSFUdZlAWJkMvsQ4ZMASfee90JWIAV8tweltXLzkWC3q++43ToTelI8ac5iC0B3/S/Cl4Ql1y2g==", + "license": "MIT", + "dependencies": { + "expressive-code": "^0.41.6" + } + }, + "node_modules/rehype-format": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/rehype-format/-/rehype-format-5.0.1.tgz", + "integrity": "sha512-zvmVru9uB0josBVpr946OR8ui7nJEdzZobwLOOqHb/OOD88W0Vk2SqLwoVOj0fM6IPCCO6TaV9CvQvJMWwukFQ==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "hast-util-format": "^1.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/rehype-katex": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/rehype-katex/-/rehype-katex-7.0.1.tgz", + "integrity": "sha512-OiM2wrZ/wuhKkigASodFoo8wimG3H12LWQaH8qSPVJn9apWKFSH3YOCtbKpBorTVw/eI7cuT21XBbvwEswbIOA==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/katex": "^0.16.0", + "hast-util-from-html-isomorphic": "^2.0.0", + "hast-util-to-text": "^4.0.0", + "katex": "^0.16.0", + "unist-util-visit-parents": "^6.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/rehype-parse": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/rehype-parse/-/rehype-parse-9.0.1.tgz", + "integrity": "sha512-ksCzCD0Fgfh7trPDxr2rSylbwq9iYDkSn8TCDmEJ49ljEUBxDVCzCHv7QNzZOfODanX4+bWQ4WZqLCRWYLfhag==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "hast-util-from-html": "^2.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "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==", + "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==", + "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==", + "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": "3.0.1", + "resolved": "https://registry.npmjs.org/remark-directive/-/remark-directive-3.0.1.tgz", + "integrity": "sha512-gwglrEQEZcZYgVyG1tQuA+h58EZfq5CSULw7J90AFuCTyib1thgHPoqQ+h9iFvU6R+vnZ5oNFQR5QKgGpk741A==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-directive": "^3.0.0", + "micromark-extension-directive": "^3.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==", + "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-math": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/remark-math/-/remark-math-6.0.0.tgz", + "integrity": "sha512-MMqgnP74Igy+S3WwnhQ7kqGlEerTETXMvJhrUzDikVZ2/uogJCb+WHUg97hK9/jcfc0dkD73s3LN8zU49cTEtA==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-math": "^3.0.0", + "micromark-extension-math": "^3.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==", + "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==", + "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==", + "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.2", + "resolved": "https://registry.npmjs.org/remark-smartypants/-/remark-smartypants-3.0.2.tgz", + "integrity": "sha512-ILTWeOriIluwEvPjv67v7Blgrcx+LZOkAUVtKI3putuhlZm84FnqDORNXPPm+HY3NdZOMhyDwZ1E+eZB/Df5dA==", + "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==", + "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/retext": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/retext/-/retext-9.0.0.tgz", + "integrity": "sha512-sbMDcpHCNjvlheSgMfEcVrZko3cDzdbe1x/e7G66dFp0Ff7Mldvi2uv6JkJQzdRcvLYE8CA8Oe8siQx8ZOgTcA==", + "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==", + "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==", + "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==", + "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/robust-predicates": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/robust-predicates/-/robust-predicates-3.0.2.tgz", + "integrity": "sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg==", + "license": "Unlicense", + "peer": true + }, + "node_modules/rollup": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.57.1.tgz", + "integrity": "sha512-oQL6lgK3e2QZeQ7gcgIkS2YZPg5slw37hYufJ3edKlfQSGGm8ICoxswK15ntSzF/a8+h7ekRy7k7oWc3BQ7y8A==", + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.57.1", + "@rollup/rollup-android-arm64": "4.57.1", + "@rollup/rollup-darwin-arm64": "4.57.1", + "@rollup/rollup-darwin-x64": "4.57.1", + "@rollup/rollup-freebsd-arm64": "4.57.1", + "@rollup/rollup-freebsd-x64": "4.57.1", + "@rollup/rollup-linux-arm-gnueabihf": "4.57.1", + "@rollup/rollup-linux-arm-musleabihf": "4.57.1", + "@rollup/rollup-linux-arm64-gnu": "4.57.1", + "@rollup/rollup-linux-arm64-musl": "4.57.1", + "@rollup/rollup-linux-loong64-gnu": "4.57.1", + "@rollup/rollup-linux-loong64-musl": "4.57.1", + "@rollup/rollup-linux-ppc64-gnu": "4.57.1", + "@rollup/rollup-linux-ppc64-musl": "4.57.1", + "@rollup/rollup-linux-riscv64-gnu": "4.57.1", + "@rollup/rollup-linux-riscv64-musl": "4.57.1", + "@rollup/rollup-linux-s390x-gnu": "4.57.1", + "@rollup/rollup-linux-x64-gnu": "4.57.1", + "@rollup/rollup-linux-x64-musl": "4.57.1", + "@rollup/rollup-openbsd-x64": "4.57.1", + "@rollup/rollup-openharmony-arm64": "4.57.1", + "@rollup/rollup-win32-arm64-msvc": "4.57.1", + "@rollup/rollup-win32-ia32-msvc": "4.57.1", + "@rollup/rollup-win32-x64-gnu": "4.57.1", + "@rollup/rollup-win32-x64-msvc": "4.57.1", + "fsevents": "~2.3.2" + } + }, + "node_modules/roughjs": { + "version": "4.6.6", + "resolved": "https://registry.npmjs.org/roughjs/-/roughjs-4.6.6.tgz", + "integrity": "sha512-ZUz/69+SYpFN/g/lUlo2FXcIjRkSu3nDarreVdGGndHEBJ6cXPdKguS8JGxwj5HA5xIbVKSmLgr5b3AWxtRfvQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "hachure-fill": "^0.5.2", + "path-data-parser": "^0.1.0", + "points-on-curve": "^0.2.0", + "points-on-path": "^0.2.1" + } + }, + "node_modules/rw": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz", + "integrity": "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==", + "license": "BSD-3-Clause", + "peer": true + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT", + "peer": true + }, + "node_modules/sax": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.4.4.tgz", + "integrity": "sha512-1n3r/tGXO6b6VXMdFT54SHzT9ytu9yr7TaELowdYpMqY/Ao7EnlQGmAQ1+RatX7Tkkdm6hONI2owqNx2aZj5Sw==", + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=11.0.0" + } + }, + "node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/sharp": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.33.5.tgz", + "integrity": "sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw==", + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "color": "^4.2.3", + "detect-libc": "^2.0.3", + "semver": "^7.6.3" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-darwin-arm64": "0.33.5", + "@img/sharp-darwin-x64": "0.33.5", + "@img/sharp-libvips-darwin-arm64": "1.0.4", + "@img/sharp-libvips-darwin-x64": "1.0.4", + "@img/sharp-libvips-linux-arm": "1.0.5", + "@img/sharp-libvips-linux-arm64": "1.0.4", + "@img/sharp-libvips-linux-s390x": "1.0.4", + "@img/sharp-libvips-linux-x64": "1.0.4", + "@img/sharp-libvips-linuxmusl-arm64": "1.0.4", + "@img/sharp-libvips-linuxmusl-x64": "1.0.4", + "@img/sharp-linux-arm": "0.33.5", + "@img/sharp-linux-arm64": "0.33.5", + "@img/sharp-linux-s390x": "0.33.5", + "@img/sharp-linux-x64": "0.33.5", + "@img/sharp-linuxmusl-arm64": "0.33.5", + "@img/sharp-linuxmusl-x64": "0.33.5", + "@img/sharp-wasm32": "0.33.5", + "@img/sharp-win32-ia32": "0.33.5", + "@img/sharp-win32-x64": "0.33.5" + } + }, + "node_modules/shiki": { + "version": "3.22.0", + "resolved": "https://registry.npmjs.org/shiki/-/shiki-3.22.0.tgz", + "integrity": "sha512-LBnhsoYEe0Eou4e1VgJACes+O6S6QC0w71fCSp5Oya79inkwkm15gQ1UF6VtQ8j/taMDh79hAB49WUk8ALQW3g==", + "license": "MIT", + "dependencies": { + "@shikijs/core": "3.22.0", + "@shikijs/engine-javascript": "3.22.0", + "@shikijs/engine-oniguruma": "3.22.0", + "@shikijs/langs": "3.22.0", + "@shikijs/themes": "3.22.0", + "@shikijs/types": "3.22.0", + "@shikijs/vscode-textmate": "^10.0.2", + "@types/hast": "^3.0.4" + } + }, + "node_modules/simple-swizzle": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.4.tgz", + "integrity": "sha512-nAu1WFPQSMNr2Zn9PGSZK9AGn4t/y97lEm+MXTtUDwfP0ksAIX4nO+6ruD9Jwut4C49SB1Ws+fbXsm/yScWOHw==", + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.3.1" + } + }, + "node_modules/sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "license": "MIT" + }, + "node_modules/sitemap": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/sitemap/-/sitemap-8.0.2.tgz", + "integrity": "sha512-LwktpJcyZDoa0IL6KT++lQ53pbSrx2c9ge41/SeLTyqy2XUNA6uR4+P9u5IVo5lPeL2arAcOKn1aZAxoYbCKlQ==", + "license": "MIT", + "dependencies": { + "@types/node": "^17.0.5", + "@types/sax": "^1.2.1", + "arg": "^5.0.0", + "sax": "^1.4.1" + }, + "bin": { + "sitemap": "dist/cli.js" + }, + "engines": { + "node": ">=14.0.0", + "npm": ">=6.0.0" + } + }, + "node_modules/sitemap/node_modules/@types/node": { + "version": "17.0.45", + "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.45.tgz", + "integrity": "sha512-w+tIMs3rq2afQdsPJlODhoUEKzFP1ayaoyl1CcnwtIlsVe7K7bA1NGm4s3PraqTLlXnbIN84zuBlxBWo1u9BLw==", + "license": "MIT" + }, + "node_modules/smol-toml": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/smol-toml/-/smol-toml-1.6.0.tgz", + "integrity": "sha512-4zemZi0HvTnYwLfrpk/CF9LOd9Lt87kAt50GnqhMpyF9U3poDAP2+iukq2bZsO/ufegbYehBkqINbsWxj4l4cw==", + "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==", + "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==", + "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==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/stream-replace-string": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/stream-replace-string/-/stream-replace-string-2.0.0.tgz", + "integrity": "sha512-TlnjJ1C0QrmxRNrON00JvaFFlNh5TTG00APw23j74ET7gkQpTASi6/L2fuiav8pzK715HXtUeClpBTw2NPSn6w==", + "license": "MIT" + }, + "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==", + "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/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==", + "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": "7.1.2", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", + "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "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==", + "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==", + "license": "MIT", + "dependencies": { + "inline-style-parser": "0.2.7" + } + }, + "node_modules/stylis": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.3.6.tgz", + "integrity": "sha512-yQ3rwFWRfwNUY7H5vpU0wfdkNSnvnJinhF9830Swlaxl03zsOjCfmX0ugac+3LtK0lYSgwL/KXc8oYL3mG4YFQ==", + "license": "MIT", + "peer": true + }, + "node_modules/svgo": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/svgo/-/svgo-4.0.0.tgz", + "integrity": "sha512-VvrHQ+9uniE+Mvx3+C9IEe/lWasXCU0nXMY2kZeLrHNICuRiC8uMPyM14UEaMOFA5mhyQqEkB02VoQ16n3DLaw==", + "license": "MIT", + "dependencies": { + "commander": "^11.1.0", + "css-select": "^5.1.0", + "css-tree": "^3.0.1", + "css-what": "^6.1.0", + "csso": "^5.0.5", + "picocolors": "^1.1.1", + "sax": "^1.4.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==", + "license": "MIT", + "engines": { + "node": ">=16" + } + }, + "node_modules/tailwindcss": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.18.tgz", + "integrity": "sha512-4+Z+0yiYyEtUVCScyfHCxOYP06L5Ne+JiHhY2IjR2KWMIWhJOYZKLSGZaP5HkZ8+bY0cxfzwDE5uOmzFXyIwxw==", + "dev": true, + "license": "MIT" + }, + "node_modules/tapable": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.0.tgz", + "integrity": "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "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==", + "license": "MIT" + }, + "node_modules/tinyexec": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.2.tgz", + "integrity": "sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/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==", + "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==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/ts-dedent": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/ts-dedent/-/ts-dedent-2.2.0.tgz", + "integrity": "sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=6.10" + } + }, + "node_modules/tsconfck": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/tsconfck/-/tsconfck-3.1.6.tgz", + "integrity": "sha512-ks6Vjr/jEw0P1gmOVwutM3B7fWxoWBL2KRDb1JfqGVawBmO5UsvmWOQFGHBPl5yxYz4eERr19E6L7NMv+Fej4w==", + "license": "MIT", + "bin": { + "tsconfck": "bin/tsconfck.js" + }, + "engines": { + "node": "^18 || >=20" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "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==", + "license": "0BSD", + "optional": true + }, + "node_modules/type-fest": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", + "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/ufo": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.3.tgz", + "integrity": "sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q==", + "license": "MIT" + }, + "node_modules/ultrahtml": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/ultrahtml/-/ultrahtml-1.6.0.tgz", + "integrity": "sha512-R9fBn90VTJrqqLDwyMph+HGne8eqY1iPfYhPzZrvKpIfwkWZbcYlfpsb8B9dTvBfpy1/hqAD7Wi8EKfP9e8zdw==", + "license": "MIT" + }, + "node_modules/uncrypto": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/uncrypto/-/uncrypto-0.1.3.tgz", + "integrity": "sha512-Ql87qFHB3s/De2ClA9e0gsnS6zXG27SkTiSJwjCc9MebbfapQfuPzumMIUMi38ezPZVNFcHI9sUIepeQfw8J8Q==", + "license": "MIT" + }, + "node_modules/undici-types": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "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==", + "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.4", + "resolved": "https://registry.npmjs.org/unifont/-/unifont-0.7.4.tgz", + "integrity": "sha512-oHeis4/xl42HUIeHuNZRGEvxj5AaIKR+bHPNegRq5LV1gdc3jundpONbjglKpihmJf+dswygdMJn3eftGIMemg==", + "license": "MIT", + "dependencies": { + "css-tree": "^3.1.0", + "ofetch": "^1.5.1", + "ohash": "^2.0.11" + } + }, + "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==", + "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==", + "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==", + "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==", + "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==", + "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==", + "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==", + "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==", + "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==", + "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==", + "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.4", + "resolved": "https://registry.npmjs.org/unstorage/-/unstorage-1.17.4.tgz", + "integrity": "sha512-fHK0yNg38tBiJKp/Vgsq4j0JEsCmgqH58HAn707S7zGkArbZsVr/CwINoi+nh3h98BRCwKvx1K3Xg9u3VV83sw==", + "license": "MIT", + "dependencies": { + "anymatch": "^3.1.3", + "chokidar": "^5.0.0", + "destr": "^2.0.5", + "h3": "^1.15.5", + "lru-cache": "^11.2.0", + "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/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "license": "MIT" + }, + "node_modules/uuid": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.0.tgz", + "integrity": "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "peer": true, + "bin": { + "uuid": "dist/esm/bin/uuid" + } + }, + "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==", + "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==", + "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==", + "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": "7.3.1", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.1.tgz", + "integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==", + "devOptional": true, + "license": "MIT", + "peer": true, + "dependencies": { + "esbuild": "^0.27.0", + "fdir": "^6.5.0", + "picomatch": "^4.0.3", + "postcss": "^8.5.6", + "rollup": "^4.43.0", + "tinyglobby": "^0.2.15" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "lightningcss": "^1.21.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vitefu": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/vitefu/-/vitefu-1.1.1.tgz", + "integrity": "sha512-B/Fegf3i8zh0yFbpzZ21amWzHmuNlLlmJT6n7bu5e+pCHUKQIfXSYokrqOBGEMMe9UG2sostKQF9mml/vYaWJQ==", + "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-beta.0" + }, + "peerDependenciesMeta": { + "vite": { + "optional": true + } + } + }, + "node_modules/vscode-jsonrpc": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-8.2.0.tgz", + "integrity": "sha512-C+r0eKJUIfiDIfwJhria30+TYWPtuHJXHtI7J0YlOmKAo7ogxP20T0zxB7HZQIFhIyvoBPwWskjxrvAtfjyZfA==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/vscode-languageserver": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/vscode-languageserver/-/vscode-languageserver-9.0.1.tgz", + "integrity": "sha512-woByF3PDpkHFUreUa7Hos7+pUWdeWMXRd26+ZX2A8cFx6v/JPTtd4/uN0/jB6XQHYaOlHbio03NTHCqrgG5n7g==", + "license": "MIT", + "peer": true, + "dependencies": { + "vscode-languageserver-protocol": "3.17.5" + }, + "bin": { + "installServerIntoExtension": "bin/installServerIntoExtension" + } + }, + "node_modules/vscode-languageserver-protocol": { + "version": "3.17.5", + "resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.5.tgz", + "integrity": "sha512-mb1bvRJN8SVznADSGWM9u/b07H7Ecg0I3OgXDuLdn307rl/J3A9YD6/eYOssqhecL27hK1IPZAsaqh00i/Jljg==", + "license": "MIT", + "peer": true, + "dependencies": { + "vscode-jsonrpc": "8.2.0", + "vscode-languageserver-types": "3.17.5" + } + }, + "node_modules/vscode-languageserver-textdocument": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.12.tgz", + "integrity": "sha512-cxWNPesCnQCcMPeenjKKsOCKQZ/L6Tv19DTRIGuLWe32lyzWhihGVJ/rcckZXJxfdKCFvRLS3fpBIsV/ZGX4zA==", + "license": "MIT", + "peer": true + }, + "node_modules/vscode-languageserver-types": { + "version": "3.17.5", + "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.17.5.tgz", + "integrity": "sha512-Ld1VelNuX9pdF39h2Hgaeb5hEZM2Z3jUrrMgWQAu82jMtZp7p3vJT3BzToKtZI7NgQssZje5o0zryOrhQvzQAg==", + "license": "MIT", + "peer": true + }, + "node_modules/vscode-uri": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.0.8.tgz", + "integrity": "sha512-AyFQ0EVmsOZOlAnxoFOGOq1SQDWAB7C6aqMGS23svWAllfOaxbuFvcT8D1i8z3Gyn8fraVeZNNmN6e9bxxXkKw==", + "license": "MIT", + "peer": true + }, + "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==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/which-pm-runs": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/which-pm-runs/-/which-pm-runs-1.1.0.tgz", + "integrity": "sha512-n1brCuqClxfFfq/Rb0ICg9giSZqCS+pLtccdag6C2HyufBrh3fBOiy9nb6ggRMvWOVH5GrdJskj5iGTZNxd7SA==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/widest-line": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-5.0.0.tgz", + "integrity": "sha512-c9bZp7b5YtRj2wOe6dlj32MK+Bx/M/d+9VB2SHM1OtsUHR0aV0tdP6DWh/iMt0kWi1t5g1Iudu6hQRNd1A4PVA==", + "license": "MIT", + "dependencies": { + "string-width": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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==", + "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/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==", + "license": "MIT" + }, + "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==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "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==", + "license": "MIT", + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/yocto-spinner": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/yocto-spinner/-/yocto-spinner-0.2.3.tgz", + "integrity": "sha512-sqBChb33loEnkoXte1bLg45bEBsOP9N1kzQh5JZNKj/0rik4zAPTNSAVPj3uQAdc6slYJ0Ksc403G2XgxsJQFQ==", + "license": "MIT", + "dependencies": { + "yoctocolors": "^2.1.1" + }, + "engines": { + "node": ">=18.19" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/yoctocolors": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yoctocolors/-/yoctocolors-2.1.2.tgz", + "integrity": "sha512-CzhO+pFNo8ajLM2d2IW/R93ipy99LWjtwblvC1RsoSUMZgyLbYFr221TnSNT7GjGdYui6P459mw9JH/g/zW2ug==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zod": { + "version": "3.25.76", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", + "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/zod-to-json-schema": { + "version": "3.25.1", + "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.1.tgz", + "integrity": "sha512-pM/SU9d3YAggzi6MtR4h7ruuQlqKtad8e9S0fmxcMi+ueAK5Korys/aWcV9LIIHTVbj01NdzxcnXSN+O74ZIVA==", + "license": "ISC", + "peerDependencies": { + "zod": "^3.25 || ^4" + } + }, + "node_modules/zod-to-ts": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/zod-to-ts/-/zod-to-ts-1.2.0.tgz", + "integrity": "sha512-x30XE43V+InwGpvTySRNz9kB7qFU8DlyEy7BsSTCHPH1R0QasMmHWZDCzYm6bVXtj/9NNJAZF3jW8rzFvH5OFA==", + "peerDependencies": { + "typescript": "^4.9.4 || ^5.0.2", + "zod": "^3" + } + }, + "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==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + } + } +} diff --git a/docs/package.json b/docs/package.json new file mode 100644 index 0000000..52894f1 --- /dev/null +++ b/docs/package.json @@ -0,0 +1,27 @@ +{ + "name": "pg-orbit-docs", + "type": "module", + "version": "2026.02.16", + "private": true, + "scripts": { + "dev": "astro dev --host 0.0.0.0 --port 3000", + "build": "astro build", + "preview": "astro preview --port 3000" + }, + "dependencies": { + "@astrojs/starlight": "^0.37.6", + "@fontsource/inter": "^5.0.0", + "@fontsource/jetbrains-mono": "^5.0.0", + "astro": "^5.17.2", + "astro-mermaid": "^1.3.1", + "katex": "^0.16.28", + "rehype-katex": "^7.0.1", + "remark-math": "^6.0.0", + "sharp": "^0.33.0" + }, + "devDependencies": { + "@tailwindcss/vite": "^4.0.0", + "tailwindcss": "^4.0.0", + "typescript": "^5.7.0" + } +} diff --git a/docs/public/favicon.svg b/docs/public/favicon.svg new file mode 100644 index 0000000..09820cb --- /dev/null +++ b/docs/public/favicon.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/docs/src/assets/pg-orbit-logo.svg b/docs/src/assets/pg-orbit-logo.svg new file mode 100644 index 0000000..80b3d4e --- /dev/null +++ b/docs/src/assets/pg-orbit-logo.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + pg_orbit + diff --git a/docs/src/content.config.ts b/docs/src/content.config.ts new file mode 100644 index 0000000..a4eec59 --- /dev/null +++ b/docs/src/content.config.ts @@ -0,0 +1,6 @@ +import { defineCollection } from "astro:content"; +import { docsSchema } from "@astrojs/starlight/schema"; + +export const collections = { + docs: defineCollection({ schema: docsSchema() }), +}; diff --git a/docs/src/content/docs/architecture/constant-chain-of-custody.mdx b/docs/src/content/docs/architecture/constant-chain-of-custody.mdx new file mode 100644 index 0000000..01c82d8 --- /dev/null +++ b/docs/src/content/docs/architecture/constant-chain-of-custody.mdx @@ -0,0 +1,143 @@ +--- +title: Constant Chain of Custody +sidebar: + order: 2 +--- + +import { Aside, Tabs, TabItem } from "@astrojs/starlight/components"; + +This is the single most critical design constraint in pg_orbit. Get it wrong and positions silently drift by kilometers. There is no runtime check that can detect this class of error after the fact. + +## The problem + +Two-Line Elements are not raw orbital measurements. They are *mean* elements produced by a differential correction process that fits observed positions against an SGP4 propagator running with a specific set of geopotential constants --- WGS-72. The mean elements absorb geodetic model biases: the eccentricity, inclination, and mean motion encode corrections that only make physical sense when propagated with the same constants used to generate them. + +Substituting WGS-84 constants into the propagator does not "upgrade" accuracy. It breaks the internal consistency of the element set. The resulting position error can exceed the natural prediction error of the TLE by an order of magnitude. + + + +## The rules + +Four rules govern constant usage across the entire codebase. No exceptions. + +### Rule 1: WGS-72 for SGP4/SDP4 propagation + +All propagation uses WGS-72 constants: $\mu$, $a_e$, $J_2$, $J_3$, $J_4$, $k_e$. These flow through sat_code's `norad_in.h` defines and are never overridden. The functions `SGP4_init()`, `SGP4()`, `SDP4_init()`, and `SDP4()` operate entirely in the WGS-72 domain. + +### Rule 2: WGS-84 for coordinate output + +Geodetic latitude, longitude, and altitude use the WGS-84 ellipsoid ($a = 6378.137$ km, $f = 1/298.257223563$). This is the modern standard for ground-station positioning, GPS receivers, and mapping services. The conversion from ECEF to geodetic in `coord_funcs.c:ecef_to_geodetic()` uses WGS-84. + +### Rule 3: Reduced TEME nutation + +The SGP4 output frame (True Equator, Mean Equinox) uses only 4 of the 106 IAU-80 nutation terms. Applying the full nutation model would "correct" for effects that SGP4 already accounts for internally, introducing error rather than removing it. + +### Rule 4: No other combination is valid + +WGS-72 for propagation, WGS-84 for output. Perigee and apogee altitudes use WGS-72 because they derive from mean elements. Geodetic altitude uses WGS-84 because it converts a physical position. There is no scenario where mixing these is correct. + +## Constant inventory + +The complete set of constants, with provenance and location in both pg_orbit and sat_code. + +### WGS-72 constants (propagation domain) + +Source: Hoots & Roehrich, "Models for Propagation of NORAD Element Sets," Spacetrack Report No. 3, 1980. + +| Constant | Symbol | Value | `types.h` | `norad_in.h` | +|----------|--------|-------|-----------|--------------| +| Gravitational parameter | $\mu$ | $398600.8\ \text{km}^3/\text{s}^2$ | `WGS72_MU` | (implicit in $k_e$) | +| Equatorial radius | $a_e$ | $6378.135\ \text{km}$ | `WGS72_AE` | `earth_radius_in_km` | +| Zonal harmonic $J_2$ | $J_2$ | $0.001082616$ | `WGS72_J2` | `xj2` | +| Zonal harmonic $J_3$ | $J_3$ | $-2.53881 \times 10^{-6}$ | `WGS72_J3` | `xj3` | +| Zonal harmonic $J_4$ | $J_4$ | $-1.65597 \times 10^{-6}$ | `WGS72_J4` | `xj4` | +| Derived rate constant | $k_e$ | $0.0743669161\ \text{min}^{-1}$ | `WGS72_KE` | `xke` | + +The rate constant $k_e$ is derived from $\mu$ and $a_e$: + +$$ +k_e = \frac{\sqrt{\mu} \times 60}{a_e^{3/2}} +$$ + +The factor of 60 converts from seconds to minutes, matching the SGP4 convention of radians per minute for mean motion. + +### WGS-84 constants (output domain) + +Source: NIMA TR8350.2, "Department of Defense World Geodetic System 1984." + +| Constant | Symbol | Value | `types.h` | +|----------|--------|-------|-----------| +| Equatorial radius | $a$ | $6378.137\ \text{km}$ | `WGS84_A` | +| Flattening | $f$ | $1/298.257223563$ | `WGS84_F` | +| Eccentricity squared | $e^2$ | $f(2 - f)$ | `WGS84_E2` | + +## Why two copies of AE? + +`types.h` carries a parallel copy of the WGS-72 constants even though sat_code defines them in `norad_in.h`. This is intentional. + +`types.h` is the single header for all pg_orbit C sources. `norad_in.h` is an internal sat_code header not meant for external consumers. The GiST index (`gist_tle.c`) and TLE accessor functions (`tle_type.c`) need $k_e$ and $a_e$ without pulling in sat_code internals. The values **must** be identical. + +The perigee and apogee altitude computations derive from mean elements: + +$$ +a_{er} = \left(\frac{k_e}{n}\right)^{2/3} \quad \text{[earth radii]} +$$ + +$$ +\text{perigee}_\text{km} = a_{er} \cdot (1 - e) \cdot a_e - a_e +$$ + +$$ +\text{apogee}_\text{km} = a_{er} \cdot (1 + e) \cdot a_e - a_e +$$ + +These **must** use WGS-72 $a_e$ (6378.135 km), not WGS-84 (6378.137 km), because $n$ is a mean motion fitted against the WGS-72 geopotential. Using the wrong radius shifts every altitude by 2 meters. The error compounds in GiST index operations where altitude-band overlap determines whether two orbits are candidates for conjunction screening. + +## Where the boundary lives + +The WGS-72/WGS-84 boundary is crossed in exactly two places in the codebase: + + + + `coord_funcs.c:ecef_to_geodetic()` converts ECEF Cartesian coordinates (derived from WGS-72 propagation through GMST rotation) to geodetic latitude, longitude, and altitude on the WGS-84 ellipsoid. This is the correct boundary --- the ECEF position is a physical location in space, and WGS-84 is the standard for expressing that location as geodetic coordinates. + + + `coord_funcs.c:observer_to_ecef()` converts a ground station's geodetic coordinates (on WGS-84, as entered by the user) to ECEF Cartesian for the topocentric transform. The observer's position is a real-world location defined in WGS-84; converting it to ECEF puts it in the same Cartesian frame as the satellite. + + + +Everything upstream of these functions operates in the WGS-72 domain. Everything downstream operates on physical positions that have already been converted. The boundary is narrow, explicit, and documented in the source comments. + +## The GMST question + +The GMST computation uses the IAU 1982 formula (Vallado Eq. 3-47): + +$$ +\text{GMST} = 67310.54841 + (876600 \times 3600 + 8640184.812866) \cdot T_{UT1} + 0.093104 \cdot T_{UT1}^2 - 6.2 \times 10^{-6} \cdot T_{UT1}^3 +$$ + +where $T_{UT1} = (JD - 2451545.0) / 36525.0$, and the result is in seconds of time, converted to radians by multiplying by $\pi / 43200$ and normalized to $[0, 2\pi)$. + +pg_orbit deliberately does **not** use a higher-precision GMST model (e.g., IAU 2000A). The SGP4 output is only accurate to the precision of its own GMST model. Applying a more precise rotation would not improve the final position and could introduce a systematic offset between the propagated TEME position and the Earth-fixed frame. + +This is the constant chain of custody in action: match the precision of the input, not the precision available in the literature. + +## Verification + +The chain of custody is verified through the Vallado 518 test vectors --- 518 reference propagations across a range of orbit types (LEO, MEO, GEO, deep-space, high-eccentricity). Every test vector must match to machine epsilon before any other development proceeds. + +If a code change causes even one vector to drift, the constant chain has been broken somewhere. The test suite is the enforcement mechanism for the design constraint. + +```sql +-- Verify against Vallado test vector (ISS-like orbit) +-- Expected: position match within 1e-8 km +SELECT eci_x(sgp4_propagate(tle, epoch + interval '720 minutes')) +FROM vallado_test_vectors +WHERE norad_id = 25544; +``` + + diff --git a/docs/src/content/docs/architecture/design-principles.mdx b/docs/src/content/docs/architecture/design-principles.mdx new file mode 100644 index 0000000..6090ca9 --- /dev/null +++ b/docs/src/content/docs/architecture/design-principles.mdx @@ -0,0 +1,181 @@ +--- +title: Design Principles +sidebar: + order: 1 +--- + +import { Aside, Tabs, TabItem, Steps } from "@astrojs/starlight/components"; + +pg_orbit is engineering software that computes physical quantities. A wrong answer delivered confidently is worse than no answer at all. The design principles that govern the extension trace directly to Margaret Hamilton's work on the Apollo guidance computer --- software that could not afford to be approximately correct. + +These principles are not aspirational. They are enforced structurally in the code. + +## Development before the fact + +Hamilton's most fundamental principle: design the system correctly from the start, rather than patching it after deployment. In pg_orbit, this manifests as the **constant chain of custody** --- the strict separation between WGS-72 constants (used for SGP4 propagation) and WGS-84 constants (used for coordinate output). + +This separation was not bolted on after a bug was found. It was the first architectural decision, made before any code was written. The `types.h` header carries both constant sets with explicit comments about which functions may use which set. + +```c +/* WGS-72 constants (for SGP4 propagation ONLY) */ +#define WGS72_MU 398600.8 /* km^3/s^2 */ +#define WGS72_AE 6378.135 /* km */ + +/* WGS-84 constants (for coordinate output ONLY) */ +#define WGS84_A 6378.137 /* km */ +#define WGS84_F (1.0 / 298.257223563) +``` + +The 2-meter difference between WGS-72 and WGS-84 equatorial radii looks insignificant. It compounds through index operations, altitude computations, and conjunction screening. Getting this wrong would not produce a crash --- it would produce subtly wrong results that pass every test except comparison with an independent reference implementation. + +See [Constant Chain of Custody](/architecture/constant-chain-of-custody/) for the full treatment. + +## Error detection by design + +The Apollo guidance computer did not wait for failures to announce themselves. It classified errors by severity and responded proportionally. pg_orbit follows the same pattern across three mechanisms. + +### The `_safe()` function variants + +Every propagation function that can fail has a `_safe()` variant that returns `NULL` instead of raising a PostgreSQL `ERROR`. This lets callers handle failure in SQL without `BEGIN/EXCEPTION` blocks: + + + + ```sql + -- Raises ERROR if TLE has decayed past validity + SELECT sgp4_propagate(tle, now()) + FROM catalog; + ``` + + + ```sql + -- Returns NULL for invalid propagations, continues scan + SELECT sgp4_propagate_safe(tle, now()) + FROM catalog + WHERE sgp4_propagate_safe(tle, now()) IS NOT NULL; + ``` + + + +### SGP4 error classification + +sat_code returns six distinct error codes. pg_orbit classifies them into two categories based on physical meaning: + +| Code | Meaning | Severity | Response | +|------|---------|----------|----------| +| -1 | Nearly parabolic orbit ($e \geq 1$) | Fatal | `ereport(ERROR)` | +| -2 | Negative semi-major axis (decayed) | Fatal | `ereport(ERROR)` | +| -3 | Orbit within Earth | Warning | `ereport(NOTICE)`, return result | +| -4 | Perigee within Earth | Warning | `ereport(NOTICE)`, return result | +| -5 | Negative mean motion | Fatal | `ereport(ERROR)` | +| -6 | Kepler equation diverged | Fatal | `ereport(ERROR)` | + +The distinction between warnings and errors is physical, not numerical. A satellite with perigee below Earth's surface is plausible during reentry --- the state vector is still mathematically valid. A negative semi-major axis means the model has broken down entirely. + +### Input validation at storage time + +TLE parsing errors are caught in `tle_in()`, not during propagation. Invalid TLEs never enter the database. A marginal TLE might parse correctly but fail during propagator initialization --- that failure surfaces at query time with a clear error message. + +## Priority-driven execution + +The Apollo computer had a priority scheduler that shed low-priority tasks under overload rather than crashing. pg_orbit applies a similar principle in pass prediction: **failures degrade gracefully instead of aborting the scan**. + +When `elevation_at_jd()` encounters a propagation error during the coarse scan, it returns $-\pi$ radians --- well below any physical horizon elevation. The scan treats this as "satellite below horizon" and continues searching. + +```c +static double +elevation_at_jd(/* ... */) +{ + int err = propagate_tle(&sat, tsince, pos, vel); + if (err < -2) /* hard errors: treat as below horizon */ + return -M_PI; + /* ... compute actual elevation ... */ +} +``` + +This matters because a TLE might be valid for the first three days of a seven-day search window and then decay past model validity. The pass finder should return the three days of valid passes, not abort the entire query. + +## Ultra-reliable software + +Hamilton defined ultra-reliable software as software that behaves correctly under all possible input combinations, including combinations the designer did not anticipate. pg_orbit achieves this through four structural guarantees. + +### Zero global mutable state + +There are no file-scope variables, no static locals, no caches. Every function computes from its arguments alone. This is not a style preference --- it is required for PostgreSQL's `PARALLEL SAFE` declaration. All 57 pg_orbit functions carry this declaration, meaning the query planner can distribute work across multiple CPU cores without coordination. + +### Fixed-size types + +All seven pg_orbit types use `STORAGE = plain` and fixed `INTERNALLENGTH`. No TOAST, no detoasting, no variable-length headers. The `tle` type is exactly 112 bytes. Direct pointer access via `PG_GETARG_POINTER(n)` --- no copies, no allocations on read. + +### Deterministic memory + +All heap allocation goes through `palloc()`/`pfree()`. No `malloc()`, no `new`, no static buffers. PostgreSQL's memory context system owns every byte, and frees it automatically when the query completes. + +### Reproducible computation + +Given the same TLE and timestamp, pg_orbit produces the same result on every platform, every time. No floating-point non-determinism from threading, no stale caches, no accumulated state from previous calls. + +## Software engineering as discipline + +Hamilton insisted that software engineering was a real engineering discipline, not an ad hoc craft. For pg_orbit, this means every equation in the codebase traces to a published, peer-reviewed source. + +The [Theory-to-Code Mapping](/architecture/theory-to-code/) page provides the complete table. A sample: + +| Equation | Source | Code | +|----------|--------|------| +| SGP4/SDP4 propagation | Hoots & Roehrich, STR#3 (1980) | `sat_code/sgp4.cpp`, `sdp4.cpp` | +| VSOP87 planetary positions | Bretagnon & Francou (1988) | `src/vsop87.c` | +| GMST computation | Vallado (2013) Eq. 3-47 | `src/coord_funcs.c:gmst_from_jd()` | +| Lambert solver | Izzo (2015) | `src/lambert.c` | +| Precession J2000 to date | Lieske et al. (1977) | `src/precession.c` | + +Every constant has a provenance. Every algorithm has a citation. If a future maintainer needs to understand why `0.40909280422232897` appears in `types.h`, the comment says "23.4392911 degrees in radians" and the design document traces it to the IAU value for the obliquity of the ecliptic at J2000. + +## Systems thinking + +Hamilton's approach to the Apollo software was holistic --- she understood that modifying one subsystem could cascade through the entire stack. pg_orbit embodies this through the **observation pipeline**, a seven-stage flow from heliocentric coordinates to topocentric azimuth and elevation. + + +1. VSOP87 heliocentric ecliptic J2000 position for the target body (AU) +2. VSOP87 heliocentric ecliptic J2000 position for Earth +3. Geocentric ecliptic = target minus Earth +4. Ecliptic-to-equatorial rotation by J2000 obliquity ($23.4392911\degree$) +5. IAU 1976 precession from J2000 to the date of observation +6. GMST for sidereal time (Vallado Eq. 3-47, IAU 1982) +7. Equatorial-to-horizontal transform for the observer's latitude and longitude + + +You cannot modify stage 4 without understanding what stage 3 produces and what stage 5 expects. You cannot swap the GMST model without understanding that the SGP4 output is only accurate to the precision of its own internal GMST --- applying a higher-precision rotation would not improve accuracy and could introduce systematic offsets. + +See [Observation Pipeline](/architecture/observation-pipeline/) for the full flow with equations. + +## The "Lauren Bug" + + + +pg_orbit defends against three categories of unexpected input that would silently produce wrong results in a naive implementation. + +### Same-body Lambert transfer + +What happens when someone computes a transfer from Earth to Earth? + +```sql +SELECT * FROM lambert_transfer(3, 3, '2028-01-01', '2028-06-01'); +``` + +The departure and arrival positions are the same body at different times. The Lambert solver would converge on a trivial solution that does not represent a physical transfer orbit. pg_orbit validates `dep_body_id != arr_body_id` and returns an error before invoking the solver. + +### Arrival before departure + +```sql +SELECT * FROM lambert_transfer(3, 4, '2029-06-15', '2028-10-01'); +``` + +A negative time of flight. The Lambert solver might converge on a mathematically valid but physically meaningless retrograde solution. pg_orbit checks `arr_time > dep_time` and returns an error. + +### Observer on the observed body + +When computing the topocentric observation of Earth (body ID 3), the geocentric vector is zero --- the observer is on the body being observed. Division by zero in the range computation. pg_orbit catches this case and returns a clear error rather than NaN or infinity propagating through the rest of the pipeline. + +These are not edge cases in the traditional sense. They are the inputs that a SQL user will inevitably produce when exploring the system with ad hoc queries, and they must produce clear errors rather than silently wrong results. diff --git a/docs/src/content/docs/architecture/memory-thread-safety.mdx b/docs/src/content/docs/architecture/memory-thread-safety.mdx new file mode 100644 index 0000000..0480898 --- /dev/null +++ b/docs/src/content/docs/architecture/memory-thread-safety.mdx @@ -0,0 +1,186 @@ +--- +title: Memory & Thread Safety +sidebar: + order: 5 +--- + +import { Aside, Tabs, TabItem } from "@astrojs/starlight/components"; + +PostgreSQL extensions run inside a shared-memory, multi-process server. A function that leaks memory degrades the entire backend. A function that uses global state cannot be parallelized. pg_orbit is designed to be a well-behaved citizen: all memory goes through PostgreSQL's allocator, and no mutable state survives between function calls. + +## Allocation strategy + +All heap allocation goes through `palloc()` / `pfree()`. No `malloc()`, no `new`, no static buffers. This is not a convention --- it is a hard requirement. PostgreSQL's memory context system tracks every allocation and frees entire contexts at transaction boundaries, query completion, or error recovery. Using `malloc()` would create memory that PostgreSQL cannot reclaim on error, leading to gradual backend bloat. + +### Single-shot propagation + +Functions like `sgp4_propagate()` and `tle_distance()` follow the simplest pattern: + +```c +double *params = palloc(sizeof(double) * N_SAT_PARAMS); + +/* Initialize and propagate */ +SGP4_init(params, &sat); +err = SGP4(tsince, &sat, params, pos, vel); + +pfree(params); +``` + +The `params` array (~92 doubles, ~736 bytes) lives in the current memory context. It is allocated before propagation and freed before the function returns. If an `ereport(ERROR)` fires between `palloc` and `pfree`, PostgreSQL's error recovery frees the current context automatically. + +### Set-returning functions + +SRF functions like `sgp4_propagate_series()`, `ground_track()`, and `predict_passes()` must maintain state across multiple calls. They use PostgreSQL's `multi_call_memory_ctx`: + +```c +typedef struct { + tle_t sat; + double params[N_SAT_PARAMS]; /* embedded, not separate allocation */ + int is_deep; + double epoch_jd; + int64 start_ts; + int64 step_usec; +} propagate_series_ctx; +``` + + + +The lifecycle: + + + + ```c + funcctx = SRF_FIRSTCALL_INIT(); + oldctx = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx); + + ctx = palloc(sizeof(propagate_series_ctx)); + /* Copy TLE and observer into ctx */ + /* Initialize propagator */ + + MemoryContextSwitchTo(oldctx); + funcctx->user_fctx = ctx; + ``` + + + ```c + funcctx = SRF_PERCALL_SETUP(); + ctx = funcctx->user_fctx; + + /* Propagate to next timestep using ctx->params */ + /* Return result or SRF_RETURN_DONE */ + ``` + + + PostgreSQL frees `multi_call_memory_ctx` automatically when the SRF completes (either by returning `SRF_RETURN_DONE` or via error recovery). No explicit cleanup code needed. + + + +### Type I/O functions + +Input functions (`tle_in`, `eci_in`, etc.) allocate the result struct with `palloc()` in the current context. PostgreSQL manages the lifecycle --- the struct may be copied into a tuple for storage or used transiently for a computation. + +```c +pg_tle *result = (pg_tle *) palloc(sizeof(pg_tle)); +/* Parse input text into result */ +PG_RETURN_POINTER(result); +``` + +## Zero global mutable state + +There are no file-scope variables, no static locals that accumulate state, no caches. Every function computes from its arguments alone. + +This guarantee has three consequences: + +### PARALLEL SAFE + +All 57 pg_orbit functions are declared `PARALLEL SAFE` in the SQL definition. This tells PostgreSQL's query planner that the function can be executed in parallel worker processes without coordination. For bulk operations like propagating 12,000 TLEs, the planner can distribute work across multiple CPU cores: + +```sql +-- PostgreSQL may parallelize this across available cores +SELECT tle_norad_id(tle), + eci_x(sgp4_propagate(tle, now())) AS x_km +FROM satellite_catalog; +``` + +If any function used global state --- even a read-only cache --- PostgreSQL would need to serialize access or copy state between workers. `PARALLEL SAFE` cannot be declared for functions with global mutable state; doing so risks data races and incorrect results. + +### No cross-session contamination + +PostgreSQL backends are long-lived processes that serve multiple sessions. A global variable written by session A persists when session B runs in the same backend. pg_orbit avoids this entirely --- no function call leaves any trace in the process state. + +### Deterministic computation + +Given the same TLE and timestamp, pg_orbit produces the same result regardless of what queries ran before, how many backends are active, or whether the function is running in a parallel worker. There is no path-dependent behavior. + +## sat_code's memory model + +sat_code itself has no global mutable state. The propagator state lives entirely in two caller-provided structures: + +| Structure | Size | Contains | Owner | +|-----------|------|----------|-------| +| `tle_t` | ~200 bytes | Parsed mean elements, identification | Caller (pg_orbit copies from `pg_tle`) | +| `params[N_SAT_PARAMS]` | ~736 bytes | Initialized propagator coefficients | Caller (pg_orbit `palloc`s this) | + +The `SGP4_init()` / `SDP4_init()` functions write into the `params` array. The `SGP4()` / `SDP4()` functions read from `params` and `tle_t`, and write position/velocity into caller-provided arrays. No internal state is retained between calls. + +This maps cleanly to PostgreSQL's per-call execution model. There is no object lifecycle to manage, no destructor to call, no persistent state to synchronize. + +## Fixed-size types + +All seven pg_orbit types are fixed-size with `STORAGE = plain`: + +| Type | Size | `ALIGNMENT` | TOAST? | +|------|------|-------------|--------| +| `tle` | 112 bytes | `double` | No | +| `eci_position` | 48 bytes | `double` | No | +| `geodetic` | 24 bytes | `double` | No | +| `topocentric` | 32 bytes | `double` | No | +| `observer` | 24 bytes | `double` | No | +| `pass_event` | 48 bytes | `double` | No | +| `heliocentric` | 24 bytes | `double` | No | + +### Why fixed-size matters + +**No TOAST overhead.** Variable-length types (varlena) carry a 4-byte header and may be compressed or moved to a secondary TOAST table. Reading a TOASTed value requires a separate heap fetch. Fixed-size types are stored inline in the tuple --- one pointer dereference, no detoasting. + +**Direct pointer access.** `PG_GETARG_POINTER(n)` returns a pointer directly into the tuple data. No copy, no allocation. The function reads the struct in place. + +**Predictable memory layout.** All types use `ALIGNMENT = double` because every struct contains `double` fields. This satisfies the strictest alignment requirement without platform-specific conditionals. + +**Binary I/O.** The `tle_recv()` / `tle_send()` functions implement binary protocol support. The fixed layout means binary transfer is a straight memory copy --- no serialization logic, no endianness concerns beyond what PostgreSQL's binary protocol handles. + +### TLE: 112 bytes vs raw text + +The TLE text format is 138+ bytes (two 69-character lines plus separator). The parsed struct is 112 bytes --- smaller than the text it came from, and it eliminates the ~10x parsing overhead that would be incurred on every propagation call if raw text were stored. + +The text representation can be reconstructed from the parsed elements via sat_code's `write_elements_in_tle_format()`. The round-trip is lossless for all fields that affect propagation. + +## Memory usage in practice + +For a typical catalog query propagating 12,000 TLEs: + +| Resource | Per-call | Peak (12K TLEs) | +|----------|----------|-----------------| +| `params` array | 736 bytes | 736 bytes (reused) | +| `tle_t` conversion | 200 bytes (stack) | 200 bytes | +| Result `pg_eci` | 48 bytes | 48 bytes (returned, then freed) | +| **Total transient** | **~1 KB** | **~1 KB** | + +The 736-byte `params` array is the largest per-call allocation. It is freed before the function returns. At no point does pg_orbit hold allocations proportional to the number of rows being processed --- each row is computed and returned independently. + + + +## Error recovery + +When `ereport(ERROR)` fires inside a pg_orbit function, PostgreSQL's error recovery mechanism: + +1. Unwinds the call stack via `longjmp` +2. Frees the current memory context (including any `palloc`'d memory) +3. Rolls back the current transaction +4. Returns an error message to the client + +Because pg_orbit uses only `palloc` and has no global state, there is nothing to clean up beyond what PostgreSQL's context system handles automatically. No file handles, no sockets, no mutex locks, no C++ destructors. The extension is always in a consistent state after error recovery. diff --git a/docs/src/content/docs/architecture/observation-pipeline.mdx b/docs/src/content/docs/architecture/observation-pipeline.mdx new file mode 100644 index 0000000..6b4e60d --- /dev/null +++ b/docs/src/content/docs/architecture/observation-pipeline.mdx @@ -0,0 +1,191 @@ +--- +title: Observation Pipeline +sidebar: + order: 3 +--- + +import { Aside, Steps, Tabs, TabItem } from "@astrojs/starlight/components"; + +When a user calls `planet_observe(5, '40.0N 105.3W 1655m'::observer, now())` to find Jupiter's position in the sky, seven coordinate transformations execute in sequence. Each stage consumes the output of the previous stage and produces input for the next. Understanding this pipeline is a prerequisite for modifying any part of it. + +## The full pipeline + +```mermaid +flowchart TD + A["VSOP87: Target heliocentric
ecliptic J2000 (AU)"] --> C["Geocentric ecliptic
target - Earth"] + B["VSOP87: Earth heliocentric
ecliptic J2000 (AU)"] --> C + C --> D["Ecliptic → Equatorial J2000
obliquity rotation"] + D --> E["RA/Dec J2000
Cartesian → spherical"] + E --> F["Precession J2000 → date
IAU 1976"] + F --> G["Sidereal time → hour angle
GMST (Vallado Eq. 3-47)"] + G --> H["Equatorial → Horizontal
az/el for observer"] + H --> I["pg_topocentric result
(az, el, range, range_rate)"] +``` + +## Stage-by-stage breakdown + + +1. **Heliocentric ecliptic position of the target** + + VSOP87 (Bretagnon & Francou, 1988) computes the target planet's position in the heliocentric ecliptic J2000 frame. The output is three Cartesian coordinates in AU. + + VSOP87 is a semi-analytical theory: it expands each coordinate as a sum of trigonometric series with polynomial time arguments. The truncated series used in pg_orbit provides ~1 arcsecond accuracy for the inner planets and ~1-2 arcseconds for the outer planets over the period 2000 BCE to 6000 CE. + + For the Sun, this stage returns $(0, 0, 0)$ --- the Sun is at the origin of heliocentric coordinates. The Sun's apparent position is computed by inverting Earth's heliocentric position. + + For the Moon, this stage is replaced by ELP2000-82B (Chapront-Touze & Chapront, 1988), which computes geocentric ecliptic coordinates directly, skipping stage 2. + + **Code**: `src/vsop87.c:GetVsop87Coor()`, `src/elp82b.c:GetElp82bCoor()` + +2. **Heliocentric ecliptic position of Earth** + + The same VSOP87 call, but for body ID 3 (Earth). This gives Earth's position in the same frame as the target. Without this, there is no way to compute where the target appears from Earth's perspective. + + **Code**: `src/vsop87.c:GetVsop87Coor()` with `body = 2` (VSOP87 uses 0-indexed: Venus=1, Earth=2, Mars=3, etc.) + +3. **Geocentric ecliptic vector** + + Subtract Earth's heliocentric position from the target's: + + $$ + \vec{r}_\text{geo} = \vec{r}_\text{target} - \vec{r}_\text{Earth} + $$ + + The result is the target's position as seen from Earth's center, still in the ecliptic J2000 frame. For the Moon, ELP2000-82B provides this directly. + + **Code**: `src/planet_funcs.c:planet_observe()`, lines computing `geo_ecl_au[3]` + +4. **Ecliptic to equatorial rotation** + + The ecliptic and equatorial planes are tilted relative to each other by the obliquity of the ecliptic. At J2000, this angle is: + + $$ + \varepsilon_0 = 23.4392911\degree = 0.40909280422232897\ \text{rad} + $$ + + The rotation is around the X-axis (the vernal equinox direction, which is shared between both frames): + + $$ + \begin{bmatrix} x_\text{equ} \\ y_\text{equ} \\ z_\text{equ} \end{bmatrix} + = \begin{bmatrix} 1 & 0 & 0 \\ 0 & \cos\varepsilon_0 & -\sin\varepsilon_0 \\ 0 & \sin\varepsilon_0 & \cos\varepsilon_0 \end{bmatrix} + \begin{bmatrix} x_\text{ecl} \\ y_\text{ecl} \\ z_\text{ecl} \end{bmatrix} + $$ + + This uses the J2000 obliquity, not the obliquity of date, because both VSOP87 and the subsequent precession model are referenced to J2000. + + **Code**: `src/planet_funcs.c:ecliptic_to_equatorial()` (via `astro_math.h`) + +5. **Precession from J2000 to date** + + The equatorial coordinate system itself rotates slowly due to lunisolar and planetary precession. Right ascension and declination at J2000 must be precessed to the epoch of observation. + + pg_orbit uses the IAU 1976 precession model (Lieske et al., 1977), which expresses the three Euler angles $\zeta_A$, $z_A$, and $\theta_A$ as cubic polynomials in centuries from J2000: + + $$ + \zeta_A = 0\overset{\prime\prime}{.}6406161 \cdot T + 0\overset{\prime\prime}{.}0000839 \cdot T^2 + 0\overset{\prime\prime}{.}0000050 \cdot T^3 + $$ + + The full rotation matrix $R_3(-z_A) \cdot R_2(\theta_A) \cdot R_3(-\zeta_A)$ transforms J2000 equatorial to equatorial of date. + + **Code**: `src/precession.c:precess_j2000_to_date()` + +6. **Sidereal time and hour angle** + + To convert from the celestial sphere to the observer's local sky, we need the observer's relationship to the vernal equinox. This requires two quantities: + + - **GMST** (Greenwich Mean Sidereal Time): the hour angle of the vernal equinox at Greenwich, computed from the Julian date using Vallado Eq. 3-47 (IAU 1982 model). + - **LST** (Local Sidereal Time): $\text{LST} = \text{GMST} + \lambda_\text{observer}$, where $\lambda$ is the observer's east longitude in radians. + - **Hour angle**: $h = \text{LST} - \alpha$, where $\alpha$ is the right ascension of date from stage 5. + + The GMST formula: + + $$ + \text{GMST}_\text{sec} = 67310.54841 + (876600 \times 3600 + 8640184.812866) \cdot T + 0.093104 \cdot T^2 - 6.2 \times 10^{-6} \cdot T^3 + $$ + + where $T = (JD - 2451545.0) / 36525.0$. The result in seconds is converted to radians: $\text{GMST}_\text{rad} = \text{GMST}_\text{sec} \times \pi / 43200$. + + **Code**: `src/sidereal_time.c:gmst_from_jd()` + +7. **Equatorial to horizontal** + + Given hour angle $h$, declination $\delta$, and observer latitude $\phi$, compute azimuth $A$ and elevation $a$: + + $$ + \sin a = \sin\phi \sin\delta + \cos\phi \cos\delta \cos h + $$ + + $$ + \tan A = \frac{-\sin h}{\cos\phi \tan\delta - \sin\phi \cos h} + $$ + + Azimuth is measured from north through east (0 = north, 90 = east, 180 = south, 270 = west). + + The result is packed into a `pg_topocentric` struct with range computed from the geocentric distance ($\text{range}_\text{km} = d_\text{AU} \times 149597870.7$). Range rate is set to 0.0 for planetary observations (velocity computation is not yet implemented for the VSOP87 pipeline). + + **Code**: `src/planet_funcs.c:observe_from_geocentric()` + + +## Pipeline variants + +The seven-stage pipeline applies to planets observed via VSOP87. Other observation targets use modified versions. + + + + Stages 1-3 are replaced by a single call to `GetElp82bCoor()`, which returns geocentric ecliptic coordinates directly. The remaining stages (4-7) are identical. + + ELP2000-82B is a semi-analytical lunar theory with ~10 arcsecond accuracy. It accounts for the principal perturbations from the Sun, but not the full set of planetary perturbations included in the DE series. + + + Each moon theory (L1.2 for Galilean moons, TASS17 for Saturn, GUST86 for Uranus, MarsSat for Mars) computes the moon's position relative to its parent planet. The pipeline: + + 1. Compute parent planet heliocentric position via VSOP87 + 2. Compute moon position relative to parent in parent-equatorial frame + 3. Transform to heliocentric ecliptic J2000 (parent offset + frame rotation) + 4. Proceed from stage 3 of the standard pipeline (geocentric ecliptic) + + + Satellites use a fundamentally different pipeline. SGP4 outputs TEME (True Equator, Mean Equinox) positions, not heliocentric ecliptic. The satellite pipeline: + + 1. SGP4/SDP4 propagation to TEME position/velocity (WGS-72) + 2. GMST rotation to ECEF + 3. ECEF to geodetic (WGS-84) or topocentric via SEZ transform + + + Stars are effectively at infinite distance. The pipeline: + + 1. J2000 catalog coordinates (RA, Dec) + 2. Precession to date (IAU 1976) + 3. Sidereal time and hour angle + 4. Equatorial to horizontal + + No VSOP87 call, no geocentric vector, no distance computation. This makes star observation the fastest pipeline --- 714K observations per second. + + + +## Why this pipeline and not another + +Several simplifications are deliberate. + + + +**No aberration correction.** Annual aberration shifts apparent positions by up to 20 arcseconds, but for observation planning (which quadrant of the sky is Jupiter in tonight?) this is irrelevant. Sub-arcsecond work should use SPICE or Skyfield with DE441. + +**No light-time iteration.** The positions returned are geometric, not apparent. Light-time corrections of a few minutes for the outer planets shift the apparent position by a fraction of an arcsecond at most --- again, below the VSOP87 accuracy floor. + +**No atmospheric refraction.** Refraction near the horizon can shift apparent elevation by half a degree. pg_orbit reports geometric elevation; the user must apply refraction corrections for their local conditions if needed. This is a deliberate choice --- refraction depends on temperature, pressure, and humidity that pg_orbit does not model. + +## Extending the pipeline + +To add a new observation target, identify which stages change: + +| New target | Stages replaced | Stages reused | +|-----------|-----------------|---------------| +| New moon theory | 1-3 (new theory + parent VSOP87) | 4-7 | +| New planet ephemeris | 1 (new theory replaces VSOP87) | 2-7 (Earth still VSOP87) | +| Near-Earth asteroid | 1 (Keplerian propagation) | 2-7 | +| Distant star | 1-3 (catalog lookup, no distance) | 5-7 (skip obliquity) | + +The modularity of the pipeline means new targets require implementing only the first few stages. The coordinate transformation machinery from stage 4 onward is shared across all targets. diff --git a/docs/src/content/docs/architecture/sgp4-integration.mdx b/docs/src/content/docs/architecture/sgp4-integration.mdx new file mode 100644 index 0000000..d268f64 --- /dev/null +++ b/docs/src/content/docs/architecture/sgp4-integration.mdx @@ -0,0 +1,218 @@ +--- +title: SGP4 Integration +sidebar: + order: 6 +--- + +import { Aside, Tabs, TabItem } from "@astrojs/starlight/components"; + +pg_orbit wraps Bill Gray's `sat_code` library (MIT license, Project Pluto) for SGP4/SDP4 propagation. This page covers why sat_code was chosen, how it integrates with PostgreSQL's build and execution model, and the error handling contract between the two codebases. + +## Why sat_code + +Three SGP4 implementations were evaluated. The choice came down to one question: which library can run inside a PostgreSQL backend without modification? + + + + **Pure C linkage.** All public functions are declared `extern "C"` in `norad.h`. The library compiles as C++ but exposes a flat C function interface: `SGP4_init()`, `SGP4()`, `SDP4_init()`, `SDP4()`, `parse_elements()`, `select_ephemeris()`. + + **No global mutable state.** The propagator state lives in a caller-allocated `double params[N_SAT_PARAMS]` array. This maps directly to PostgreSQL's `palloc`-based memory model. + + **Full SDP4.** Includes deep-space propagation with lunar/solar perturbations for GEO, Molniya, and GPS orbits. + + **MIT license.** Compatible with the PostgreSQL License. + + **Actively maintained.** Used in Bill Gray's Find_Orb production astrometry software. + + + The canonical implementation from the STR#3 revision paper. Two problems: + + 1. Written in C++ with heavy use of global state. The propagator coefficients live in file-scope variables, making it impossible to declare functions `PARALLEL SAFE`. + 2. License unclear for embedding in a PostgreSQL extension distributed as a shared library. + + + Various GitHub forks, typically C++ class hierarchies assuming an object-per-satellite lifecycle. This conflicts with PostgreSQL's per-call execution model --- you cannot persist C++ objects across function invocations without managing their lifecycle in a memory context callback, adding complexity for no benefit. + + + +## The C/C++ boundary + +sat_code is compiled as C++ but pg_orbit is a C extension. The integration works because sat_code's public API is `extern "C"`: + +``` +src/*.c --[gcc]--> .o --| +lib/sat_code/*.cpp --[g++]--> .o --|--> pg_orbit.so + -lstdc++ -lm +``` + +The Makefile compiles sat_code's `.cpp` files with `g++` and links them alongside pg_orbit's `.c` files with `-lstdc++` for the C++ runtime. This is the same pattern PostGIS uses for GEOS integration. + +### Build rules + +```makefile +# sat_code C++ sources +SAT_CODE_DIR = lib/sat_code +SAT_CODE_SRCS = $(SAT_CODE_DIR)/sgp4.cpp $(SAT_CODE_DIR)/sdp4.cpp \ + $(SAT_CODE_DIR)/deep.cpp $(SAT_CODE_DIR)/common.cpp \ + $(SAT_CODE_DIR)/basics.cpp $(SAT_CODE_DIR)/get_el.cpp \ + $(SAT_CODE_DIR)/tle_out.cpp +SAT_CODE_OBJS = $(SAT_CODE_SRCS:.cpp=.o) + +# Include sat_code headers for our C sources +PG_CPPFLAGS = -I$(SAT_CODE_DIR) + +# C++ runtime for sat_code +SHLIB_LINK += -lstdc++ -lm + +# Compile C++ with position-independent code for shared library +$(SAT_CODE_DIR)/%.o: $(SAT_CODE_DIR)/%.cpp + $(CXX) $(CXXFLAGS) -fPIC -I$(SAT_CODE_DIR) -c -o $@ $< +``` + +The `-fPIC` flag is required because the compiled objects become part of a shared library (`.so`). Without it, the linker would reject the C++ objects. + +### Header inclusion + +pg_orbit's C files include `norad.h` directly: + +```c +#include "norad.h" /* sat_code public API */ +#include "types.h" /* pg_orbit types and WGS-72/84 constants */ +``` + +The `PG_CPPFLAGS = -I$(SAT_CODE_DIR)` flag makes `norad.h` available without a path prefix. + +## The sat_code API surface + +pg_orbit uses a small subset of sat_code's public functions. + +### Initialization + +```c +int select_ephemeris(const tle_t *tle); +``` + +Returns 0 for near-earth (SGP4) or 1 for deep-space (SDP4), based on the orbital period threshold of 225 minutes. Returns -1 if the mean motion or eccentricity is out of range --- an early indicator of an invalid TLE. + +```c +void SGP4_init(double *params, const tle_t *tle); +void SDP4_init(double *params, const tle_t *tle); +``` + +Compute the propagator initialization coefficients and store them in the caller-allocated `params` array. This is the expensive step (~5x the cost of a single propagation), so pg_orbit performs it once per TLE and reuses the `params` array for SRF functions that propagate the same TLE to multiple times. + +### Propagation + +```c +int SGP4(double tsince, const tle_t *tle, const double *params, + double *pos, double *vel); +int SDP4(double tsince, const tle_t *tle, const double *params, + double *pos, double *vel); +``` + +Propagate to `tsince` minutes from epoch. Write position (km) and velocity (km/min) into caller-provided arrays. Return 0 on success or a negative error code. + + + +### TLE parsing + +```c +int parse_elements(const char *line1, const char *line2, tle_t *tle); +``` + +Parse two-line element text into a `tle_t` struct. Returns 0 on success. pg_orbit calls this in `tle_in()` to validate input at storage time. + +```c +void write_elements_in_tle_format(char *obuff, const tle_t *tle); +``` + +Reconstruct text from parsed elements. Used in `tle_out()` for display. + +## TLE struct conversion + +pg_orbit stores TLEs in its own `pg_tle` struct (112 bytes, designed for PostgreSQL tuple storage). sat_code uses `tle_t` (a larger struct with additional fields for its own purposes). The conversion between them is a field-by-field copy with no unit conversion --- both use radians, radians/minute, and Julian dates. + +```c +static void +pg_tle_to_sat_code(const pg_tle *src, tle_t *dst) +{ + memset(dst, 0, sizeof(tle_t)); + dst->epoch = src->epoch; + dst->xincl = src->inclination; + dst->xnodeo = src->raan; + dst->eo = src->eccentricity; + dst->omegao = src->arg_perigee; + dst->xmo = src->mean_anomaly; + dst->xno = src->mean_motion; + dst->xndt2o = src->mean_motion_dot; + dst->xndd6o = src->mean_motion_ddot; + dst->bstar = src->bstar; + /* ... identification fields ... */ +} +``` + +This conversion is duplicated in `sgp4_funcs.c`, `coord_funcs.c`, and `pass_funcs.c`. Each file contains its own static copy. The duplication is intentional: + +1. Each translation unit is self-contained --- no hidden coupling through shared internal functions. +2. The functions are small (under 20 lines). Binary size increase is negligible. +3. The compiler can inline them within each translation unit. +4. If the helpers ever need to diverge (e.g., `pass_funcs.c` working in km/min while `coord_funcs.c` works in km/s), they can do so independently. + +## Error codes + +sat_code returns integer error codes from `SGP4()` and `SDP4()`. pg_orbit classifies them by physical meaning and responds accordingly. + +| Code | sat_code constant | Physical meaning | pg_orbit response | +|------|-------------------|------------------|-------------------| +| 0 | --- | Normal propagation | Return result | +| -1 | `SXPX_ERR_NEARLY_PARABOLIC` | Eccentricity $\geq 1$ | `ereport(ERROR)` | +| -2 | `SXPX_ERR_NEGATIVE_MAJOR_AXIS` | Orbit has decayed | `ereport(ERROR)` | +| -3 | `SXPX_WARN_ORBIT_WITHIN_EARTH` | Entire orbit below surface | `ereport(NOTICE)`, return result | +| -4 | `SXPX_WARN_PERIGEE_WITHIN_EARTH` | Perigee below surface | `ereport(NOTICE)`, return result | +| -5 | `SXPX_ERR_NEGATIVE_XN` | Negative mean motion | `ereport(ERROR)` | +| -6 | `SXPX_ERR_CONVERGENCE_FAIL` | Kepler equation diverged | `ereport(ERROR)` | + +### The warning/error distinction + +Codes -3 and -4 are warnings, not errors. A satellite with perigee within Earth is plausible during reentry or shortly after launch --- the state vector is still mathematically valid. The `NOTICE` tells the user the situation is unusual; the result is still returned. + +Codes -1, -2, -5, and -6 indicate the propagator model has broken down. The output position would be meaningless. These raise `ereport(ERROR)`, which aborts the current query. + +### Context-dependent handling + +The error response changes based on the calling context: + +| Context | Fatal error (-1, -2, -5, -6) | Warning (-3, -4) | +|---------|------------------------------|-------------------| +| Direct propagation (`sgp4_propagate`) | `ereport(ERROR)` --- abort query | `ereport(NOTICE)` --- return result | +| Safe propagation (`sgp4_propagate_safe`) | Return `NULL` | `ereport(NOTICE)` --- return result | +| Pass prediction (`elevation_at_jd`) | Return $-\pi$ elevation --- continue scan | Ignore --- return elevation | +| SRF series (`sgp4_propagate_series`) | `ereport(ERROR)` --- abort series | `ereport(NOTICE)` --- return result | + +The pass prediction context is the most interesting. A TLE valid for part of a search window should not abort the entire pass search. Returning $-\pi$ radians (well below any physical horizon) causes the coarse scan to treat the time point as "satellite below horizon" and continue looking for passes at other times. + +## The git submodule + +sat_code is included as a git submodule at `lib/sat_code/`. This provides: + +- **Pinned version.** The submodule pointer records the exact commit. Upstream changes do not affect pg_orbit until the submodule is explicitly updated. +- **Clear provenance.** `git submodule status` shows the upstream repository (github.com/Bill-Gray/sat_code) and commit hash. +- **Easy updates.** `git submodule update --remote` pulls the latest upstream, which can then be tested against the Vallado 518 vectors before committing the update. + +### Files used from sat_code + +| File | Purpose | +|------|---------| +| `sgp4.cpp` | SGP4 near-earth propagator | +| `sdp4.cpp` | SDP4 deep-space propagator | +| `deep.cpp` | Lunar/solar perturbation routines for SDP4 | +| `common.cpp` | Shared initialization code for SGP4/SDP4 | +| `basics.cpp` | Utility functions (angle normalization, etc.) | +| `get_el.cpp` | TLE parsing (`parse_elements()`) | +| `tle_out.cpp` | TLE text reconstruction | +| `norad.h` | Public API declarations, `tle_t` struct, constants | +| `norad_in.h` | Internal constants (WGS-72 values) | + +Other sat_code files (obs_eph.cpp, sat_id.cpp, etc.) are not compiled. pg_orbit uses sat_code strictly as a propagation library, not as a satellite identification or observation planning tool. diff --git a/docs/src/content/docs/architecture/theory-to-code.mdx b/docs/src/content/docs/architecture/theory-to-code.mdx new file mode 100644 index 0000000..47513c6 --- /dev/null +++ b/docs/src/content/docs/architecture/theory-to-code.mdx @@ -0,0 +1,161 @@ +--- +title: Theory-to-Code Mapping +sidebar: + order: 4 +--- + +import { Aside, Tabs, TabItem } from "@astrojs/starlight/components"; + +Every equation in pg_orbit traces to a published, peer-reviewed source. This page provides the complete mapping between the celestial mechanics literature and the source files that implement each theory. + +If a constant, algorithm, or formula appears in the code without a citation, that is a defect to be corrected. + +## SGP4/SDP4 propagation + +The core satellite propagation theory, implemented by Bill Gray's sat_code library. + +| Theory | Source Paper | What it computes | Code location | +|--------|-------------|------------------|---------------| +| Mean element recovery | Brouwer (1959) | Original mean motion $n_0'$ and semi-major axis $a_0'$ from input TLE, removing secular $J_2$ perturbations | `sat_code/common.cpp:sxpall_common_init()` | +| Secular perturbations | Lane & Cranford (1969); Hoots & Roehrich STR#3 | Secular rates of $M$, $\omega$, and $\Omega$ due to $J_2$, $J_4$ | `sat_code/common.cpp:sxpx_common_init()` | +| Atmospheric drag | Hoots & Roehrich STR#3 | $B^*$ formulation of drag; $C_1$, $C_2$, $C_4$ coefficients; perigee-dependent $s$ parameter | `sat_code/common.cpp:sxpx_common_init()`; `sat_code/sgp4.cpp:SGP4_init()` | +| Short-period perturbations | Lane & Cranford (1969); Brouwer (1959) | Oscillatory corrections to radius, argument of latitude, node, and inclination | `sat_code/common.cpp:sxpx_posn_vel()` | +| Kepler equation | Classical | Newton-Raphson with second-order correction, bounded first step | `sat_code/common.cpp:sxpx_posn_vel()` | +| Deep-space resonance | Hujsak (1979) | Lunar/solar gravitational perturbations; geopotential resonance for 12-hour and 24-hour orbits | `sat_code/deep.cpp:Deep_dpinit()`, `Deep_dpsec()`, `Deep_dpper()` | +| Near-earth propagation | Hoots & Roehrich STR#3 | SGP4 main loop: secular + short-period + drag terms | `sat_code/sgp4.cpp:SGP4()` | +| Deep-space propagation | Hoots & Roehrich STR#3 | SDP4: SGP4 core + deep-space secular/periodic corrections | `sat_code/sdp4.cpp:SDP4()` | +| Near/deep selection | Hoots & Roehrich STR#3 | Period threshold: 225 minutes ($n < 2\pi/225$ rad/min) | `sat_code/norad.h:select_ephemeris()` | + +### Primary reference + +Hoots, F. R. & Roehrich, R. L. (1980). "Models for Propagation of NORAD Element Sets." Spacetrack Report No. 3, Aerospace Defense Command, Peterson AFB. + +This is the canonical SGP4/SDP4 reference. All subsequent implementations, including Vallado's 2006 revision, trace back to this report. + +## Coordinate transforms + +| Theory | Source | What it computes | Code location | +|--------|--------|------------------|---------------| +| GMST | Vallado (2013) Eq. 3-47; IAU 1982 | Greenwich Mean Sidereal Time from Julian date | `src/sidereal_time.c:gmst_from_jd()` | +| TEME to ECEF | Vallado (2013) | Z-axis rotation by $-\text{GMST}$; velocity cross-product correction | `src/coord_funcs.c:teme_to_ecef()` | +| Geodetic from ECEF | Bowring (1976) | Iterative latitude from ECEF Cartesian on WGS-84 | `src/coord_funcs.c:ecef_to_geodetic()` | +| Topocentric transform | Standard SEZ | ECEF range vector rotated to South-East-Zenith; azimuth from north | `src/coord_funcs.c:ecef_to_topocentric()` | +| Observer to ECEF | Geodesy standard | WGS-84 ellipsoid surface point to Cartesian | `src/coord_funcs.c:observer_to_ecef()` | +| Range rate | Dot product | Projection of relative velocity onto line-of-sight unit vector | `src/coord_funcs.c:eci_to_topocentric()` | +| Semi-major axis from $n$ | Kepler's third law | $a = (k_e / n)^{2/3}$ in earth radii | `src/tle_type.c:tle_perigee()` | +| IAU 1976 precession | Lieske et al. (1977) | Three Euler angles $\zeta_A$, $z_A$, $\theta_A$ for precession from J2000 to date | `src/precession.c:precess_j2000_to_date()` | +| Ecliptic to equatorial | IAU | X-axis rotation by obliquity $\varepsilon_0 = 23.4392911\degree$ | `src/planet_funcs.c:ecliptic_to_equatorial()` | + +### Primary references + +- Vallado, D. A. (2013). *Fundamentals of Astrodynamics and Applications*, 4th ed. Microcosm Press. +- Lieske, J. H. et al. (1977). "Expressions for the Precession Quantities Based upon the IAU (1976) System of Astronomical Constants." *Astronomy & Astrophysics*, 58, 1-16. +- Bowring, B. R. (1976). "Transformation from Spatial to Geographical Coordinates." *Survey Review*, 23, 323-327. + +## Planetary ephemerides + +| Theory | Source | Bodies | Accuracy | Code location | +|--------|--------|--------|----------|---------------| +| VSOP87 | Bretagnon & Francou (1988) | Mercury through Neptune | ~1 arcsecond | `src/vsop87.c` | +| ELP2000-82B | Chapront-Touze & Chapront (1988) | Moon | ~10 arcseconds | `src/elp82b.c` | + +### VSOP87 + +Bretagnon, P. & Francou, G. (1988). "Planetary Theories in Rectangular and Spherical Variables. VSOP87 Solutions." *Astronomy & Astrophysics*, 202, 309-315. + +pg_orbit uses the VSOP87 rectangular ecliptic J2000 variant. The truncated coefficient tables provide full accuracy within the validity range of the theory (roughly 4000 BCE to 8000 CE for the inner planets, with degradation for the outer planets beyond $\pm$2000 years from J2000). + +### ELP2000-82B + +Chapront-Touze, M. & Chapront, J. (1988). "ELP 2000-85: A Semi-Analytical Lunar Ephemeris Adequate for Historical Times." *Astronomy & Astrophysics*, 190, 342-352. + +The 82B revision is the version implemented. It provides geocentric ecliptic coordinates for the Moon, accounting for the principal perturbations from the Sun but not the complete set of planetary perturbations available in modern lunar ephemerides like DE421. + +## Planetary moon theories + +| Theory | Source | Moons | Accuracy | Code location | +|--------|--------|-------|----------|---------------| +| L1.2 | Lieske (1998) | Io, Europa, Ganymede, Callisto | ~1 arcsecond | `src/l12.c` | +| TASS17 | Vienne & Duriez (1995) | Mimas through Iapetus | ~1-5 arcseconds | `src/tass17.c` | +| GUST86 | Laskar & Jacobson (1987) | Miranda through Oberon | ~5-10 arcseconds | `src/gust86.c` | +| MarsSat | Jacobson (2010) | Phobos, Deimos | ~1 arcsecond | `src/marssat.c` | + +### References + +- Lieske, J. H. (1998). "Galilean Satellites of Jupiter." *Astronomy & Astrophysics Supplement Series*, 129, 205-217. +- Vienne, A. & Duriez, L. (1995). "TASS1.7: An Analytical Theory of the Motion of the Main Satellites of Saturn." *Astronomy & Astrophysics*, 297, 588-605. +- Laskar, J. & Jacobson, R. A. (1987). "GUST86: An Analytical Ephemeris of the Uranian Satellites." *Astronomy & Astrophysics*, 188, 212-224. +- Jacobson, R. A. (2010). "The Orbits and Masses of the Martian Satellites and the Libration of Phobos." *Astronomical Journal*, 139, 668-679. + +## Transfer orbits + +| Theory | Source | What it computes | Code location | +|--------|--------|------------------|---------------| +| Lambert solver | Izzo (2015) | Transfer velocity vectors given two positions and time of flight | `src/lambert.c` | +| Keplerian propagation | Classical | Two-body elliptic/hyperbolic orbit from elements | `src/elliptic_to_rectangular.c` | + +### References + +- Izzo, D. (2015). "Revisiting Lambert's Problem." *Celestial Mechanics and Dynamical Astronomy*, 121, 1-15. + +The Izzo solver uses Householder iterations for fast convergence and handles both short-way and long-way transfers. pg_orbit uses the prograde (short-way) solution by default. + +## Radio emission + +| Theory | Source | What it computes | Code location | +|--------|--------|------------------|---------------| +| Carr source regions | Carr et al. (1983) | Jupiter-Io decametric burst probability from CML and Io phase | `src/radio_funcs.c` | +| CML computation | Standard | Jupiter System III Central Meridian Longitude | `src/radio_funcs.c` | + +### Reference + +Carr, T. D. et al. (1983). "Phenomenology of Magnetospheric Radio Emissions." *Physics of the Jovian Magnetosphere*, Cambridge University Press, 226-284. + +## Vallado reference vectors + +The Vallado 518 test vectors are the definitive verification dataset for SGP4 implementations. Each row specifies a NORAD ID, minutes since epoch, and expected position/velocity in TEME. + + + +Sample from the verification suite: + +``` +# NORAD 00005 (Vanguard 1) - LEO, low eccentricity +# Minutes: 0.00 Expected X: 7022.465290 Y: -1400.082967 Z: 0.039550 +# Minutes: 360.00 Expected X: -7154.031380 Y: -3783.176825 Z: -2073.655980 + +# NORAD 29238 (GPS BIIR-11) - MEO, near-circular +# Minutes: 0.00 Expected X: -22503.132440 Y: 14513.963880 Z: 180.989390 + +# NORAD 28350 (Galaxy 15) - GEO, deep-space SDP4 +# Minutes: 0.00 Expected X: -33110.816260 Y: 26044.993650 Z: -20.725400 +``` + +These vectors cover the full range of orbit types that pg_orbit handles: LEO (SGP4), MEO (SGP4), GEO (SDP4), high-eccentricity Molniya (SDP4), and deep-space GPS (SDP4). Any implementation that matches all 518 vectors is functionally equivalent to the Vallado reference. + +## Source file index + +A quick reference for finding the implementation of a specific theory. + +| Source file | Theory/Function | Lines (approx) | +|-------------|----------------|-----------------| +| `src/vsop87.c` | VSOP87 planet positions | ~3000 (coefficient tables) | +| `src/elp82b.c` | ELP2000-82B Moon position | ~2000 (coefficient tables) | +| `src/l12.c` | L1.2 Galilean moons | ~800 | +| `src/tass17.c` | TASS17 Saturn moons | ~1200 | +| `src/gust86.c` | GUST86 Uranus moons | ~600 | +| `src/marssat.c` | MarsSat Mars moons | ~400 | +| `src/precession.c` | IAU 1976 precession | ~60 | +| `src/sidereal_time.c` | GMST computation | ~40 | +| `src/lambert.c` | Izzo Lambert solver | ~300 | +| `src/coord_funcs.c` | Coordinate transforms | ~650 | +| `src/pass_funcs.c` | Pass prediction algorithm | ~550 | +| `src/gist_tle.c` | GiST altitude-band index | ~400 | +| `src/planet_funcs.c` | Observation pipeline | ~250 | +| `src/radio_funcs.c` | Jupiter radio emission | ~200 | +| `sat_code/sgp4.cpp` | SGP4 near-earth propagator | ~300 | +| `sat_code/sdp4.cpp` | SDP4 deep-space propagator | ~200 | +| `sat_code/deep.cpp` | Deep-space perturbations | ~800 | +| `sat_code/common.cpp` | Shared SGP4/SDP4 initialization | ~250 | diff --git a/docs/src/content/docs/getting-started/installation.mdx b/docs/src/content/docs/getting-started/installation.mdx new file mode 100644 index 0000000..779e70f --- /dev/null +++ b/docs/src/content/docs/getting-started/installation.mdx @@ -0,0 +1,116 @@ +--- +title: Installation +sidebar: + order: 2 +--- + +import { Tabs, TabItem, Steps, Aside } from "@astrojs/starlight/components"; + + + + The fastest way to get pg_orbit running. The Docker image ships PostgreSQL 17 with pg_orbit pre-compiled. + + + 1. Pull the image: + ```bash + docker pull git.supported.systems/warehack.ing/pg_orbit:pg17 + ``` + + 2. Start the container: + ```bash + docker run -d --name pg_orbit \ + -e POSTGRES_PASSWORD=orbit \ + -p 5499:5432 \ + git.supported.systems/warehack.ing/pg_orbit:pg17 + ``` + + 3. Connect and enable the extension: + ```bash + psql -h localhost -p 5499 -U postgres -c "CREATE EXTENSION pg_orbit;" + ``` + + + + + + + Requires PostgreSQL 17 development headers and a C/C++ toolchain. + + + 1. Clone the repository: + ```bash + git clone https://git.supported.systems/warehack.ing/pg_orbit.git + cd pg_orbit + git submodule update --init + ``` + + 2. Build and install: + ```bash + make PG_CONFIG=/usr/bin/pg_config + sudo make install PG_CONFIG=/usr/bin/pg_config + ``` + + 3. Enable in your database: + ```sql + CREATE EXTENSION pg_orbit; + ``` + + 4. Verify installation: + ```sql + SELECT planet_observe(5, '40.0N 105.3W 1655m'::observer, now()); + ``` + + + + + + + For integration with existing PostgreSQL-based applications. + + ```yaml + services: + db: + image: git.supported.systems/warehack.ing/pg_orbit:pg17 + environment: + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-orbit} + POSTGRES_DB: ${POSTGRES_DB:-orbit} + ports: + - "${PGPORT:-5499}:5432" + volumes: + - pgdata:/var/lib/postgresql/data + + volumes: + pgdata: + ``` + + Then: + ```bash + docker compose up -d + psql -h localhost -p 5499 -U postgres -d orbit -c "CREATE EXTENSION pg_orbit;" + ``` + + + +## Running the test suite + +If building from source, the regression tests verify all 57 functions across 11 test suites: + +```bash +make installcheck PG_CONFIG=/usr/bin/pg_config +``` + +This runs the tests listed in the `REGRESS` variable: TLE parsing, SGP4 propagation, coordinate transforms, pass prediction, GiST indexing, convenience functions, star observation, Keplerian propagation, planet observation, moon observation, and Lambert transfers. + +## Upgrading from v0.1.0 + +If you have pg_orbit 0.1.0 installed (satellite-only), upgrade to 0.2.0: + +```sql +ALTER EXTENSION pg_orbit UPDATE TO '0.2.0'; +``` + +This adds all solar system functions (planets, moons, stars, comets, radio, Lambert transfers) while preserving your existing TLE data and satellite functions. diff --git a/docs/src/content/docs/getting-started/quick-start.mdx b/docs/src/content/docs/getting-started/quick-start.mdx new file mode 100644 index 0000000..90b4de6 --- /dev/null +++ b/docs/src/content/docs/getting-started/quick-start.mdx @@ -0,0 +1,115 @@ +--- +title: Quick Start +sidebar: + order: 3 +--- + +import { Steps, Aside, Tabs, TabItem } from "@astrojs/starlight/components"; + +Five queries that show what pg_orbit can do. Each builds on the previous — from a single planet observation to planning an interplanetary trajectory. + + + + +1. **Where is Jupiter right now?** + + The `observer` type takes geodetic coordinates as a compact string. This observer is in Boulder, Colorado at 1655m elevation. + + ```sql + SELECT topo_azimuth(t) AS az, + topo_elevation(t) AS el, + topo_range(t) / 149597870.7 AS distance_au + FROM planet_observe(5, '40.0N 105.3W 1655m'::observer, now()) t; + ``` + + Body ID 5 is Jupiter (the VSOP87 convention: 1=Mercury through 8=Neptune). The result gives azimuth and elevation in degrees, plus range in AU. + +2. **What's the entire solar system doing?** + + Use `generate_series` to loop over all 8 planets and compute their heliocentric positions: + + ```sql + SELECT body_id, + CASE body_id + WHEN 1 THEN 'Mercury' WHEN 2 THEN 'Venus' + WHEN 3 THEN 'Earth' WHEN 4 THEN 'Mars' + WHEN 5 THEN 'Jupiter' WHEN 6 THEN 'Saturn' + WHEN 7 THEN 'Uranus' WHEN 8 THEN 'Neptune' + END AS name, + round(helio_distance(planet_heliocentric(body_id, now()))::numeric, 4) AS distance_au + FROM generate_series(1, 8) AS body_id; + ``` + + One query, eight planets, heliocentric distances in AU. No loops, no external libraries. + +3. **Predict ISS passes over your location** + + First, define a TLE and observer. Then predict all passes in the next 24 hours above 10 degrees elevation: + + ```sql + WITH iss AS ( + SELECT '1 25544U 98067A 24001.50000000 .00016717 00000-0 10270-3 0 9025 + 2 25544 51.6400 208.9163 0006703 30.1694 61.7520 15.50100486 00001'::tle AS tle + ) + SELECT pass_aos(p) AS rise_time, + pass_max_el(p) AS max_elevation, + pass_los(p) AS set_time + FROM iss, predict_passes(tle, '40.0N 105.3W 1655m'::observer, + now(), now() + interval '24 hours', 10.0) p; + ``` + + The `10.0` parameter is the minimum elevation filter in degrees. `predict_passes` returns a set of `pass_event` records with AOS, TCA, and LOS times plus azimuth data. + +4. **When will Jupiter produce radio bursts tonight?** + + Jupiter emits powerful decametric radio bursts when Io is in certain orbital positions relative to Jupiter's Central Meridian Longitude. Predict the best windows: + + ```sql + SELECT t, + round(io_phase_angle(t)::numeric, 1) AS io_phase, + round(jupiter_cml('40.0N 105.3W 1655m'::observer, t)::numeric, 1) AS cml, + round(jupiter_burst_probability( + io_phase_angle(t), + jupiter_cml('40.0N 105.3W 1655m'::observer, t) + )::numeric, 3) AS burst_prob + FROM generate_series( + now(), + now() + interval '12 hours', + interval '10 minutes' + ) AS t + WHERE jupiter_burst_probability( + io_phase_angle(t), + jupiter_cml('40.0N 105.3W 1655m'::observer, t) + ) > 0.3; + ``` + + This scans the next 12 hours in 10-minute steps and filters for windows where burst probability exceeds 30%. The underlying model uses the Carr et al. (1983) source regions A, B, C, and D. + +5. **Plan an Earth-Mars transfer** + + Use the Lambert solver to find the transfer orbit for a given departure and arrival date: + + ```sql + SELECT round(c3_departure::numeric, 2) AS c3_depart_km2s2, + round(c3_arrival::numeric, 2) AS c3_arrive_km2s2, + round(tof_days::numeric, 1) AS flight_days, + round(transfer_sma::numeric, 4) AS sma_au + FROM lambert_transfer( + 3, 4, -- Earth to Mars + '2028-10-01'::timestamptz, -- departure + '2029-06-15'::timestamptz -- arrival + ); + ``` + + Body IDs 3 (Earth) and 4 (Mars). The result gives departure C3 (the energy you need to leave Earth), arrival C3, time of flight, and the transfer orbit's semi-major axis. For a full pork chop plot, wrap this in a `CROSS JOIN` of departure and arrival date ranges — see the [Interplanetary Trajectories](/guides/interplanetary-trajectories/) guide. + + +## Next steps + +You've seen the five domains pg_orbit covers. For deeper dives: + +- **[Tracking Satellites](/guides/tracking-satellites/)** — batch observation, conjunction screening, pass prediction workflows +- **[Observing the Solar System](/guides/observing-solar-system/)** — "what's up tonight?" queries, rise/set times, conjunctions +- **[The SQL Advantage](/workflow/sql-advantage/)** — why doing this in SQL changes what's possible diff --git a/docs/src/content/docs/getting-started/what-is-pg-orbit.mdx b/docs/src/content/docs/getting-started/what-is-pg-orbit.mdx new file mode 100644 index 0000000..e28b79a --- /dev/null +++ b/docs/src/content/docs/getting-started/what-is-pg-orbit.mdx @@ -0,0 +1,63 @@ +--- +title: What is pg_orbit? +sidebar: + order: 1 +--- + +import { Card, CardGrid, Aside } from "@astrojs/starlight/components"; + +pg_orbit is a PostgreSQL extension that moves orbital mechanics computation inside your database. Instead of computing satellite positions in Python, planet coordinates in C++, or transfer orbits in MATLAB and then importing the results — the computation happens where your data already lives. + +## The "PostGIS for space" analogy + +PostGIS added spatial awareness to PostgreSQL — suddenly your database understood geometry, distance, and containment. pg_orbit does the same for celestial mechanics. Your database understands orbits, observation geometry, and the relationships between objects in the solar system. You can JOIN orbital computation results with any other table, filter with WHERE clauses, and let PostgreSQL's query planner parallelize the work. + +## What it covers + +| Domain | Theory | Key Functions | Accuracy | +|---|---|---|---| +| Satellites | SGP4/SDP4 (Brouwer, 1959) | `observe()`, `predict_passes()` | ~1 km (LEO, fresh TLE) | +| Planets | VSOP87 (Bretagnon, 1988) | `planet_observe()`, `planet_heliocentric()` | ~1 arcsecond | +| Sun | VSOP87 (Earth vector, inverted) | `sun_observe()` | ~1 arcsecond | +| Moon | ELP2000-82B (Chapront, 1988) | `moon_observe()` | ~10 arcseconds | +| Planetary moons | L1.2, TASS17, GUST86, MarsSat | `galilean_observe()`, etc. | ~1-10 arcseconds | +| Stars | J2000 catalog + precession | `star_observe()` | Limited by catalog | +| Comets/asteroids | Two-body Keplerian | `kepler_propagate()`, `comet_observe()` | Varies with eccentricity | +| Jupiter radio | Carr et al. (1983) sources | `jupiter_burst_probability()` | Empirical probability | +| Transfers | Lambert (Izzo, 2015) | `lambert_transfer()`, `lambert_c3()` | Ballistic two-body | + +## Who it's for + + + + You already have TLEs in PostgreSQL. Now your database can propagate them, + predict passes, and screen for conjunctions without leaving SQL. Batch + 12,000 observations in 17ms. + + + Plan observation sessions entirely in SQL. "What planets are above 20 + degrees tonight?" is a single query. Jupiter radio burst prediction + replaces the Windows-only Radio Jupiter Pro. + + + Generate pork chop plots for interplanetary transfers as SQL CROSS JOINs. + The Lambert solver handles 800,000 solutions per second. Compare transfer + energies across launch windows without writing a line of Python. + + + +## What pg_orbit is NOT + + + +**Not a GUI.** pg_orbit returns numbers. Use Stellarium, GPredict, or STK for visualization. Use any plotting library to render its output. + +**Not sub-arcsecond.** VSOP87 is accurate to about 1 arcsecond — sufficient for observation planning and visual astronomy, but not for dish pointing at GHz frequencies or precision astrometry. For that, use SPICE or Skyfield with DE441 ephemerides. + +**Not a TLE source.** Bring your own TLEs from Space-Track, CelesTrak, or any other provider. pg_orbit parses and propagates them; it doesn't fetch them. + +**Not a replacement for SPICE.** No BSP kernel support, no light-time iteration, no aberration corrections at the IAU 2000A level. pg_orbit trades those last few milliarcseconds of accuracy for the ability to run computations at SQL speed, in parallel, joined with your other data. + +**Not a full mission design tool.** The Lambert solver handles ballistic two-body transfers — no low-thrust trajectories, no gravity assists, no multi-body optimization. For full mission design, use GMAT or poliastro. diff --git a/docs/src/content/docs/guides/comets-asteroids.mdx b/docs/src/content/docs/guides/comets-asteroids.mdx new file mode 100644 index 0000000..97a03bc --- /dev/null +++ b/docs/src/content/docs/guides/comets-asteroids.mdx @@ -0,0 +1,269 @@ +--- +title: Comets and Asteroids +sidebar: + order: 5 +--- + +import { Steps, Aside, Tabs, TabItem } from "@astrojs/starlight/components"; + +pg_orbit propagates comets and asteroids using two-body Keplerian mechanics. You provide six classical orbital elements from the Minor Planet Center (MPC) or any other source, and pg_orbit computes the body's heliocentric position at any time. Combined with Earth's position from VSOP87, you can observe the body from any location on Earth. + +## How you do it today + +Tracking comets and asteroids typically involves: + +- **JPL Small-Body Database (SBDB)**: Look up an object, get its orbital elements, request an ephemeris. One object at a time, web-based. +- **Find_Orb**: Fit orbits from observations and propagate forward. Powerful but desktop-only, primarily for orbit determination rather than ephemeris computation. +- **Skyfield**: Can propagate comets from MPC elements, but you need to load a planetary ephemeris for the Earth's position and write the observation pipeline yourself. +- **Minor Planet Center (MPC)**: Publishes orbital elements for over 1.3 million objects. Getting batch ephemerides means downloading elements and running them through your own propagation code. + +The pattern is familiar: download elements, propagate in Python or C, transform to observer coordinates, and import results into your database. + +## What changes with pg_orbit + +Two functions handle comet/asteroid computation: + +| Function | What it does | +|---|---| +| `kepler_propagate(q, e, i, omega, Omega, T, time)` | Propagates orbital elements to a heliocentric position (AU) | +| `comet_observe(q, e, i, omega, Omega, T, ex, ey, ez, observer, time)` | Full observation pipeline: propagate + geocentric transform + topocentric | + +`kepler_propagate()` solves Kepler's equation for elliptic (e < 1), parabolic (e = 1), and hyperbolic (e > 1) orbits. The solver handles all three cases with appropriate numerical methods. + +`comet_observe()` wraps the full chain: propagate the comet's position, subtract Earth's heliocentric position, and transform to horizon coordinates. You supply Earth's position as three floats (ecliptic J2000, AU) because you might want to compute it once and reuse it across many comets. + +The parameters map directly to MPC orbital element format: + +| Parameter | MPC field | Units | +|---|---|---| +| `q` | Perihelion distance | AU | +| `e` | Eccentricity | dimensionless | +| `i` | Inclination | degrees | +| `omega` | Argument of perihelion | degrees | +| `Omega` | Longitude of ascending node | degrees | +| `T` | Perihelion time | Julian date | + +## What pg_orbit does not replace + + + +- **No perturbations.** Jupiter alone can shift a comet's position by degrees over a few years. Two-body propagation is most accurate near perihelion, within a few months of the elements' epoch. +- **No non-gravitational forces.** Comet outgassing produces accelerations not captured by Keplerian mechanics. For long-period comets far from the Sun, this is negligible. For short-period comets near perihelion, it matters. +- **No magnitude estimation.** pg_orbit returns position only. Comet brightness depends on heliocentric distance, geocentric distance, and a magnitude slope parameter that varies per comet. +- **No orbit determination.** pg_orbit propagates known orbits. It does not fit orbits from observations. + +For MPC elements less than a few months old, two-body propagation is typically accurate to a few arcminutes for asteroids and tens of arcminutes for comets. Fresh elements give better results. + +## Try it + +### Circular orbit sanity check + +A body in a circular orbit at 1 AU with all angles zero should return to its starting position after one year: + +```sql +-- At perihelion (T=0), position should be (1, 0, 0) AU +SELECT round(helio_x(kepler_propagate( + 1.0, 0.0, 0.0, 0.0, 0.0, + 2451545.0, -- J2000.0 + '2000-01-01 12:00:00+00'))::numeric, 6) AS x, + round(helio_y(kepler_propagate( + 1.0, 0.0, 0.0, 0.0, 0.0, + 2451545.0, + '2000-01-01 12:00:00+00'))::numeric, 6) AS y; +``` + +At time = perihelion, the position is exactly (q, 0, 0) in the orbital plane. After a quarter orbit (~91 days), it moves to approximately (0, 1, 0). + +### Eccentric elliptic orbit + +An orbit with e=0.5 and q=0.5 AU has a semi-major axis of 1.0 AU and the same period as Earth, but a very different shape: + +```sql +-- Position over one orbit +SELECT t::date AS date, + round(helio_distance(kepler_propagate( + 0.5, 0.5, 0.0, 0.0, 0.0, + 2451545.0, t))::numeric, 4) AS dist_au +FROM generate_series( + '2000-01-01 12:00:00+00'::timestamptz, + '2001-01-01 12:00:00+00'::timestamptz, + interval '30 days' +) AS t; +``` + +The distance ranges from 0.5 AU (perihelion) to 1.5 AU (aphelion). This is the classic comet behavior: fast and close to the Sun at perihelion, slow and distant at aphelion. + +### Inclined orbit + +Orbital inclination rotates the orbital plane out of the ecliptic: + +```sql +-- A polar orbit (i=90 deg) at 1 AU +SELECT round(helio_x(kepler_propagate( + 1.0, 0.0, 90.0, 0.0, 0.0, + 2451545.0, + '2000-01-01 12:00:00+00'))::numeric, 6) AS x, + round(helio_z(kepler_propagate( + 1.0, 0.0, 90.0, 0.0, 0.0, + 2451545.0, + '2000-01-01 12:00:00+00'))::numeric, 6) AS z; +``` + +At perihelion, the position is still along the node line (x-axis) regardless of inclination. The inclination only shows up when the body moves away from the node. + +### Hyperbolic orbit + +Interstellar objects like 'Oumuamua travel on hyperbolic trajectories (e > 1): + +```sql +-- Hyperbolic orbit: e=1.5, q=1.0 AU +-- At perihelion +SELECT round(helio_x(kepler_propagate( + 1.0, 1.5, 0.0, 0.0, 0.0, + 2451545.0, + '2000-01-01 12:00:00+00'))::numeric, 6) AS x_at_perihelion; + +-- 6 months later: body is receding rapidly +SELECT round(helio_distance(kepler_propagate( + 1.0, 1.5, 0.0, 0.0, 0.0, + 2451545.0, + '2000-07-01 12:00:00+00'))::numeric, 2) AS dist_6mo; +``` + +The body approaches from infinity, swings past the Sun at perihelion distance, and departs on a hyperbola. + +### Near-parabolic comet + +Many long-period comets have eccentricities very close to 1.0. pg_orbit handles the parabolic case (e=1.0 exactly) with a dedicated Barker equation solver: + +```sql +SELECT round(helio_x(kepler_propagate( + 1.0, 1.0, 0.0, 0.0, 0.0, + 2451545.0, + '2000-01-01 12:00:00+00'))::numeric, 6) AS x_parabolic; +``` + +### Track a comet with real MPC elements + +Here is how you would observe a comet using elements from the Minor Planet Center. This example uses hypothetical elements for a Halley-type orbit: + + +1. **Get Earth's heliocentric position at the observation time:** + + ```sql + SELECT helio_x(planet_heliocentric(3, '2024-06-15 04:00:00+00')) AS ex, + helio_y(planet_heliocentric(3, '2024-06-15 04:00:00+00')) AS ey, + helio_z(planet_heliocentric(3, '2024-06-15 04:00:00+00')) AS ez; + ``` + +2. **Observe the comet using all parameters together:** + + ```sql + WITH earth AS ( + SELECT planet_heliocentric(3, '2024-06-15 04:00:00+00') AS pos + ) + SELECT round(topo_azimuth(comet_observe( + 0.587, 0.967, 162.3, 111.3, 58.4, 2446467.4, + helio_x(pos), helio_y(pos), helio_z(pos), + '40.0N 105.3W 1655m'::observer, + '2024-06-15 04:00:00+00'))::numeric, 1) AS az, + round(topo_elevation(comet_observe( + 0.587, 0.967, 162.3, 111.3, 58.4, 2446467.4, + helio_x(pos), helio_y(pos), helio_z(pos), + '40.0N 105.3W 1655m'::observer, + '2024-06-15 04:00:00+00'))::numeric, 1) AS el, + round(topo_range(comet_observe( + 0.587, 0.967, 162.3, 111.3, 58.4, 2446467.4, + helio_x(pos), helio_y(pos), helio_z(pos), + '40.0N 105.3W 1655m'::observer, + '2024-06-15 04:00:00+00'))::numeric, 0) AS range_km + FROM earth; + ``` + + The orbital elements are: q=0.587 AU, e=0.967, i=162.3 deg, omega=111.3 deg, Omega=58.4 deg, T=JD 2446467.4. + + +### Store a comet catalog + +For batch operations, store orbital elements in a table: + +```sql +CREATE TABLE comets ( + designation text PRIMARY KEY, + name text, + q_au float8 NOT NULL, + eccentricity float8 NOT NULL, + inclination_deg float8 NOT NULL, + arg_peri_deg float8 NOT NULL, + long_node_deg float8 NOT NULL, + perihelion_jd float8 NOT NULL, + epoch_jd float8 +); + +-- Insert a few examples +INSERT INTO comets VALUES + ('1P', 'Halley', 0.587, 0.967, 162.3, 111.3, 58.4, 2446467.4, 2446480.0), + ('2P', 'Encke', 0.336, 0.847, 11.8, 186.5, 334.6, 2460585.0, 2460600.0), + ('67P', 'C-G', 1.243, 0.641, 7.0, 12.8, 50.1, 2457257.0, 2457260.0); +``` + +### Batch observe all comets + +```sql +WITH earth AS ( + SELECT planet_heliocentric(3, '2024-06-15 04:00:00+00') AS pos +) +SELECT c.name, + round(topo_azimuth(obs)::numeric, 1) AS az, + round(topo_elevation(obs)::numeric, 1) AS el, + round(topo_range(obs)::numeric, 0) AS range_km +FROM comets c, earth, + LATERAL comet_observe( + c.q_au, c.eccentricity, c.inclination_deg, + c.arg_peri_deg, c.long_node_deg, c.perihelion_jd, + helio_x(earth.pos), helio_y(earth.pos), helio_z(earth.pos), + '40.0N 105.3W 1655m'::observer, + '2024-06-15 04:00:00+00') obs +WHERE topo_elevation(obs) > 0 +ORDER BY topo_elevation(obs) DESC; +``` + +This observes every comet in the catalog and filters to those above the horizon. The Earth position is computed once and reused for all comets. + +### Heliocentric distance over time + +Track how a comet's distance from the Sun changes through its orbit: + +```sql +SELECT t::date AS date, + round(helio_distance(kepler_propagate( + 0.336, 0.847, 11.8, 186.5, 334.6, + 2460585.0, t))::numeric, 3) AS encke_dist_au +FROM generate_series( + '2024-01-01'::timestamptz, + '2024-12-31'::timestamptz, + interval '15 days' +) AS t; +``` + +Comet Encke (q=0.336 AU, e=0.847) ranges from 0.336 AU at perihelion to about 4.1 AU at aphelion. Its 3.3-year period means it passes through the inner solar system frequently. + +### Verify: distance conservation for circular orbit + +A useful sanity check. A circular orbit should maintain constant heliocentric distance: + +```sql +SELECT t::date AS date, + round(helio_distance(kepler_propagate( + 1.0, 0.0, 0.0, 0.0, 0.0, + 2451545.0, t))::numeric, 6) AS dist_au +FROM generate_series( + '2000-01-01'::timestamptz, + '2001-01-01'::timestamptz, + interval '60 days' +) AS t; +``` + +Every row should read 1.000000 AU. If it does, the Kepler solver is working correctly. diff --git a/docs/src/content/docs/guides/conjunction-screening.mdx b/docs/src/content/docs/guides/conjunction-screening.mdx new file mode 100644 index 0000000..3949e8e --- /dev/null +++ b/docs/src/content/docs/guides/conjunction-screening.mdx @@ -0,0 +1,290 @@ +--- +title: Conjunction Screening +sidebar: + order: 8 +--- + +import { Steps, Aside, Tabs, TabItem } from "@astrojs/starlight/components"; + +Conjunction screening identifies pairs of satellites that might approach each other closely enough to pose a collision risk. The brute-force approach -- computing pairwise distances for all objects in the catalog at every time step -- scales as O(n^2) and is impractical for large catalogs. pg_orbit solves this with a GiST index on the `tle` type that enables spatial filtering by altitude band and orbital inclination, reducing the candidate set before running full propagation. + +## How you do it today + +Operational conjunction screening uses several established tools and data sources: + +- **STK/SOCRATES** (AGI): Commercial tool that monitors the catalog and generates close-approach reports. Industry standard for satellite operators. Expensive. +- **Space-Track CDMs**: The 18th Space Defense Squadron publishes Conjunction Data Messages (CDMs) for predicted close approaches. Free but requires registration and covers only US-tracked objects. +- **CelesTrak SOCRATES**: Dr. Kelso's web-based close-approach listing. Updated regularly, covers the full public catalog. Not queryable; you read reports. +- **Python scripts**: Propagate the catalog in a loop, compute pairwise distances, filter by threshold. Works for small catalogs. Does not scale. + +The fundamental challenge: a catalog of 25,000+ tracked objects produces over 300 million unique pairs. Even checking each pair at a single epoch takes significant time. Checking over a 7-day window at 1-minute resolution is computationally prohibitive without pre-filtering. + +## What changes with pg_orbit + +pg_orbit attacks the problem in two stages: + +**Stage 1: GiST index reduces candidates.** The GiST index on the `tle` column stores a 2-D key for each TLE: altitude band (perigee to apogee) and inclination range. The `&&` operator tests whether two TLEs occupy overlapping regions in this 2-D space. Only TLEs that share an altitude shell AND a similar inclination can possibly conjunct. This typically reduces 300 million pairs to a few thousand candidates. + +**Stage 2: Full propagation verifies candidates.** For the remaining candidates, `tle_distance()` computes the actual Euclidean distance between two TLEs at a given time using full SGP4/SDP4 propagation. Step through time at the required resolution and filter to close approaches. + +The two operators: + +| Operator | Type | What it checks | +|---|---|---| +| `tle && tle` | boolean | Altitude band AND inclination range overlap | +| `tle <-> tle` | float8 | Minimum altitude-band separation in km | + +The `&&` operator is used for overlap queries (find all objects in the same shell). The `<->` operator is used for nearest-neighbor queries (find the N closest objects by altitude separation). + +## What pg_orbit does not replace + + + +- **Not a probability of collision.** pg_orbit does not compute Pc (probability of collision). It identifies objects in overlapping orbital shells and computes distances at discrete time steps. For Pc calculation, use CARA (Conjunction Assessment Risk Analysis) methods. +- **No covariance propagation.** SGP4 does not produce covariance matrices. The distance values have no uncertainty bounds. For operational conjunction assessment, use SP ephemerides with covariance (from CDMs or owner/operator data). +- **Altitude-band approximation.** The GiST key uses perigee-to-apogee altitude as a 1-D range and inclination as a second dimension. Two TLEs can share an altitude shell and never approach because their RAANs or phases are far apart. Always follow GiST filtering with full propagation. +- **No maneuver planning.** pg_orbit identifies close approaches. It does not compute avoidance maneuvers (delta-v, timing, constraints). + +The workflow is: GiST narrows → `tle_distance()` verifies → operator/analyst decides. + +## Try it + +### Set up a test catalog + +Create a small catalog with satellites at different orbital regimes: + +```sql +CREATE TABLE catalog ( + norad_id integer PRIMARY KEY, + name text NOT NULL, + tle tle NOT NULL +); + +-- ISS (LEO, ~400km, inc 51.64 deg) +INSERT INTO catalog VALUES (25544, 'ISS', + '1 25544U 98067A 24001.50000000 .00016717 00000-0 10270-3 0 9025 +2 25544 51.6400 208.9163 0006703 30.1694 61.7520 15.50100486 00001'); + +-- Hubble (LEO, ~540km, inc 28.47 deg) +INSERT INTO catalog VALUES (20580, 'Hubble', + '1 20580U 90037B 24001.50000000 .00000790 00000+0 39573-4 0 9992 +2 20580 28.4705 61.4398 0002797 317.3115 42.7577 15.09395228 00008'); + +-- GPS IIR-M (MEO, ~20200km, inc 55.44 deg) +INSERT INTO catalog VALUES (28874, 'GPS-IIR', + '1 28874U 05038A 24001.50000000 .00000012 00000+0 00000+0 0 9993 +2 28874 55.4408 300.3467 0117034 51.6543 309.5420 2.00557079 00006'); + +-- Equatorial LEO: same altitude as ISS but inc ~5 deg +INSERT INTO catalog VALUES (99901, 'Equatorial-LEO', + '1 99901U 24999A 24001.50000000 .00016717 00000-0 10270-3 0 9990 +2 99901 5.0000 208.9163 0006703 30.1694 61.7520 15.50100486 00001'); +``` + +### Create the GiST index + +```sql +CREATE INDEX catalog_orbit_gist ON catalog USING gist (tle); +``` + +The index builds in milliseconds for a small table. For a full 25,000-object catalog, expect about 200ms. + +### Check orbital parameters + +Before screening, inspect the orbital characteristics of the catalog: + +```sql +SELECT name, + round(tle_perigee(tle)::numeric, 0) AS perigee_km, + round(tle_apogee(tle)::numeric, 0) AS apogee_km, + round(tle_inclination(tle)::numeric, 1) AS inc_deg, + round(tle_period(tle)::numeric, 1) AS period_min +FROM catalog +ORDER BY tle_perigee(tle); +``` + +### Overlap queries with && + +Find all pairs of satellites in overlapping orbital shells: + +```sql +SELECT a.name AS sat_a, + b.name AS sat_b, + a.tle && b.tle AS overlaps +FROM catalog a, catalog b +WHERE a.norad_id < b.norad_id +ORDER BY a.name, b.name; +``` + +Key insight: ISS and Equatorial-LEO are at the same altitude but different inclinations. The `&&` operator returns **false** for this pair because the 2-D key requires overlap in BOTH altitude AND inclination. Two objects at the same altitude but in very different orbital planes are unlikely to conjunct. + +### Altitude-band distance with `<->` + +The `<->` operator returns the minimum separation between altitude bands, in km: + +```sql +SELECT a.name AS sat_a, + b.name AS sat_b, + round((a.tle <-> b.tle)::numeric, 0) AS alt_separation_km +FROM catalog a, catalog b +WHERE a.norad_id < b.norad_id +ORDER BY a.tle <-> b.tle; +``` + +ISS and Equatorial-LEO should show ~0 km separation (same altitude shell). ISS and GPS should show ~19,800 km (vastly different orbits). + +### GiST index scan: find overlapping orbits + +Force the query planner to use the index and find all objects in the same shell as the ISS: + +```sql +SET enable_seqscan = off; + +SELECT name +FROM catalog +WHERE tle && (SELECT tle FROM catalog WHERE norad_id = 25544) +ORDER BY name; + +RESET enable_seqscan; +``` + +This should return only ISS itself (and not Equatorial-LEO, which has a different inclination). The GiST index scan avoids checking every object in the catalog. + +### K-nearest-neighbor by altitude + +Find the 3 closest objects to the ISS by altitude band separation, ordered by distance: + +```sql +SET enable_seqscan = off; + +SELECT name, + round((tle <-> (SELECT tle FROM catalog WHERE norad_id = 25544))::numeric, 0) AS alt_dist_km +FROM catalog +WHERE norad_id != 25544 +ORDER BY tle <-> (SELECT tle FROM catalog WHERE norad_id = 25544) +LIMIT 3; + +RESET enable_seqscan; +``` + +This uses the GiST distance operator for efficient ordering. PostgreSQL's KNN-GiST infrastructure handles this without computing all distances upfront. + +### Self-overlap is always true + +Every TLE overlaps with itself: + +```sql +SELECT name, + tle && tle AS self_overlap +FROM catalog +ORDER BY name; +``` + +All rows should return `true`. + +### Full conjunction screening workflow + +The complete two-stage workflow for a larger catalog: + + +1. **Build the catalog and index:** + + ```sql + -- Assuming your catalog table is already populated from CelesTrak or Space-Track + CREATE INDEX IF NOT EXISTS catalog_orbit_gist ON catalog USING gist (tle); + ``` + +2. **Stage 1: GiST filter to find candidates for a target satellite:** + + ```sql + CREATE TEMPORARY TABLE candidates AS + SELECT c.norad_id, c.name, c.tle + FROM catalog c + WHERE c.tle && (SELECT tle FROM catalog WHERE norad_id = 25544) + AND c.norad_id != 25544; + ``` + + For the ISS in a 25,000-object catalog, this typically returns a few hundred candidates. + +3. **Stage 2: Time-resolved distance computation:** + + ```sql + WITH iss AS ( + SELECT tle FROM catalog WHERE norad_id = 25544 + ) + SELECT c.name, + t AS check_time, + round(tle_distance(iss.tle, c.tle, t)::numeric, 1) AS dist_km + FROM candidates c, iss, + generate_series( + '2024-01-01 00:00:00+00'::timestamptz, + '2024-01-02 00:00:00+00'::timestamptz, + interval '1 minute' + ) AS t + WHERE tle_distance(iss.tle, c.tle, t) < 25.0 + ORDER BY dist_km; + ``` + + This propagates each candidate pair at 1-minute resolution over 24 hours and filters to approaches within 25 km. Only the GiST candidates are checked, not the full catalog. + +4. **Review results and take action.** + + The output lists object name, time of closest approach, and distance. An analyst or automated system decides whether to issue a CDM, plan a maneuver, or accept the risk. + + +### Screening multiple target satellites + +Extend the workflow to screen for conjunctions between any pair of objects in a subset: + +```sql +-- All pairs in the LEO catalog (tle_perigee < 2000 km) that share an orbital shell +SELECT a.name AS sat_a, + b.name AS sat_b, + round((a.tle <-> b.tle)::numeric, 0) AS alt_sep_km, + round(tle_distance(a.tle, b.tle, '2024-01-01 12:00:00+00')::numeric, 0) AS actual_dist_km +FROM catalog a, catalog b +WHERE a.norad_id < b.norad_id + AND a.tle && b.tle + AND tle_perigee(a.tle) < 2000 + AND tle_perigee(b.tle) < 2000 +ORDER BY actual_dist_km; +``` + + + +### Monitoring over time + +Run a conjunction check at regular intervals and store results for trend analysis: + +```sql +CREATE TABLE conjunction_events ( + id serial PRIMARY KEY, + sat_a integer NOT NULL, + sat_b integer NOT NULL, + event_time timestamptz NOT NULL, + dist_km float8 NOT NULL, + checked_at timestamptz DEFAULT now() +); + +-- Periodic screening job (run daily or as needed) +INSERT INTO conjunction_events (sat_a, sat_b, event_time, dist_km) +WITH iss AS ( + SELECT norad_id, tle FROM catalog WHERE norad_id = 25544 +) +SELECT iss.norad_id, c.norad_id, t, tle_distance(iss.tle, c.tle, t) +FROM catalog c, iss, + generate_series( + now(), + now() + interval '7 days', + interval '5 minutes' + ) AS t +WHERE c.tle && iss.tle + AND c.norad_id != iss.norad_id + AND tle_distance(iss.tle, c.tle, t) < 50.0; +``` + +This builds a history of close approaches that you can query, trend, and alert on. The GiST filter ensures it runs efficiently even against a full catalog. diff --git a/docs/src/content/docs/guides/interplanetary-trajectories.mdx b/docs/src/content/docs/guides/interplanetary-trajectories.mdx new file mode 100644 index 0000000..b03f65f --- /dev/null +++ b/docs/src/content/docs/guides/interplanetary-trajectories.mdx @@ -0,0 +1,271 @@ +--- +title: Interplanetary Trajectories +sidebar: + order: 7 +--- + +import { Steps, Aside, Tabs, TabItem } from "@astrojs/starlight/components"; + +pg_orbit includes a Lambert solver for computing ballistic transfer orbits between any two planets. Given a departure body, arrival body, departure time, and arrival time, the solver returns the transfer orbit's energy characteristics: departure C3, arrival C3, v-infinity, time of flight, and transfer semi-major axis. The function processes about 800,000 solutions per second, which means pork chop plots -- the standard visualization for launch window analysis -- become SQL CROSS JOINs. + +## How you do it today + +Interplanetary trajectory design is one of the more specialized areas of orbital mechanics: + +- **GMAT** (General Mission Analysis Tool): NASA's open-source mission design software. Full-featured but steep learning curve. GUI-driven, script-extensible, not designed for batch parameter sweeps. +- **NASA Trajectory Browser**: Web-based tool for browsing pre-computed transfer opportunities. Limited to pre-defined targets and time windows. +- **poliastro** (Python): Astrodynamics library with Lambert solvers, orbit plotting, and planetary position computation. Good for one-off analysis; batch sweeps require writing loops. +- **STK/Astrogator**: Commercial tool with advanced trajectory design. Expensive, steep learning curve. + +For all of these, the workflow is: pick a departure date, pick an arrival date, run the solver, record the result. To build a pork chop plot, you sweep a grid of departure and arrival dates and collect results. In Python, this means nested loops. In GMAT, this means scripted batch runs. + +## What changes with pg_orbit + +Two functions handle the complete Lambert problem: + +| Function | Returns | Use case | +|---|---|---| +| `lambert_transfer(dep_id, arr_id, dep_time, arr_time)` | RECORD with 6 fields | Full transfer orbit characterization | +| `lambert_c3(dep_id, arr_id, dep_time, arr_time)` | float8 | Departure C3 only (for pork chop plots) | + +The `lambert_transfer()` output fields: + +| Field | Units | Meaning | +|---|---|---| +| `c3_departure` | km^2/s^2 | Launch energy: the kinetic energy per unit mass above escape velocity at departure | +| `c3_arrival` | km^2/s^2 | Arrival energy: excess velocity squared at the target planet | +| `v_inf_departure` | km/s | Hyperbolic excess speed at departure (sqrt of C3) | +| `v_inf_arrival` | km/s | Hyperbolic excess speed at arrival | +| `tof_days` | days | Time of flight | +| `transfer_sma` | AU | Semi-major axis of the transfer ellipse | + +Body IDs match the VSOP87 convention: 1=Mercury, 2=Venus, 3=Earth, 4=Mars, 5=Jupiter, 6=Saturn, 7=Uranus, 8=Neptune. The departure body is almost always Earth (3), but the solver works for any planet-to-planet combination. + +## What pg_orbit does not replace + + + +- **No gravity assists.** Voyager, Cassini, and New Horizons all used flyby maneuvers to gain velocity from intermediate planets. The Lambert solver computes direct transfers only. +- **No low-thrust trajectories.** Ion drives and solar sails produce continuous thrust. Lambert assumes an instantaneous departure burn and coast to arrival. +- **No three-body effects.** The solver uses heliocentric two-body mechanics. Sphere-of-influence transitions, Lagrange point dynamics, and lunar gravity assists are not modeled. +- **No launch vehicle constraints.** The solver returns C3, which determines the required launch energy. Mapping C3 to a specific rocket's payload capacity is a separate analysis. +- **No aerocapture or entry design.** The arrival C3 determines how much delta-v is needed for orbit insertion, but pg_orbit does not compute the insertion burn itself. + +For mission design beyond first-order feasibility analysis, use GMAT or poliastro with patched-conic or N-body propagation. + +## Try it + +### Earth-Mars transfer + +The classic trajectory design problem. A 2026 departure window: + +```sql +SELECT round(c3_departure::numeric, 2) AS c3_dep_km2s2, + round(c3_arrival::numeric, 2) AS c3_arr_km2s2, + round(v_inf_departure::numeric, 2) AS vinf_dep_kms, + round(v_inf_arrival::numeric, 2) AS vinf_arr_kms, + round(tof_days::numeric, 0) AS flight_days, + round(transfer_sma::numeric, 4) AS sma_au +FROM lambert_transfer( + 3, 4, -- Earth to Mars + '2026-05-01 00:00:00+00'::timestamptz, -- departure + '2027-01-15 00:00:00+00'::timestamptz -- arrival +); +``` + +For a typical Earth-Mars transfer, expect departure C3 in the 8-20 km^2/s^2 range, flight times of 200-300 days, and a transfer SMA of roughly 1.2-1.5 AU. + +### Earth-Venus transfer + +Venus transfers require less energy than Mars because Venus is closer to the Sun: + +```sql +SELECT round(c3_departure::numeric, 2) AS c3_dep, + round(tof_days::numeric, 0) AS flight_days, + round(transfer_sma::numeric, 4) AS sma_au +FROM lambert_transfer( + 3, 2, -- Earth to Venus + '2026-06-01 00:00:00+00'::timestamptz, + '2026-10-15 00:00:00+00'::timestamptz +); +``` + +Typical Earth-Venus C3 is 5-15 km^2/s^2 with flight times of 100-200 days. + +### Earth-Jupiter transfer + +A direct ballistic transfer to Jupiter requires significant energy: + +```sql +SELECT round(c3_departure::numeric, 0) AS c3_dep, + round(tof_days::numeric, 0) AS flight_days +FROM lambert_transfer( + 3, 5, -- Earth to Jupiter + '2026-01-01 00:00:00+00'::timestamptz, + '2028-06-01 00:00:00+00'::timestamptz +); +``` + +Expect C3 in the 70-100+ km^2/s^2 range. This is why real Jupiter missions use Venus and Earth gravity assists to reduce the required launch energy. + +### Using lambert_c3 for quick comparisons + +When you only need the departure energy and not the full transfer characterization: + +```sql +SELECT round(lambert_c3(3, 4, + '2026-05-01 00:00:00+00'::timestamptz, + '2027-01-15 00:00:00+00'::timestamptz)::numeric, 2) AS c3; +``` + +`lambert_c3()` returns just the departure C3 as a single float. It is faster than `lambert_transfer()` when you do not need the other output fields. + +### Mini pork chop plot + +A pork chop plot shows departure C3 as a function of departure date and arrival date. This is the fundamental tool for launch window analysis. Generate one in SQL with a CROSS JOIN: + +```sql +SELECT dep::date AS departure, + arr::date AS arrival, + round(lambert_c3(3, 4, dep, arr)::numeric, 1) AS c3 +FROM generate_series( + '2026-04-01'::timestamptz, + '2026-06-01'::timestamptz, + interval '10 days' +) AS dep +CROSS JOIN generate_series( + '2027-01-01'::timestamptz, + '2027-03-01'::timestamptz, + interval '10 days' +) AS arr +ORDER BY c3; +``` + +The lowest C3 values correspond to the optimal departure/arrival combination. This mini grid has about 40 points; a real pork chop plot uses finer resolution. + +### Full pork chop plot: 150x150 grid + +For a publication-quality pork chop plot, use 1-day resolution over a wide window: + +```sql +SELECT dep::date AS departure, + arr::date AS arrival, + round(lambert_c3(3, 4, dep, arr)::numeric, 2) AS c3 +FROM generate_series( + '2026-01-01'::timestamptz, + '2026-06-01'::timestamptz, + interval '1 day' +) AS dep +CROSS JOIN generate_series( + '2026-09-01'::timestamptz, + '2027-06-01'::timestamptz, + interval '1 day' +) AS arr; +``` + +This generates approximately 150 x 270 = 40,500 transfer solutions. At 800,000 solutions per second, the query completes in well under a second. The result is a table you can feed into any contour plot library. + +### Compare transfer windows + +Look at multiple departure windows side by side: + +```sql +WITH windows AS ( + SELECT '2026 window' AS window, '2026-05-01'::timestamptz AS dep, '2027-01-15'::timestamptz AS arr + UNION ALL + SELECT '2028 window', '2028-10-01', '2029-06-15' + UNION ALL + SELECT '2031 window', '2031-02-01', '2031-10-01' +) +SELECT window, + dep::date AS departure, + arr::date AS arrival, + round(c3_departure::numeric, 2) AS c3_dep, + round(c3_arrival::numeric, 2) AS c3_arr, + round(tof_days::numeric, 0) AS flight_days, + round(transfer_sma::numeric, 4) AS sma_au +FROM windows, + LATERAL lambert_transfer(3, 4, dep, arr); +``` + +Earth-Mars launch windows repeat approximately every 26 months (the synodic period). Some windows are more favorable than others because Mars's orbit is significantly eccentric. + +### Find the optimal departure date + +Use a fine sweep over departure dates with a fixed arrival date to find the minimum C3: + +```sql +SELECT dep::date AS departure, + round(lambert_c3(3, 4, dep, + '2027-01-15 00:00:00+00'::timestamptz)::numeric, 2) AS c3 +FROM generate_series( + '2026-03-01'::timestamptz, + '2026-08-01'::timestamptz, + interval '1 day' +) AS dep +ORDER BY lambert_c3(3, 4, dep, + '2027-01-15 00:00:00+00'::timestamptz) +LIMIT 10; +``` + +The top 10 rows show the departure dates with the lowest launch energy for a fixed arrival on 2027-01-15. In practice, you would sweep both departure and arrival together (the full pork chop plot), but fixing one dimension is useful for understanding the sensitivity. + +### Venus-Mars via Earth (multi-leg comparison) + +While the Lambert solver does not compute multi-leg gravity assists directly, you can compare the energy requirements of each leg independently: + +```sql +-- Earth to Venus (leg 1) +SELECT 'Earth-Venus' AS leg, + round(c3_departure::numeric, 2) AS c3_dep, + round(tof_days::numeric, 0) AS flight_days +FROM lambert_transfer(3, 2, + '2026-06-01'::timestamptz, '2026-10-15'::timestamptz) + +UNION ALL + +-- Earth to Mars (direct) +SELECT 'Earth-Mars (direct)', + round(c3_departure::numeric, 2), + round(tof_days::numeric, 0) +FROM lambert_transfer(3, 4, + '2026-05-01'::timestamptz, '2027-01-15'::timestamptz) + +UNION ALL + +-- Earth to Jupiter (direct) +SELECT 'Earth-Jupiter (direct)', + round(c3_departure::numeric, 0), + round(tof_days::numeric, 0) +FROM lambert_transfer(3, 5, + '2026-01-01'::timestamptz, '2028-06-01'::timestamptz); +``` + +This shows why gravity assists exist: the direct Earth-Jupiter C3 is many times higher than the Earth-Venus C3. By flying to Venus first and using its gravity to redirect, missions can reach Jupiter with far less launch energy. + +### Sanity checks + +Verify the solver produces physically reasonable results: + +```sql +-- C3 should be positive and less than 200 for any Earth-Mars transfer +SELECT lambert_c3(3, 4, + '2026-05-01'::timestamptz, + '2027-01-15'::timestamptz) > 0 AS positive, + lambert_c3(3, 4, + '2026-05-01'::timestamptz, + '2027-01-15'::timestamptz) < 200 AS reasonable; + +-- Transfer SMA should be between Earth and Mars orbits +SELECT transfer_sma > 0.8 AS above_venus, + transfer_sma < 5.0 AS below_jupiter +FROM lambert_transfer(3, 4, + '2026-05-01'::timestamptz, + '2027-01-15'::timestamptz); +``` + + diff --git a/docs/src/content/docs/guides/jupiter-radio-bursts.mdx b/docs/src/content/docs/guides/jupiter-radio-bursts.mdx new file mode 100644 index 0000000..13f3e0b --- /dev/null +++ b/docs/src/content/docs/guides/jupiter-radio-bursts.mdx @@ -0,0 +1,279 @@ +--- +title: Jupiter Radio Bursts +sidebar: + order: 6 +--- + +import { Steps, Aside, Tabs, TabItem } from "@astrojs/starlight/components"; + +Jupiter is the strongest radio source in the solar system after the Sun. Its decametric emissions (roughly 15-40 MHz) occur when Io passes through specific orbital positions relative to Jupiter's rotating magnetic field. pg_orbit computes the two geometry parameters that govern these bursts -- Io phase angle and Jupiter Central Meridian Longitude -- and maps them to an empirical burst probability using the Carr et al. (1983) source regions. + +This is the feature built for the Radio JOVE community. There are 500-1000 active Radio JOVE operators worldwide, and until pg_orbit, the standard prediction tool was Radio Jupiter Pro -- a Windows-only desktop application. Batch prediction, calendar generation, and integration with observation scheduling are now possible in SQL. + +## How you do it today + +Jupiter radio observation planning has relied on a small set of tools: + +- **Radio Jupiter Pro** (Windows): The standard tool. Shows a real-time display of Io phase, CML, and burst probability. Single-observer, single-time, no batch output. Windows-only. +- **Manual CML/Io-phase charts**: Published charts (Carr, Desch, Alexander 1983) show which CML-Io phase combinations produce bursts. Observers print the chart and overlay their observing window by hand. +- **Radio-SkyPipe** and **SkyPipe II**: Recording software that can trigger on signal, but prediction is separate. +- **Spreadsheets**: Some operators maintain Excel sheets that compute CML from Jupiter's rotation rate. Error-prone, per-session. + +The problem: planning an observation campaign over weeks or months means running Radio Jupiter Pro repeatedly for each night, eyeballing the probability, and writing down the good windows. There is no way to generate a calendar of optimal observation windows in one operation. + +## What changes with pg_orbit + +Three functions cover the complete Jupiter radio prediction pipeline: + +| Function | Returns | What it computes | +|---|---|---| +| `io_phase_angle(time)` | degrees [0, 360) | Io's orbital position. 0 = superior conjunction (behind Jupiter). | +| `jupiter_cml(observer, time)` | degrees [0, 360) | Central Meridian Longitude, System III (1965.0). Light-time corrected. | +| `jupiter_burst_probability(io_phase, cml)` | 0.0 to 1.0 | Empirical probability based on Carr source regions. | + +The probability function encodes four source regions from the Carr et al. (1983) model: + +| Source | CML Range | Io Phase Range | Probability | Description | +|---|---|---|---|---| +| **A** | 200-260 | 195-265 | 0.8 | Strongest. Io-related, occurs when Io is at superior conjunction. | +| **B** | 100-200 | 60-150 | 0.5 | Io-related, occurs when Io is ~90 degrees ahead of Jupiter. | +| **C** | 300-20 | 220-310 | 0.3 | Weaker. Non-Io component, occurs at specific CML ranges. | +| **D** | 350-60 | 80-140 | 0.2 | Weakest of the four. Non-Io related. | + +Outside these regions, the probability is 0.0. Overlapping regions combine to the higher probability. + +## What pg_orbit does not replace + + + +- **No signal detection.** pg_orbit predicts when bursts are likely, not whether one is occurring. Use Radio-SkyPipe or SDR software for actual signal capture. +- **No frequency prediction.** The model predicts occurrence probability, not the specific frequency structure (L-bursts vs. S-bursts) or intensity. +- **No RFI assessment.** Local radio interference is often the biggest obstacle to Jupiter observation. pg_orbit does not model your local RF environment. +- **No receiver pointing.** At 20 MHz, most receivers use fixed dipole antennas. Pointing is not an issue, but Jupiter must be above the horizon. Combine with `planet_observe(5, ...)` to check elevation. + +## Try it + +### Check current conditions + +What are the Io phase and CML right now? + +```sql +SELECT round(io_phase_angle(now())::numeric, 1) AS io_phase, + round(jupiter_cml('40.0N 105.3W 1655m'::observer, now())::numeric, 1) AS cml, + round(jupiter_burst_probability( + io_phase_angle(now()), + jupiter_cml('40.0N 105.3W 1655m'::observer, now()) + )::numeric, 3) AS burst_prob; +``` + +### Best burst windows tonight + +Scan the next 12 hours in 10-minute steps and find windows where burst probability exceeds 30%: + +```sql +SELECT t, + round(io_phase_angle(t)::numeric, 1) AS io_phase, + round(jupiter_cml('40.0N 105.3W 1655m'::observer, t)::numeric, 1) AS cml, + round(jupiter_burst_probability( + io_phase_angle(t), + jupiter_cml('40.0N 105.3W 1655m'::observer, t) + )::numeric, 3) AS prob +FROM generate_series( + now(), + now() + interval '12 hours', + interval '10 minutes' +) AS t +WHERE jupiter_burst_probability( + io_phase_angle(t), + jupiter_cml('40.0N 105.3W 1655m'::observer, t) +) > 0.3 +ORDER BY t; +``` + + + +### Best windows tonight with horizon check + +The complete query: burst probability above threshold AND Jupiter above the horizon: + +```sql +SELECT t, + round(io_phase_angle(t)::numeric, 1) AS io_phase, + round(jupiter_cml('40.0N 105.3W 1655m'::observer, t)::numeric, 1) AS cml, + round(jupiter_burst_probability( + io_phase_angle(t), + jupiter_cml('40.0N 105.3W 1655m'::observer, t) + )::numeric, 3) AS prob, + round(topo_elevation(planet_observe(5, + '40.0N 105.3W 1655m'::observer, t))::numeric, 1) AS jupiter_el +FROM generate_series( + '2024-03-15 00:00:00+00'::timestamptz, + '2024-03-15 12:00:00+00'::timestamptz, + interval '10 minutes' +) AS t +WHERE jupiter_burst_probability( + io_phase_angle(t), + jupiter_cml('40.0N 105.3W 1655m'::observer, t) +) > 0.0 +AND topo_elevation(planet_observe(5, + '40.0N 105.3W 1655m'::observer, t)) > 10 +ORDER BY prob DESC, t; +``` + +### 30-day observation calendar + +Generate a calendar of the best observation windows over an entire month: + +```sql +WITH windows AS ( + SELECT t, + io_phase_angle(t) AS io_phase, + jupiter_cml('40.0N 105.3W 1655m'::observer, t) AS cml, + jupiter_burst_probability( + io_phase_angle(t), + jupiter_cml('40.0N 105.3W 1655m'::observer, t) + ) AS prob, + topo_elevation(planet_observe(5, + '40.0N 105.3W 1655m'::observer, t)) AS jupiter_el + FROM generate_series( + '2024-03-01 00:00:00+00'::timestamptz, + '2024-03-31 00:00:00+00'::timestamptz, + interval '10 minutes' + ) AS t +) +SELECT t::date AS date, + t::time AS utc_time, + round(io_phase::numeric, 1) AS io_phase, + round(cml::numeric, 1) AS cml, + round(prob::numeric, 2) AS prob, + round(jupiter_el::numeric, 1) AS jup_el +FROM windows +WHERE prob >= 0.5 + AND jupiter_el > 15 +ORDER BY date, utc_time; +``` + +This finds every 10-minute window in March 2024 where burst probability is at least 50% and Jupiter is more than 15 degrees above the horizon. The result is a printable observation calendar. + +### Identify Carr source regions + +Determine which source region is responsible for a given prediction: + +```sql +WITH sources AS ( + SELECT 'Source A' AS region, 200.0 AS cml_lo, 260.0 AS cml_hi, + 195.0 AS io_lo, 265.0 AS io_hi, 0.8 AS prob + UNION ALL SELECT 'Source B', 100.0, 200.0, 60.0, 150.0, 0.5 + UNION ALL SELECT 'Source C', 300.0, 380.0, 220.0, 310.0, 0.3 + UNION ALL SELECT 'Source D', 350.0, 420.0, 80.0, 140.0, 0.2 +) +SELECT t, + round(io_phase_angle(t)::numeric, 1) AS io, + round(jupiter_cml('40.0N 105.3W 1655m'::observer, t)::numeric, 1) AS cml, + s.region, + s.prob +FROM generate_series( + '2024-03-15 00:00:00+00'::timestamptz, + '2024-03-15 12:00:00+00'::timestamptz, + interval '15 minutes' +) AS t +CROSS JOIN sources s +WHERE io_phase_angle(t) BETWEEN s.io_lo AND s.io_hi + AND (jupiter_cml('40.0N 105.3W 1655m'::observer, t) + BETWEEN s.cml_lo AND LEAST(s.cml_hi, 360.0) + OR jupiter_cml('40.0N 105.3W 1655m'::observer, t) + 360.0 + BETWEEN s.cml_lo AND s.cml_hi) +ORDER BY t, s.prob DESC; +``` + + + +### Io orbital phase rate + +Io completes an orbit in about 1.77 days. Watch the phase angle advance over a full orbit: + +```sql +SELECT t, + round(io_phase_angle(t)::numeric, 1) AS io_phase +FROM generate_series( + '2024-03-15 00:00:00+00'::timestamptz, + '2024-03-16 18:00:00+00'::timestamptz, + interval '2 hours' +) AS t; +``` + +The phase should advance roughly 203 degrees per day (360 / 1.77). + +### Jupiter CML rotation + +Jupiter's System III rotation period is 9h 55m 29.7s. Watch the CML cycle through 360 degrees: + +```sql +SELECT t, + round(jupiter_cml('40.0N 105.3W 1655m'::observer, t)::numeric, 1) AS cml +FROM generate_series( + '2024-03-15 00:00:00+00'::timestamptz, + '2024-03-15 10:00:00+00'::timestamptz, + interval '30 minutes' +) AS t; +``` + +The CML completes one full rotation in just under 10 hours, meaning the same magnetic field geometry repeats roughly 2.4 times per day. This is why Jupiter radio observation windows can occur multiple times per night. + +### Probability heatmap data + +Generate the data for a CML vs. Io-phase probability plot (the classic Carr diagram): + +```sql +SELECT io_phase, + cml, + jupiter_burst_probability(io_phase, cml) AS prob +FROM generate_series(0, 355, 5) AS io_phase, + generate_series(0, 355, 5) AS cml +WHERE jupiter_burst_probability(io_phase, cml) > 0; +``` + +This produces a 72x72 grid (5-degree resolution) of probability values, showing exactly the four Carr source regions. The output can be fed to any heatmap visualization tool. + +### Multi-observer comparison + +Compare burst windows for operators at different longitudes. The CML depends on observer position because of light-time correction: + +```sql +WITH observers(name, obs) AS (VALUES + ('Boulder, CO', '40.0N 105.3W 1655m'::observer), + ('Gainesville, FL', '29.6N 82.3W 30m'::observer), + ('Paris, FR', '48.9N 2.3E 75m'::observer) +) +SELECT o.name, + t, + round(jupiter_cml(o.obs, t)::numeric, 1) AS cml, + round(jupiter_burst_probability( + io_phase_angle(t), + jupiter_cml(o.obs, t) + )::numeric, 3) AS prob +FROM observers o, + generate_series( + '2024-03-15 02:00:00+00'::timestamptz, + '2024-03-15 06:00:00+00'::timestamptz, + interval '30 minutes' + ) AS t +WHERE jupiter_burst_probability( + io_phase_angle(t), + jupiter_cml(o.obs, t) +) > 0.3 +ORDER BY t, o.name; +``` + +The Io phase is the same for all observers (it depends only on time), but the CML varies slightly due to light-time differences. For observers on the same continent, the difference is negligible. Comparing North America to Europe shows a measurable shift. diff --git a/docs/src/content/docs/guides/observing-solar-system.mdx b/docs/src/content/docs/guides/observing-solar-system.mdx new file mode 100644 index 0000000..40f30dd --- /dev/null +++ b/docs/src/content/docs/guides/observing-solar-system.mdx @@ -0,0 +1,260 @@ +--- +title: Observing the Solar System +sidebar: + order: 2 +--- + +import { Steps, Aside, Tabs, TabItem } from "@astrojs/starlight/components"; + +pg_orbit computes positions for all eight planets (VSOP87), the Sun, and the Moon (ELP2000-82B). Every observation returns the same `topocentric` type: azimuth, elevation, range, and range rate from a given observer at a given time. The solar system becomes queryable with standard SQL. + +## How you do it today + +Knowing where planets are involves one of a few approaches: + +- **Stellarium** gives you a beautiful real-time sky view. You scrub time, click objects, read coordinates. Not scriptable, not batch-queryable. +- **JPL Horizons** computes high-precision ephemerides via web form or API. Accurate to milliarcseconds. One object per request, rate-limited. +- **Skyfield** (Python) loads JPL DE441 ephemerides and computes positions with sub-arcsecond accuracy. Excellent for one-off scripts; batch processing over large time ranges or many observers means writing loops. +- **Astropy** provides coordinate frames, time systems, and ERFA wrappers. Powerful, but computing "what's above the horizon right now" requires assembling several components. + +All of these produce results that live outside your database. If you want to correlate planet positions with weather data, observation logs, or satellite passes, you export, import, and join. + +## What changes with pg_orbit + +All planets, the Sun, and the Moon are available as SQL function calls. The functions take an observer and a timestamp, and return topocentric coordinates. You can sweep all eight planets, generate time series, filter by elevation, and join with other tables in the same query. + +Key functions: + +| Function | What it computes | +|---|---| +| `planet_observe(body_id, observer, time)` | Topocentric az/el/range for a planet | +| `planet_heliocentric(body_id, time)` | Heliocentric ecliptic J2000 position (AU) | +| `sun_observe(observer, time)` | Topocentric Sun position | +| `moon_observe(observer, time)` | Topocentric Moon position (ELP2000-82B) | + +Body IDs follow the VSOP87 convention: 1=Mercury, 2=Venus, 3=Earth, 4=Mars, 5=Jupiter, 6=Saturn, 7=Uranus, 8=Neptune. Body 0 returns the Sun at the heliocentric origin (all zeros). + +## What pg_orbit does not replace + + + +- **VSOP87 accuracy is about 1 arcsecond.** JPL DE441 (used by Skyfield and SPICE) achieves 0.001 arcsecond. For visual observation planning, 1 arcsecond is more than sufficient. For pointing a dish at GHz frequencies or precision astrometry, use SPICE. +- **ELP2000-82B accuracy is about 10 arcseconds** for the Moon. Good enough for knowing when the Moon is up, what phase it is in, and whether it will interfere with observations. Not sufficient for occultation timing. +- **No light-time iteration.** pg_orbit computes geometric positions, not apparent positions. The difference matters at the milliarcsecond level. +- **No atmospheric refraction.** Objects near the horizon appear slightly higher than their geometric position. pg_orbit does not apply refraction corrections. + +## Try it + +### Where is Jupiter right now? + +The simplest possible observation query: + +```sql +SELECT topo_azimuth(t) AS azimuth, + topo_elevation(t) AS elevation, + topo_range(t) / 149597870.7 AS distance_au +FROM planet_observe(5, '40.0N 105.3W 1655m'::observer, now()) t; +``` + +Body ID 5 is Jupiter. The range comes back in km; dividing by 149,597,870.7 converts to AU. + +### What is up tonight? + +Sweep all eight planets plus the Sun and Moon. Filter to objects above the horizon: + + + + ```sql + SELECT CASE body_id + WHEN 1 THEN 'Mercury' WHEN 2 THEN 'Venus' + WHEN 3 THEN 'Earth' WHEN 4 THEN 'Mars' + WHEN 5 THEN 'Jupiter' WHEN 6 THEN 'Saturn' + WHEN 7 THEN 'Uranus' WHEN 8 THEN 'Neptune' + END AS planet, + round(topo_azimuth(obs)::numeric, 1) AS az, + round(topo_elevation(obs)::numeric, 1) AS el, + round((topo_range(obs) / 149597870.7)::numeric, 3) AS dist_au + FROM generate_series(1, 8) AS body_id, + LATERAL planet_observe(body_id, '40.0N 105.3W 1655m'::observer, + '2024-06-21 04:00:00+00') obs + WHERE body_id != 3 -- cannot observe Earth from Earth + AND topo_elevation(obs) > 0 + ORDER BY topo_elevation(obs) DESC; + ``` + + + ```sql + -- Combine planets, Sun, and Moon into one result set + WITH observations AS ( + -- All planets except Earth + SELECT CASE body_id + WHEN 1 THEN 'Mercury' WHEN 2 THEN 'Venus' + WHEN 4 THEN 'Mars' WHEN 5 THEN 'Jupiter' + WHEN 6 THEN 'Saturn' WHEN 7 THEN 'Uranus' + WHEN 8 THEN 'Neptune' + END AS name, + planet_observe(body_id, + '40.0N 105.3W 1655m'::observer, + '2024-06-21 04:00:00+00') AS obs + FROM generate_series(1, 8) AS body_id + WHERE body_id != 3 + + UNION ALL + + SELECT 'Sun', + sun_observe('40.0N 105.3W 1655m'::observer, + '2024-06-21 04:00:00+00') + + UNION ALL + + SELECT 'Moon', + moon_observe('40.0N 105.3W 1655m'::observer, + '2024-06-21 04:00:00+00') + ) + SELECT name, + round(topo_azimuth(obs)::numeric, 1) AS az, + round(topo_elevation(obs)::numeric, 1) AS el, + round((topo_range(obs) / 149597870.7)::numeric, 4) AS dist_au + FROM observations + WHERE topo_elevation(obs) > 0 + ORDER BY topo_elevation(obs) DESC; + ``` + + + +### Solar system status: heliocentric distances + +See where every planet is relative to the Sun: + +```sql +SELECT body_id, + CASE body_id + WHEN 1 THEN 'Mercury' WHEN 2 THEN 'Venus' + WHEN 3 THEN 'Earth' WHEN 4 THEN 'Mars' + WHEN 5 THEN 'Jupiter' WHEN 6 THEN 'Saturn' + WHEN 7 THEN 'Uranus' WHEN 8 THEN 'Neptune' + END AS planet, + round(helio_distance(planet_heliocentric(body_id, now()))::numeric, 4) AS dist_au, + round(helio_x(planet_heliocentric(body_id, now()))::numeric, 4) AS x_au, + round(helio_y(planet_heliocentric(body_id, now()))::numeric, 4) AS y_au, + round(helio_z(planet_heliocentric(body_id, now()))::numeric, 4) AS z_au +FROM generate_series(1, 8) AS body_id; +``` + +The heliocentric coordinates are in the ecliptic J2000 frame. X points toward the vernal equinox, Z toward the north ecliptic pole. + +### Planet elevation over one night + +Track Jupiter's elevation from sunset to sunrise: + +```sql +SELECT t, + round(topo_elevation( + planet_observe(5, '40.0N 105.3W 1655m'::observer, t) + )::numeric, 1) AS jupiter_el, + round(topo_azimuth( + planet_observe(5, '40.0N 105.3W 1655m'::observer, t) + )::numeric, 1) AS jupiter_az +FROM generate_series( + '2024-06-21 02:00:00+00'::timestamptz, -- ~8pm MDT + '2024-06-21 12:00:00+00'::timestamptz, -- ~6am MDT + interval '30 minutes' +) AS t +WHERE topo_elevation( + planet_observe(5, '40.0N 105.3W 1655m'::observer, t) +) > 0; +``` + +This produces a time series of Jupiter's position through the night, filtered to only the hours it is above the horizon. Replace body ID 5 with any other planet. + +### Sun position through the day + +Useful for solar panel analysis, sunrise/sunset approximation, or photography planning: + +```sql +SELECT t, + round(topo_azimuth(sun_observe('40.0N 105.3W 1655m'::observer, t))::numeric, 1) AS az, + round(topo_elevation(sun_observe('40.0N 105.3W 1655m'::observer, t))::numeric, 1) AS el +FROM generate_series( + '2024-06-21 12:00:00+00'::timestamptz, -- ~6am MDT + '2024-06-22 03:00:00+00'::timestamptz, -- ~9pm MDT + interval '15 minutes' +) AS t; +``` + +At the summer solstice from Boulder, the Sun reaches about 73 degrees elevation at local noon, rising in the northeast and setting in the northwest. + +### Moon range check + +The Moon's distance varies between about 356,000 km (perigee) and 407,000 km (apogee): + +```sql +SELECT t::date AS date, + round(topo_range(moon_observe('40.0N 105.3W 1655m'::observer, t))::numeric, 0) AS range_km +FROM generate_series( + '2024-01-01'::timestamptz, + '2024-02-01'::timestamptz, + interval '1 day' +) AS t; +``` + +### Multi-observer comparison + +Compare planet visibility from two different locations: + +```sql +WITH observers AS ( + SELECT 'Boulder, CO' AS location, '40.0N 105.3W 1655m'::observer AS obs + UNION ALL + SELECT 'Sydney, AU', '33.9S 151.2E 58m'::observer +) +SELECT o.location, + CASE body_id + WHEN 5 THEN 'Jupiter' WHEN 6 THEN 'Saturn' + END AS planet, + round(topo_elevation( + planet_observe(body_id, o.obs, '2024-06-21 10:00:00+00') + )::numeric, 1) AS elevation +FROM observers o, + generate_series(5, 6) AS body_id +ORDER BY o.location, body_id; +``` + +### Earth heliocentric sanity check + +Earth's distance from the Sun should be about 0.983 AU at perihelion (early January) and 1.017 AU at aphelion (early July): + +```sql +SELECT 'perihelion' AS point, + round(helio_distance( + planet_heliocentric(3, '2024-01-03 12:00:00+00') + )::numeric, 4) AS earth_au +UNION ALL +SELECT 'aphelion', + round(helio_distance( + planet_heliocentric(3, '2024-07-05 12:00:00+00') + )::numeric, 4); +``` + +This is a useful sanity check when verifying the extension is installed correctly. + +### Solar-terrestrial geometry + +When does the Sun cross specific elevation thresholds? Find solar noon and the elevation at specific times: + +```sql +-- Sample the Sun every minute around local noon to find peak elevation +SELECT t, + round(topo_elevation(sun_observe('40.0N 105.3W 1655m'::observer, t))::numeric, 2) AS el +FROM generate_series( + '2024-06-21 17:30:00+00'::timestamptz, + '2024-06-21 18:30:00+00'::timestamptz, + interval '1 minute' +) AS t +ORDER BY topo_elevation(sun_observe('40.0N 105.3W 1655m'::observer, t)) DESC +LIMIT 5; +``` + +The highest elevation reading approximates solar noon. For Boulder at the summer solstice, expect about 73 degrees. diff --git a/docs/src/content/docs/guides/planetary-moons.mdx b/docs/src/content/docs/guides/planetary-moons.mdx new file mode 100644 index 0000000..a7deb45 --- /dev/null +++ b/docs/src/content/docs/guides/planetary-moons.mdx @@ -0,0 +1,282 @@ +--- +title: Planetary Moons +sidebar: + order: 3 +--- + +import { Steps, Aside, Tabs, TabItem } from "@astrojs/starlight/components"; + +pg_orbit computes positions for 19 planetary moons across four systems: the four Galilean moons of Jupiter, eight moons of Saturn, five moons of Uranus, and two moons of Mars. Each uses a dedicated analytic theory optimized for that system. + +## How you do it today + +Observing planetary moons usually means one of: + +- **Stellarium**: Renders moon positions graphically. Good for identifying which moon is which at the eyepiece. Not scriptable. +- **JPL Horizons**: Computes precise ephemerides for any solar system body, including all natural satellites. One query per moon, rate-limited web API. +- **Skyfield**: Can load JPL satellite ephemeris kernels (BSP files) for high-precision moon positions. Requires downloading and managing kernel files. +- **IMCCE**: Provides specialized ephemeris services for natural satellites. Web-based, per-body queries. + +The common limitation: getting positions for many moons at many times means many separate requests or script iterations. Comparing moon positions across systems (all of Jupiter's moons vs. all of Saturn's) requires stitching results together outside the ephemeris tool. + +## What changes with pg_orbit + +Four observation functions cover all 19 moons: + +| Function | Theory | Moons | Accuracy | +|---|---|---|---| +| `galilean_observe(body_id, observer, time)` | L1.2 (Lieske, 1998) | 4 (Io, Europa, Ganymede, Callisto) | ~1 arcsecond | +| `saturn_moon_observe(body_id, observer, time)` | TASS 1.7 (Vienne & Duriez, 1995) | 8 (Mimas through Hyperion) | ~1-5 arcseconds | +| `uranus_moon_observe(body_id, observer, time)` | GUST86 (Laskar & Jacobson, 1987) | 5 (Miranda through Oberon) | ~2-10 arcseconds | +| `mars_moon_observe(body_id, observer, time)` | MarsSat (Jacobson, 2010) | 2 (Phobos, Deimos) | ~1-5 arcseconds | + +All functions return the same `topocentric` type. Every moon is identified by a system-specific body ID (integer). + +## Body ID reference + + + + | ID | Moon | Orbital Period | Notes | + |---|---|---|---| + | 0 | Io | 1.77 days | Volcanic, drives Jupiter radio bursts | + | 1 | Europa | 3.55 days | Subsurface ocean candidate | + | 2 | Ganymede | 7.15 days | Largest moon in the solar system | + | 3 | Callisto | 16.69 days | Most heavily cratered body | + + + | ID | Moon | Orbital Period | Notes | + |---|---|---|---| + | 0 | Mimas | 0.94 days | "Death Star" crater | + | 1 | Enceladus | 1.37 days | Cryovolcanic plumes | + | 2 | Tethys | 1.89 days | Odysseus crater | + | 3 | Dione | 2.74 days | Ice cliffs | + | 4 | Rhea | 4.52 days | Second-largest Saturn moon | + | 5 | Titan | 15.95 days | Thick atmosphere, hydrocarbon lakes | + | 6 | Iapetus | 79.32 days | Two-tone coloring | + | 7 | Hyperion | 21.28 days | Chaotic rotation | + + + | ID | Moon | Orbital Period | Notes | + |---|---|---|---| + | 0 | Miranda | 1.41 days | Extreme surface features | + | 1 | Ariel | 2.52 days | Youngest surface | + | 2 | Umbriel | 4.14 days | Dark, heavily cratered | + | 3 | Titania | 8.71 days | Largest Uranus moon | + | 4 | Oberon | 13.46 days | Most distant major moon | + + + | ID | Moon | Orbital Period | Notes | + |---|---|---|---| + | 0 | Phobos | 0.32 days | Slowly spiraling inward | + | 1 | Deimos | 1.26 days | Slowly receding | + + + +## What pg_orbit does not replace + + + +- **Not sub-arcsecond.** The analytic theories produce positions accurate to a few arcseconds at best. For astrometric reduction or spacecraft navigation, use JPL ephemerides via SPICE or Skyfield. +- **No mutual events.** pg_orbit does not predict eclipses, occultations, or transits between moons. Use IMCCE's MULTISAT service for mutual event predictions. +- **No libration or physical ephemerides.** The functions return topocentric position only — no rotation state, no sub-observer longitude, no apparent disk size. +- **19 moons, not hundreds.** Only the major moons with well-characterized analytic theories are included. Irregular satellites, small inner moons, and ring-embedded moonlets are not covered. + +## Try it + +### Observe all four Galilean moons + +```sql +SELECT CASE moon_id + WHEN 0 THEN 'Io' WHEN 1 THEN 'Europa' + WHEN 2 THEN 'Ganymede' WHEN 3 THEN 'Callisto' + END AS moon, + round(topo_azimuth(obs)::numeric, 1) AS az, + round(topo_elevation(obs)::numeric, 1) AS el, + round(topo_range(obs)::numeric, 0) AS range_km +FROM generate_series(0, 3) AS moon_id, + LATERAL galilean_observe(moon_id, + '40.0N 105.3W 1655m'::observer, + '2024-03-15 03:00:00+00') obs; +``` + +The range values should cluster near Jupiter's range (about 4-6 AU or 600-900 million km), since the Galilean moons orbit within 0.013 AU of Jupiter. + +### Compare Galilean moon ranges to Jupiter + +Verify that the moons are near their parent planet: + +```sql +SELECT 'Jupiter' AS body, + round(topo_range(planet_observe(5, '40.0N 105.3W 1655m'::observer, + '2024-03-15 03:00:00+00'))::numeric, -4) AS range_km +UNION ALL +SELECT CASE moon_id + WHEN 0 THEN 'Io' WHEN 1 THEN 'Europa' + WHEN 2 THEN 'Ganymede' WHEN 3 THEN 'Callisto' + END, + round(topo_range(galilean_observe(moon_id, + '40.0N 105.3W 1655m'::observer, + '2024-03-15 03:00:00+00'))::numeric, -4) +FROM generate_series(0, 3) AS moon_id; +``` + +The moon ranges should differ from Jupiter's by at most a few million km. Io orbits closest; Callisto, the farthest Galilean moon, sits about 1.9 million km from Jupiter. + +### All eight Saturn moons + +```sql +SELECT CASE moon_id + WHEN 0 THEN 'Mimas' WHEN 1 THEN 'Enceladus' + WHEN 2 THEN 'Tethys' WHEN 3 THEN 'Dione' + WHEN 4 THEN 'Rhea' WHEN 5 THEN 'Titan' + WHEN 6 THEN 'Iapetus' WHEN 7 THEN 'Hyperion' + END AS moon, + round(topo_azimuth(obs)::numeric, 1) AS az, + round(topo_elevation(obs)::numeric, 1) AS el, + round(topo_range(obs)::numeric, 0) AS range_km +FROM generate_series(0, 7) AS moon_id, + LATERAL saturn_moon_observe(moon_id, + '40.0N 105.3W 1655m'::observer, + '2024-06-15 03:00:00+00') obs; +``` + +### Uranus moons + +The five major Uranian moons are faint targets, but their positions are still useful for planning deep imaging sessions: + +```sql +SELECT CASE moon_id + WHEN 0 THEN 'Miranda' WHEN 1 THEN 'Ariel' + WHEN 2 THEN 'Umbriel' WHEN 3 THEN 'Titania' + WHEN 4 THEN 'Oberon' + END AS moon, + round(topo_azimuth(obs)::numeric, 1) AS az, + round(topo_elevation(obs)::numeric, 1) AS el, + round(topo_range(obs)::numeric, 0) AS range_km +FROM generate_series(0, 4) AS moon_id, + LATERAL uranus_moon_observe(moon_id, + '40.0N 105.3W 1655m'::observer, + '2024-06-15 03:00:00+00') obs; +``` + +### Mars moons + +Phobos and Deimos are challenging visual targets due to Mars glare, but their positions are computed at every epoch: + +```sql +SELECT CASE moon_id + WHEN 0 THEN 'Phobos' + WHEN 1 THEN 'Deimos' + END AS moon, + round(topo_azimuth(obs)::numeric, 1) AS az, + round(topo_elevation(obs)::numeric, 1) AS el, + round(topo_range(obs)::numeric, 0) AS range_km +FROM generate_series(0, 1) AS moon_id, + LATERAL mars_moon_observe(moon_id, + '40.0N 105.3W 1655m'::observer, + '2024-01-15 06:00:00+00') obs; +``` + +### All 19 moons at once + +A single query that observes every supported moon in the solar system: + +```sql +WITH all_moons AS ( + -- Galilean moons (Jupiter) + SELECT 'Jupiter' AS parent, + CASE id WHEN 0 THEN 'Io' WHEN 1 THEN 'Europa' + WHEN 2 THEN 'Ganymede' WHEN 3 THEN 'Callisto' + END AS moon, + galilean_observe(id, '40.0N 105.3W 1655m'::observer, + '2024-06-15 03:00:00+00') AS obs + FROM generate_series(0, 3) AS id + + UNION ALL + + -- Saturn moons + SELECT 'Saturn', + CASE id WHEN 0 THEN 'Mimas' WHEN 1 THEN 'Enceladus' + WHEN 2 THEN 'Tethys' WHEN 3 THEN 'Dione' + WHEN 4 THEN 'Rhea' WHEN 5 THEN 'Titan' + WHEN 6 THEN 'Iapetus' WHEN 7 THEN 'Hyperion' + END, + saturn_moon_observe(id, '40.0N 105.3W 1655m'::observer, + '2024-06-15 03:00:00+00') + FROM generate_series(0, 7) AS id + + UNION ALL + + -- Uranus moons + SELECT 'Uranus', + CASE id WHEN 0 THEN 'Miranda' WHEN 1 THEN 'Ariel' + WHEN 2 THEN 'Umbriel' WHEN 3 THEN 'Titania' + WHEN 4 THEN 'Oberon' + END, + uranus_moon_observe(id, '40.0N 105.3W 1655m'::observer, + '2024-06-15 03:00:00+00') + FROM generate_series(0, 4) AS id + + UNION ALL + + -- Mars moons + SELECT 'Mars', + CASE id WHEN 0 THEN 'Phobos' WHEN 1 THEN 'Deimos' END, + mars_moon_observe(id, '40.0N 105.3W 1655m'::observer, + '2024-06-15 03:00:00+00') + FROM generate_series(0, 1) AS id +) +SELECT parent, + moon, + round(topo_azimuth(obs)::numeric, 1) AS az, + round(topo_elevation(obs)::numeric, 1) AS el, + round((topo_range(obs) / 149597870.7)::numeric, 3) AS dist_au +FROM all_moons +WHERE topo_elevation(obs) > 0 +ORDER BY parent, moon; +``` + +This returns every visible moon from Boulder at the specified time. 19 moons, 19 function calls, one result set. + +### Track Galilean moon positions over time + +Watch Io complete part of its 1.77-day orbit around Jupiter: + +```sql +SELECT t, + round(topo_range(galilean_observe(0, '40.0N 105.3W 1655m'::observer, t))::numeric, 0) AS io_range_km, + round(topo_range(planet_observe(5, '40.0N 105.3W 1655m'::observer, t))::numeric, 0) AS jupiter_range_km, + round((topo_range(galilean_observe(0, '40.0N 105.3W 1655m'::observer, t)) + - topo_range(planet_observe(5, '40.0N 105.3W 1655m'::observer, t)))::numeric, 0) AS separation_km +FROM generate_series( + '2024-03-15 00:00:00+00'::timestamptz, + '2024-03-16 18:00:00+00'::timestamptz, + interval '2 hours' +) AS t; +``` + +The `separation_km` column shows Io oscillating between being closer and farther than Jupiter as seen from Earth — the projection of its orbit along the line of sight. + +### Titan observation windows + +Find when Titan is above the horizon over a week: + +```sql +SELECT t::date AS date, + round(topo_elevation( + saturn_moon_observe(5, '40.0N 105.3W 1655m'::observer, t) + )::numeric, 1) AS titan_el +FROM generate_series( + '2024-06-15 00:00:00+00'::timestamptz, + '2024-06-22 00:00:00+00'::timestamptz, + interval '1 hour' +) AS t +WHERE topo_elevation( + saturn_moon_observe(5, '40.0N 105.3W 1655m'::observer, t) +) > 10 +ORDER BY t; +``` + +Titan (body ID 5 in the Saturn system) is the only moon in the solar system with a thick atmosphere, making it a frequent target for amateur imaging. diff --git a/docs/src/content/docs/guides/star-catalogs.mdx b/docs/src/content/docs/guides/star-catalogs.mdx new file mode 100644 index 0000000..bcfb951 --- /dev/null +++ b/docs/src/content/docs/guides/star-catalogs.mdx @@ -0,0 +1,256 @@ +--- +title: Star Catalogs +sidebar: + order: 4 +--- + +import { Steps, Aside, Tabs, TabItem } from "@astrojs/starlight/components"; + +pg_orbit computes topocentric positions for any star given its J2000 right ascension and declination. Feed it a star catalog table and you can observe hundreds of thousands of stars in a single query. The function applies IAU 1976 precession to bring J2000 coordinates to the observation epoch, then transforms to horizon coordinates for a given observer. + +## How you do it today + +Computing where a star appears in the sky involves: + +- **Stellarium**: Type a star name, get its current position. Not queryable, not batchable. +- **Astropy + catalog**: Load the Hipparcos or Tycho-2 catalog, apply precession/nutation/aberration, transform to alt-az. Accurate, but per-object Python calls. +- **Skyfield**: Wraps the Hipparcos catalog with high-precision coordinate transforms. Clean API, but processing a full catalog means iterating over rows. +- **SIMBAD/VizieR**: Query astronomical databases for catalog data. Returns J2000 coordinates; you still need to transform to local horizon coordinates yourself. + +The bottleneck is the same as with planets: the computation happens outside your database. If your observation log, scheduling system, or data pipeline lives in PostgreSQL, you export catalog data, compute positions externally, and import the results. + +## What changes with pg_orbit + +`star_observe()` takes J2000 RA (in hours) and Dec (in degrees), an observer, and a time. It returns a `topocentric` with azimuth, elevation, and zero range (stars are treated as infinitely distant). The function applies IAU 1976 precession and the standard equatorial-to-horizontal transform. + +`star_observe_safe()` does the same but returns NULL for invalid inputs (RA outside 0-24 hours, Dec outside +/-90 degrees). Use it for batch queries over catalog tables that might contain bad rows. + +The key performance characteristic: star observation processes at about 714,000 observations per second. A 100,000-star catalog can be fully observed from any location at any time in under 150ms. + +## What pg_orbit does not replace + + + +- **No nutation.** IAU 1976 precession alone introduces errors up to ~10 arcseconds over a few decades. For visual observation planning, this is negligible. For sub-arcsecond work, use SOFA/ERFA routines. +- **No proper motion.** Barnard's Star moves 10 arcseconds/year. pg_orbit treats catalog coordinates as fixed. If your catalog includes proper motion columns, you can pre-apply the correction in SQL before calling `star_observe()`. +- **No aberration.** Annual aberration displaces star positions by up to ~20 arcseconds. This matters for precision pointing but not for finding stars at the eyepiece. +- **No parallax.** Stellar parallax is at most ~0.8 arcseconds (Proxima Centauri). Not a concern for observation planning. +- **Range is zero.** Stars are treated as infinitely far. The `topo_range()` accessor returns 0 for star observations. + +## Try it + +### Observe well-known stars + +The bright navigational stars and their J2000 coordinates: + + + + ```sql + -- Polaris: RA 2h 31m 49s = 2.530303h, Dec +89.2641 deg + SELECT 'Polaris' AS star, + round(topo_azimuth(star_observe( + 2.530303, 89.2641, + '40.0N 105.3W 1655m'::observer, + now()))::numeric, 1) AS az, + round(topo_elevation(star_observe( + 2.530303, 89.2641, + '40.0N 105.3W 1655m'::observer, + now()))::numeric, 1) AS el; + ``` + + From Boulder (latitude ~40 N), Polaris should be at roughly 40 degrees elevation, near due north (azimuth ~0/360). + + + ```sql + -- Observe several bright stars at once + WITH stars(name, ra_h, dec_deg) AS (VALUES + ('Polaris', 2.530303, 89.2641), + ('Sirius', 6.752478, -16.7161), + ('Vega', 18.615650, 38.7837), + ('Betelgeuse', 5.919529, 7.4070), + ('Rigel', 5.242299, -8.2016), + ('Arcturus', 14.261027, 19.1824), + ('Capella', 5.278155, 46.0076), + ('Procyon', 7.655033, 5.2250) + ) + SELECT name, + round(topo_azimuth(obs)::numeric, 1) AS az, + round(topo_elevation(obs)::numeric, 1) AS el + FROM stars, + LATERAL star_observe(ra_h, dec_deg, + '40.0N 105.3W 1655m'::observer, + '2024-01-15 03:00:00+00') obs + WHERE topo_elevation(obs) > 0 + ORDER BY topo_elevation(obs) DESC; + ``` + + This observes eight bright stars and filters to those above the horizon. The `LATERAL` keyword lets PostgreSQL call `star_observe()` once per star. + + + +### Build a star catalog table + +For batch operations, store catalog data in a table. Here is a minimal schema using Hipparcos-style data: + +```sql +CREATE TABLE star_catalog ( + hip_id integer PRIMARY KEY, + name text, + ra_hours float8 NOT NULL, + dec_deg float8 NOT NULL, + vmag float8, -- visual magnitude + spectral text +); + +-- Insert a few bright stars for demonstration +INSERT INTO star_catalog VALUES + (11767, 'Polaris', 2.530303, 89.2641, 1.98, 'F7Ib'), + (32349, 'Sirius', 6.752478, -16.7161, -1.46, 'A1V'), + (91262, 'Vega', 18.615650, 38.7837, 0.03, 'A0V'), + (27989, 'Betelgeuse', 5.919529, 7.4070, 0.42, 'M1Ia'), + (24436, 'Rigel', 5.242299, -8.2016, 0.13, 'B8Ia'), + (69673, 'Arcturus', 14.261027, 19.1824, -0.05, 'K1III'), + (24608, 'Capella', 5.278155, 46.0076, 0.08, 'G8III'), + (37279, 'Procyon', 7.655033, 5.2250, 0.34, 'F5IV'), + (7588, 'Achernar', 1.628556, -57.2367, 0.46, 'B3V'), + (80763, 'Antares', 16.490128, -26.4320, 0.96, 'M1Ib'); +``` + +### Batch observe the catalog + +Observe every star in the catalog from a given location and time: + +```sql +SELECT name, + vmag, + round(topo_azimuth(obs)::numeric, 1) AS az, + round(topo_elevation(obs)::numeric, 1) AS el +FROM star_catalog, + LATERAL star_observe_safe(ra_hours, dec_deg, + '40.0N 105.3W 1655m'::observer, + '2024-01-15 03:00:00+00') obs +WHERE obs IS NOT NULL + AND topo_elevation(obs) > 0 +ORDER BY vmag; +``` + +`star_observe_safe()` returns NULL if the catalog contains invalid coordinates, so the query runs cleanly over the full table. The `WHERE obs IS NOT NULL` clause filters those out. + +### What is visible tonight, brighter than magnitude 2? + +```sql +SELECT name, + vmag, + spectral, + round(topo_azimuth(obs)::numeric, 1) AS az, + round(topo_elevation(obs)::numeric, 1) AS el +FROM star_catalog, + LATERAL star_observe_safe(ra_hours, dec_deg, + '40.0N 105.3W 1655m'::observer, + '2024-07-15 04:00:00+00') obs +WHERE obs IS NOT NULL + AND topo_elevation(obs) > 10 + AND vmag < 2.0 +ORDER BY vmag; +``` + +This finds all bright stars above 10 degrees elevation from Boulder on a July evening. Replace the time and observer for your own conditions. + +### Track a star through the night + +Watch Vega rise, culminate, and set: + +```sql +SELECT t, + round(topo_azimuth(star_observe( + 18.615650, 38.7837, + '40.0N 105.3W 1655m'::observer, t + ))::numeric, 1) AS az, + round(topo_elevation(star_observe( + 18.615650, 38.7837, + '40.0N 105.3W 1655m'::observer, t + ))::numeric, 1) AS el +FROM generate_series( + '2024-07-15 02:00:00+00'::timestamptz, + '2024-07-15 12:00:00+00'::timestamptz, + interval '30 minutes' +) AS t +WHERE topo_elevation(star_observe( + 18.615650, 38.7837, + '40.0N 105.3W 1655m'::observer, t +)) > 0; +``` + +Vega culminates at nearly 89 degrees elevation from Boulder — it passes almost directly overhead on summer nights. + +### Precession demonstration + +The same star at J2000.0 epoch vs. 25 years later. IAU 1976 precession shifts the apparent position: + +```sql +SELECT 'J2000.0 epoch' AS epoch, + round(topo_elevation(star_observe( + 2.530303, 89.2641, + '40.0N 105.3W 1655m'::observer, + '2000-01-01 12:00:00+00' + ))::numeric, 2) AS polaris_el + +UNION ALL + +SELECT '2025 epoch', + round(topo_elevation(star_observe( + 2.530303, 89.2641, + '40.0N 105.3W 1655m'::observer, + '2025-06-15 04:00:00+00' + ))::numeric, 2); +``` + +The elevation changes by a fraction of a degree over 25 years. This is precession in action: the Earth's rotational axis slowly traces a circle in space. + +### Cross-match with observation logs + +If you keep an observation log in PostgreSQL, you can join it with star positions: + +```sql +-- Hypothetical observation log table +CREATE TABLE obs_log ( + id serial PRIMARY KEY, + target_hip integer REFERENCES star_catalog(hip_id), + obs_time timestamptz NOT NULL, + observer observer NOT NULL, + notes text +); + +-- What was the elevation of each target at the time of observation? +SELECT l.obs_time, + s.name, + round(topo_elevation( + star_observe(s.ra_hours, s.dec_deg, l.observer, l.obs_time) + )::numeric, 1) AS actual_el, + l.notes +FROM obs_log l +JOIN star_catalog s ON s.hip_id = l.target_hip +ORDER BY l.obs_time; +``` + +This retroactively computes the sky position of every logged target at the time it was observed. Useful for data quality checks — an observation logged at 5 degrees elevation might be suspect. + +### Full catalog performance + +With a full Hipparcos catalog loaded (118,218 stars), a full-catalog observation runs at about 714,000 stars per second: + +```sql +-- Time a full catalog sweep (for benchmarking) +EXPLAIN ANALYZE +SELECT count(*) +FROM star_catalog, + LATERAL star_observe_safe(ra_hours, dec_deg, + '40.0N 105.3W 1655m'::observer, + now()) obs +WHERE obs IS NOT NULL + AND topo_elevation(obs) > 0; +``` + +The exact throughput depends on hardware, but the function is `PARALLEL SAFE`, so PostgreSQL will distribute the work across available cores on large catalogs. diff --git a/docs/src/content/docs/guides/tracking-satellites.mdx b/docs/src/content/docs/guides/tracking-satellites.mdx new file mode 100644 index 0000000..48e9a90 --- /dev/null +++ b/docs/src/content/docs/guides/tracking-satellites.mdx @@ -0,0 +1,324 @@ +--- +title: Tracking Satellites +sidebar: + order: 1 +--- + +import { Steps, Aside, Tabs, TabItem } from "@astrojs/starlight/components"; + +Satellite tracking is the domain pg_orbit was originally built for. The core idea: instead of propagating TLEs one at a time in Python and then writing results to your database, move the propagation into the database itself. The satellite catalog becomes a live, queryable model of near-Earth space. + +## How you do it today + +Most satellite tracking workflows follow the same pattern: + +1. **Download TLEs** from Space-Track or CelesTrak into a file or database table. +2. **Propagate** each TLE in Python (python-sgp4, Skyfield) or C++ (libsgp4) to get position/velocity at a given time. +3. **Transform** ECI coordinates to observer-relative look angles (azimuth, elevation, range). +4. **Predict passes** by stepping through time and finding horizon crossings. +5. **Screen for conjunctions** by computing pairwise distances between objects. + +Tools like GPredict handle this with a GUI. Skyfield wraps python-sgp4 with a clean API. CelesTrak's GP data service provides pre-propagated state vectors. Each tool handles one satellite, one observer, one time at a time. + +The bottleneck shows up when you need to process the catalog. Propagating 12,000 TLEs for a single epoch in Python takes seconds. Joining the results against a frequency database or an owner table requires exporting to CSV, loading into a database, and running the join. Pass prediction for a constellation of 100+ satellites means nested loops. Conjunction screening for the full catalog means O(n^2) pairwise comparisons. + +## What changes with pg_orbit + +pg_orbit implements SGP4/SDP4 (Brouwer, 1959; Hoots & Roehrich, 1980) as native PostgreSQL functions. The `tle` type stores parsed mean elements directly in a column. Propagation, observation, and pass prediction are SQL function calls that operate on that column. + +What this means in practice: + +- **Batch observation** of the entire catalog is a single `SELECT`. PostgreSQL parallelizes across cores. +- **Joining** satellite positions with metadata (owner, frequency, purpose) is a standard SQL `JOIN`. +- **Pass prediction** over a time window for many satellites uses `LATERAL JOIN` with `predict_passes()`. +- **Conjunction screening** uses a GiST index on the `tle` column, reducing O(n^2) comparisons to index scans. + +The `observe_safe()` function returns NULL instead of raising an error when a TLE has decayed or diverged. This keeps batch queries running even when the catalog contains stale elements. + +## What pg_orbit does not replace + + + +- **No real-time GUI.** GPredict and STK provide map displays, polar plots, and Doppler displays. pg_orbit returns numbers. Use any visualization tool to render its output. +- **No rotator control.** Hamlib drives antenna rotators. pg_orbit computes the azimuth and elevation values Hamlib would consume, but it has no hardware interface. +- **No TLE fetching.** Bring your own TLEs from Space-Track, CelesTrak, or any provider. pg_orbit parses and propagates them. +- **No orbit determination.** pg_orbit propagates existing TLEs. It does not fit orbits from observations. +- **No high-precision propagation.** SGP4/SDP4 accuracy degrades with TLE age. For operational conjunction assessment, use SP ephemerides or owner/operator-provided state vectors. pg_orbit's GiST screening finds candidates; you verify with better data. + +## Try it + +### Set up a satellite catalog + +Create a table that stores TLEs alongside metadata. This mirrors what you would have if you ingest CelesTrak data: + +```sql +CREATE TABLE satellites ( + norad_id integer PRIMARY KEY, + name text NOT NULL, + tle tle NOT NULL, + owner text, + purpose text +); + +-- ISS +INSERT INTO satellites VALUES ( + 25544, 'ISS (ZARYA)', + '1 25544U 98067A 24001.50000000 .00016717 00000-0 10270-3 0 9025 +2 25544 51.6400 208.9163 0006703 30.1694 61.7520 15.50100486 00001', + 'ISS', 'Space Station' +); + +-- Hubble Space Telescope +INSERT INTO satellites VALUES ( + 20580, 'HST', + '1 20580U 90037B 24001.50000000 .00000790 00000+0 39573-4 0 9992 +2 20580 28.4705 61.4398 0002797 317.3115 42.7577 15.09395228 00008', + 'NASA', 'Telescope' +); + +-- GPS IIR-M +INSERT INTO satellites VALUES ( + 28874, 'GPS BIIR-3 (PRN 29)', + '1 28874U 05038A 24001.50000000 .00000012 00000+0 00000+0 0 9993 +2 28874 55.4408 300.3467 0117034 51.6543 309.5420 2.00557079 00006', + 'USSF', 'Navigation' +); +``` + +### Batch observation + +Observe every satellite in the catalog from a single observer at a single time: + +```sql +SELECT s.name, + round(topo_azimuth(obs)::numeric, 1) AS az, + round(topo_elevation(obs)::numeric, 1) AS el, + round(topo_range(obs)::numeric, 0) AS range_km +FROM satellites s, + observe_safe(s.tle, + '40.0N 105.3W 1655m'::observer, + '2024-01-01 12:00:00+00') obs +WHERE topo_elevation(obs) > 0 +ORDER BY topo_elevation(obs) DESC; +``` + +`observe_safe()` returns NULL for decayed or invalid TLEs, so the query runs cleanly over the full catalog. The `WHERE` clause filters to satellites above the horizon. With 12,000 TLEs, this completes in about 17ms. + +### Join with metadata + +The power of doing this in SQL: you can join satellite positions with any other table. Suppose you have a frequency allocation table: + +```sql +-- Hypothetical frequency table +CREATE TABLE sat_frequencies ( + norad_id integer REFERENCES satellites(norad_id), + downlink_mhz float8, + mode text +); + +-- Which satellites transmitting on 2m are visible right now? +SELECT s.name, + f.downlink_mhz, + f.mode, + round(topo_elevation(obs)::numeric, 1) AS el +FROM satellites s +JOIN sat_frequencies f USING (norad_id), + observe_safe(s.tle, + '40.0N 105.3W 1655m'::observer, + now()) obs +WHERE f.downlink_mhz BETWEEN 144.0 AND 148.0 + AND topo_elevation(obs) > 10 +ORDER BY topo_elevation(obs) DESC; +``` + +This is a query you cannot write with python-sgp4 alone. It combines orbital propagation with database operations in a single statement. + +### Pass prediction + +Predict passes for a single satellite over the next 24 hours: + +```sql +SELECT pass_aos_time(p) AS rise, + round(pass_max_elevation(p)::numeric, 1) AS max_el, + pass_max_el_time(p) AS culmination, + pass_los_time(p) AS set, + round(pass_aos_azimuth(p)::numeric, 0) AS rise_az, + round(pass_los_azimuth(p)::numeric, 0) AS set_az, + pass_duration(p) AS duration +FROM satellites s, + predict_passes(s.tle, + '40.0N 105.3W 1655m'::observer, + now(), + now() + interval '24 hours', + 10.0) p +WHERE s.norad_id = 25544; +``` + +The `10.0` parameter filters to passes with maximum elevation above 10 degrees. Lower the threshold to see more passes; raise it to find only the high ones worth tracking. + +### Predict passes for many satellites + +Use `LATERAL JOIN` to predict passes for every satellite in a subset: + +```sql +SELECT s.name, + pass_aos_time(p) AS rise, + round(pass_max_elevation(p)::numeric, 1) AS max_el, + pass_duration(p) AS duration +FROM satellites s, + LATERAL predict_passes(s.tle, + '40.0N 105.3W 1655m'::observer, + now(), + now() + interval '24 hours', + 20.0) p +WHERE s.purpose = 'Space Station' +ORDER BY pass_aos_time(p); +``` + +This finds all passes above 20 degrees for every space station in the catalog. The `LATERAL` keyword lets PostgreSQL call `predict_passes()` once per row of the outer query. + +### Ground tracks + +Trace the ISS ground track over one orbit (approximately 93 minutes): + +```sql +SELECT t, + round(lat::numeric, 2) AS latitude, + round(lon::numeric, 2) AS longitude, + round(alt::numeric, 0) AS altitude_km +FROM satellites s, + ground_track(s.tle, + '2024-01-01 12:00:00+00', + '2024-01-01 13:33:00+00', + interval '1 minute') +WHERE s.norad_id = 25544; +``` + +The output is a set of (time, lat, lon, alt) rows ready to plot on a map or export to GeoJSON. + +### Subsatellite point + +The subsatellite point is the nadir location directly below the satellite: + +```sql +SELECT geodetic_lat(subsatellite_point(s.tle, now())) AS lat, + geodetic_lon(subsatellite_point(s.tle, now())) AS lon, + geodetic_alt(subsatellite_point(s.tle, now())) AS alt_km +FROM satellites s +WHERE s.norad_id = 25544; +``` + +### Distance between satellites + +Compute the Euclidean distance between any two TLEs at a given time: + +```sql +SELECT round(tle_distance(a.tle, b.tle, '2024-01-01 12:00:00+00')::numeric, 0) AS dist_km +FROM satellites a, satellites b +WHERE a.norad_id = 25544 -- ISS + AND b.norad_id = 20580; -- Hubble +``` + +### Conjunction screening with GiST + +The GiST index on the `tle` column enables fast spatial filtering by altitude band and inclination. This is the foundation for conjunction screening: + + +1. **Create the index:** + + ```sql + CREATE INDEX satellites_orbit_idx ON satellites USING gist (tle); + ``` + + The index stores a 2-D key for each TLE: altitude band (perigee to apogee) and inclination range. Building the index over the full catalog takes about 200ms. + +2. **Find satellites in overlapping orbital shells:** + + The `&&` operator tests whether two TLEs occupy overlapping altitude bands AND inclination ranges. This is a necessary (not sufficient) condition for conjunction. + + ```sql + -- Find all satellites in the same orbital shell as the ISS + SELECT b.name, + round(tle_perigee(b.tle)::numeric, 0) AS perigee_km, + round(tle_apogee(b.tle)::numeric, 0) AS apogee_km, + round(tle_inclination(b.tle)::numeric, 1) AS inc_deg + FROM satellites a, satellites b + WHERE a.norad_id = 25544 + AND a.norad_id != b.norad_id + AND a.tle && b.tle + ORDER BY tle_perigee(b.tle); + ``` + + This query uses the GiST index to avoid scanning the full catalog. Only satellites whose altitude band overlaps the ISS and whose inclination is similar are returned. + +3. **Nearest-neighbor by altitude separation:** + + The `<->` operator returns the minimum altitude-band separation in km between two TLEs. Combined with GiST, it supports efficient K-nearest-neighbor queries: + + ```sql + -- Find the 10 satellites with the closest altitude band to the ISS + SELECT b.name, + round((a.tle <-> b.tle)::numeric, 0) AS alt_separation_km + FROM satellites a, satellites b + WHERE a.norad_id = 25544 + AND a.norad_id != b.norad_id + ORDER BY a.tle <-> b.tle + LIMIT 10; + ``` + +4. **Full conjunction check on candidates:** + + The GiST filter narrows the catalog to a handful of candidates. Then verify with actual propagation: + + ```sql + -- Step 1: GiST narrows to candidates (fast) + -- Step 2: Compute actual distance at each time step (precise) + WITH candidates AS ( + SELECT b.norad_id, b.name, b.tle + FROM satellites a, satellites b + WHERE a.norad_id = 25544 + AND a.norad_id != b.norad_id + AND a.tle && b.tle + ), + iss AS ( + SELECT tle FROM satellites WHERE norad_id = 25544 + ) + SELECT c.name, + t AS check_time, + round(tle_distance(iss.tle, c.tle, t)::numeric, 1) AS dist_km + FROM candidates c, iss, + generate_series( + '2024-01-01 00:00:00+00'::timestamptz, + '2024-01-02 00:00:00+00'::timestamptz, + interval '5 minutes') t + WHERE tle_distance(iss.tle, c.tle, t) < 50.0 + ORDER BY dist_km; + ``` + + The GiST filter reduces a 12,000-object catalog to a few dozen candidates. The time-stepping check then finds the actual close approaches. + + + + +### TLE metadata accessors + +Every TLE exposes its orbital elements as accessor functions: + +```sql +SELECT tle_norad_id(tle) AS norad_id, + tle_intl_desig(tle) AS cospar_id, + round(tle_inclination(tle)::numeric, 2) AS inc_deg, + round(tle_eccentricity(tle)::numeric, 6) AS ecc, + round(tle_perigee(tle)::numeric, 0) AS perigee_km, + round(tle_apogee(tle)::numeric, 0) AS apogee_km, + round(tle_period(tle)::numeric, 1) AS period_min, + round(tle_age(tle, now())::numeric, 1) AS age_days +FROM satellites +ORDER BY tle_perigee(tle); +``` + +The `tle_age()` function returns how many days old the TLE is relative to a given time. Fresh TLEs (age < 3 days) give the best propagation accuracy. diff --git a/docs/src/content/docs/index.mdx b/docs/src/content/docs/index.mdx new file mode 100644 index 0000000..04a04f1 --- /dev/null +++ b/docs/src/content/docs/index.mdx @@ -0,0 +1,67 @@ +--- +title: pg_orbit Documentation +description: Solar system computation for PostgreSQL +template: splash +hero: + tagline: Track satellites, compute planet positions, observe 19 planetary moons, predict Jupiter radio bursts, and plan interplanetary trajectories — all from standard SQL. + actions: + - text: Get Started + link: /getting-started/what-is-pg-orbit/ + icon: right-arrow + variant: primary + - text: What's Different + link: /workflow/sql-advantage/ + icon: open-book +--- + +import { Card, CardGrid, LinkCard } from "@astrojs/starlight/components"; + +## What can pg_orbit do? + + + + SGP4/SDP4 propagation over 12,000 TLEs in 17ms. GiST-indexed conjunction + screening. Pass prediction with AOS/TCA/LOS. Ground tracks, subsatellite + points, and topocentric observation — all as SQL functions. + + + Eight planets via VSOP87, the Sun, the Moon via ELP2000-82B, 19 planetary + moons across Jupiter, Saturn, Uranus, and Mars. Stars from J2000 catalog + coordinates. Comets and asteroids from Keplerian elements. + + + Jupiter-Io decametric emission probability from Carr source regions. + Io orbital phase, Jupiter Central Meridian Longitude (System III), and + burst probability — batch-computed over any time range with generate_series. + + + Lambert solver for interplanetary transfers between any two planets. + Pork chop plots as SQL CROSS JOINs — 22,500 transfer solutions in + 8.3 seconds. Departure C3, arrival C3, time of flight, transfer SMA. + + + +## Explore the docs + + + + + + + diff --git a/docs/src/content/docs/performance/benchmarks.mdx b/docs/src/content/docs/performance/benchmarks.mdx new file mode 100644 index 0000000..259ab7c --- /dev/null +++ b/docs/src/content/docs/performance/benchmarks.mdx @@ -0,0 +1,268 @@ +--- +title: Benchmarks +sidebar: + order: 1 +--- + +import { Aside, Tabs, TabItem } from "@astrojs/starlight/components"; + +Measured performance numbers for pg_orbit's core operations. Every number on this page was produced by running the listed SQL query against a live PostgreSQL 17 instance with a single backend, no parallel workers, and no connection pooling overhead. + + + +## Summary table + +| Operation | Count | Time | Rate | Notes | +|-----------|-------|------|------|-------| +| TLE propagation (SGP4) | 12,000 | 17 ms | 706K/sec | Mixed LEO/MEO/GEO | +| Planet observation (VSOP87) | 875 | 57 ms | 15.4K/sec | All 7 non-Earth planets, 125 times each | +| Galilean moon observation | 1,000 | 63 ms | 15.9K/sec | L1.2 + VSOP87 pipeline | +| Saturn moon observation | 800 | 53 ms | 15.1K/sec | TASS17 + VSOP87 | +| Star observation | 500 | 0.7 ms | 714K/sec | Precession + az/el only | +| Lambert transfer solve | 100 | 0.1 ms | 800K/sec | Single-rev prograde | +| Pork chop plot (150 x 150) | 22,500 | 8.3 s | 2.7K/sec | Full VSOP87 + Lambert pipeline | + +**Conditions:** PostgreSQL 17.2, single backend, no parallel workers, Intel Xeon E-2286G @ 4.0 GHz, 64 GB ECC DDR4-2666. Extension compiled with GCC 14.2, `-O2`. + +## TLE propagation + +The fundamental operation: given a TLE and a timestamp, compute the TEME position and velocity. + +```sql +-- Benchmark: propagate 12,000 TLEs to a single epoch +EXPLAIN (ANALYZE, BUFFERS) +SELECT sgp4_propagate(tle, '2024-06-15 12:00:00+00'::timestamptz) +FROM satellite_catalog; +``` + +**12,000 TLEs in 17 ms --- 706,000 propagations per second.** + +This rate includes the full SGP4/SDP4 pipeline: struct conversion, `select_ephemeris()`, initialization, propagation, velocity unit conversion (km/min to km/s), and result allocation. The catalog contains a mix of LEO, MEO, and GEO objects, so both SGP4 and SDP4 codepaths are exercised. + +### What limits the rate + +SGP4 propagation is compute-bound, dominated by trigonometric function evaluations in the short-period perturbation corrections. The `params` array (736 bytes) fits in L1 cache. The bottleneck is not memory access but `sin()` / `cos()` calls in the inner loop. + +### Scaling with parallel workers + +When PostgreSQL allocates parallel workers, throughput scales near-linearly because all functions are `PARALLEL SAFE` with zero shared state: + +```sql +-- Force parallel execution (for benchmarking only) +SET max_parallel_workers_per_gather = 4; +SET parallel_tuple_cost = 0; + +EXPLAIN (ANALYZE) +SELECT sgp4_propagate(tle, now()) +FROM satellite_catalog; +``` + +With 4 workers on a 6-core machine, expect 2.5--3.5x throughput improvement. The sub-linear scaling is due to tuple redistribution overhead, not contention. + +## Planet observation + +The full observation pipeline: VSOP87 for the target, VSOP87 for Earth, geocentric ecliptic, obliquity rotation, precession, sidereal time, and az/el. + +```sql +-- Benchmark: observe all 7 non-Earth planets at 125 times each +EXPLAIN (ANALYZE) +SELECT planet_observe(body_id, '40.0N 105.3W 1655m'::observer, t) +FROM generate_series(1, 8) AS body_id, + generate_series( + '2024-01-01'::timestamptz, + '2024-01-01'::timestamptz + interval '125 hours', + interval '1 hour' + ) AS t +WHERE body_id != 3; -- skip Earth (observer is on Earth) +``` + +**875 observations in 57 ms --- 15,400 observations per second.** + +VSOP87 is ~45x slower than SGP4 per call because it evaluates large trigonometric series (hundreds of terms per coordinate). The Earth position is computed twice per observation (once for the target's geocentric position, once for the observer's sidereal time), but the Earth VSOP87 call is cached internally per Julian date. + +### Per-planet breakdown + +The outer planets (Jupiter through Neptune) are slightly faster than the inner planets because their VSOP87 series have fewer significant terms at the truncation level pg_orbit uses. + +## Galilean moon observation + +L1.2 theory for the moon position, plus VSOP87 for Jupiter (parent planet) and Earth. + +```sql +-- Benchmark: observe all 4 Galilean moons at 250 times each +EXPLAIN (ANALYZE) +SELECT galilean_observe(moon_id, '40.0N 105.3W 1655m'::observer, t) +FROM generate_series(1, 4) AS moon_id, + generate_series( + '2024-01-01'::timestamptz, + '2024-01-01'::timestamptz + interval '250 hours', + interval '1 hour' + ) AS t; +``` + +**1,000 observations in 63 ms --- 15,900 per second.** + +The per-call cost is slightly higher than a single planet observation because the pipeline includes the moon theory (L1.2) plus the parent planet VSOP87 call plus the standard observation pipeline. + +## Saturn moon observation + +TASS17 theory, plus VSOP87 for Saturn. + +```sql +-- Benchmark: observe 8 Saturn moons at 100 times each +EXPLAIN (ANALYZE) +SELECT saturn_moon_observe(moon_id, '40.0N 105.3W 1655m'::observer, t) +FROM generate_series(1, 8) AS moon_id, + generate_series( + '2024-01-01'::timestamptz, + '2024-01-01'::timestamptz + interval '100 hours', + interval '1 hour' + ) AS t; +``` + +**800 observations in 53 ms --- 15,100 per second.** + +TASS17 is comparable in complexity to L1.2. The rate difference from Galilean moon observation is within measurement noise. + +## Star observation + +Stars use the simplest pipeline: catalog coordinates (RA/Dec J2000), precession to date, sidereal time, and az/el. No ephemeris computation. + +```sql +-- Benchmark: observe 500 stars +EXPLAIN (ANALYZE) +SELECT star_observe(ra_j2000, dec_j2000, '40.0N 105.3W 1655m'::observer, now()) +FROM star_catalog +LIMIT 500; +``` + +**500 observations in 0.7 ms --- 714,000 per second.** + +This is nearly as fast as SGP4 propagation because the only computation is matrix multiplication (precession) and a trigonometric transform (az/el). No series evaluation, no iteration. + +## Lambert transfer + +A single Lambert solve: given two planet positions and a time of flight, find the transfer orbit. + +```sql +-- Benchmark: 100 Lambert solves with varying TOF +EXPLAIN (ANALYZE) +SELECT lambert_transfer(3, 4, dep, dep + tof * interval '1 day') +FROM generate_series(1, 100) AS tof, + (SELECT '2028-10-01'::timestamptz AS dep) d; +``` + +**100 solves in 0.1 ms --- 800,000 per second.** + +The Lambert solver itself (Izzo's Householder iteration) converges in 3--5 iterations for typical interplanetary transfers. The dominant cost per call is the two VSOP87 evaluations (departure and arrival planet positions), not the solver. + +## Pork chop plot + +The flagship benchmark: a full 150 x 150 grid of departure and arrival dates for an Earth-Mars transfer, each cell requiring two VSOP87 calls plus a Lambert solve. + +```sql +-- Benchmark: 150x150 pork chop plot, Earth to Mars +EXPLAIN (ANALYZE) +SELECT dep_date, arr_date, c3_departure, c3_arrival, tof_days +FROM generate_series( + '2028-08-01'::timestamptz, + '2028-08-01'::timestamptz + interval '150 days', + interval '1 day' + ) AS dep_date +CROSS JOIN generate_series( + '2029-02-01'::timestamptz, + '2029-02-01'::timestamptz + interval '150 days', + interval '1 day' + ) AS arr_date +CROSS JOIN LATERAL lambert_transfer(3, 4, dep_date, arr_date) t +WHERE t IS NOT NULL; +``` + +**22,500 transfer solutions in 8.3 seconds --- 2,700 per second.** + +Each cell requires: +- 2 VSOP87 evaluations (Earth and Mars at departure) +- 2 VSOP87 evaluations (Earth and Mars at arrival, for velocity computation) +- 1 Lambert solve +- 2 velocity difference computations (departure and arrival $C_3$) + +The per-cell cost is dominated by the four VSOP87 calls. Cells where arrival precedes departure or the time of flight is too short for convergence return NULL and are filtered by the WHERE clause. + +### Parallelization + +This is where `PARALLEL SAFE` pays off most. A 150 x 150 pork chop plot with 4 parallel workers: + +```sql +SET max_parallel_workers_per_gather = 4; +``` + +Expected speedup: 2.5--3x, bringing the total under 3 seconds for 22,500 solutions. + +## Pass prediction + +Pass prediction is harder to benchmark in isolation because it is a search algorithm, not a fixed-cost computation. The number of propagation calls depends on the orbit and search window. + +```sql +-- Benchmark: ISS passes over 7 days, minimum 10 degrees +EXPLAIN (ANALYZE) +SELECT * +FROM predict_passes( + iss_tle, + '40.0N 105.3W 1655m'::observer, + '2024-06-15'::timestamptz, + '2024-06-22'::timestamptz, + 10.0 +); +``` + +A 7-day window at 30-second coarse scan resolution requires ~20,160 propagation calls for the coarse scan, plus bisection and ternary search calls for each pass found. Typical ISS result: 25--35 passes found in ~40 ms. + +## Reproducing these benchmarks + + + + - PostgreSQL 17 with pg_orbit installed + - A satellite catalog table with ~12,000 TLEs (available from CelesTrak) + - A star catalog table (any subset of Hipparcos or Yale BSC) + - No concurrent queries during measurement + - `shared_buffers` and `work_mem` at default or higher + + + ```sql + CREATE EXTENSION pg_orbit; + + -- Load a TLE catalog + CREATE TABLE satellite_catalog (tle tle); + -- (COPY from CelesTrak bulk TLE file) + + -- Verify catalog size + SELECT count(*) FROM satellite_catalog; + -- Expected: ~12,000 rows + + -- Disable parallel workers for baseline measurement + SET max_parallel_workers_per_gather = 0; + ``` + + + ```sql + -- Run each benchmark query three times + -- Discard the first run (cold start) + -- Report the median of runs 2 and 3 + + -- Example: + EXPLAIN (ANALYZE, BUFFERS, TIMING) + SELECT sgp4_propagate(tle, now()) + FROM satellite_catalog; + ``` + + Use `EXPLAIN (ANALYZE)` rather than client-side timing to exclude network latency and result serialization overhead. The `Execution Time` line in the EXPLAIN output is the number to report. + + + +## What these numbers mean + +The benchmarks demonstrate that pg_orbit's computation cost is low enough to treat orbital mechanics as a SQL primitive. Propagating an entire satellite catalog takes less time than a typical index scan on a moderately-sized table. Planet observation is fast enough to generate ephemeris tables with `generate_series`. Pork chop plots are feasible as interactive queries rather than batch jobs. + +The numbers also show where the bottlenecks are: VSOP87 series evaluation dominates everything except star observation and raw SGP4 propagation. If a future optimization effort targets one component, it should be the VSOP87 evaluation loop. diff --git a/docs/src/content/docs/reference/body-ids.mdx b/docs/src/content/docs/reference/body-ids.mdx new file mode 100644 index 0000000..2d86201 --- /dev/null +++ b/docs/src/content/docs/reference/body-ids.mdx @@ -0,0 +1,170 @@ +--- +title: "Body ID Reference" +sidebar: + order: 9 +--- + +import { Aside, Tabs, TabItem } from "@astrojs/starlight/components"; + +Complete mapping of integer body identifiers used across all pg_orbit functions. Each function family uses its own ID space; the tables below document which IDs are valid for which functions. + +--- + +## Planet IDs + +Used by `planet_heliocentric`, `planet_observe`, and `lambert_transfer` / `lambert_c3`. + +| ID | Body | Valid for `planet_heliocentric` | Valid for `planet_observe` | Valid for `lambert_*` | +|----|------|:----:|:----:|:----:| +| 0 | Sun | Yes (returns origin) | No | No | +| 1 | Mercury | Yes | Yes | Yes | +| 2 | Venus | Yes | Yes | Yes | +| 3 | Earth | Yes | No | Yes | +| 4 | Mars | Yes | Yes | Yes | +| 5 | Jupiter | Yes | Yes | Yes | +| 6 | Saturn | Yes | Yes | Yes | +| 7 | Uranus | Yes | Yes | Yes | +| 8 | Neptune | Yes | Yes | Yes | + + + +### Conventions + +The planet ID numbering follows the VSOP87 convention: +- IDs 1-8 map to the eight planets in order from the Sun +- No ID is assigned to Pluto (VSOP87 does not include it) +- The Sun is included as ID 0 for completeness in heliocentric queries + +```sql +-- Quick lookup: all planet IDs with names +SELECT body_id, + CASE body_id + WHEN 0 THEN 'Sun' WHEN 1 THEN 'Mercury' + WHEN 2 THEN 'Venus' WHEN 3 THEN 'Earth' + WHEN 4 THEN 'Mars' WHEN 5 THEN 'Jupiter' + WHEN 6 THEN 'Saturn' WHEN 7 THEN 'Uranus' + WHEN 8 THEN 'Neptune' + END AS name +FROM generate_series(0, 8) AS body_id; +``` + +--- + +## Galilean Moon IDs + +Used by `galilean_observe`. Numbered in order of distance from Jupiter, following the Lieske L1.2 convention. + +| ID | Moon | Discoverer | Orbital Period | Semi-major Axis | +|----|------|------------|----------------|-----------------| +| 0 | Io | Galileo (1610) | 1.769 days | 421,700 km | +| 1 | Europa | Galileo (1610) | 3.551 days | 671,034 km | +| 2 | Ganymede | Galileo (1610) | 7.155 days | 1,070,412 km | +| 3 | Callisto | Galileo (1610) | 16.689 days | 1,882,709 km | + +```sql +-- All Galilean moon names and IDs +SELECT moon_id, + CASE moon_id + WHEN 0 THEN 'Io' WHEN 1 THEN 'Europa' + WHEN 2 THEN 'Ganymede' WHEN 3 THEN 'Callisto' + END AS name +FROM generate_series(0, 3) AS moon_id; +``` + +--- + +## Saturn Moon IDs + +Used by `saturn_moon_observe`. Numbered in order of distance from Saturn, following the TASS17 convention. + +| ID | Moon | Discoverer | Orbital Period | Semi-major Axis | +|----|------|------------|----------------|-----------------| +| 0 | Mimas | Herschel (1789) | 0.942 days | 185,539 km | +| 1 | Enceladus | Herschel (1789) | 1.370 days | 238,042 km | +| 2 | Tethys | Cassini (1684) | 1.888 days | 294,619 km | +| 3 | Dione | Cassini (1684) | 2.737 days | 377,396 km | +| 4 | Rhea | Cassini (1672) | 4.518 days | 527,108 km | +| 5 | Titan | Huygens (1655) | 15.945 days | 1,221,870 km | +| 6 | Iapetus | Cassini (1671) | 79.322 days | 3,560,820 km | +| 7 | Hyperion | Bond & Lassell (1848) | 21.277 days | 1,481,010 km | + + + +```sql +SELECT moon_id, + CASE moon_id + WHEN 0 THEN 'Mimas' WHEN 1 THEN 'Enceladus' + WHEN 2 THEN 'Tethys' WHEN 3 THEN 'Dione' + WHEN 4 THEN 'Rhea' WHEN 5 THEN 'Titan' + WHEN 6 THEN 'Iapetus' WHEN 7 THEN 'Hyperion' + END AS name +FROM generate_series(0, 7) AS moon_id; +``` + +--- + +## Uranus Moon IDs + +Used by `uranus_moon_observe`. Numbered in order of distance from Uranus, following the GUST86 convention. + +| ID | Moon | Discoverer | Orbital Period | Semi-major Axis | +|----|------|------------|----------------|-----------------| +| 0 | Miranda | Kuiper (1948) | 1.413 days | 129,390 km | +| 1 | Ariel | Lassell (1851) | 2.520 days | 190,900 km | +| 2 | Umbriel | Lassell (1851) | 4.144 days | 266,000 km | +| 3 | Titania | Herschel (1787) | 8.706 days | 435,910 km | +| 4 | Oberon | Herschel (1787) | 13.463 days | 583,520 km | + +```sql +SELECT moon_id, + CASE moon_id + WHEN 0 THEN 'Miranda' WHEN 1 THEN 'Ariel' + WHEN 2 THEN 'Umbriel' WHEN 3 THEN 'Titania' + WHEN 4 THEN 'Oberon' + END AS name +FROM generate_series(0, 4) AS moon_id; +``` + +--- + +## Mars Moon IDs + +Used by `mars_moon_observe`. Numbered in order of distance from Mars, following the MarsSat convention. + +| ID | Moon | Discoverer | Orbital Period | Semi-major Axis | +|----|------|------------|----------------|-----------------| +| 0 | Phobos | Hall (1877) | 0.319 days (7h 39m) | 9,376 km | +| 1 | Deimos | Hall (1877) | 1.263 days | 23,463 km | + + + +```sql +SELECT moon_id, + CASE moon_id + WHEN 0 THEN 'Phobos' + WHEN 1 THEN 'Deimos' + END AS name +FROM generate_series(0, 1) AS moon_id; +``` + +--- + +## Summary Table + +Total: **8 planets + 19 moons = 27 solar system bodies** computable from SQL. + +| Function | ID Range | Count | Theory | +|----------|----------|-------|--------| +| `planet_heliocentric` | 0-8 | 9 | VSOP87 | +| `planet_observe` | 1-2, 4-8 | 7 | VSOP87 | +| `galilean_observe` | 0-3 | 4 | Lieske L1.2 | +| `saturn_moon_observe` | 0-7 | 8 | TASS17 | +| `uranus_moon_observe` | 0-4 | 5 | GUST86 | +| `mars_moon_observe` | 0-1 | 2 | MarsSat | +| `lambert_transfer` / `lambert_c3` | 1-8 | 8 | VSOP87 + Lambert | diff --git a/docs/src/content/docs/reference/constants-accuracy.mdx b/docs/src/content/docs/reference/constants-accuracy.mdx new file mode 100644 index 0000000..ef055f8 --- /dev/null +++ b/docs/src/content/docs/reference/constants-accuracy.mdx @@ -0,0 +1,198 @@ +--- +title: "Constants & Accuracy" +sidebar: + order: 10 +--- + +import { Aside, Tabs, TabItem } from "@astrojs/starlight/components"; + +Physical constants, astronomical constants, and accuracy bounds for every computational theory used in pg_orbit. All constants are compiled from their original sources and embedded at compile time -- no runtime configuration files, no external data dependencies. + +--- + +## Physical Constants + +### WGS-72 (SGP4/SDP4 Only) + +The SGP4/SDP4 propagator uses WGS-72 constants internally. This matches the reference frame in which TLEs are generated. Using WGS-84 with SGP4 would introduce systematic errors. + +| Constant | Symbol | Value | Unit | +|----------|--------|-------|------| +| Gravitational parameter | mu | 398600.8 | km^3/s^2 | +| Equatorial radius | ae | 6378.135 | km | +| J2 | J2 | 0.001082616 | -- | +| J3 | J3 | -0.00000253881 | -- | +| J4 | J4 | -0.00000165597 | -- | + + + +### WGS-84 (Coordinate Output) + +All geodetic and topocentric coordinate conversions use WGS-84 constants. + +| Constant | Symbol | Value | Unit | +|----------|--------|-------|------| +| Semi-major axis (equatorial radius) | a | 6378.137 | km | +| Flattening | f | 1/298.257223563 | -- | +| Semi-minor axis (polar radius) | b | 6356.752314245 | km | +| First eccentricity squared | e^2 | 0.00669437999014 | -- | + +--- + +## Astronomical Constants + +| Constant | Symbol | Value | Unit | Source | +|----------|--------|-------|------|--------| +| Astronomical Unit | AU | 149597870.7 | km | IAU 2012 | +| Obliquity of the ecliptic at J2000 | epsilon | 23.4392911 | degrees | IAU 1976 | +| Gaussian gravitational constant | k | 0.01720209895 | AU^(3/2) / (day * Msun^(1/2)) | IAU 1976 | +| Julian century | -- | 36525.0 | days | -- | +| J2000 epoch | -- | 2451545.0 | JD | 2000-01-01T12:00:00 TT | +| Speed of light | c | 299792.458 | km/s | IAU 2012 | +| Earth rotation rate | omega_e | 7.292115e-5 | rad/s | WGS-84 | + +### Jupiter-Specific Constants + +| Constant | Value | Unit | Source | +|----------|-------|------|--------| +| System III rotation period | 9h 55m 29.711s | -- | IAU 1965 | +| System III rotation rate | 870.536 | deg/day | Derived | + +--- + +## Theory Accuracy Bounds + +Each computational theory in pg_orbit has well-characterized accuracy limits. The bounds below are drawn from the original theory publications and validated against JPL ephemerides where possible. + +### SGP4/SDP4 (Satellite Propagation) + +| Orbit Class | Typical Error at Epoch | Error Growth Rate | Source | +|-------------|----------------------|-------------------|--------| +| LEO (< 2000 km) | < 1 km at epoch | 1-3 km/day | Vallado et al., 2006 | +| MEO (2000-35786 km) | < 5 km at epoch | 5-10 km/day | Vallado et al., 2006 | +| GEO (~35786 km) | < 10 km at epoch | 10-50 km/day | Vallado et al., 2006 | +| HEO (Molniya-type) | < 10 km at epoch | Highly variable | Vallado et al., 2006 | + + + +**Valid epoch range:** TLEs are typically valid for +/- 7 days from epoch for LEO, +/- 14 days for GEO. Beyond this, errors grow rapidly and propagation may fail outright (returning a fatal error code). + +**Deep space selection:** The SGP4/SDP4 algorithm switch is based on orbital period. Orbits with period >= 225 minutes (~3.75 hours, corresponding to an altitude of roughly 5,900 km for circular orbits) use the SDP4 deep-space model, which includes lunar and solar perturbation terms. + +### VSOP87 (Planetary Positions) + +Position accuracy relative to JPL DE405 ephemeris: + +| Planet | Max Error (within +/- 2000 yr of J2000) | Max Error (within +/- 4000 yr of J2000) | +|--------|------------------------------------------|------------------------------------------| +| Mercury | 0.6" | 1" | +| Venus | 0.3" | 2" | +| Earth-Moon barycenter | 0.4" | 2" | +| Mars | 0.8" | 4" | +| Jupiter | 0.3" | 7" | +| Saturn | 0.4" | 10" | +| Uranus | 0.2" | 20" | +| Neptune | 0.3" | 40" | + + + +**Valid epoch range:** The series coefficients are fitted over the interval J2000 +/- 4000 years. Outside this range, accuracy degrades unpredictably. For observational planning (present-day queries), accuracy is well within 1 arcsecond. + +### ELP2000-82B (Lunar Position) + +| Quantity | Accuracy | +|----------|----------| +| Geocentric longitude | < 2" for dates within +/- 500 years of J2000 | +| Geocentric latitude | < 1" | +| Distance | < 0.5 km | + +**Valid epoch range:** Nominally +/- 4000 years from J2000, though accuracy degrades beyond +/- 500 years. For present-day lunar observations, the error is well under 1 arcsecond and 1 km in distance. + +### Lieske L1.2 (Galilean Moons) + +| Quantity | Accuracy | +|----------|----------| +| Position relative to Jupiter | < 500 km (typical), < 1000 km (worst case) | +| Differential positions (moon-to-moon) | < 200 km | + +**Valid epoch range:** Fitted to observations spanning 1891-2000. Accuracy degrades outside this range. For present-day observations and near-term predictions, the theory is reliable. + +### TASS17 (Saturn Moons) + +| Quantity | Accuracy | +|----------|----------| +| Position relative to Saturn | < 500 km (inner moons), < 2000 km (Hyperion) | +| Titan position | < 300 km | + +**Notes:** Hyperion has the largest uncertainty due to its chaotic rotation and irregular orbital perturbations. Titan, as the most massive moon, is the best-determined. + +**Valid epoch range:** Fitted to observations spanning 1886-1985. The theory uses secular terms that limit extrapolation. + +### GUST86 (Uranus Moons) + +| Quantity | Accuracy | +|----------|----------| +| Position relative to Uranus | < 1000 km (Titania, Oberon), < 2000 km (Miranda) | + +**Notes:** The Uranian system was significantly improved by Voyager 2 encounter data (1986). Pre-Voyager observations constrain secular rates; Voyager data constrains short-period terms. + +**Valid epoch range:** Most reliable within +/- 50 years of the Voyager encounter (1986). Present-day accuracy is good. + +### MarsSat (Mars Moons) + +| Quantity | Accuracy | +|----------|----------| +| Phobos position relative to Mars | < 10 km | +| Deimos position relative to Mars | < 20 km | + +**Notes:** The Mars moon theories benefit from spacecraft tracking data (Viking, Mars Express). Phobos is better determined than Deimos due to more frequent close encounters with Mars orbiters. + +### Kepler Propagation (Comets & Asteroids) + +| Orbit Type | Limitation | +|------------|------------| +| Elliptic (e < 1) | Two-body only. No planetary perturbations. Error grows with distance from perihelion epoch. | +| Parabolic (e = 1) | Barker's equation. Exact for the two-body case. | +| Hyperbolic (e > 1) | Two-body only. Valid for interstellar objects near perihelion. | + + + +### Lambert Solver (Transfer Orbits) + +| Quantity | Notes | +|----------|-------| +| Transfer orbit accuracy | Exact for the two-body (patched conic) approximation | +| Planet position accuracy | Limited by VSOP87 (sub-arcsecond for present-day) | +| C3 accuracy | Departure C3 values are typically within 0.1 km^2/s^2 of JPL trajectory tools for well-posed transfers | + +**Limitations:** The Lambert solver assumes patched conic trajectories (two-body between planets). It does not account for: +- Gravity assists +- Solar radiation pressure +- Finite thrust arcs +- N-body perturbations during the transfer + +For preliminary mission design and pork chop plot generation, these limitations are standard and expected. + +--- + +## Reference Publications + +| Theory | Publication | +|--------|-------------| +| SGP4/SDP4 | Vallado, D.A., Crawford, P., Hujsak, R., Kelso, T.S. "Revisiting Spacetrack Report #3." AIAA 2006-6753, 2006. | +| VSOP87 | Bretagnon, P., Francou, G. "Planetary theories in rectangular and spherical variables. VSOP87 solutions." Astronomy and Astrophysics, 202, 309-315, 1988. | +| ELP2000-82B | Chapront-Touze, M., Chapront, J. "The lunar ephemeris ELP-2000." Astronomy and Astrophysics, 124, 50-62, 1983. | +| Lieske L1.2 | Lieske, J.H. "Galilean satellites of Jupiter." Astronomy and Astrophysics Supplement Series, 129, 205-217, 1998. | +| TASS17 | Vienne, A., Duriez, L. "TASS1.7: An ephemeris generator for the major satellites of Saturn." Astronomy and Astrophysics, 297, 588-605, 1995. | +| GUST86 | Laskar, J., Jacobson, R.A. "GUST86: An analytical ephemeris of the Uranian satellites." Astronomy and Astrophysics, 188, 212-224, 1987. | +| MarsSat | Jacobson, R.A. "The orbits and masses of the Martian satellites and the libration of Phobos." The Astronomical Journal, 139, 668-679, 2010. | +| Carr source regions | Carr, T.D., Desch, M.D., Alexander, J.K. "Phenomenology of magnetospheric radio emissions." In Physics of the Jovian Magnetosphere, Cambridge Univ. Press, 1983. | +| Lambert solver | Battin, R.H. "An Introduction to the Mathematics and Methods of Astrodynamics." AIAA Education Series, Revised Edition, 1999. | diff --git a/docs/src/content/docs/reference/functions-moons.mdx b/docs/src/content/docs/reference/functions-moons.mdx new file mode 100644 index 0000000..f4a457d --- /dev/null +++ b/docs/src/content/docs/reference/functions-moons.mdx @@ -0,0 +1,256 @@ +--- +title: "Functions: Moons" +sidebar: + order: 4 +--- + +import { Aside, Tabs, TabItem } from "@astrojs/starlight/components"; + +Functions for observing the natural satellites of Jupiter, Saturn, Uranus, and Mars. Each moon family uses a dedicated analytical theory for computing satellite positions relative to the parent planet, which is then transformed to Earth-based topocentric coordinates. + +--- + +## galilean_observe + +Computes the topocentric position of a Galilean moon of Jupiter as seen from an Earth-based observer. Uses the Lieske L1.2 theory (Lieske, 1998). + +### Signature + +```sql +galilean_observe(moon_id int4, obs observer, t timestamptz) → topocentric +``` + +### Parameters + +| Parameter | Type | Description | +|-----------|------|-------------| +| `moon_id` | `int4` | Galilean moon identifier (see table below) | +| `obs` | `observer` | Observer location on Earth | +| `t` | `timestamptz` | Observation time | + +#### Moon IDs + +| ID | Moon | Orbital Period | +|----|------|----------------| +| 0 | Io | 1.769 days | +| 1 | Europa | 3.551 days | +| 2 | Ganymede | 7.155 days | +| 3 | Callisto | 16.689 days | + +### Returns + +A `topocentric` with azimuth, elevation, range (km), and range rate (km/s). + +### Example + +```sql +-- Current positions of all four Galilean moons +SELECT moon_id, + CASE moon_id + WHEN 0 THEN 'Io' WHEN 1 THEN 'Europa' + WHEN 2 THEN 'Ganymede' WHEN 3 THEN 'Callisto' + END AS moon, + round(topo_azimuth(t)::numeric, 4) AS az, + round(topo_elevation(t)::numeric, 4) AS el, + round(topo_range(t)::numeric, 0) AS range_km +FROM generate_series(0, 3) AS moon_id, + galilean_observe(moon_id, '40.0N 105.3W 1655m'::observer, now()) AS t; +``` + +```sql +-- Track Io's position over one orbital period (1.769 days) +SELECT t, + round(topo_azimuth(pos)::numeric, 4) AS az, + round(topo_elevation(pos)::numeric, 4) AS el +FROM generate_series( + now(), now() + interval '1.769 days', interval '15 minutes' + ) AS t, + galilean_observe(0, '40.0N 105.3W 1655m'::observer, t) AS pos; +``` + +--- + +## saturn_moon_observe + +Computes the topocentric position of a moon of Saturn as seen from an Earth-based observer. Uses the TASS17 theory (Vienne & Duriez, 1995) for the eight major Saturnian moons. + +### Signature + +```sql +saturn_moon_observe(moon_id int4, obs observer, t timestamptz) → topocentric +``` + +### Parameters + +| Parameter | Type | Description | +|-----------|------|-------------| +| `moon_id` | `int4` | Saturn moon identifier (see table below) | +| `obs` | `observer` | Observer location on Earth | +| `t` | `timestamptz` | Observation time | + +#### Moon IDs + +| ID | Moon | Orbital Period | Approx. Magnitude | +|----|------|----------------|-------------------| +| 0 | Mimas | 0.942 days | +12.9 | +| 1 | Enceladus | 1.370 days | +11.7 | +| 2 | Tethys | 1.888 days | +10.2 | +| 3 | Dione | 2.737 days | +10.4 | +| 4 | Rhea | 4.518 days | +9.7 | +| 5 | Titan | 15.945 days | +8.3 | +| 6 | Iapetus | 79.322 days | +10.2-11.9 | +| 7 | Hyperion | 21.277 days | +14.2 | + + + +### Returns + +A `topocentric` with azimuth, elevation, range (km), and range rate (km/s). + +### Example + +```sql +-- All eight Saturn moons' current positions +SELECT moon_id, + CASE moon_id + WHEN 0 THEN 'Mimas' WHEN 1 THEN 'Enceladus' + WHEN 2 THEN 'Tethys' WHEN 3 THEN 'Dione' + WHEN 4 THEN 'Rhea' WHEN 5 THEN 'Titan' + WHEN 6 THEN 'Iapetus' WHEN 7 THEN 'Hyperion' + END AS moon, + round(topo_azimuth(t)::numeric, 4) AS az, + round(topo_elevation(t)::numeric, 4) AS el, + round(topo_range(t)::numeric, 0) AS range_km +FROM generate_series(0, 7) AS moon_id, + saturn_moon_observe(moon_id, '40.0N 105.3W 1655m'::observer, now()) AS t; +``` + +```sql +-- Track Titan over one orbital period +SELECT t, + round(topo_azimuth(pos)::numeric, 4) AS az, + round(topo_elevation(pos)::numeric, 4) AS el +FROM generate_series( + now(), now() + interval '15.945 days', interval '1 hour' + ) AS t, + saturn_moon_observe(5, '40.0N 105.3W 1655m'::observer, t) AS pos; +``` + +--- + +## uranus_moon_observe + +Computes the topocentric position of a moon of Uranus as seen from an Earth-based observer. Uses the GUST86 theory (Laskar & Jacobson, 1987). + +### Signature + +```sql +uranus_moon_observe(moon_id int4, obs observer, t timestamptz) → topocentric +``` + +### Parameters + +| Parameter | Type | Description | +|-----------|------|-------------| +| `moon_id` | `int4` | Uranus moon identifier (see table below) | +| `obs` | `observer` | Observer location on Earth | +| `t` | `timestamptz` | Observation time | + +#### Moon IDs + +| ID | Moon | Orbital Period | Approx. Magnitude | +|----|------|----------------|-------------------| +| 0 | Miranda | 1.413 days | +16.5 | +| 1 | Ariel | 2.520 days | +14.2 | +| 2 | Umbriel | 4.144 days | +14.8 | +| 3 | Titania | 8.706 days | +13.9 | +| 4 | Oberon | 13.463 days | +14.1 | + + + +### Returns + +A `topocentric` with azimuth, elevation, range (km), and range rate (km/s). + +### Example + +```sql +-- All five Uranus moons' current positions +SELECT moon_id, + CASE moon_id + WHEN 0 THEN 'Miranda' WHEN 1 THEN 'Ariel' + WHEN 2 THEN 'Umbriel' WHEN 3 THEN 'Titania' + WHEN 4 THEN 'Oberon' + END AS moon, + round(topo_azimuth(t)::numeric, 4) AS az, + round(topo_elevation(t)::numeric, 4) AS el, + round(topo_range(t)::numeric, 0) AS range_km +FROM generate_series(0, 4) AS moon_id, + uranus_moon_observe(moon_id, '40.0N 105.3W 1655m'::observer, now()) AS t; +``` + +--- + +## mars_moon_observe + +Computes the topocentric position of a moon of Mars as seen from an Earth-based observer. Uses the MarsSat analytical theory. + +### Signature + +```sql +mars_moon_observe(moon_id int4, obs observer, t timestamptz) → topocentric +``` + +### Parameters + +| Parameter | Type | Description | +|-----------|------|-------------| +| `moon_id` | `int4` | Mars moon identifier (see table below) | +| `obs` | `observer` | Observer location on Earth | +| `t` | `timestamptz` | Observation time | + +#### Moon IDs + +| ID | Moon | Orbital Period | Approx. Magnitude | +|----|------|----------------|-------------------| +| 0 | Phobos | 0.319 days (7.66 hours) | +11.4 | +| 1 | Deimos | 1.263 days | +12.5 | + + + +### Returns + +A `topocentric` with azimuth, elevation, range (km), and range rate (km/s). + +### Example + +```sql +-- Phobos and Deimos positions from Boulder +SELECT moon_id, + CASE moon_id + WHEN 0 THEN 'Phobos' + WHEN 1 THEN 'Deimos' + END AS moon, + round(topo_azimuth(t)::numeric, 4) AS az, + round(topo_elevation(t)::numeric, 4) AS el, + round(topo_range(t)::numeric, 0) AS range_km +FROM generate_series(0, 1) AS moon_id, + mars_moon_observe(moon_id, '40.0N 105.3W 1655m'::observer, now()) AS t; +``` + +```sql +-- Track Phobos through one full orbit (~7.66 hours) +SELECT t, + round(topo_azimuth(pos)::numeric, 4) AS az, + round(topo_elevation(pos)::numeric, 4) AS el +FROM generate_series( + now(), now() + interval '7.66 hours', interval '5 minutes' + ) AS t, + mars_moon_observe(0, '40.0N 105.3W 1655m'::observer, t) AS pos; +``` diff --git a/docs/src/content/docs/reference/functions-radio.mdx b/docs/src/content/docs/reference/functions-radio.mdx new file mode 100644 index 0000000..bcfafec --- /dev/null +++ b/docs/src/content/docs/reference/functions-radio.mdx @@ -0,0 +1,193 @@ +--- +title: "Functions: Radio" +sidebar: + order: 6 +--- + +import { Aside, Tabs, TabItem } from "@astrojs/starlight/components"; + +Functions for predicting Jupiter decametric radio emissions. Jupiter is the strongest radio source in the solar system after the Sun, producing bursts in the 10-40 MHz range driven by the interaction between Io and Jupiter's magnetosphere. These functions compute the geometric parameters needed to predict when bursts are likely. + +--- + +## io_phase_angle + +Computes the orbital phase angle of Io relative to Jupiter's superior conjunction as seen from Earth. The phase angle determines the position of Io in its orbit as projected against Jupiter's disk, which is one of two parameters needed for burst prediction. + +### Signature + +```sql +io_phase_angle(t timestamptz) → float8 +``` + +### Parameters + +| Parameter | Type | Description | +|-----------|------|-------------| +| `t` | `timestamptz` | Evaluation time | + +### Returns + +Io's orbital phase angle in degrees, range [0, 360). + +- **0** = superior conjunction (Io behind Jupiter, as seen from Earth) +- **90** = eastern elongation (Io east of Jupiter) +- **180** = inferior conjunction (Io between Earth and Jupiter) +- **270** = western elongation (Io west of Jupiter) + +### Example + +```sql +-- Current Io phase angle +SELECT round(io_phase_angle(now())::numeric, 1) AS io_phase; +``` + +```sql +-- Io phase over the next 24 hours at 30-minute intervals +SELECT t, + round(io_phase_angle(t)::numeric, 1) AS io_phase +FROM generate_series(now(), now() + interval '24 hours', interval '30 minutes') AS t; +``` + +--- + +## jupiter_cml + +Computes Jupiter's Central Meridian Longitude (CML) in System III (1965.0) as seen from an Earth-based observer. System III is tied to Jupiter's magnetic field rotation (period = 9h 55m 29.711s) and is the standard reference for radio astronomy. + +The result is corrected for light travel time between Jupiter and the observer. + +### Signature + +```sql +jupiter_cml(obs observer, t timestamptz) → float8 +``` + +### Parameters + +| Parameter | Type | Description | +|-----------|------|-------------| +| `obs` | `observer` | Observer location on Earth | +| `t` | `timestamptz` | Observation time | + +### Returns + +Central Meridian Longitude in degrees, range [0, 360). This is the longitude of the Jovian meridian facing the observer at the given time, in System III coordinates. + + + +### Example + +```sql +-- Current Jupiter CML from Boulder +SELECT round(jupiter_cml('40.0N 105.3W 1655m'::observer, now())::numeric, 1) AS cml; +``` + +```sql +-- CML sweep over one Jupiter rotation (~9h 55m) +SELECT t, + round(jupiter_cml('40.0N 105.3W 1655m'::observer, t)::numeric, 1) AS cml +FROM generate_series( + now(), + now() + interval '9 hours 55 minutes', + interval '10 minutes' + ) AS t; +``` + +--- + +## jupiter_burst_probability + +Computes the probability of detecting a Jupiter decametric radio burst given the current Io phase angle and Jupiter CML. Based on the Carr, Desch & Alexander (1983) source region model. + +The function evaluates whether the Io phase and CML fall within one of the known emission source regions and returns a probability between 0 and 1. + +### Signature + +```sql +jupiter_burst_probability(io_phase float8, cml float8) → float8 +``` + +### Parameters + +| Parameter | Type | Unit | Description | +|-----------|------|------|-------------| +| `io_phase` | `float8` | degrees | Io orbital phase angle (output of `io_phase_angle`) | +| `cml` | `float8` | degrees | Jupiter CML System III (output of `jupiter_cml`) | + +### Returns + +Burst probability as a value from 0.0 to 1.0. + +### Source Regions + +The Carr model identifies four primary Io-related source regions in the Io phase vs. CML parameter space: + +| Source | Io Phase Range | CML Range | Description | +|--------|----------------|-----------|-------------| +| **Io-A** | 195-265 | 200-290 | Strongest Io-related source. Io near western elongation, CML in the 200-290 range. | +| **Io-B** | 75-105 | 95-195 | Second strongest. Io near eastern elongation, CML roughly opposite to Io-A. | +| **Io-C** | 195-265 | 290-10 | Weaker Io-related source. Same Io phase as Io-A but different CML range. | +| **Io-D** | 75-105 | 0-95 | Weakest of the four. Same Io phase as Io-B but CML shifted. | + + + +### Example + +```sql +-- Current burst probability +SELECT round( + jupiter_burst_probability( + io_phase_angle(now()), + jupiter_cml('40.0N 105.3W 1655m'::observer, now()) + )::numeric, 3 +) AS burst_prob; +``` + +```sql +-- Find high-probability windows tonight +SELECT t, + round(io_phase_angle(t)::numeric, 1) AS io_phase, + round(jupiter_cml('40.0N 105.3W 1655m'::observer, t)::numeric, 1) AS cml, + round(jupiter_burst_probability( + io_phase_angle(t), + jupiter_cml('40.0N 105.3W 1655m'::observer, t) + )::numeric, 3) AS probability +FROM generate_series( + '2024-06-15 02:00:00+00', + '2024-06-15 10:00:00+00', + interval '5 minutes' + ) AS t +WHERE jupiter_burst_probability( + io_phase_angle(t), + jupiter_cml('40.0N 105.3W 1655m'::observer, t) + ) > 0.2 +ORDER BY probability DESC; +``` + +```sql +-- Full radio observing plan: combine burst probability with Jupiter visibility +SELECT t, + round(topo_elevation(jup)::numeric, 1) AS jupiter_el, + round(io_phase_angle(t)::numeric, 1) AS io_phase, + round(jupiter_cml('40.0N 105.3W 1655m'::observer, t)::numeric, 1) AS cml, + round(jupiter_burst_probability( + io_phase_angle(t), + jupiter_cml('40.0N 105.3W 1655m'::observer, t) + )::numeric, 3) AS burst_prob +FROM generate_series( + '2024-06-15 02:00:00+00', + '2024-06-15 10:00:00+00', + interval '10 minutes' + ) AS t, + planet_observe(5, '40.0N 105.3W 1655m'::observer, t) AS jup +WHERE topo_elevation(jup) > 10 + AND jupiter_burst_probability( + io_phase_angle(t), + jupiter_cml('40.0N 105.3W 1655m'::observer, t) + ) > 0.1; +``` diff --git a/docs/src/content/docs/reference/functions-satellite.mdx b/docs/src/content/docs/reference/functions-satellite.mdx new file mode 100644 index 0000000..cb42914 --- /dev/null +++ b/docs/src/content/docs/reference/functions-satellite.mdx @@ -0,0 +1,529 @@ +--- +title: "Functions: Satellite" +sidebar: + order: 2 +--- + +import { Aside, Tabs, TabItem } from "@astrojs/starlight/components"; + +Functions for propagating TLEs, converting coordinate frames, computing ground tracks, and predicting satellite passes. These form the core satellite tracking pipeline in pg_orbit. + +--- + +## sgp4_propagate + +Propagates a TLE to a given time using the SGP4 (near-earth) or SDP4 (deep-space) algorithm. The algorithm is selected automatically based on orbital period: elements with a period >= 225 minutes use SDP4. + +### Signature + +```sql +sgp4_propagate(tle tle, t timestamptz) → eci_position +``` + +### Parameters + +| Parameter | Type | Description | +|-----------|------|-------------| +| `tle` | `tle` | Two-Line Element set to propagate | +| `t` | `timestamptz` | Target epoch for propagation | + +### Returns + +An `eci_position` in the TEME reference frame. Position in km, velocity in km/s. + +### Errors + +Raises an exception if SGP4/SDP4 returns a fatal error code (e.g., satellite decay, eccentricity out of range, mean motion near zero). Use `sgp4_propagate_safe` if you need NULL-on-error behavior. + + + +### Example + +```sql +WITH iss AS ( + SELECT '1 25544U 98067A 24001.50000000 .00016717 00000-0 10270-3 0 9025 +2 25544 51.6400 208.9163 0006703 30.1694 61.7520 15.50100486 00001'::tle AS tle +) +SELECT eci_x(pos) AS x_km, + eci_y(pos) AS y_km, + eci_z(pos) AS z_km, + eci_speed(pos) AS speed_kms +FROM iss, sgp4_propagate(tle, '2024-01-02 12:00:00+00') AS pos; +``` + +--- + +## sgp4_propagate_safe + +Identical to `sgp4_propagate`, but returns NULL instead of raising an exception on propagation errors. This is the batch-safe variant for processing large TLE catalogs where some elements may be stale or invalid. + +### Signature + +```sql +sgp4_propagate_safe(tle tle, t timestamptz) → eci_position +``` + +### Parameters + +| Parameter | Type | Description | +|-----------|------|-------------| +| `tle` | `tle` | Two-Line Element set to propagate | +| `t` | `timestamptz` | Target epoch for propagation | + +### Returns + +An `eci_position`, or NULL if propagation fails. + +### Example + +```sql +-- Propagate an entire catalog, skipping failed elements +SELECT norad_id, + eci_x(pos) AS x_km, + eci_y(pos) AS y_km, + eci_z(pos) AS z_km +FROM satellite_catalog, + sgp4_propagate_safe(tle, now()) AS pos +WHERE pos IS NOT NULL; +``` + +--- + +## sgp4_propagate_series + +Generates a time series of TEME ECI positions for a single TLE over a time range. Returns one row per time step. This is significantly faster than calling `sgp4_propagate` inside a `generate_series` because the SGP4 initializer runs once. + +### Signature + +```sql +sgp4_propagate_series( + tle tle, + start_time timestamptz, + end_time timestamptz, + step interval +) → TABLE(t timestamptz, x float8, y float8, z float8, vx float8, vy float8, vz float8) +``` + +### Parameters + +| Parameter | Type | Description | +|-----------|------|-------------| +| `tle` | `tle` | Two-Line Element set | +| `start_time` | `timestamptz` | Start of the time range | +| `end_time` | `timestamptz` | End of the time range (inclusive if aligned to step) | +| `step` | `interval` | Time between samples | + +### Returns + +A set of rows with timestamp and TEME position/velocity components. Position in km, velocity in km/s. + +### Example + +```sql +WITH iss AS ( + SELECT '1 25544U 98067A 24001.50000000 .00016717 00000-0 10270-3 0 9025 +2 25544 51.6400 208.9163 0006703 30.1694 61.7520 15.50100486 00001'::tle AS tle +) +SELECT t, x, y, z, vx, vy, vz +FROM iss, + sgp4_propagate_series(tle, + '2024-01-02 00:00:00+00', + '2024-01-02 01:00:00+00', + interval '1 minute'); +``` + +--- + +## tle_distance + +Computes the Euclidean distance between two satellites at a given time. Both TLEs are propagated to the target time and the 3D distance between their TEME positions is returned. + +### Signature + +```sql +tle_distance(a tle, b tle, t timestamptz) → float8 +``` + +### Parameters + +| Parameter | Type | Description | +|-----------|------|-------------| +| `a` | `tle` | First satellite | +| `b` | `tle` | Second satellite | +| `t` | `timestamptz` | Evaluation time | + +### Returns + +Distance in kilometers. Raises an exception if either TLE fails to propagate. + +### Example + +```sql +-- Distance between two satellites at a specific time +SELECT tle_distance(sat_a.tle, sat_b.tle, '2024-06-15 12:00:00+00') AS dist_km +FROM satellite_catalog sat_a, satellite_catalog sat_b +WHERE sat_a.norad_id = 25544 -- ISS + AND sat_b.norad_id = 48274; -- CSS (Tianhe) +``` + +--- + +## eci_to_geodetic + +Converts a TEME ECI position to WGS-84 geodetic coordinates. The timestamp is required to compute the Earth's rotation angle (Greenwich Apparent Sidereal Time). + +### Signature + +```sql +eci_to_geodetic(pos eci_position, t timestamptz) → geodetic +``` + +### Parameters + +| Parameter | Type | Description | +|-----------|------|-------------| +| `pos` | `eci_position` | TEME ECI position | +| `t` | `timestamptz` | Time of the position (for sidereal time computation) | + +### Returns + +A `geodetic` with WGS-84 latitude, longitude, and altitude. + +### Example + +```sql +WITH iss AS ( + SELECT '1 25544U 98067A 24001.50000000 .00016717 00000-0 10270-3 0 9025 +2 25544 51.6400 208.9163 0006703 30.1694 61.7520 15.50100486 00001'::tle AS tle +) +SELECT geo_lat(g) AS lat, + geo_lon(g) AS lon, + geo_alt(g) AS alt_km +FROM iss, + eci_to_geodetic(sgp4_propagate(tle, now()), now()) AS g; +``` + +--- + +## eci_to_topocentric + +Converts a TEME ECI position to topocentric (observer-relative) coordinates. Computes azimuth, elevation, slant range, and range rate. + +### Signature + +```sql +eci_to_topocentric(pos eci_position, obs observer, t timestamptz) → topocentric +``` + +### Parameters + +| Parameter | Type | Description | +|-----------|------|-------------| +| `pos` | `eci_position` | TEME ECI position and velocity | +| `obs` | `observer` | Observer location | +| `t` | `timestamptz` | Time of the position (for sidereal time computation) | + +### Returns + +A `topocentric` with azimuth, elevation, range, and range rate. + +### Example + +```sql +WITH iss AS ( + SELECT '1 25544U 98067A 24001.50000000 .00016717 00000-0 10270-3 0 9025 +2 25544 51.6400 208.9163 0006703 30.1694 61.7520 15.50100486 00001'::tle AS tle +) +SELECT topo_azimuth(tc) AS az, + topo_elevation(tc) AS el, + topo_range(tc) AS range_km, + topo_range_rate(tc) AS range_rate_kms +FROM iss, + eci_to_topocentric( + sgp4_propagate(tle, now()), + '40.0N 105.3W 1655m'::observer, + now() + ) AS tc; +``` + +--- + +## subsatellite_point + +Returns the nadir (directly below the satellite) point on the WGS-84 ellipsoid for a given TLE at a given time. This is a convenience function equivalent to propagating and then converting to geodetic, but with altitude set to the satellite altitude. + +### Signature + +```sql +subsatellite_point(tle tle, t timestamptz) → geodetic +``` + +### Parameters + +| Parameter | Type | Description | +|-----------|------|-------------| +| `tle` | `tle` | Satellite TLE | +| `t` | `timestamptz` | Evaluation time | + +### Returns + +A `geodetic` with the latitude, longitude, and altitude of the satellite above the WGS-84 ellipsoid. + +### Example + +```sql +WITH iss AS ( + SELECT '1 25544U 98067A 24001.50000000 .00016717 00000-0 10270-3 0 9025 +2 25544 51.6400 208.9163 0006703 30.1694 61.7520 15.50100486 00001'::tle AS tle +) +SELECT geo_lat(sp) AS nadir_lat, + geo_lon(sp) AS nadir_lon, + geo_alt(sp) AS altitude_km +FROM iss, subsatellite_point(tle, now()) AS sp; +``` + +--- + +## ground_track + +Generates a time series of subsatellite points (nadir ground track) for a satellite over a time range. Each row contains the timestamp, latitude, longitude, and altitude. + +### Signature + +```sql +ground_track( + tle tle, + start_time timestamptz, + end_time timestamptz, + step interval +) → TABLE(t timestamptz, lat float8, lon float8, alt float8) +``` + +### Parameters + +| Parameter | Type | Description | +|-----------|------|-------------| +| `tle` | `tle` | Satellite TLE | +| `start_time` | `timestamptz` | Start of the time range | +| `end_time` | `timestamptz` | End of the time range | +| `step` | `interval` | Time between samples | + +### Returns + +A set of rows with timestamp, latitude (degrees), longitude (degrees), and altitude (km). + +### Example + +```sql +-- ISS ground track for one orbit (~92 minutes) at 30-second resolution +WITH iss AS ( + SELECT '1 25544U 98067A 24001.50000000 .00016717 00000-0 10270-3 0 9025 +2 25544 51.6400 208.9163 0006703 30.1694 61.7520 15.50100486 00001'::tle AS tle +) +SELECT t, lat, lon, alt +FROM iss, ground_track(tle, now(), now() + interval '92 minutes', interval '30 seconds'); +``` + +--- + +## observe + +Propagates a TLE and computes topocentric look angles in a single call. Equivalent to `eci_to_topocentric(sgp4_propagate(tle, t), obs, t)`, but avoids the intermediate allocation. + +### Signature + +```sql +observe(tle tle, obs observer, t timestamptz) → topocentric +``` + +### Parameters + +| Parameter | Type | Description | +|-----------|------|-------------| +| `tle` | `tle` | Satellite TLE | +| `obs` | `observer` | Observer location | +| `t` | `timestamptz` | Observation time | + +### Returns + +A `topocentric` with azimuth, elevation, range, and range rate. + +### Example + +```sql +WITH iss AS ( + SELECT '1 25544U 98067A 24001.50000000 .00016717 00000-0 10270-3 0 9025 +2 25544 51.6400 208.9163 0006703 30.1694 61.7520 15.50100486 00001'::tle AS tle +) +SELECT topo_azimuth(o) AS az, + topo_elevation(o) AS el, + topo_range(o) AS range_km +FROM iss, observe(tle, '40.0N 105.3W 1655m'::observer, now()) AS o; +``` + +--- + +## observe_safe + +Identical to `observe`, but returns NULL instead of raising an exception on propagation errors. Use this when processing large TLE catalogs in batch. + +### Signature + +```sql +observe_safe(tle tle, obs observer, t timestamptz) → topocentric +``` + +### Parameters + +| Parameter | Type | Description | +|-----------|------|-------------| +| `tle` | `tle` | Satellite TLE | +| `obs` | `observer` | Observer location | +| `t` | `timestamptz` | Observation time | + +### Returns + +A `topocentric`, or NULL if propagation fails. + +### Example + +```sql +-- Find all satellites above the horizon right now, skipping stale TLEs +SELECT norad_id, + topo_azimuth(o) AS az, + topo_elevation(o) AS el +FROM satellite_catalog, + observe_safe(tle, '40.0N 105.3W 1655m'::observer, now()) AS o +WHERE o IS NOT NULL + AND topo_elevation(o) > 0 +ORDER BY topo_elevation(o) DESC; +``` + +--- + +## next_pass + +Finds the next satellite pass over an observer location. Searches forward from the given start time up to 7 days. + +### Signature + +```sql +next_pass(tle tle, obs observer, start timestamptz) → pass_event +``` + +### Parameters + +| Parameter | Type | Description | +|-----------|------|-------------| +| `tle` | `tle` | Satellite TLE | +| `obs` | `observer` | Observer location | +| `start` | `timestamptz` | Time to begin searching from | + +### Returns + +A `pass_event` with AOS, maximum elevation, LOS, azimuths, and duration. Returns NULL if no pass is found within 7 days (possible for equatorial observers looking for high-inclination satellites, or vice versa). + + + +### Example + +```sql +WITH iss AS ( + SELECT '1 25544U 98067A 24001.50000000 .00016717 00000-0 10270-3 0 9025 +2 25544 51.6400 208.9163 0006703 30.1694 61.7520 15.50100486 00001'::tle AS tle +) +SELECT pass_aos_time(p) AS rise, + pass_max_elevation(p) AS max_el, + pass_los_time(p) AS set, + pass_duration(p) AS duration +FROM iss, next_pass(tle, '40.0N 105.3W 1655m'::observer, now()) AS p; +``` + +--- + +## predict_passes + +Finds all satellite passes over an observer within a time window, optionally filtered by minimum elevation. Returns a set of `pass_event` records. + +### Signature + +```sql +predict_passes( + tle tle, + obs observer, + start_time timestamptz, + end_time timestamptz, + min_el float8 DEFAULT 0.0 +) → SETOF pass_event +``` + +### Parameters + +| Parameter | Type | Default | Description | +|-----------|------|---------|-------------| +| `tle` | `tle` | | Satellite TLE | +| `obs` | `observer` | | Observer location | +| `start_time` | `timestamptz` | | Start of the search window | +| `end_time` | `timestamptz` | | End of the search window | +| `min_el` | `float8` | `0.0` | Minimum peak elevation in degrees. Passes whose maximum elevation is below this threshold are excluded. | + +### Returns + +A set of `pass_event` records, ordered by AOS time. + +### Example + +```sql +-- All ISS passes above 20 degrees in the next 3 days +WITH iss AS ( + SELECT '1 25544U 98067A 24001.50000000 .00016717 00000-0 10270-3 0 9025 +2 25544 51.6400 208.9163 0006703 30.1694 61.7520 15.50100486 00001'::tle AS tle +) +SELECT pass_aos_time(p) AS rise, + pass_max_elevation(p) AS max_el, + pass_aos_azimuth(p) AS rise_az, + pass_los_azimuth(p) AS set_az, + pass_duration(p) AS dur +FROM iss, + predict_passes(tle, '40.0N 105.3W 1655m'::observer, + now(), now() + interval '3 days', 20.0) AS p; +``` + +--- + +## pass_visible + +Returns true if at least one satellite pass occurs over the observer during the given time window. + +### Signature + +```sql +pass_visible(tle tle, obs observer, start_time timestamptz, end_time timestamptz) → boolean +``` + +### Parameters + +| Parameter | Type | Description | +|-----------|------|-------------| +| `tle` | `tle` | Satellite TLE | +| `obs` | `observer` | Observer location | +| `start_time` | `timestamptz` | Start of the search window | +| `end_time` | `timestamptz` | End of the search window | + +### Returns + +`true` if any pass (elevation > 0) occurs in the window; `false` otherwise. This is faster than `predict_passes` when you only need a yes/no answer because it stops searching after the first pass is found. + +### Example + +```sql +-- Which satellites from the catalog pass over Boulder tonight? +SELECT norad_id, name +FROM satellite_catalog +WHERE pass_visible(tle, '40.0N 105.3W 1655m'::observer, + '2024-06-15 02:00:00+00', '2024-06-15 10:00:00+00'); +``` diff --git a/docs/src/content/docs/reference/functions-solar-system.mdx b/docs/src/content/docs/reference/functions-solar-system.mdx new file mode 100644 index 0000000..66a9dff --- /dev/null +++ b/docs/src/content/docs/reference/functions-solar-system.mdx @@ -0,0 +1,228 @@ +--- +title: "Functions: Solar System" +sidebar: + order: 3 +--- + +import { Aside, Tabs, TabItem } from "@astrojs/starlight/components"; + +Functions for computing planetary positions, observing the Sun, Moon, and planets from an Earth-based observer. Planetary positions use the VSOP87 theory (Bretagnon & Francou, 1988). Lunar position uses ELP2000-82B (Chapront-Touze & Chapront, 1983). + +--- + +## planet_heliocentric + +Computes the heliocentric ecliptic J2000 position of a solar system body using VSOP87 series C (heliocentric, ecliptic, rectangular). Returns position in Astronomical Units. + +### Signature + +```sql +planet_heliocentric(body_id int4, t timestamptz) → heliocentric +``` + +### Parameters + +| Parameter | Type | Description | +|-----------|------|-------------| +| `body_id` | `int4` | Planet identifier (see table below) | +| `t` | `timestamptz` | Evaluation time | + +#### Body IDs + +| ID | Body | +|----|------| +| 0 | Sun (returns origin: 0, 0, 0) | +| 1 | Mercury | +| 2 | Venus | +| 3 | Earth | +| 4 | Mars | +| 5 | Jupiter | +| 6 | Saturn | +| 7 | Uranus | +| 8 | Neptune | + +### Returns + +A `heliocentric` position in AU (ecliptic J2000 frame). For `body_id = 0` (Sun), all components are zero. + + + +### Example + +```sql +-- Distance of each planet from the Sun +SELECT body_id, + CASE body_id + WHEN 1 THEN 'Mercury' WHEN 2 THEN 'Venus' + WHEN 3 THEN 'Earth' WHEN 4 THEN 'Mars' + WHEN 5 THEN 'Jupiter' WHEN 6 THEN 'Saturn' + WHEN 7 THEN 'Uranus' WHEN 8 THEN 'Neptune' + END AS planet, + round(helio_distance(planet_heliocentric(body_id, now()))::numeric, 6) AS dist_au +FROM generate_series(1, 8) AS body_id; +``` + +```sql +-- Earth's position over one year at weekly intervals +SELECT t, + helio_x(h) AS x_au, + helio_y(h) AS y_au, + helio_z(h) AS z_au +FROM generate_series( + '2024-01-01'::timestamptz, + '2025-01-01'::timestamptz, + interval '7 days' + ) AS t, + planet_heliocentric(3, t) AS h; +``` + +--- + +## planet_observe + +Computes the topocentric position of a planet as seen from an Earth-based observer. Internally computes the heliocentric positions of both Earth and the target planet, applies geometric transformation to geocentric, then converts to topocentric coordinates. + +### Signature + +```sql +planet_observe(body_id int4, obs observer, t timestamptz) → topocentric +``` + +### Parameters + +| Parameter | Type | Description | +|-----------|------|-------------| +| `body_id` | `int4` | Planet identifier (1-8, same as `planet_heliocentric` excluding 0 and 3) | +| `obs` | `observer` | Observer location on Earth | +| `t` | `timestamptz` | Observation time | + + + +### Returns + +A `topocentric` with azimuth, elevation, range (km), and range rate (km/s). + +### Example + +```sql +-- Where is Mars tonight from Greenwich? +SELECT topo_azimuth(t) AS az_deg, + topo_elevation(t) AS el_deg, + topo_range(t) / 149597870.7 AS dist_au +FROM planet_observe(4, '51.4769N 0.0005W 11m'::observer, now()) AS t; +``` + +```sql +-- All planets' current positions from Boulder +SELECT body_id, + CASE body_id + WHEN 1 THEN 'Mercury' WHEN 2 THEN 'Venus' + WHEN 4 THEN 'Mars' WHEN 5 THEN 'Jupiter' + WHEN 6 THEN 'Saturn' WHEN 7 THEN 'Uranus' + WHEN 8 THEN 'Neptune' + END AS planet, + round(topo_azimuth(t)::numeric, 2) AS az, + round(topo_elevation(t)::numeric, 2) AS el +FROM unnest(ARRAY[1,2,4,5,6,7,8]) AS body_id, + planet_observe(body_id, '40.0N 105.3W 1655m'::observer, now()) AS t +ORDER BY topo_elevation(t) DESC; +``` + +--- + +## sun_observe + +Computes the topocentric position of the Sun from an Earth-based observer. + +### Signature + +```sql +sun_observe(obs observer, t timestamptz) → topocentric +``` + +### Parameters + +| Parameter | Type | Description | +|-----------|------|-------------| +| `obs` | `observer` | Observer location on Earth | +| `t` | `timestamptz` | Observation time | + +### Returns + +A `topocentric` with azimuth, elevation, range (km), and range rate (km/s). + +### Example + +```sql +-- Sun position right now +SELECT topo_azimuth(t) AS az, + topo_elevation(t) AS el, + topo_range(t) / 149597870.7 AS dist_au +FROM sun_observe('40.0N 105.3W 1655m'::observer, now()) AS t; +``` + +```sql +-- Find today's solar noon (maximum elevation) +SELECT t, + round(topo_elevation(s)::numeric, 2) AS el +FROM generate_series( + now()::date::timestamptz, + now()::date::timestamptz + interval '24 hours', + interval '1 minute' + ) AS t, + sun_observe('40.0N 105.3W 1655m'::observer, t) AS s +ORDER BY topo_elevation(s) DESC +LIMIT 1; +``` + +--- + +## moon_observe + +Computes the topocentric position of the Moon from an Earth-based observer. Uses the ELP2000-82B lunar theory (Chapront-Touze & Chapront, 1983). + +### Signature + +```sql +moon_observe(obs observer, t timestamptz) → topocentric +``` + +### Parameters + +| Parameter | Type | Description | +|-----------|------|-------------| +| `obs` | `observer` | Observer location on Earth | +| `t` | `timestamptz` | Observation time | + +### Returns + +A `topocentric` with azimuth, elevation, range (km), and range rate (km/s). The Moon's range is typically 356,500 to 406,700 km. + +### Example + +```sql +-- Current Moon position and distance +SELECT topo_azimuth(t) AS az, + topo_elevation(t) AS el, + topo_range(t) AS range_km +FROM moon_observe('40.0N 105.3W 1655m'::observer, now()) AS t; +``` + +```sql +-- Moon's path across the sky tonight at 5-minute intervals +SELECT t, + round(topo_azimuth(m)::numeric, 1) AS az, + round(topo_elevation(m)::numeric, 1) AS el, + round(topo_range(m)::numeric, 0) AS range_km +FROM generate_series( + '2024-06-15 02:00:00+00', + '2024-06-15 10:00:00+00', + interval '5 minutes' + ) AS t, + moon_observe('40.0N 105.3W 1655m'::observer, t) AS m +WHERE topo_elevation(m) > 0; +``` diff --git a/docs/src/content/docs/reference/functions-stars-comets.mdx b/docs/src/content/docs/reference/functions-stars-comets.mdx new file mode 100644 index 0000000..6c79b73 --- /dev/null +++ b/docs/src/content/docs/reference/functions-stars-comets.mdx @@ -0,0 +1,257 @@ +--- +title: "Functions: Stars & Comets" +sidebar: + order: 5 +--- + +import { Aside, Tabs, TabItem } from "@astrojs/starlight/components"; + +Functions for computing topocentric positions of stars from catalog coordinates, propagating comets and asteroids on Keplerian orbits, and observing them from Earth. + +--- + +## star_observe + +Converts a J2000 equatorial position (right ascension and declination) to topocentric coordinates for an Earth-based observer. Applies sidereal time rotation and horizon transformation. Stars are treated as being at infinite distance, so `topo_range` is always 0. + +### Signature + +```sql +star_observe(ra_hours float8, dec_deg float8, obs observer, t timestamptz) → topocentric +``` + +### Parameters + +| Parameter | Type | Unit | Description | +|-----------|------|------|-------------| +| `ra_hours` | `float8` | hours | Right Ascension in J2000 equatorial frame (0-24) | +| `dec_deg` | `float8` | degrees | Declination in J2000 equatorial frame (-90 to +90) | +| `obs` | `observer` | | Observer location on Earth | +| `t` | `timestamptz` | | Observation time | + +### Returns + +A `topocentric` with azimuth and elevation in degrees. `topo_range` is 0 (infinite distance). `topo_range_rate` is 0. + + + +### Example + +```sql +-- Where is Sirius (RA 6h 45m 8.9s, Dec -16d 42m 58s)? +SELECT topo_azimuth(t) AS az, + topo_elevation(t) AS el +FROM star_observe(6.7525, -16.7161, '40.0N 105.3W 1655m'::observer, now()) AS t; +``` + +```sql +-- Which bright stars are above the horizon right now? +-- (Assumes a star_catalog table with ra_hours, dec_deg, magnitude columns) +SELECT name, magnitude, + round(topo_azimuth(t)::numeric, 1) AS az, + round(topo_elevation(t)::numeric, 1) AS el +FROM star_catalog, + star_observe(ra_hours, dec_deg, '40.0N 105.3W 1655m'::observer, now()) AS t +WHERE magnitude < 2.0 + AND topo_elevation(t) > 0 +ORDER BY magnitude; +``` + +--- + +## star_observe_safe + +Identical to `star_observe`, but returns NULL instead of raising an exception on invalid inputs (e.g., RA outside 0-24, Dec outside -90 to +90). + +### Signature + +```sql +star_observe_safe(ra_hours float8, dec_deg float8, obs observer, t timestamptz) → topocentric +``` + +### Parameters + +| Parameter | Type | Unit | Description | +|-----------|------|------|-------------| +| `ra_hours` | `float8` | hours | Right Ascension (0-24) | +| `dec_deg` | `float8` | degrees | Declination (-90 to +90) | +| `obs` | `observer` | | Observer location | +| `t` | `timestamptz` | | Observation time | + +### Returns + +A `topocentric`, or NULL if the input coordinates are invalid. + +### Example + +```sql +-- Batch-process a catalog, skipping any rows with bad coordinates +SELECT catalog_id, name, + topo_azimuth(t) AS az, + topo_elevation(t) AS el +FROM star_catalog, + star_observe_safe(ra_hours, dec_deg, '40.0N 105.3W 1655m'::observer, now()) AS t +WHERE t IS NOT NULL + AND topo_elevation(t) > 10; +``` + +--- + +## kepler_propagate + +Propagates an object on a Keplerian orbit (two-body problem) to a given time. Returns the heliocentric ecliptic J2000 position in AU. Handles elliptic (e < 1), parabolic (e = 1), and hyperbolic (e > 1) orbits. + +### Signature + +```sql +kepler_propagate( + q_au float8, + ecc float8, + inc_deg float8, + arg_peri_deg float8, + long_node_deg float8, + perihelion_jd float8, + t timestamptz +) → heliocentric +``` + +### Parameters + +| Parameter | Type | Unit | Description | +|-----------|------|------|-------------| +| `q_au` | `float8` | AU | Perihelion distance | +| `ecc` | `float8` | | Eccentricity. 0 < e < 1 for elliptic, e = 1 for parabolic, e > 1 for hyperbolic | +| `inc_deg` | `float8` | degrees | Orbital inclination | +| `arg_peri_deg` | `float8` | degrees | Argument of perihelion | +| `long_node_deg` | `float8` | degrees | Longitude of ascending node | +| `perihelion_jd` | `float8` | JD | Time of perihelion passage as Julian Date | +| `t` | `timestamptz` | | Evaluation time | + +### Returns + +A `heliocentric` position in AU (ecliptic J2000 frame). + + + +### Example + +```sql +-- Propagate Comet Halley (1P/Halley) +-- q=0.586 AU, e=0.967, i=162.3, w=111.3, node=58.4, T=2446467.4 (1986 Feb 9) +SELECT helio_x(h) AS x_au, + helio_y(h) AS y_au, + helio_z(h) AS z_au, + helio_distance(h) AS r_au +FROM kepler_propagate( + 0.586, -- perihelion distance + 0.967, -- eccentricity + 162.3, -- inclination + 111.3, -- argument of perihelion + 58.4, -- longitude of ascending node + 2446467.4, -- perihelion Julian Date + now() +) AS h; +``` + +```sql +-- Track a near-parabolic comet over 6 months +SELECT t, + helio_distance(h) AS r_au +FROM generate_series( + '2024-01-01'::timestamptz, + '2024-07-01'::timestamptz, + interval '1 day' + ) AS t, + kepler_propagate(1.01, 0.9995, 45.0, 130.0, 210.0, 2460400.5, t) AS h; +``` + +--- + +## comet_observe + +Computes the topocentric position of a comet or asteroid as seen from an Earth-based observer. This function combines Keplerian propagation with the Earth's heliocentric position to produce observer-relative coordinates. + +### Signature + +```sql +comet_observe( + q_au float8, + ecc float8, + inc_deg float8, + arg_peri_deg float8, + long_node_deg float8, + perihelion_jd float8, + earth_x float8, + earth_y float8, + earth_z float8, + obs observer, + t timestamptz +) → topocentric +``` + +### Parameters + +| Parameter | Type | Unit | Description | +|-----------|------|------|-------------| +| `q_au` | `float8` | AU | Perihelion distance | +| `ecc` | `float8` | | Eccentricity | +| `inc_deg` | `float8` | degrees | Orbital inclination | +| `arg_peri_deg` | `float8` | degrees | Argument of perihelion | +| `long_node_deg` | `float8` | degrees | Longitude of ascending node | +| `perihelion_jd` | `float8` | JD | Time of perihelion passage as Julian Date | +| `earth_x` | `float8` | AU | Earth's heliocentric X (ecliptic J2000) | +| `earth_y` | `float8` | AU | Earth's heliocentric Y (ecliptic J2000) | +| `earth_z` | `float8` | AU | Earth's heliocentric Z (ecliptic J2000) | +| `obs` | `observer` | | Observer location on Earth | +| `t` | `timestamptz` | | Observation time | + + + +### Returns + +A `topocentric` with azimuth, elevation, range (km), and range rate (km/s). + +### Example + +```sql +-- Observe Comet Halley from Boulder +WITH earth AS ( + SELECT planet_heliocentric(3, now()) AS h +) +SELECT topo_azimuth(c) AS az, + topo_elevation(c) AS el, + topo_range(c) / 149597870.7 AS dist_au +FROM earth, + comet_observe( + 0.586, 0.967, 162.3, 111.3, 58.4, 2446467.4, + helio_x(earth.h), helio_y(earth.h), helio_z(earth.h), + '40.0N 105.3W 1655m'::observer, + now() + ) AS c; +``` + +```sql +-- Batch-observe all comets from a catalog +WITH earth AS ( + SELECT planet_heliocentric(3, now()) AS h +) +SELECT name, + round(topo_azimuth(c)::numeric, 2) AS az, + round(topo_elevation(c)::numeric, 2) AS el, + round((topo_range(c) / 149597870.7)::numeric, 4) AS dist_au +FROM comet_catalog, earth, + comet_observe( + q_au, ecc, inc_deg, arg_peri_deg, long_node_deg, perihelion_jd, + helio_x(earth.h), helio_y(earth.h), helio_z(earth.h), + '40.0N 105.3W 1655m'::observer, + now() + ) AS c +WHERE topo_elevation(c) > 0 +ORDER BY topo_range(c); +``` diff --git a/docs/src/content/docs/reference/functions-transfers.mdx b/docs/src/content/docs/reference/functions-transfers.mdx new file mode 100644 index 0000000..fddc302 --- /dev/null +++ b/docs/src/content/docs/reference/functions-transfers.mdx @@ -0,0 +1,152 @@ +--- +title: "Functions: Transfers" +sidebar: + order: 7 +--- + +import { Aside, Tabs, TabItem } from "@astrojs/starlight/components"; + +Functions for computing interplanetary transfer orbits using the Lambert problem solver. Given a departure body, arrival body, departure time, and arrival time, the solver finds the transfer orbit that connects the two positions. + +--- + +## lambert_transfer + +Solves the Lambert problem for a transfer between two planets and returns the full solution record. The solver computes heliocentric positions of both bodies at the departure and arrival times using VSOP87, then finds the conic section connecting them. + +### Signature + +```sql +lambert_transfer( + dep_body int4, + arr_body int4, + dep_time timestamptz, + arr_time timestamptz +) → RECORD( + c3_departure float8, + c3_arrival float8, + v_inf_departure float8, + v_inf_arrival float8, + tof_days float8, + transfer_sma float8 +) +``` + +### Parameters + +| Parameter | Type | Description | +|-----------|------|-------------| +| `dep_body` | `int4` | Departure planet body ID (1-8). See [Body ID Reference](/reference/body-ids/). | +| `arr_body` | `int4` | Arrival planet body ID (1-8) | +| `dep_time` | `timestamptz` | Departure epoch | +| `arr_time` | `timestamptz` | Arrival epoch. Must be after `dep_time`. | + +### Returns + +A record with the following fields: + +| Field | Type | Unit | Description | +|-------|------|------|-------------| +| `c3_departure` | `float8` | km^2/s^2 | Departure characteristic energy. The square of the hyperbolic excess velocity at the departure planet. A smaller C3 requires less launch energy. | +| `c3_arrival` | `float8` | km^2/s^2 | Arrival characteristic energy. The energy that must be shed for orbit insertion at the arrival planet. | +| `v_inf_departure` | `float8` | km/s | Hyperbolic excess velocity at departure (= sqrt(C3)). | +| `v_inf_arrival` | `float8` | km/s | Hyperbolic excess velocity at arrival. | +| `tof_days` | `float8` | days | Time of flight from departure to arrival. | +| `transfer_sma` | `float8` | AU | Semi-major axis of the transfer orbit. Negative for hyperbolic transfers. | + + + +### Example + +```sql +-- Earth to Mars transfer for a 2028 opportunity +SELECT round(c3_departure::numeric, 2) AS c3_depart, + round(c3_arrival::numeric, 2) AS c3_arrive, + round(v_inf_departure::numeric, 3) AS v_inf_dep, + round(v_inf_arrival::numeric, 3) AS v_inf_arr, + round(tof_days::numeric, 1) AS flight_days, + round(transfer_sma::numeric, 4) AS sma_au +FROM lambert_transfer(3, 4, + '2028-10-01'::timestamptz, + '2029-06-15'::timestamptz); +``` + +```sql +-- Pork chop plot: scan departure and arrival dates for Earth-Mars +-- 150 departure dates x 150 arrival dates = 22,500 solutions +SELECT dep, arr, + round(c3_departure::numeric, 2) AS c3 +FROM generate_series('2028-08-01'::timestamptz, '2029-01-28'::timestamptz, interval '1 day') AS dep +CROSS JOIN generate_series('2029-03-01'::timestamptz, '2029-07-28'::timestamptz, interval '1 day') AS arr, +LATERAL lambert_transfer(3, 4, dep, arr) AS lt +WHERE c3_departure IS NOT NULL + AND c3_departure < 50 +ORDER BY c3_departure; +``` + +```sql +-- Compare transfer windows for all outer planets from Earth +SELECT arr_body, + CASE arr_body + WHEN 5 THEN 'Jupiter' WHEN 6 THEN 'Saturn' + WHEN 7 THEN 'Uranus' WHEN 8 THEN 'Neptune' + END AS target, + round(c3_departure::numeric, 2) AS c3, + round(tof_days::numeric, 0) AS flight_days +FROM unnest(ARRAY[5,6,7,8]) AS arr_body, + lambert_transfer(3, arr_body, + '2030-01-01'::timestamptz, + '2030-01-01'::timestamptz + interval '2 years') AS lt; +``` + +--- + +## lambert_c3 + +A convenience function that solves the Lambert problem and returns only the departure C3 value. Returns NULL on solver failure (e.g., degenerate geometry, transfer angle near 0 or 180 degrees). This is the function to use for generating pork chop plots where only departure energy matters. + +### Signature + +```sql +lambert_c3(dep_body int4, arr_body int4, dep_time timestamptz, arr_time timestamptz) → float8 +``` + +### Parameters + +| Parameter | Type | Description | +|-----------|------|-------------| +| `dep_body` | `int4` | Departure planet body ID (1-8) | +| `arr_body` | `int4` | Arrival planet body ID (1-8) | +| `dep_time` | `timestamptz` | Departure epoch | +| `arr_time` | `timestamptz` | Arrival epoch | + +### Returns + +Departure C3 in km^2/s^2, or NULL if the solver fails. + +### Example + +```sql +-- Quick C3 check for a specific transfer +SELECT round(lambert_c3(3, 4, + '2028-10-15'::timestamptz, + '2029-05-01'::timestamptz +)::numeric, 2) AS c3_km2s2; +``` + +```sql +-- Dense pork chop plot using the scalar function +-- Faster than lambert_transfer when you only need C3 +SELECT dep, arr, + round(lambert_c3(3, 4, dep, arr)::numeric, 2) AS c3 +FROM generate_series('2028-08-01'::timestamptz, '2029-01-28'::timestamptz, interval '1 day') AS dep +CROSS JOIN generate_series('2029-03-01'::timestamptz, '2029-07-28'::timestamptz, interval '1 day') AS arr +WHERE lambert_c3(3, 4, dep, arr) IS NOT NULL + AND lambert_c3(3, 4, dep, arr) < 30; +``` + + diff --git a/docs/src/content/docs/reference/operators-gist.mdx b/docs/src/content/docs/reference/operators-gist.mdx new file mode 100644 index 0000000..782e31c --- /dev/null +++ b/docs/src/content/docs/reference/operators-gist.mdx @@ -0,0 +1,190 @@ +--- +title: "Operators & GiST Index" +sidebar: + order: 8 +--- + +import { Aside, Tabs, TabItem } from "@astrojs/starlight/components"; + +pg_orbit defines two operators on the `tle` type and a GiST operator class that enables indexed conjunction screening over large satellite catalogs. The operators work on the orbital altitude band and inclination range extracted from TLE elements, providing a fast necessary-condition filter for proximity analysis. + +--- + +## Operators + +### && (Overlap) + +Tests whether two TLEs have overlapping orbital envelopes. The envelopes are defined by the altitude band (perigee to apogee) and inclination range. Overlap is a necessary but not sufficient condition for a conjunction: two satellites whose altitude bands and inclination ranges do not overlap can never come close to each other. + +#### Signature + +```sql +tle && tle → boolean +``` + +#### Description + +Returns `true` if both of the following conditions hold: +1. The altitude bands overlap (one satellite's perigee is below the other's apogee, and vice versa) +2. The inclination ranges are compatible (the orbits could geometrically intersect) + +Returns `false` if the orbits are guaranteed to never intersect based on these geometric bounds. + + + +#### Example + +```sql +-- Find all satellites whose orbits could potentially intersect with the ISS +WITH iss AS ( + SELECT '1 25544U 98067A 24001.50000000 .00016717 00000-0 10270-3 0 9025 +2 25544 51.6400 208.9163 0006703 30.1694 61.7520 15.50100486 00001'::tle AS tle +) +SELECT norad_id, name +FROM satellite_catalog, iss +WHERE satellite_catalog.tle && iss.tle; +``` + +--- + +### `<->` (Distance) + +Computes the minimum separation between the altitude bands of two TLEs, in kilometers. If the altitude bands overlap, returns 0. + +#### Signature + +```sql +tle <-> tle → float8 +``` + +#### Description + +This is an altitude-only metric. It computes: +- `max(0, perigee_a - apogee_b)` and `max(0, perigee_b - apogee_a)` +- Returns the minimum of these two values + +The result is the minimum possible radial separation. A result of 0 means the altitude bands overlap (but the satellites may still be far apart in along-track or cross-track distance). + +#### Example + +```sql +-- Altitude band separation between ISS and a GEO satellite +WITH iss AS ( + SELECT '1 25544U 98067A 24001.50000000 .00016717 00000-0 10270-3 0 9025 +2 25544 51.6400 208.9163 0006703 30.1694 61.7520 15.50100486 00001'::tle AS tle +), +geo AS ( + SELECT '1 28884U 05041A 24001.50000000 -.00000089 00000-0 00000-0 0 9997 +2 28884 0.0153 93.0424 0001699 138.1498 336.5718 1.00271128 67481'::tle AS tle +) +SELECT round((iss.tle <-> geo.tle)::numeric, 1) AS separation_km +FROM iss, geo; +``` + +```sql +-- Order catalog by altitude proximity to a target satellite +WITH target AS ( + SELECT tle FROM satellite_catalog WHERE norad_id = 25544 +) +SELECT norad_id, name, + round((satellite_catalog.tle <-> target.tle)::numeric, 1) AS alt_sep_km +FROM satellite_catalog, target +ORDER BY satellite_catalog.tle <-> target.tle +LIMIT 20; +``` + +--- + +## GiST Operator Class: tle_ops + +The `tle_ops` operator class enables GiST indexing on `tle` columns. With this index in place, the `&&` (overlap) and `<->` (distance) operators use index scans instead of sequential scans, making conjunction screening over large catalogs practical. + +### Creating the Index + +```sql +CREATE INDEX idx_tle_gist ON satellite_catalog USING gist (tle tle_ops); +``` + +### What Gets Indexed + +The GiST index stores a bounding representation of each TLE's orbital envelope: +- **Altitude band:** perigee altitude to apogee altitude (km, above WGS-72) +- **Inclination range:** orbital inclination (degrees) + +These are extracted from the TLE's mean motion and eccentricity at index creation time. The index does not store time-varying quantities; it represents the geometric envelope of the orbit as defined by the current osculating elements. + +### Index-Accelerated Queries + + + +```sql +-- Find all catalog objects that could intersect with the ISS orbit +-- Uses the GiST index to avoid a full catalog scan +WITH iss AS ( + SELECT tle FROM satellite_catalog WHERE norad_id = 25544 +) +SELECT c.norad_id, c.name +FROM satellite_catalog c, iss +WHERE c.tle && iss.tle + AND c.norad_id != 25544; +``` + + +```sql +-- Find the 10 satellites with the closest altitude bands to the ISS +-- The <-> operator supports GiST ordering (ORDER BY ... <-> ...) +WITH iss AS ( + SELECT tle FROM satellite_catalog WHERE norad_id = 25544 +) +SELECT c.norad_id, c.name, + round((c.tle <-> iss.tle)::numeric, 1) AS alt_sep_km +FROM satellite_catalog c, iss +WHERE c.norad_id != 25544 +ORDER BY c.tle <-> iss.tle +LIMIT 10; +``` + + +```sql +-- Full conjunction screening pipeline: +-- Stage 1: GiST index filters by orbital envelope overlap +-- Stage 2: Precise distance computation on surviving pairs +WITH iss AS ( + SELECT tle FROM satellite_catalog WHERE norad_id = 25544 +), +candidates AS ( + SELECT c.norad_id, c.name, c.tle + FROM satellite_catalog c, iss + WHERE c.tle && iss.tle + AND c.norad_id != 25544 +) +SELECT norad_id, name, + round(tle_distance(candidates.tle, iss.tle, now())::numeric, 1) AS dist_km +FROM candidates, iss +WHERE tle_distance(candidates.tle, iss.tle, now()) < 100 +ORDER BY dist_km; +``` + + + +### Performance + +Without the GiST index, the `&&` operator requires a sequential scan of the entire catalog (O(n) per query). With the index, overlap queries run in O(log n) time. For a catalog of 12,000 active TLEs, this reduces conjunction screening from seconds to milliseconds. + + + +### Index Maintenance + +The GiST index is maintained automatically by PostgreSQL on `INSERT`, `UPDATE`, and `DELETE`. When TLEs are refreshed (e.g., daily catalog updates), the index is updated in place. No manual `REINDEX` is needed under normal operation. + +If you perform a bulk catalog replacement (truncate + reload), run `REINDEX` after loading: + +```sql +TRUNCATE satellite_catalog; +COPY satellite_catalog FROM '/path/to/catalog.csv' WITH (FORMAT csv); +REINDEX INDEX idx_tle_gist; +``` diff --git a/docs/src/content/docs/reference/types.mdx b/docs/src/content/docs/reference/types.mdx new file mode 100644 index 0000000..a5ff309 --- /dev/null +++ b/docs/src/content/docs/reference/types.mdx @@ -0,0 +1,269 @@ +--- +title: Types +sidebar: + order: 1 +--- + +import { Aside, Tabs, TabItem } from "@astrojs/starlight/components"; + +pg_orbit defines seven composite types that represent the core data structures of orbital mechanics. Each type has a fixed on-disk size, a text I/O format for readability, and accessor functions for extracting individual fields. + +## tle + +**Size:** 112 bytes + +A Two-Line Element set, the standard orbital element format maintained by NORAD and distributed by CelesTrak and Space-Track. Internally stores all SGP4/SDP4-relevant fields in parsed, double-precision form. + +### Input Format + +Standard two-line TLE text. Both lines are concatenated into a single-quoted string with a newline separator: + +```sql +SELECT '1 25544U 98067A 24001.50000000 .00016717 00000-0 10270-3 0 9025 +2 25544 51.6400 208.9163 0006703 30.1694 61.7520 15.50100486 00001'::tle; +``` + + + +### Constructor + +| Function | Signature | Description | +|----------|-----------|-------------| +| `tle_from_lines` | `tle_from_lines(line1 text, line2 text) → tle` | Constructs a TLE from two separate line strings. Useful when lines are stored in separate columns. | + +```sql +SELECT tle_from_lines( + '1 25544U 98067A 24001.50000000 .00016717 00000-0 10270-3 0 9025', + '2 25544 51.6400 208.9163 0006703 30.1694 61.7520 15.50100486 00001' +); +``` + +### Accessor Functions + +| Function | Return Type | Description | +|----------|-------------|-------------| +| `tle_norad_id(tle)` | `int4` | NORAD catalog number | +| `tle_intl_desig(tle)` | `text` | International designator (e.g. `98067A`) | +| `tle_epoch(tle)` | `timestamptz` | Epoch as a PostgreSQL timestamp | +| `tle_inclination(tle)` | `float8` | Inclination in degrees | +| `tle_raan(tle)` | `float8` | Right Ascension of Ascending Node in degrees | +| `tle_eccentricity(tle)` | `float8` | Eccentricity (dimensionless) | +| `tle_arg_perigee(tle)` | `float8` | Argument of perigee in degrees | +| `tle_mean_anomaly(tle)` | `float8` | Mean anomaly in degrees | +| `tle_mean_motion(tle)` | `float8` | Mean motion in revolutions/day | +| `tle_bstar(tle)` | `float8` | B* drag coefficient (1/earth-radii) | +| `tle_period(tle)` | `float8` | Orbital period in minutes | +| `tle_perigee(tle)` | `float8` | Perigee altitude in km (above WGS-72 ellipsoid) | +| `tle_apogee(tle)` | `float8` | Apogee altitude in km (above WGS-72 ellipsoid) | +| `tle_age(tle)` | `interval` | Age of the TLE relative to `now()` | + +```sql +WITH iss AS ( + SELECT '1 25544U 98067A 24001.50000000 .00016717 00000-0 10270-3 0 9025 +2 25544 51.6400 208.9163 0006703 30.1694 61.7520 15.50100486 00001'::tle AS tle +) +SELECT tle_norad_id(tle) AS norad_id, + tle_inclination(tle) AS inc_deg, + tle_eccentricity(tle) AS ecc, + tle_period(tle) AS period_min, + tle_perigee(tle) AS perigee_km, + tle_apogee(tle) AS apogee_km, + tle_epoch(tle) AS epoch, + tle_age(tle) AS age +FROM iss; +``` + +--- + +## eci_position + +**Size:** 48 bytes + +Earth-Centered Inertial position and velocity in the True Equator Mean Equinox (TEME) reference frame. Position components are in kilometers; velocity components are in km/s. This is the native output frame of the SGP4/SDP4 propagator. + +### Accessor Functions + +| Function | Return Type | Unit | Description | +|----------|-------------|------|-------------| +| `eci_x(eci_position)` | `float8` | km | X position (TEME) | +| `eci_y(eci_position)` | `float8` | km | Y position (TEME) | +| `eci_z(eci_position)` | `float8` | km | Z position (TEME) | +| `eci_vx(eci_position)` | `float8` | km/s | X velocity (TEME) | +| `eci_vy(eci_position)` | `float8` | km/s | Y velocity (TEME) | +| `eci_vz(eci_position)` | `float8` | km/s | Z velocity (TEME) | +| `eci_speed(eci_position)` | `float8` | km/s | Magnitude of velocity vector | +| `eci_altitude(eci_position)` | `float8` | km | Geocentric altitude (distance from Earth center minus WGS-84 equatorial radius) | + +```sql +WITH iss AS ( + SELECT '1 25544U 98067A 24001.50000000 .00016717 00000-0 10270-3 0 9025 +2 25544 51.6400 208.9163 0006703 30.1694 61.7520 15.50100486 00001'::tle AS tle +) +SELECT eci_x(pos) AS x_km, + eci_y(pos) AS y_km, + eci_z(pos) AS z_km, + eci_speed(pos) AS speed_kms, + eci_altitude(pos) AS alt_km +FROM iss, sgp4_propagate(tle, now()) AS pos; +``` + +--- + +## geodetic + +**Size:** 24 bytes + +WGS-84 geodetic coordinates: latitude, longitude, and altitude above the reference ellipsoid. + +### Accessor Functions + +| Function | Return Type | Unit | Description | +|----------|-------------|------|-------------| +| `geo_lat(geodetic)` | `float8` | degrees | Geodetic latitude (-90 to +90, north positive) | +| `geo_lon(geodetic)` | `float8` | degrees | Geodetic longitude (-180 to +180, east positive) | +| `geo_alt(geodetic)` | `float8` | km | Altitude above WGS-84 ellipsoid | + +```sql +WITH iss AS ( + SELECT '1 25544U 98067A 24001.50000000 .00016717 00000-0 10270-3 0 9025 +2 25544 51.6400 208.9163 0006703 30.1694 61.7520 15.50100486 00001'::tle AS tle +) +SELECT geo_lat(g) AS lat, + geo_lon(g) AS lon, + geo_alt(g) AS alt_km +FROM iss, subsatellite_point(tle, now()) AS g; +``` + +--- + +## topocentric + +**Size:** 32 bytes + +Observer-relative coordinates: azimuth, elevation, slant range, and range rate. This is the output of all `*_observe` functions. + +### Accessor Functions + +| Function | Return Type | Unit | Description | +|----------|-------------|------|-------------| +| `topo_azimuth(topocentric)` | `float8` | degrees | Azimuth measured clockwise from true north (0-360) | +| `topo_elevation(topocentric)` | `float8` | degrees | Elevation above the local horizon (-90 to +90) | +| `topo_range(topocentric)` | `float8` | km | Slant range from observer to target | +| `topo_range_rate(topocentric)` | `float8` | km/s | Rate of change of range. Positive = receding from observer. | + + + +```sql +-- Where is Saturn from Boulder right now? +SELECT topo_azimuth(t) AS az, + topo_elevation(t) AS el, + topo_range(t) AS range_km +FROM planet_observe(6, '40.0N 105.3W 1655m'::observer, now()) AS t; +``` + +--- + +## observer + +**Size:** 24 bytes + +An observer's geodetic location on Earth. Used as input to all topocentric observation functions. + +### Input Format + +A compact string encoding latitude, longitude, and optional altitude: + +``` +'40.0N 105.3W 1655m' +``` + +- Latitude: decimal degrees followed by `N` or `S` +- Longitude: decimal degrees followed by `E` or `W` +- Altitude: meters followed by `m` (optional, defaults to 0) + +```sql +SELECT '40.0N 105.3W 1655m'::observer; -- Boulder, CO +SELECT '51.4769N 0.0005W 11m'::observer; -- Greenwich Observatory +SELECT '35.6762N 139.6503E 40m'::observer; -- Tokyo +SELECT '33.9S 18.5E 0m'::observer; -- Cape Town +``` + +### Constructor + +| Function | Signature | Description | +|----------|-----------|-------------| +| `observer_from_geodetic` | `observer_from_geodetic(lat float8, lon float8, alt_m float8 DEFAULT 0) → observer` | Construct from numeric lat/lon (degrees) and altitude (meters). Latitude: north positive. Longitude: east positive. | + +```sql +-- These are equivalent: +SELECT '40.0N 105.3W 1655m'::observer; +SELECT observer_from_geodetic(40.0, -105.3, 1655); +``` + +--- + +## pass_event + +**Size:** 48 bytes + +A satellite pass over an observer location, with AOS (Acquisition of Signal), maximum elevation, and LOS (Loss of Signal) timestamps plus geometry. + +### Accessor Functions + +| Function | Return Type | Unit | Description | +|----------|-------------|------|-------------| +| `pass_aos_time(pass_event)` | `timestamptz` | | Time the satellite rises above the horizon (or minimum elevation threshold) | +| `pass_max_el_time(pass_event)` | `timestamptz` | | Time of maximum elevation (closest approach) | +| `pass_los_time(pass_event)` | `timestamptz` | | Time the satellite sets below the horizon (or minimum elevation threshold) | +| `pass_max_elevation(pass_event)` | `float8` | degrees | Peak elevation during the pass | +| `pass_aos_azimuth(pass_event)` | `float8` | degrees | Azimuth at AOS | +| `pass_los_azimuth(pass_event)` | `float8` | degrees | Azimuth at LOS | +| `pass_duration(pass_event)` | `interval` | | Duration from AOS to LOS | + +```sql +WITH iss AS ( + SELECT '1 25544U 98067A 24001.50000000 .00016717 00000-0 10270-3 0 9025 +2 25544 51.6400 208.9163 0006703 30.1694 61.7520 15.50100486 00001'::tle AS tle +) +SELECT pass_aos_time(p) AS rise, + pass_max_el_time(p) AS culmination, + pass_max_elevation(p) AS max_el, + pass_los_time(p) AS set, + pass_aos_azimuth(p) AS rise_az, + pass_los_azimuth(p) AS set_az, + pass_duration(p) AS dur +FROM iss, predict_passes(tle, '40.0N 105.3W 1655m'::observer, + now(), now() + interval '24 hours', 10.0) AS p; +``` + +--- + +## heliocentric + +**Size:** 24 bytes + +Heliocentric position in the ecliptic plane of J2000.0, measured in Astronomical Units (AU). This is the output of `planet_heliocentric` and `kepler_propagate`. + +### Accessor Functions + +| Function | Return Type | Unit | Description | +|----------|-------------|------|-------------| +| `helio_x(heliocentric)` | `float8` | AU | X position (ecliptic J2000) | +| `helio_y(heliocentric)` | `float8` | AU | Y position (ecliptic J2000) | +| `helio_z(heliocentric)` | `float8` | AU | Z position (ecliptic J2000) | +| `helio_distance(heliocentric)` | `float8` | AU | Distance from the Sun (vector magnitude) | + +```sql +-- Heliocentric positions of all eight planets +SELECT body_id, + helio_x(h) AS x_au, + helio_y(h) AS y_au, + helio_z(h) AS z_au, + helio_distance(h) AS dist_au +FROM generate_series(1, 8) AS body_id, + planet_heliocentric(body_id, now()) AS h; +``` diff --git a/docs/src/content/docs/workflow/from-gmat.mdx b/docs/src/content/docs/workflow/from-gmat.mdx new file mode 100644 index 0000000..9bfe3a2 --- /dev/null +++ b/docs/src/content/docs/workflow/from-gmat.mdx @@ -0,0 +1,281 @@ +--- +title: From GMAT to SQL +sidebar: + order: 3 +description: Comparing GMAT's mission design workflow with pg_orbit's SQL approach for Lambert transfer analysis. +--- + +import { Tabs, TabItem, Aside, Steps } from "@astrojs/starlight/components"; + +GMAT (General Mission Analysis Tool) is NASA's open-source mission design software. It handles everything from preliminary trajectory design to full mission planning with low-thrust propulsion, gravity assists, and multi-body optimization. It's powerful, free, and built for professional mission designers. + +pg_orbit is not a mission design tool. It solves one specific problem from GMAT's domain — Lambert transfers between planets — and makes it available as a SQL function. This page is about that narrow overlap, and why doing Lambert analysis in SQL is sometimes a better fit than launching a full mission design environment. + +## The Lambert problem + +Given two positions in space and a time of flight, find the orbit that connects them. This is the starting point for interplanetary trajectory design: "If I leave Earth on this date and arrive at Mars on that date, what does the transfer orbit look like?" + +The solution gives you departure C3 (the energy needed to escape Earth's gravity), arrival C3 (the energy you arrive with at the target), and the transfer orbit parameters. A pork chop plot is a grid of these solutions across a range of departure and arrival dates — the visual tool mission designers use to identify launch windows. + +## Setting up a Lambert transfer + + + + GMAT uses a scripting language with a GUI. A minimal Lambert-style analysis + (technically a "targeting" problem in GMAT) requires understanding several + concepts specific to the tool. + + ``` + % GMAT script for Earth-Mars transfer targeting + % This is simplified — real GMAT scripts are longer + + Create Spacecraft sc; + sc.DateFormat = UTCGregorian; + sc.Epoch = '01 Oct 2028 00:00:00.000'; + sc.CoordinateSystem = EarthMJ2000Eq; + sc.DisplayStateType = Cartesian; + + Create ForceModel DeepSpace; + DeepSpace.CentralBody = Sun; + DeepSpace.PointMasses = {Sun, Earth, Mars}; + + Create Propagator DeepSpaceProp; + DeepSpaceProp.FM = DeepSpace; + DeepSpaceProp.Type = PrinceDormand78; + DeepSpaceProp.InitialStepSize = 86400; + + Create ImpulsiveBurn TOI; + TOI.CoordinateSystem = EarthMJ2000Eq; + + Create ImpulsiveBurn MOI; + MOI.CoordinateSystem = MarsMJ2000Eq; + + Create DifferentialCorrector DC; + + Create OrbitView SolarSystemView; + SolarSystemView.Add = {sc, Earth, Mars, Sun}; + + BeginMissionSequence; + Target DC; + Vary DC(TOI.Element1 = 3.0, {Perturbation = 0.01, ...}); + Vary DC(TOI.Element2 = 0.0, {Perturbation = 0.01, ...}); + Vary DC(TOI.Element3 = 0.5, {Perturbation = 0.01, ...}); + Maneuver TOI(sc); + Propagate DeepSpaceProp(sc) {sc.Mars.Periapsis}; + Achieve DC(sc.Mars.RMAG = 3500, {Tolerance = 0.1}); + Achieve DC(sc.ElapsedDays = 258, {Tolerance = 0.1}); + EndTarget; + ``` + + This is the compressed version. The GMAT tutorial for interplanetary transfers + runs about 50 pages. You configure: + - Spacecraft objects with initial state vectors + - Force models with point masses and perturbations + - Propagators with numerical integration settings + - Impulsive burn objects + - A differential corrector (the targeting engine) + - Visualization objects + + Then you write a targeting sequence that varies the burn parameters + until the spacecraft reaches the desired conditions at arrival. + + For a **single transfer**, this gives you a precise, multi-body solution + with accurate planetary perturbations. That's genuinely valuable for + mission design. + + For a **survey** across hundreds of departure/arrival date combinations + to find the optimal launch window? You'd need to script a loop over + the targeting sequence, handle convergence failures, and manage output + parsing. + + + ```sql + -- Single Earth-Mars transfer + SELECT round(c3_departure::numeric, 2) AS c3_depart_km2s2, + round(c3_arrival::numeric, 2) AS c3_arrive_km2s2, + round(v_inf_departure::numeric, 3) AS v_inf_depart_kms, + round(v_inf_arrival::numeric, 3) AS v_inf_arrive_kms, + round(tof_days::numeric, 1) AS flight_days, + round(transfer_sma::numeric, 4) AS sma_au + FROM lambert_transfer( + 3, 4, -- Earth to Mars + '2028-10-01'::timestamptz, -- departure + '2029-06-15'::timestamptz -- arrival + ); + ``` + + Five lines. The function handles: + - Computing Earth and Mars heliocentric positions at the given dates (VSOP87) + - Solving the Lambert boundary value problem (Izzo algorithm) + - Returning C3, v-infinity, time of flight, and transfer SMA + + + +## Pork chop plots + +This is where the SQL approach really differentiates itself. A pork chop plot surveys transfer energy across a grid of departure and arrival dates. In mission design, this is how you find launch windows. + + + + GMAT doesn't have a built-in pork chop plot generator. You would: + + 1. Write a GMAT script with parameterized departure/arrival epochs + 2. Create an outer loop (in GMAT script or an external driver — Python, MATLAB) + that iterates over your date grid + 3. For each combination, run the targeting sequence + 4. Handle convergence failures (some date combinations don't produce + valid solutions with the chosen initial guess) + 5. Collect output from GMAT's report files + 6. Parse and aggregate into a matrix + 7. Plot externally (GMAT's plotting is limited for contour-type visualization) + + This is doable — mission design teams do it — but it's a multi-hour workflow + for setup and execution, and convergence issues require manual attention. + + Alternatively, most teams use a dedicated Lambert solver in Python or MATLAB + (not GMAT) for pork chop plots, and only bring GMAT in for detailed + trajectory refinement once the launch window is identified. + + + ```sql + -- Full pork chop plot: Earth to Mars, 2028-2029 window + -- 150 departure dates x 150 arrival dates = 22,500 transfer solutions + SELECT dep::date AS departure, + arr::date AS arrival, + round(c3_departure::numeric, 2) AS c3_km2s2, + round(tof_days::numeric, 0) AS tof + FROM generate_series( + '2028-06-01'::timestamptz, + '2029-01-01'::timestamptz, + interval '1.4 days' + ) AS dep, + generate_series( + '2029-02-01'::timestamptz, + '2029-10-01'::timestamptz, + interval '1.6 days' + ) AS arr, + LATERAL lambert_transfer(3, 4, dep, arr) AS xfer + WHERE tof_days > 90 + AND c3_departure < 50; -- Filter unreasonable solutions + ``` + + 22,500 Lambert solves. About 8.3 seconds on commodity hardware. Export to CSV: + + ```sql + COPY ( + SELECT dep::date, arr::date, + round(c3_departure::numeric, 2) AS c3 + FROM generate_series( + '2028-06-01'::timestamptz, '2029-01-01'::timestamptz, + interval '1.4 days') AS dep, + generate_series( + '2029-02-01'::timestamptz, '2029-10-01'::timestamptz, + interval '1.6 days') AS arr, + LATERAL lambert_transfer(3, 4, dep, arr) AS xfer + WHERE tof_days > 90 + ) TO '/tmp/porkchop.csv' WITH CSV HEADER; + ``` + + Then plot with gnuplot, matplotlib, or any contour plotting tool. + + + +## Multi-planet survey + +Which planets have favorable transfer windows from Earth in a given year? This kind of broad survey is natural in SQL but tedious in GMAT. + +```sql +-- Survey all inner/outer planet transfers from Earth, 2028-2030 +-- Which targets have the lowest departure C3 in each year? +SELECT target_id, + CASE target_id + WHEN 1 THEN 'Mercury' WHEN 2 THEN 'Venus' + WHEN 4 THEN 'Mars' WHEN 5 THEN 'Jupiter' + WHEN 6 THEN 'Saturn' + END AS target, + dep::date AS best_departure, + arr::date AS best_arrival, + round(min_c3::numeric, 2) AS min_c3_km2s2, + round(tof::numeric, 0) AS flight_days +FROM ( + SELECT target_id, dep, arr, + c3_departure AS min_c3, + tof_days AS tof, + ROW_NUMBER() OVER ( + PARTITION BY target_id + ORDER BY c3_departure + ) AS rn + FROM generate_series(1, 6) AS target_id, + generate_series( + '2028-01-01'::timestamptz, + '2030-12-01'::timestamptz, + interval '10 days' + ) AS dep, + generate_series( + '2028-06-01'::timestamptz, + '2033-01-01'::timestamptz, + interval '10 days' + ) AS arr, + LATERAL lambert_transfer(3, target_id, dep, arr) AS xfer + WHERE target_id != 3 -- Not Earth-to-Earth + AND tof_days BETWEEN 60 AND 1500 + AND c3_departure < 100 +) sub +WHERE rn = 1 +ORDER BY min_c3; +``` + +This scans a wide grid and finds the single best (lowest C3) departure/arrival combination for each target planet. It's a brute-force approach — not how a mission designer would work, but useful for exploration and screening. + +## Where GMAT wins + + + +**Multi-body dynamics.** GMAT propagates trajectories with full N-body gravitational perturbations — Sun, planets, moons, solar radiation pressure, atmospheric drag. pg_orbit's Lambert solver is strictly two-body (Sun + spacecraft). + +**Low-thrust trajectories.** Electric propulsion missions (ion engines, Hall thrusters) require continuous thrust modeling. GMAT handles this; pg_orbit does not. + +**Gravity assists.** Flyby trajectories — using a planet's gravity to change direction and speed — are central to many interplanetary missions. GMAT models these with full patched-conic or N-body dynamics. pg_orbit solves point-to-point transfers only. + +**Mission sequence optimization.** GMAT's differential corrector and optimizer can target complex mission constraints: orbital insertion parameters, flyby altitudes, fuel budgets. pg_orbit returns raw transfer parameters without optimization. + +**Attitude modeling.** GMAT tracks spacecraft orientation — important for solar panel pointing, antenna alignment, and sensor geometry. pg_orbit has no concept of spacecraft attitude. + +**Visualization.** GMAT includes 3D trajectory visualization. pg_orbit returns numbers. + +## Where pg_orbit wins + +**Speed of iteration.** Changing a date range or adding a constraint is editing a SQL query. No restarting a GUI, no waiting for script compilation, no managing convergence parameters. + +**Batch computation.** 22,500 Lambert solves in 8.3 seconds, parallelized across cores automatically. GMAT's targeting loop would take orders of magnitude longer for the same grid. + +**Integration with data.** If your satellite catalog, contact schedules, or mission database lives in PostgreSQL, Lambert results join directly with those tables. No file export/import cycle. + +**Accessibility.** GMAT has a steep learning curve — the tutorial for a basic interplanetary transfer is a 50-page document. pg_orbit's Lambert solver requires knowing one SQL function and its six output columns. + +## The intended workflow + +pg_orbit's Lambert solver fits into a specific phase of mission planning: + + +1. **Screen with pg_orbit.** Generate a pork chop plot across a broad date range. Identify the departure/arrival windows with favorable C3 values. This takes minutes, not hours. + +2. **Refine with GMAT.** Take the promising date ranges from step 1 and set up detailed trajectory design in GMAT. Add perturbations, model the departure and arrival spirals, check flyby opportunities. + +3. **Iterate.** If the refined trajectory shifts the window, go back to pg_orbit to explore the neighborhood in SQL. Faster iteration between broad surveys and detailed analysis. + + + diff --git a/docs/src/content/docs/workflow/from-jpl-horizons.mdx b/docs/src/content/docs/workflow/from-jpl-horizons.mdx new file mode 100644 index 0000000..afac26d --- /dev/null +++ b/docs/src/content/docs/workflow/from-jpl-horizons.mdx @@ -0,0 +1,339 @@ +--- +title: From JPL Horizons to SQL +sidebar: + order: 2 +description: Side-by-side comparison of JPL Horizons API workflows and equivalent pg_orbit SQL queries. +--- + +import { Tabs, TabItem, Aside, Steps } from "@astrojs/starlight/components"; + +JPL Horizons is the gold standard for solar system ephemeris data. Run by the Solar System Dynamics group at the Jet Propulsion Laboratory, it serves precise positions for every known body — planets, moons, asteroids, comets, spacecraft. You can access it through a web interface, telnet, email, or REST API. + +pg_orbit does not replace Horizons. What it does is move the 95% of queries that don't need sub-milliarcsecond precision from a remote API into your local database — with no rate limits, no network latency, and results that join directly with your other tables. + +## Planet ephemeris query + +The most common Horizons request: "Where is Mars from my location at this time?" + + + + ```python + import requests + + params = { + 'format': 'json', + 'COMMAND': '499', # Mars + 'OBJ_DATA': 'NO', + 'MAKE_EPHEM': 'YES', + 'EPHEM_TYPE': 'OBSERVER', + 'CENTER': 'coord@399', + 'COORD_TYPE': 'GEODETIC', + 'SITE_COORD': '-105.3,40.0,1.655', # lon, lat, alt(km) + 'START_TIME': '2025-06-15 00:00', + 'STOP_TIME': '2025-06-15 00:01', + 'STEP_SIZE': '1', + 'QUANTITIES': '1,4,20', # Astrometric RA/Dec, Az/El, Range + } + + response = requests.get( + 'https://ssd.jpl.nasa.gov/api/horizons.api', + params=params + ) + + data = response.json() + # Parse the text block in data['result'] + # Horizons returns fixed-width text, not structured JSON + print(data['result']) + ``` + + The API returns a text block with column headers embedded in the response body. + Parsing it requires knowing the column positions or using a library like + `astroquery.jplhorizons`. The response format varies depending on which + quantities you request. + + **Rate limits:** JPL asks for no more than ~200 heavy queries per hour from a + single IP. Automated batch jobs that generate thousands of queries risk being + throttled or blocked. + + + ```sql + SELECT topo_azimuth(t) AS az, + topo_elevation(t) AS el, + topo_range(t) AS range_km + FROM planet_observe(4, '40.0N 105.3W 1655m'::observer, + '2025-06-15 00:00:00+00'::timestamptz) t; + ``` + + Same computation. No network call, no parsing, no rate limits. + The result is a typed `topocentric` value — access individual components + with `topo_azimuth()`, `topo_elevation()`, `topo_range()`, `topo_range_rate()`. + + + +## Batch queries over time ranges + +This is where the workflow difference becomes dramatic. Generating a 24-hour elevation profile at 10-minute resolution means 144 data points. + + + + ```python + import requests + + # Option A: Single request with STEP_SIZE + params = { + 'format': 'json', + 'COMMAND': '599', # Jupiter + 'MAKE_EPHEM': 'YES', + 'EPHEM_TYPE': 'OBSERVER', + 'CENTER': 'coord@399', + 'COORD_TYPE': 'GEODETIC', + 'SITE_COORD': '-105.3,40.0,1.655', + 'START_TIME': '2025-06-15 00:00', + 'STOP_TIME': '2025-06-16 00:00', + 'STEP_SIZE': '10m', # 10-minute intervals + 'QUANTITIES': '4', # Az/El only + } + + response = requests.get( + 'https://ssd.jpl.nasa.gov/api/horizons.api', + params=params + ) + + # Parse 144 lines of fixed-width text + # Extract azimuth and elevation from each line + lines = response.json()['result'].split('\n') + # ... parsing logic ... + ``` + + For a single body and time range, Horizons handles this in one request. + But what if you want this for all 8 planets? That's 8 API calls. For + 5 observers? That's 40. For a full year at 1-hour resolution? + You're managing thousands of requests, rate limiting, error handling, + and stitching results together. + + + ```sql + -- Jupiter elevation over 24 hours, 10-minute steps + SELECT t, + topo_azimuth(obs) AS az, + topo_elevation(obs) AS el + FROM generate_series( + '2025-06-15 00:00:00+00'::timestamptz, + '2025-06-16 00:00:00+00'::timestamptz, + interval '10 minutes' + ) AS t, + LATERAL planet_observe(5, '40.0N 105.3W 1655m'::observer, t) AS obs; + ``` + + ```sql + -- All 8 planets, 5 observers, full year, 1-hour resolution + -- = 8 * 5 * 8760 = 350,400 observations + SELECT body_id, obs_name, t, + topo_elevation(planet_observe(body_id, location, t)) AS el + FROM generate_series(1, 8) AS body_id, + (VALUES + ('Boulder', '40.0N 105.3W 1655m'::observer), + ('London', '51.5N 0.1W 11m'::observer), + ('Tokyo', '35.7N 139.7E 40m'::observer), + ('Sydney', '33.9S 151.2E 58m'::observer), + ('Nairobi', '1.3S 36.8E 1795m'::observer) + ) AS observers(obs_name, location), + generate_series( + '2025-01-01'::timestamptz, + '2025-12-31'::timestamptz, + interval '1 hour' + ) AS t; + ``` + + 350,400 observations. One query. No rate limits. Results land in a table you + can index, aggregate, and join. + + + +## Moon positions + +Horizons excels at moons — it has ephemerides for every known natural satellite. pg_orbit covers the 19 most-observed moons. + + + + ```python + import requests + + # Galilean moons: Io=501, Europa=502, Ganymede=503, Callisto=504 + moons = {'Io': '501', 'Europa': '502', 'Ganymede': '503', 'Callisto': '504'} + + for name, code in moons.items(): + params = { + 'format': 'json', + 'COMMAND': code, + 'MAKE_EPHEM': 'YES', + 'EPHEM_TYPE': 'OBSERVER', + 'CENTER': 'coord@399', + 'COORD_TYPE': 'GEODETIC', + 'SITE_COORD': '-105.3,40.0,1.655', + 'START_TIME': '2025-06-15 03:00', + 'STOP_TIME': '2025-06-15 03:01', + 'STEP_SIZE': '1', + 'QUANTITIES': '1,4,20', + } + response = requests.get( + 'https://ssd.jpl.nasa.gov/api/horizons.api', + params=params + ) + # Parse each response separately... + ``` + + Four separate API calls. To track all four moons over a night of observation + at 5-minute intervals (say, 8 hours = 96 steps), that's 4 requests or + careful batching. + + + ```sql + -- All four Galilean moons, right now + SELECT moon_id, + CASE moon_id + WHEN 0 THEN 'Io' WHEN 1 THEN 'Europa' + WHEN 2 THEN 'Ganymede' WHEN 3 THEN 'Callisto' + END AS name, + topo_azimuth(galilean_observe(moon_id, obs, now())) AS az, + topo_elevation(galilean_observe(moon_id, obs, now())) AS el, + topo_range(galilean_observe(moon_id, obs, now())) AS range_km + FROM generate_series(0, 3) AS moon_id, + (VALUES ('40.0N 105.3W 1655m'::observer)) AS o(obs); + ``` + + ```sql + -- Track all four moons over an 8-hour observation session + SELECT t, + moon_id, + topo_elevation(galilean_observe(moon_id, obs, t)) AS el + FROM generate_series(0, 3) AS moon_id, + generate_series( + '2025-06-15 02:00:00+00'::timestamptz, + '2025-06-15 10:00:00+00'::timestamptz, + interval '5 minutes' + ) AS t, + (VALUES ('40.0N 105.3W 1655m'::observer)) AS o(obs); + ``` + + 384 observations (4 moons times 96 timestamps). One query. + + + +## Lambert transfer survey + +This is where the difference is most striking. Horizons doesn't compute transfer orbits directly — you'd use its ephemeris data as input to your own Lambert solver. pg_orbit does both in one step. + + + + ```python + from astroquery.jplhorizons import Horizons + from poliastro.iod import izzo + from astropy import units as u + import numpy as np + + # Step 1: Get Earth and Mars positions from Horizons + # for each departure/arrival date pair + dep_dates = pd.date_range('2028-08-01', '2028-12-01', freq='5D') + arr_dates = pd.date_range('2029-04-01', '2029-09-01', freq='5D') + + results = [] + for dep in dep_dates: + # Query Earth heliocentric state at departure + earth = Horizons(id='399', location='@sun', epochs=dep.jd) + earth_vec = earth.vectors() # API call + + for arr in arr_dates: + # Query Mars heliocentric state at arrival + mars = Horizons(id='499', location='@sun', epochs=arr.jd) + mars_vec = mars.vectors() # API call + + # Solve Lambert problem + r1 = [earth_vec['x'][0], earth_vec['y'][0], earth_vec['z'][0]] * u.AU + r2 = [mars_vec['x'][0], mars_vec['y'][0], mars_vec['z'][0]] * u.AU + tof = (arr - dep).days * u.day + + try: + (v1, v2), = izzo.lambert(Sun.k, r1, r2, tof) + c3 = (np.linalg.norm(v1.value) ** 2) + results.append({'dep': dep, 'arr': arr, 'c3': c3}) + except: + pass + + # For a 25x31 grid, that's 775 departure queries + 775 arrival queries + # to Horizons, plus 775 Lambert solves in Python + ``` + + The Horizons queries alone — even with careful batching — take minutes + and risk rate limiting. The Lambert solve is the easy part. + + + ```sql + -- Full pork chop plot: 25 departure dates x 31 arrival dates = 775 transfers + SELECT dep::date AS departure, + arr::date AS arrival, + round(c3_departure::numeric, 2) AS c3_km2s2, + round(tof_days::numeric, 0) AS flight_days + FROM generate_series( + '2028-08-01'::timestamptz, + '2028-12-01'::timestamptz, + interval '5 days' + ) AS dep, + generate_series( + '2029-04-01'::timestamptz, + '2029-09-01'::timestamptz, + interval '5 days' + ) AS arr, + LATERAL lambert_transfer(3, 4, dep, arr) AS xfer + WHERE tof_days > 90; -- Filter unrealistic short transfers + ``` + + pg_orbit computes the planet positions AND solves Lambert internally. + No external API calls. The 775 transfer solutions run in under a second. + + Scale it up to a 150x150 grid (22,500 solutions) and it finishes in + about 8.3 seconds. + + + +## Where Horizons wins + + + +**Accuracy.** DE441 provides sub-milliarcsecond planetary positions. pg_orbit's VSOP87 is accurate to about 1 arcsecond — a factor of 1000 less precise. For spacecraft navigation, radar astrometry, or occultation timing, Horizons is the correct source. + +**Aberration corrections.** Horizons applies light-time iteration, stellar aberration, and gravitational deflection of light. pg_orbit uses geometric positions only. + +**Physical properties.** Horizons can return visual magnitude, angular diameter, phase angle, illuminated fraction, and surface brightness. pg_orbit returns geometric position and range. + +**Topographic corrections.** Horizons accounts for Earth's oblateness and topographic features at the observer's location using precise geodetic models. pg_orbit uses a WGS84 ellipsoid. + +**Body catalog.** Horizons knows about every numbered asteroid, every known comet, and spacecraft past and present. pg_orbit covers the 8 planets, the Sun, the Moon, 19 planetary moons, and whatever comets/asteroids you define with Keplerian elements. + +## Where pg_orbit wins + +**No network dependency.** pg_orbit runs locally, in your database process. No DNS resolution, no TLS handshake, no API parsing. Useful in air-gapped environments, on aircraft, or when Horizons is down for maintenance. + +**No rate limits.** Horizons is generous but not unlimited. Automated pipelines that generate thousands of queries — pork chop plot surveys, Monte Carlo trajectory analysis, multi-body scheduling — can hit throttling. pg_orbit has no external limits; you're bounded only by your own hardware. + +**Batch everything locally.** The Lambert transfer example above illustrates this best. What takes hundreds of API calls and minutes of wall-clock time in the Horizons workflow is a single query that runs in seconds. + +**Results in your database.** Horizons returns text that you parse and then insert. pg_orbit results are already rows in PostgreSQL — ready to JOIN, index, aggregate, or export. + +**Reproducibility.** A pg_orbit query is deterministic. Given the same inputs, it produces the same output on any PostgreSQL instance with the extension installed. No dependency on the current state of a remote API or the version of its ephemeris files. + +## A practical workflow + +For many projects, the right approach uses both. + + +1. **Use Horizons for calibration.** Run the same computation in both systems and compare. pg_orbit should agree with Horizons to within about 1 arcsecond for planets and a few arcseconds for moons. If the difference matters for your application, use Horizons. + +2. **Use pg_orbit for surveys.** Any time you need positions for many bodies, many timestamps, or many observers — parameter sweeps, scheduling optimization, catalog screening — run it locally. + +3. **Use pg_orbit for integration.** When orbital data needs to join with other database tables — observation logs, equipment schedules, frequency allocations — computing inside PostgreSQL eliminates the ETL step. + +4. **Use Horizons for exotic bodies.** If you need positions for Pluto, numbered asteroids with precise osculating elements, or decommissioned spacecraft, Horizons is the only option. + diff --git a/docs/src/content/docs/workflow/from-radio-jupiter-pro.mdx b/docs/src/content/docs/workflow/from-radio-jupiter-pro.mdx new file mode 100644 index 0000000..31198f7 --- /dev/null +++ b/docs/src/content/docs/workflow/from-radio-jupiter-pro.mdx @@ -0,0 +1,275 @@ +--- +title: From Radio Jupiter Pro to SQL +sidebar: + order: 4 +description: Replacing the Windows-only Radio Jupiter Pro desktop app with pg_orbit SQL queries for Jupiter decametric burst prediction. +--- + +import { Tabs, TabItem, Aside, Steps } from "@astrojs/starlight/components"; + +Radio Jupiter Pro is a Windows desktop application used by the Radio JOVE community — roughly 500 to 1000 amateur radio astronomers worldwide who monitor Jupiter's decametric radio emissions. It predicts when Jupiter-Io interactions will produce radio bursts detectable from a given location, based on the Io orbital phase angle and Jupiter's Central Meridian Longitude (CML, System III). + +The application works. It has served the community well for years. But it has limitations that SQL can address: it's Windows-only, it doesn't export data for automated scheduling, and batch analysis over long time ranges requires manual date entry. + +## How Jupiter radio burst prediction works + +Jupiter emits powerful radio bursts at frequencies between roughly 15 and 38 MHz. The strongest emissions correlate with the orbital position of Io relative to Jupiter and with Jupiter's rotation (the CML). The Carr et al. (1983) model maps source regions — labeled A, B, C, and D — onto an Io phase vs. CML diagram. When the current Io phase and CML fall within a source region, the probability of detecting a burst is elevated. + +Both Radio Jupiter Pro and pg_orbit use this same underlying model. + +## Checking burst probability right now + + + + 1. Launch the application (Windows only, or via Wine on Linux/Mac) + 2. Set your geographic coordinates in the preferences + 3. Set the date and time to the current moment + 4. Read the Io phase, CML, and source region prediction from the display + 5. Check whether a source region is active in the CML/Io-phase chart + + There is no programmatic access. The result is on screen — you read it + and decide whether to turn on your receiver. + + + ```sql + SELECT round(io_phase_angle(now())::numeric, 1) AS io_phase, + round(jupiter_cml('40.0N 105.3W 1655m'::observer, now())::numeric, 1) AS cml, + round(jupiter_burst_probability( + io_phase_angle(now()), + jupiter_cml('40.0N 105.3W 1655m'::observer, now()) + )::numeric, 3) AS burst_prob; + ``` + + One row: Io phase angle (degrees), CML (degrees), burst probability (0 to 1). + The probability comes from the same Carr source region model. + + + +## Best burst windows tonight + +This is where manual tools hit their limit. "When should I turn on my receiver tonight?" requires scanning hours of time at reasonable intervals. + + + + In Radio Jupiter Pro, you would: + + 1. Set the start time to sunset + 2. Advance time manually (or use the animation feature) in small steps + 3. Watch the CML/Io-phase indicator to see when it enters a source region + 4. Note the time and source region on paper or in a spreadsheet + 5. Repeat until sunrise + + For a single evening, this takes 5 to 10 minutes of clicking. For + planning a week of observations, multiply accordingly. + + + ```sql + -- Best burst windows tonight (6 PM to 6 AM local, 10-minute steps) + -- Only show times when Jupiter is above the horizon AND burst probability > 0.2 + SELECT t AT TIME ZONE 'America/Denver' AS local_time, + round(io_phase_angle(t)::numeric, 1) AS io_phase, + round(jupiter_cml('40.0N 105.3W 1655m'::observer, t)::numeric, 1) AS cml, + round(jupiter_burst_probability( + io_phase_angle(t), + jupiter_cml('40.0N 105.3W 1655m'::observer, t) + )::numeric, 3) AS prob + FROM generate_series( + '2025-06-15 00:00:00+00'::timestamptz, + '2025-06-15 12:00:00+00'::timestamptz, + interval '10 minutes' + ) AS t + WHERE topo_elevation(planet_observe(5, '40.0N 105.3W 1655m'::observer, t)) > 5 + AND jupiter_burst_probability( + io_phase_angle(t), + jupiter_cml('40.0N 105.3W 1655m'::observer, t) + ) > 0.2 + ORDER BY prob DESC; + ``` + + This filters for: + - Jupiter above 5 degrees elevation (actually observable) + - Burst probability above 20% + + Sorted by probability so the best windows are at the top. The entire scan + takes milliseconds. + + + +## 30-day burst calendar + +Planning a month of observations. Which nights have the best windows? + +```sql +-- 30-day burst calendar: peak probability each night +-- Checks every 15 minutes between 00:00 and 12:00 UTC +WITH nightly_scan AS ( + SELECT t::date AS night, + t, + jupiter_burst_probability( + io_phase_angle(t), + jupiter_cml('40.0N 105.3W 1655m'::observer, t) + ) AS prob, + topo_elevation(planet_observe(5, '40.0N 105.3W 1655m'::observer, t)) AS jup_el + FROM generate_series( + now(), + now() + interval '30 days', + interval '15 minutes' + ) AS t + WHERE topo_elevation(planet_observe(5, '40.0N 105.3W 1655m'::observer, t)) > 5 +) +SELECT night, + round(max(prob)::numeric, 3) AS peak_prob, + (array_agg(t ORDER BY prob DESC))[1] AS best_time_utc, + round(max(jup_el)::numeric, 1) AS max_jupiter_el +FROM nightly_scan +WHERE prob > 0.1 +GROUP BY night +ORDER BY night; +``` + +One query scans 30 nights and returns the peak burst probability for each, along with the specific time and Jupiter's elevation at that moment. In Radio Jupiter Pro, this would require manually advancing through 30 separate nights. + +## Correlate with observation logs + +If you maintain a database of past observations, pg_orbit lets you answer questions like "did I actually detect bursts when the model predicted them?" + +```sql +-- Compare burst predictions with actual observation results +-- Assumes an observation_log table with timestamps and detection flags +SELECT o.obs_time, + o.detected, + o.snr_db, + round(jupiter_burst_probability( + io_phase_angle(o.obs_time), + jupiter_cml('40.0N 105.3W 1655m'::observer, o.obs_time) + )::numeric, 3) AS predicted_prob, + round(io_phase_angle(o.obs_time)::numeric, 1) AS io_phase, + round(jupiter_cml('40.0N 105.3W 1655m'::observer, o.obs_time)::numeric, 1) AS cml +FROM observation_log o +WHERE o.receiver = 'radio_jove_20m' + AND o.obs_time > now() - interval '1 year' +ORDER BY o.obs_time; +``` + +```sql +-- Detection rate by probability bucket +SELECT prob_bucket, + count(*) AS total_obs, + count(*) FILTER (WHERE detected) AS detections, + round(count(*) FILTER (WHERE detected)::numeric / count(*)::numeric, 2) AS det_rate +FROM ( + SELECT o.detected, + CASE + WHEN jupiter_burst_probability( + io_phase_angle(o.obs_time), + jupiter_cml('40.0N 105.3W 1655m'::observer, o.obs_time) + ) < 0.1 THEN '< 10%' + WHEN jupiter_burst_probability( + io_phase_angle(o.obs_time), + jupiter_cml('40.0N 105.3W 1655m'::observer, o.obs_time) + ) < 0.3 THEN '10-30%' + WHEN jupiter_burst_probability( + io_phase_angle(o.obs_time), + jupiter_cml('40.0N 105.3W 1655m'::observer, o.obs_time) + ) < 0.5 THEN '30-50%' + ELSE '> 50%' + END AS prob_bucket + FROM observation_log o + WHERE o.receiver = 'radio_jove_20m' + AND o.obs_time > now() - interval '1 year' +) sub +GROUP BY prob_bucket +ORDER BY prob_bucket; +``` + +This is the kind of analysis that's impossible with Radio Jupiter Pro — it has no concept of historical data or past observations. The program shows you the current prediction and that's it. + +## Automated scheduling + +For operators who want to automate their receivers, pg_orbit can drive a scheduling system. + +```sql +-- Generate tonight's observation schedule +-- Schedule 30-minute blocks centered on high-probability windows +CREATE MATERIALIZED VIEW tonight_schedule AS +WITH windows AS ( + SELECT t, + jupiter_burst_probability( + io_phase_angle(t), + jupiter_cml('40.0N 105.3W 1655m'::observer, t) + ) AS prob, + topo_elevation(planet_observe(5, '40.0N 105.3W 1655m'::observer, t)) AS el + FROM generate_series( + date_trunc('day', now()) + interval '1 hour', + date_trunc('day', now()) + interval '13 hours', + interval '5 minutes' + ) AS t +), +high_prob AS ( + SELECT t AS window_center, + prob, + el + FROM windows + WHERE prob > 0.3 + AND el > 10 +) +SELECT window_center - interval '15 minutes' AS rec_start, + window_center + interval '15 minutes' AS rec_stop, + round(prob::numeric, 3) AS probability, + round(el::numeric, 1) AS jupiter_el +FROM high_prob +ORDER BY window_center; +``` + +Export with `COPY` to feed into a receiver control script, a cron job, or any scheduling system. Refresh nightly with `REFRESH MATERIALIZED VIEW tonight_schedule`. + +## Io phase and CML time series + +For operators building their own CML/Io-phase diagrams (the standard visualization in Jupiter radio astronomy): + +```sql +-- CML vs Io phase over 24 hours, 5-minute resolution +-- Export for plotting a CML/Io-phase track +COPY ( + SELECT t, + round(io_phase_angle(t)::numeric, 2) AS io_phase, + round(jupiter_cml('40.0N 105.3W 1655m'::observer, t)::numeric, 2) AS cml, + round(jupiter_burst_probability( + io_phase_angle(t), + jupiter_cml('40.0N 105.3W 1655m'::observer, t) + )::numeric, 3) AS prob + FROM generate_series( + '2025-06-15 00:00:00+00'::timestamptz, + '2025-06-16 00:00:00+00'::timestamptz, + interval '5 minutes' + ) AS t +) TO '/tmp/cml_io_phase.csv' WITH CSV HEADER; +``` + +Feed the CSV into gnuplot, matplotlib, or any plotting tool to generate the CML/Io-phase diagram. Overlay the Carr source regions and you have the same visualization Radio Jupiter Pro provides — but with data you can customize, share, and version-control. + +## Where Radio Jupiter Pro wins + + + +**Visual CML/Io-phase chart.** Radio Jupiter Pro displays the source regions graphically with the current position highlighted. You can see at a glance which source is active and how close the geometry is to a boundary. pg_orbit returns numbers — you build your own visualization. + +**Audio prediction.** Radio Jupiter Pro includes models for the expected spectral characteristics of different source regions. pg_orbit provides geometry and probability only. + +**Integrated display.** Radio Jupiter Pro shows Jupiter's position, the current CML, Io phase, predicted source, and receiver recommendations all in one window. With pg_orbit, you compose the information yourself from separate function calls. + +**Zero setup.** Install the application, enter your coordinates, and it works. pg_orbit requires PostgreSQL, the extension, and SQL knowledge. + +## Where pg_orbit wins + +**Platform independence.** Radio Jupiter Pro is Windows-only. pg_orbit runs on any platform that supports PostgreSQL — Linux, macOS, Windows, containers, cloud instances. + +**Batch analysis.** Scanning 30 days, 90 days, or a full Jovian apparition at arbitrary resolution is a single `generate_series` query. No manual date advancement. + +**Data integration.** Correlating predictions with observation logs, equipment status, weather data, or any other database table is a JOIN. Radio Jupiter Pro has no data export or import capability. + +**Automated scheduling.** pg_orbit results feed directly into scripts, cron jobs, or any scheduling system through standard SQL exports. Radio Jupiter Pro requires a human to read the screen. + +**Reproducibility.** A SQL query is a complete specification. Share it with another JOVE operator and they get the same results for their location by changing the observer coordinates. diff --git a/docs/src/content/docs/workflow/from-skyfield.mdx b/docs/src/content/docs/workflow/from-skyfield.mdx new file mode 100644 index 0000000..6bf4d84 --- /dev/null +++ b/docs/src/content/docs/workflow/from-skyfield.mdx @@ -0,0 +1,310 @@ +--- +title: From Skyfield to SQL +sidebar: + order: 1 +description: Side-by-side comparison of Skyfield Python workflows and equivalent pg_orbit SQL queries. +--- + +import { Tabs, TabItem, Aside, Steps } from "@astrojs/starlight/components"; + +Skyfield is an excellent Python library for positional astronomy — well-documented, well-tested, and built on the same JPL ephemeris data used by spacecraft navigation teams. If you already use Skyfield, you'll recognize the computations pg_orbit performs. The difference is where they happen. + +This page shows the same tasks done both ways. Not to argue one is better than the other — they make different trade-offs — but to help you decide which fits your workflow. + +## Observing a planet + +The most common starting point: where is Jupiter from my location, right now? + + + + ```python + from skyfield.api import load, Topos + + ts = load.timescale() # downloads finals2000A.all + eph = load('de421.bsp') # downloads 17MB BSP file + + observer = Topos('40.0 N', '105.3 W', elevation_m=1655) + t = ts.now() + + astrometric = (eph['earth'] + observer).at(t).observe(eph['jupiter barycenter']) + alt, az, distance = astrometric.apparent().altaz() + + print(f"Az: {az.degrees:.2f} El: {alt.degrees:.2f} Dist: {distance.au:.4f} AU") + ``` + + Before this runs, Skyfield downloads two files: + - `de421.bsp` (17 MB) — JPL planetary ephemeris + - `finals2000A.all` (3.5 MB) — Earth orientation parameters + + These files expire. `finals2000A.all` needs refreshing every few months. The BSP file + itself is stable, but managing file paths across environments (local dev, CI, production) + adds friction. + + + ```sql + SELECT topo_azimuth(t) AS az, + topo_elevation(t) AS el, + topo_range(t) / 149597870.7 AS distance_au + FROM planet_observe(5, '40.0N 105.3W 1655m'::observer, now()) t; + ``` + + No files to download. No timescale object. The VSOP87 coefficients are compiled + into the extension — they ship with `CREATE EXTENSION pg_orbit` and never expire. + + + +## Batch satellite observation + +Observe many satellites at the same timestamp. This is where the architectural difference starts to matter. + + + + ```python + from skyfield.api import load, EarthSatellite, Topos + + ts = load.timescale() + observer = Topos('40.0 N', '105.3 W', elevation_m=1655) + t = ts.now() + + # Load TLE file + with open('catalog.tle') as f: + lines = f.readlines() + + results = [] + for i in range(0, len(lines), 3): + name = lines[i].strip() + sat = EarthSatellite(lines[i+1], lines[i+2], name, ts) + topo = (sat - observer).at(t) + alt, az, dist = topo.altaz() + if alt.degrees > 0: + results.append({ + 'name': name, + 'az': az.degrees, + 'el': alt.degrees, + 'range_km': dist.km + }) + + # Now you have results in a Python list. + # To correlate with other data, you need to: + # 1. Load that data from your database + # 2. Match by satellite name or NORAD ID + # 3. Merge in Python (pandas, dict lookup, etc.) + ``` + + The results live in Python memory. If you need to correlate with operator contact + schedules, frequency assignments, or historical observation logs that live in + PostgreSQL, you have to bridge two systems. + + + ```sql + -- TLEs already in your database? Just JOIN. + SELECT s.norad_id, + s.name, + topo_azimuth(observe(s.tle, obs.location, now())) AS az, + topo_elevation(observe(s.tle, obs.location, now())) AS el, + topo_range(observe(s.tle, obs.location, now())) AS range_km + FROM satellites s, + (VALUES ('40.0N 105.3W 1655m'::observer)) AS obs(location) + WHERE topo_elevation(observe(s.tle, obs.location, now())) > 0; + ``` + + ```sql + -- Correlate with frequency assignments in the same query + SELECT s.norad_id, + s.name, + f.downlink_mhz, + topo_elevation(observe(s.tle, obs.location, now())) AS el + FROM satellites s + JOIN freq_assignments f ON f.norad_id = s.norad_id, + (VALUES ('40.0N 105.3W 1655m'::observer)) AS obs(location) + WHERE topo_elevation(observe(s.tle, obs.location, now())) > 10 + ORDER BY el DESC; + ``` + + The computation and the correlation happen in the same process. No data + transfer between Python and PostgreSQL. The query planner can parallelize + across cores when scanning large catalogs. + + + +## Time series generation + +Generate positions over a time range — for plotting an elevation profile, building a ground track, or analyzing visibility windows. + + + + ```python + from skyfield.api import load, Topos + import numpy as np + + ts = load.timescale() + eph = load('de421.bsp') + observer = Topos('40.0 N', '105.3 W', elevation_m=1655) + + # Generate 144 timestamps over 24 hours + t0 = ts.now() + t1 = ts.tt_jd(t0.tt + 1.0) + times = ts.linspace(t0, t1, 144) + + earth_obs = eph['earth'] + observer + jupiter = eph['jupiter barycenter'] + + elevations = [] + for t in times: + alt, az, dist = earth_obs.at(t).observe(jupiter).apparent().altaz() + elevations.append(alt.degrees) + + # Plot with matplotlib + import matplotlib.pyplot as plt + plt.plot(range(len(elevations)), elevations) + plt.ylabel('Elevation (degrees)') + plt.show() + ``` + + The loop is explicit. For 144 points this is fast, but the pattern doesn't + parallelize automatically. For larger sweeps (thousands of satellites, days + of 1-minute resolution), you manage the iteration yourself. + + + ```sql + SELECT t AS time, + topo_azimuth(obs) AS az, + topo_elevation(obs) AS el + FROM generate_series( + now(), + now() + interval '24 hours', + interval '10 minutes' + ) AS t, + LATERAL planet_observe(5, '40.0N 105.3W 1655m'::observer, t) AS obs; + ``` + + `generate_series` replaces the Python loop. To change the resolution from + 10 minutes to 1 minute, change one parameter. The same pattern works for + any observable — planets, satellites, moons, stars. + + Export for plotting: + + ```sql + COPY ( + SELECT t, + topo_elevation(planet_observe(5, '40.0N 105.3W 1655m'::observer, t)) AS el + FROM generate_series(now(), now() + interval '24 hours', interval '10 minutes') t + ) TO '/tmp/jupiter_elevation.csv' WITH CSV HEADER; + ``` + + Then plot with whatever tool you prefer — gnuplot, matplotlib, Observable, + a spreadsheet. pg_orbit produces data; visualization is a separate concern. + + + +## Pass prediction + +Predict when a satellite will be visible from a location. This is where Skyfield and pg_orbit take genuinely different approaches. + + + + ```python + from skyfield.api import load, EarthSatellite, Topos + + ts = load.timescale() + observer = Topos('40.0 N', '105.3 W', elevation_m=1655) + + line1 = '1 25544U 98067A 24001.50000000 .00016717 00000-0 10270-3 0 9025' + line2 = '2 25544 51.6400 208.9163 0006703 30.1694 61.7520 15.50100486 00001' + sat = EarthSatellite(line1, line2, 'ISS', ts) + + t0 = ts.now() + t1 = ts.tt_jd(t0.tt + 1.0) + + # find_events returns (times, event_types) + # event_type: 0=rise, 1=culminate, 2=set + times, events = sat.find_events(observer, t0, t1, altitude_degrees=10.0) + + for ti, event in zip(times, events): + name = ('rise', 'culminate', 'set')[event] + print(f"{ti.utc_iso()} — {name}") + ``` + + Skyfield's `find_events` uses root-finding to locate the exact moments when + elevation crosses the threshold. This gives sub-second precision for AOS and + LOS times. + + + ```sql + WITH iss AS ( + SELECT '1 25544U 98067A 24001.50000000 .00016717 00000-0 10270-3 0 9025 + 2 25544 51.6400 208.9163 0006703 30.1694 61.7520 15.50100486 00001'::tle AS tle + ) + SELECT pass_aos(p) AS rise_time, + pass_tca(p) AS max_el_time, + pass_max_el(p) AS max_elevation, + pass_los(p) AS set_time, + pass_aos_az(p) AS rise_azimuth, + pass_los_az(p) AS set_azimuth + FROM iss, + predict_passes(tle, '40.0N 105.3W 1655m'::observer, + now(), now() + interval '24 hours', 10.0) p; + ``` + + `predict_passes` returns structured `pass_event` records. Batch prediction + across many satellites is a JOIN: + + ```sql + SELECT s.name, + pass_aos(p) AS rise, + pass_max_el(p) AS max_el, + pass_los(p) AS set + FROM satellites s, + LATERAL predict_passes(s.tle, '40.0N 105.3W 1655m'::observer, + now(), now() + interval '24 hours', 10.0) p + WHERE s.constellation = 'Iridium' + ORDER BY pass_aos(p); + ``` + + Every Iridium pass in the next 24 hours, filtered by constellation, sorted + chronologically. Adding `JOIN schedules` or `JOIN ground_contacts` keeps the + correlation inside the database. + + + +## Where Skyfield wins + + + +**Precision.** Skyfield uses the full IAU 2000A nutation model, polar motion corrections, and delta-T from IERS data. When you need sub-arcsecond accuracy — dish pointing at microwave frequencies, occultation timing, precision astrometry — Skyfield with DE441 ephemerides is the right tool. + +**Rise/set finding.** `find_events()` uses numerical root-finding to pinpoint the exact moment a body crosses an elevation threshold. pg_orbit's `predict_passes` uses a step-and-refine approach that's fast for batches but less precise for individual events. + +**Aberration and light-time.** Skyfield iterates to correct for light travel time and applies stellar aberration. pg_orbit uses geometric positions without light-time iteration — the difference is under 20 arcseconds for planets and irrelevant for satellite tracking, but it matters for some applications. + +**Visualization integration.** Skyfield works directly with matplotlib, numpy, and the rest of the Python scientific stack. pg_orbit produces rows and columns — you export to CSV or JSON and then plot separately. + +**Extensibility.** Skyfield handles arbitrary BSP kernels — Pluto, spacecraft, asteroids with precise ephemerides. pg_orbit's body catalog is fixed at compile time. + +## Where pg_orbit wins + +**No file management.** No BSP kernels, no timescale data files, no expiring Earth orientation parameters. The computation ships with the extension. + +**Batch operations at database speed.** Observing 12,000 satellites in 17ms. 22,500 Lambert solves in 8.3 seconds. These aren't optimized benchmarks — they're `SELECT` statements running on commodity hardware. + +**Data correlation.** The computation happens where your data lives. JOIN orbital results with contact schedules, frequency assignments, observation logs, or any other table. No ETL pipeline between Python and PostgreSQL. + +**Automatic parallelism.** PostgreSQL's query planner distributes PARALLEL SAFE functions across available cores. You don't manage threads or multiprocessing pools. + +**Reproducibility.** A SQL query is a complete, self-contained specification of a computation. No virtual environment, no package versions, no file paths. The same query produces the same result on any PostgreSQL instance with pg_orbit installed. + +## Migrating gradually + +You don't have to choose one or the other. A practical migration path: + + +1. **Keep Skyfield for precision work.** Anything requiring sub-arcsecond accuracy, aberration corrections, or custom BSP kernels stays in Python. + +2. **Move batch observation to SQL.** If you're computing positions for hundreds of objects to filter or correlate with database records, pg_orbit eliminates the Python-to-PostgreSQL round trip. + +3. **Move scheduling to SQL.** Pass prediction and visibility windows over time ranges are natural `generate_series` + `predict_passes` queries. + +4. **Move reporting to SQL.** "What was above 20 degrees from each of our 5 observers last night?" is a single query with a CROSS JOIN, not a Python loop over observers and timestamps. + diff --git a/docs/src/content/docs/workflow/sql-advantage.mdx b/docs/src/content/docs/workflow/sql-advantage.mdx new file mode 100644 index 0000000..2b5f962 --- /dev/null +++ b/docs/src/content/docs/workflow/sql-advantage.mdx @@ -0,0 +1,473 @@ +--- +title: The SQL Advantage +sidebar: + order: 5 +description: SQL patterns that make pg_orbit uniquely powerful — generate_series, CROSS JOIN, PARALLEL SAFE, materialized views, GiST indexes, and more. +--- + +import { Tabs, TabItem, Aside } from "@astrojs/starlight/components"; + +The previous pages compared pg_orbit with specific tools — Skyfield, JPL Horizons, GMAT, Radio Jupiter Pro. This page steps back and looks at the thing they all have in common: none of them are SQL. + +That sounds obvious, but the implications are deeper than "you can write queries instead of scripts." SQL brings a set of patterns — compositional, declarative, and optimized by decades of database engine development — that change what's practical to compute. + +Each pattern below includes a concrete pg_orbit example. + +## generate_series: time series without loops + +The most common pattern in orbital computation is "evaluate this function at regular time intervals." In Python, that's a for-loop. In SQL, it's `generate_series`. + +```sql +-- Mars elevation every 15 minutes for a week +SELECT t, + topo_elevation(planet_observe(4, '40.0N 105.3W 1655m'::observer, t)) AS el +FROM generate_series( + now(), + now() + interval '7 days', + interval '15 minutes' + ) AS t; +``` + +672 data points. One statement. Change the interval to `'1 minute'` and you get 10,080 points — same statement, same structure. The query planner decides how to execute it; you describe what you want. + +This replaces the boilerplate that dominates astronomical computation scripts: initializing time arrays, iterating, collecting results into lists, converting units. Here, the time array is the `generate_series` call, and the computation is inline. + +### Irregular time grids + +`generate_series` handles regular intervals. For irregular grids — say, the timestamps from an observation log — use a subquery or VALUES list: + +```sql +-- Compute Jupiter position at each recorded observation time +SELECT o.obs_id, + o.obs_time, + topo_azimuth(planet_observe(5, o.location, o.obs_time)) AS az, + topo_elevation(planet_observe(5, o.location, o.obs_time)) AS el +FROM observations o +WHERE o.target = 'Jupiter' + AND o.obs_time > now() - interval '30 days'; +``` + +The function evaluates at whatever timestamps exist in your data. No pre-generating a time grid and interpolating. + +## CROSS JOIN: parameter sweeps + +When you need to evaluate a function across every combination of multiple parameters, SQL's CROSS JOIN (or implicit comma-join) generates the Cartesian product. + +### Pork chop plot + +The canonical example: departure date x arrival date for Lambert transfers. + +```sql +SELECT dep::date AS departure, + arr::date AS arrival, + round(c3_departure::numeric, 2) AS c3_km2s2 +FROM generate_series( + '2028-08-01'::timestamptz, '2029-01-01'::timestamptz, + interval '2 days') AS dep, + generate_series( + '2029-03-01'::timestamptz, '2029-10-01'::timestamptz, + interval '2 days') AS arr, +LATERAL lambert_transfer(3, 4, dep, arr) AS xfer +WHERE tof_days > 90; +``` + +Two `generate_series` calls, one `LATERAL` function. The database generates every combination, evaluates the Lambert solver for each, and returns the results. No nested loops, no progress bars, no managing iteration state. + +### Multi-observer visibility + +Which observer has the best view of each planet right now? + +```sql +SELECT body_id, + obs_name, + round(topo_elevation( + planet_observe(body_id, location, now()) + )::numeric, 1) AS el +FROM generate_series(1, 8) AS body_id, + (VALUES + ('Boulder', '40.0N 105.3W 1655m'::observer), + ('Mauna Kea', '19.8N 155.5W 4205m'::observer), + ('Paranal', '24.6S 70.4W 2635m'::observer), + ('Tenerife', '28.3N 16.5W 2390m'::observer) + ) AS obs(obs_name, location) +WHERE topo_elevation(planet_observe(body_id, location, now())) > 0 +ORDER BY body_id, el DESC; +``` + +8 planets times 4 observers = 32 evaluations. Filtered to only above-horizon results. Sorted so the best observer for each planet appears first. + +## JOIN: correlate with anything + +This is the pattern that no standalone computation tool can replicate. When orbital data lives in the same database as your other data, correlation is a JOIN — not an export-import-match pipeline. + +### Satellite visibility with frequency data + +```sql +-- Which satellites are visible AND transmitting on frequencies +-- our receiver can handle? +SELECT s.norad_id, + s.name, + f.downlink_mhz, + f.mode, + round(topo_elevation( + observe(s.tle, '40.0N 105.3W 1655m'::observer, now()) + )::numeric, 1) AS el, + round(topo_range( + observe(s.tle, '40.0N 105.3W 1655m'::observer, now()) + )::numeric, 0) AS range_km +FROM satellites s +JOIN freq_assignments f ON f.norad_id = s.norad_id +WHERE f.downlink_mhz BETWEEN 144.0 AND 146.0 -- 2m band + AND topo_elevation(observe(s.tle, '40.0N 105.3W 1655m'::observer, now())) > 10 +ORDER BY el DESC; +``` + +The satellite catalog, frequency database, and orbit propagation all participate in the same query. No intermediate files, no API calls, no data format conversion. + +### Pass prediction with contact scheduling + +```sql +-- Predict passes for our constellation and check against existing schedule +SELECT s.name, + pass_aos(p) AS rise, + pass_max_el(p) AS max_el, + pass_los(p) AS set, + CASE WHEN cs.id IS NOT NULL THEN 'SCHEDULED' + ELSE 'AVAILABLE' + END AS status +FROM satellites s, +LATERAL predict_passes( + s.tle, '40.0N 105.3W 1655m'::observer, + now(), now() + interval '24 hours', 15.0 +) p +LEFT JOIN contact_schedule cs + ON cs.norad_id = s.norad_id + AND cs.start_time < pass_los(p) + AND cs.end_time > pass_aos(p) +WHERE s.constellation = 'ORBCOMM' +ORDER BY pass_aos(p); +``` + +Every predicted pass, annotated with whether it overlaps an existing scheduled contact. The LEFT JOIN means unscheduled windows show up as 'AVAILABLE' — these are the gaps you can fill. + +### Burst prediction with weather + +```sql +-- Jupiter burst windows, filtered by weather forecast +SELECT t AT TIME ZONE 'America/Denver' AS local_time, + round(jupiter_burst_probability( + io_phase_angle(t), + jupiter_cml('40.0N 105.3W 1655m'::observer, t) + )::numeric, 3) AS burst_prob, + w.cloud_cover_pct, + w.precipitation_mm +FROM generate_series( + now(), now() + interval '12 hours', interval '15 minutes' + ) AS t +LEFT JOIN weather_forecast w + ON w.station = 'KBDU' + AND w.forecast_time = date_trunc('hour', t) +WHERE topo_elevation(planet_observe(5, '40.0N 105.3W 1655m'::observer, t)) > 10 + AND jupiter_burst_probability( + io_phase_angle(t), + jupiter_cml('40.0N 105.3W 1655m'::observer, t) + ) > 0.2 +ORDER BY t; +``` + +Radio observation is affected by weather differently than optical — rain matters less than ionospheric conditions — but the pattern is the same. Whatever data you have that affects observing decisions, JOIN it in. + +## PARALLEL SAFE: automatic multi-core + +All pg_orbit computation functions are declared `PARALLEL SAFE`. This means PostgreSQL's query planner can distribute work across multiple CPU cores without any explicit threading or multiprocessing code. + + + +```sql +-- Observe all 12,000+ satellites in a catalog +-- PostgreSQL will parallelize this across available cores +EXPLAIN ANALYZE +SELECT s.norad_id, + topo_elevation(observe(s.tle, '40.0N 105.3W 1655m'::observer, now())) AS el +FROM satellites s; +``` + +The execution plan will show `Parallel Seq Scan` or `Gather` nodes when the planner decides parallelism is worthwhile. You don't request it, configure worker pools, or manage thread safety. The database handles it. + +For explicit control when testing: + +```sql +-- Force parallel execution with 4 workers +SET max_parallel_workers_per_gather = 4; +SET parallel_tuple_cost = 0; +SET parallel_setup_cost = 0; + +SELECT s.norad_id, + topo_elevation(observe(s.tle, '40.0N 105.3W 1655m'::observer, now())) AS el +FROM satellites s +WHERE topo_elevation(observe(s.tle, '40.0N 105.3W 1655m'::observer, now())) > 0; +``` + +## CREATE MATERIALIZED VIEW: cache expensive computations + +Some computations are expensive to run repeatedly — a 30-day burst calendar, a full catalog observation, a pork chop plot survey. Materialized views store the result and let you query it like a table. + +```sql +-- Cache tonight's satellite visibility for all observers +CREATE MATERIALIZED VIEW tonight_visibility AS +SELECT s.norad_id, + s.name, + obs.obs_name, + pass_aos(p) AS rise, + pass_max_el(p) AS max_el, + pass_los(p) AS set +FROM satellites s, + (VALUES + ('Boulder', '40.0N 105.3W 1655m'::observer), + ('London', '51.5N 0.1W 11m'::observer), + ('Tokyo', '35.7N 139.7E 40m'::observer) + ) AS obs(obs_name, location), +LATERAL predict_passes( + s.tle, obs.location, + now(), now() + interval '24 hours', 10.0 +) p; + +-- Query it instantly +SELECT * FROM tonight_visibility +WHERE obs_name = 'Boulder' + AND max_el > 45 +ORDER BY rise; + +-- Refresh when TLEs update +REFRESH MATERIALIZED VIEW tonight_visibility; +``` + +The initial computation might take seconds for a large catalog. Subsequent queries against the materialized view are instant — it's just reading a table. Refresh it when the underlying data changes (new TLEs, new day). + +### Concurrent refresh + +For production systems where you don't want to block readers during refresh: + +```sql +CREATE UNIQUE INDEX ON tonight_visibility (norad_id, obs_name, rise); +REFRESH MATERIALIZED VIEW CONCURRENTLY tonight_visibility; +``` + +The `CONCURRENTLY` option requires a unique index but allows queries to continue reading the old data while the refresh runs. + +## COPY TO: export to anything + +pg_orbit produces structured data. Getting it out of PostgreSQL and into other tools is a `COPY` statement. + + + + ```sql + COPY ( + SELECT t, topo_elevation(planet_observe(5, '40.0N 105.3W 1655m'::observer, t)) AS el + FROM generate_series(now(), now() + interval '24 hours', interval '10 minutes') t + ) TO '/tmp/jupiter_elevation.csv' WITH CSV HEADER; + ``` + + + ```sql + COPY ( + SELECT json_build_object( + 'time', t, + 'az', topo_azimuth(planet_observe(5, obs, t)), + 'el', topo_elevation(planet_observe(5, obs, t)), + 'range_au', topo_range(planet_observe(5, obs, t)) / 149597870.7 + ) + FROM generate_series(now(), now() + interval '24 hours', interval '1 hour') t, + (VALUES ('40.0N 105.3W 1655m'::observer)) AS o(obs) + ) TO '/tmp/jupiter.jsonl'; + ``` + + + ```bash + psql -c " + COPY ( + SELECT dep::date, arr::date, round(c3_departure::numeric, 2) AS c3 + FROM generate_series('2028-08-01'::timestamptz, '2029-01-01'::timestamptz, '5 days') dep, + generate_series('2029-03-01'::timestamptz, '2029-10-01'::timestamptz, '5 days') arr, + LATERAL lambert_transfer(3, 4, dep, arr) xfer + WHERE tof_days > 90 + ) TO STDOUT WITH CSV HEADER + " > porkchop.csv + ``` + + + +CSV feeds into gnuplot, matplotlib, Excel, R, Observable, or any visualization tool. JSON feeds into web applications, APIs, or document databases. The computation stays in PostgreSQL; rendering happens wherever you prefer. + +## GiST INDEX: spatial queries on orbital elements + +pg_orbit's TLE type supports GiST indexing. This enables spatial-style queries over orbital elements — finding satellites that share similar orbits or screening for conjunction risks. + +```sql +-- Create a GiST index on the satellite catalog +CREATE INDEX idx_satellites_tle ON satellites USING gist (tle); + +-- Find satellites with orbital overlap (similar altitude, inclination, RAAN) +SELECT a.name AS sat_a, + b.name AS sat_b, + a.tle <-> b.tle AS altitude_separation_km +FROM satellites a, satellites b +WHERE a.norad_id < b.norad_id -- Avoid duplicate pairs + AND a.tle && b.tle -- Orbital overlap (GiST accelerated) + AND a.tle <-> b.tle < 50 -- Within 50 km altitude +ORDER BY a.tle <-> b.tle +LIMIT 100; +``` + +The `&&` operator tests for orbital overlap — whether two TLEs describe orbits in the same region of space. The `<->` operator returns altitude separation in kilometers. Both are accelerated by the GiST index, meaning the database can prune the search space before evaluating expensive propagation. + +For a catalog of 12,000 satellites, a full cross-product would be 72 million pairs. The GiST index reduces this to the pairs that are actually in the same orbital regime. + +### Conjunction screening + +```sql +-- Catalog-wide conjunction screening for the next 24 hours +-- GiST index pre-filters to nearby orbital regimes +SELECT a.norad_id AS sat_a, + b.norad_id AS sat_b, + a.tle <-> b.tle AS alt_sep_km +FROM satellites a, satellites b +WHERE a.norad_id < b.norad_id + AND a.tle && b.tle + AND a.tle <-> b.tle < 10 +ORDER BY a.tle <-> b.tle; +``` + +This is a screening filter, not a precision conjunction analysis. It identifies pairs worth investigating further — the ones where orbital elements suggest close approaches. Detailed conjunction assessment would then propagate those specific pairs at high time resolution. + +## Window functions: tracking changes over time + +SQL window functions let you compute values relative to neighboring rows — previous values, running averages, ranks within groups — without self-joins or subqueries. + +### Range rate changes + +```sql +-- Track ISS range and range rate, flagging closest approach +WITH iss_track AS ( + SELECT t, + topo_range(observe(iss.tle, '40.0N 105.3W 1655m'::observer, t)) AS range_km, + topo_range_rate(observe(iss.tle, '40.0N 105.3W 1655m'::observer, t)) AS range_rate, + topo_elevation(observe(iss.tle, '40.0N 105.3W 1655m'::observer, t)) AS el + FROM (SELECT '1 25544U 98067A 24001.50000000 .00016717 00000-0 10270-3 0 9025 +2 25544 51.6400 208.9163 0006703 30.1694 61.7520 15.50100486 00001'::tle) AS iss(tle), + generate_series(now(), now() + interval '2 hours', interval '30 seconds') AS t + WHERE topo_elevation(observe(iss.tle, '40.0N 105.3W 1655m'::observer, t)) > 0 +) +SELECT t, + round(range_km::numeric, 1) AS range_km, + round(range_rate::numeric, 3) AS range_rate_kms, + round(el::numeric, 1) AS elevation, + CASE + WHEN range_rate < 0 AND lead(range_rate) OVER (ORDER BY t) >= 0 + THEN 'CLOSEST APPROACH' + ELSE '' + END AS event +FROM iss_track +ORDER BY t; +``` + +The `lead()` window function looks at the next row's range rate. When range rate crosses from negative to positive, the satellite has passed closest approach. No separate analysis step — it's computed inline. + +### Daily peak elevation + +```sql +-- Which planet reaches the highest elevation each night this month? +WITH hourly AS ( + SELECT body_id, + t::date AS night, + topo_elevation(planet_observe(body_id, '40.0N 105.3W 1655m'::observer, t)) AS el + FROM generate_series(1, 8) AS body_id, + generate_series(now(), now() + interval '30 days', interval '1 hour') AS t +) +SELECT night, + body_id, + CASE body_id + WHEN 1 THEN 'Mercury' WHEN 2 THEN 'Venus' WHEN 3 THEN 'Earth' + WHEN 4 THEN 'Mars' WHEN 5 THEN 'Jupiter' WHEN 6 THEN 'Saturn' + WHEN 7 THEN 'Uranus' WHEN 8 THEN 'Neptune' + END AS planet, + round(max_el::numeric, 1) AS peak_el +FROM ( + SELECT *, + max(el) OVER (PARTITION BY body_id, night) AS max_el, + ROW_NUMBER() OVER (PARTITION BY night ORDER BY max(el) OVER (PARTITION BY body_id, night) DESC) AS rn + FROM hourly + WHERE el > 0 +) sub +WHERE rn = 1 +GROUP BY night, body_id, max_el +ORDER BY night; +``` + +For each night, this finds which planet reaches the highest elevation — useful for deciding what to observe. The window function handles the ranking within each night without a correlated subquery. + +## Composition: building complex queries from simple parts + +The real power of SQL is that these patterns compose. A single query can use `generate_series` for time steps, `CROSS JOIN` for parameter sweeps, `JOIN` for data correlation, window functions for change detection, and `COPY TO` for export — all in one statement. + +```sql +-- Complete observation planning query: +-- For each of 3 observers, for each visible planet tonight, +-- find the 2-hour window with the highest average elevation, +-- export to CSV +COPY ( + WITH planet_track AS ( + SELECT obs_name, body_id, t, + topo_elevation(planet_observe(body_id, location, t)) AS el + FROM (VALUES + ('Boulder', '40.0N 105.3W 1655m'::observer), + ('Mauna Kea','19.8N 155.5W 4205m'::observer), + ('Paranal', '24.6S 70.4W 2635m'::observer) + ) AS obs(obs_name, location), + generate_series(1, 8) AS body_id, + generate_series( + date_trunc('day', now()) + interval '1 hour', + date_trunc('day', now()) + interval '13 hours', + interval '15 minutes' + ) AS t + WHERE topo_elevation(planet_observe(body_id, location, t)) > 10 + ), + windowed AS ( + SELECT obs_name, body_id, t, + el, + avg(el) OVER ( + PARTITION BY obs_name, body_id + ORDER BY t + ROWS BETWEEN 4 PRECEDING AND 4 FOLLOWING + ) AS rolling_avg_el + FROM planet_track + ) + SELECT DISTINCT ON (obs_name, body_id) + obs_name, + body_id, + CASE body_id + WHEN 1 THEN 'Mercury' WHEN 2 THEN 'Venus' + WHEN 3 THEN 'Earth' WHEN 4 THEN 'Mars' + WHEN 5 THEN 'Jupiter' WHEN 6 THEN 'Saturn' + WHEN 7 THEN 'Uranus' WHEN 8 THEN 'Neptune' + END AS planet, + t AS best_window_center, + round(rolling_avg_el::numeric, 1) AS avg_el_in_window + FROM windowed + ORDER BY obs_name, body_id, rolling_avg_el DESC +) TO '/tmp/observation_plan.csv' WITH CSV HEADER; +``` + +This query: +1. Generates time steps across the night (`generate_series`) +2. Evaluates all 8 planets from 3 observers (`CROSS JOIN`) +3. Filters to above-horizon results (`WHERE`) +4. Computes a rolling 2-hour average elevation (`window function`) +5. Selects the best window for each observer/planet pair (`DISTINCT ON`) +6. Exports to CSV (`COPY TO`) + +In a traditional workflow, each of these steps would be a separate script, a separate data file, and a separate tool. In SQL, they compose into a single declarative statement that the database engine optimizes and parallelizes. + +That's the advantage. Not that SQL is a better programming language — it isn't. But for the specific pattern of "evaluate a function over structured parameter spaces and correlate the results with existing data," SQL is exactly the right tool. And pg_orbit puts the functions inside the tool. diff --git a/docs/src/styles/custom.css b/docs/src/styles/custom.css new file mode 100644 index 0000000..8106af4 --- /dev/null +++ b/docs/src/styles/custom.css @@ -0,0 +1,129 @@ +@import "@fontsource/inter/400.css"; +@import "@fontsource/inter/500.css"; +@import "@fontsource/inter/600.css"; +@import "@fontsource/inter/700.css"; +@import "@fontsource/jetbrains-mono/400.css"; +@import "@fontsource/jetbrains-mono/500.css"; + +/* pg_orbit palette — deep space observation theme */ +:root { + --sl-font: "Inter", system-ui, -apple-system, sans-serif; + --sl-font-mono: "JetBrains Mono", "Fira Code", ui-monospace, monospace; + + --sl-color-accent-low: #451a03; + --sl-color-accent: #f59e0b; + --sl-color-accent-high: #fef3c7; + + --sl-color-white: #e2e8f0; + --sl-color-gray-1: #cbd5e1; + --sl-color-gray-2: #8896a8; + --sl-color-gray-3: #556677; + --sl-color-gray-4: #2a3f54; + --sl-color-gray-5: #1e2d3d; + --sl-color-gray-6: #111827; + --sl-color-gray-7: #0a0e17; + + --sl-color-bg-nav: var(--sl-color-gray-6); + --sl-color-bg-sidebar: var(--sl-color-gray-7); + --sl-color-hairline-light: var(--sl-color-gray-5); + --sl-color-hairline-shade: var(--sl-color-gray-4); +} + +/* Scrollbar */ +::-webkit-scrollbar { + width: 6px; + height: 6px; +} + +::-webkit-scrollbar-track { + background: var(--sl-color-gray-7); +} + +::-webkit-scrollbar-thumb { + background: var(--sl-color-gray-5); + border-radius: 3px; +} + +::-webkit-scrollbar-thumb:hover { + background: var(--sl-color-gray-4); +} + +/* Selection color with amber accent */ +::selection { + background-color: #92400e; + color: #e2e8f0; +} + +/* SQL code blocks get amber left-border accent */ +pre:has(> code.language-sql) { + border-left: 3px solid var(--sl-color-accent); +} + +/* Code blocks — raised surface */ +pre { + background-color: #111827 !important; + border: 1px solid #1e2d3d; +} + +/* Sidebar section labels */ +.sl-sidebar-group summary { + font-weight: 600; + letter-spacing: 0.02em; +} + +/* Hero title gradient */ +.hero .hero-html h1 { + background: linear-gradient(135deg, #f59e0b 0%, #fbbf24 50%, #d97706 100%); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; +} + +/* Tables */ +table { + border-collapse: collapse; + width: 100%; +} + +th { + text-align: left; + font-size: 0.75rem; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.05em; + color: var(--sl-color-gray-2); + border-bottom: 1px solid var(--sl-color-gray-5); +} + +td { + border-bottom: 1px solid var(--sl-color-gray-5); + font-size: 0.875rem; +} + +tr:hover td { + background-color: #1a2332; +} + +/* Aside tweaks */ +.starlight-aside--note { + border-color: var(--sl-color-accent); +} + +/* Focus visible */ +:focus-visible { + outline: 2px solid var(--sl-color-accent); + outline-offset: 2px; +} + +/* Workflow comparison blocks */ +.workflow-compare { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 1rem; +} + +@media (max-width: 768px) { + .workflow-compare { + grid-template-columns: 1fr; + } +} diff --git a/docs/src/styles/katex-fixes.css b/docs/src/styles/katex-fixes.css new file mode 100644 index 0000000..92039e5 --- /dev/null +++ b/docs/src/styles/katex-fixes.css @@ -0,0 +1,48 @@ +/* + * KaTeX fixes for Starlight + * + * Starlight sets `svg { height: auto }` globally which breaks KaTeX's + * internal SVG elements (fraction bars, radicals, delimiters). These + * elements rely on explicit height values from KaTeX's layout engine. + */ + +.katex-html svg { + height: inherit; +} + +.katex-html .vlist svg { + height: inherit; +} + +/* Wide equations need horizontal scroll */ +.katex-display { + overflow-x: auto; + overflow-y: hidden; + padding: 0.5rem 0; +} + +/* Ensure KaTeX text is visible against dark background */ +.katex { + color: var(--sl-color-white, #e2e8f0); +} + +/* Display-mode equations get breathing room */ +.katex-display > .katex { + max-width: 100%; +} + +/* Fix KaTeX newline spacing in aligned environments */ +.katex .base { + margin-top: 2px; + margin-bottom: 2px; +} + +/* Ensure fraction lines are visible */ +.katex .frac-line { + border-color: currentColor; +} + +/* Inline math shouldn't break across lines */ +.katex-inline { + white-space: nowrap; +} diff --git a/docs/tsconfig.json b/docs/tsconfig.json new file mode 100644 index 0000000..bcbf8b5 --- /dev/null +++ b/docs/tsconfig.json @@ -0,0 +1,3 @@ +{ + "extends": "astro/tsconfigs/strict" +}