From a2f723ca2d8a37b2b23e14f177d6f539d9e998b8 Mon Sep 17 00:00:00 2001
From: Claude
Date: Sun, 11 Oct 2026 18:40:55 +0000
Subject: [PATCH 01/16] Start the rebuild: note schema, vault kit and data
migration
Phase 1 of the functional design plan.
- Move the old site into legacy/, unchanged, for reference. It still
builds from there until cutover.
- Add packages/schema: the one definition of all 20 note types, with
field titles and descriptions, note and vault checks, and JSON Schema
export so later editing tools and the MCP server share it.
- Add vault-kit: Obsidian templates generated from the schema, and the
Site guide for 24.01_PersonalWebsite.
- Add tools/migrate: turns the legacy JSON, collections, store data and
page text into 545 Site folder notes and 84 images, and checks the
result with the schema.
Co-Authored-By: Claude Opus 5.5 (1M context)
Claude-Session: https://claude.ai/code/session_012aSbo2D1qP8cMFQCykf7kx
---
.gitignore | 21 +-
README.md | 171 +-
docs/content-model.md | 48 +
.dockerignore => legacy/.dockerignore | 0
.env.example => legacy/.env.example | 0
legacy/.gitignore | 30 +
...ss_cfbaea37-9611-497f-804c-b438e5e659b3.md | 0
Dockerfile => legacy/Dockerfile | 0
legacy/LEGACY.md | 7 +
.../Profile_Website_Spec.md | 0
legacy/README.md | 156 +
astro.config.mjs => legacy/astro.config.mjs | 0
.../docker-compose.yml | 0
{docs => legacy/docs}/CONTENT.md | 0
{docs => legacy/docs}/DEPLOYMENT.md | 0
{docs => legacy/docs}/PHOTOS.md | 0
{docs => legacy/docs}/STORE.md | 0
{docs => legacy/docs}/THEMING.md | 0
legacy/package-lock.json | 8470 +++++++++++++++++
legacy/package.json | 39 +
.../public}/.well-known/security.txt | 0
.../public}/apple-touch-icon.png | Bin
.../public}/brand/patiun-seal.svg | 0
{public => legacy/public}/favicon-16.png | Bin
{public => legacy/public}/favicon-32.png | Bin
{public => legacy/public}/favicon-48.png | Bin
{public => legacy/public}/favicon.ico | Bin
{public => legacy/public}/favicon.svg | 0
{public => legacy/public}/icon-192.png | Bin
{public => legacy/public}/icon-512.png | Bin
.../public}/icon-maskable-192.png | Bin
.../public}/icon-maskable-512.png | Bin
{public => legacy/public}/icon_2.png | Bin
.../public}/images/OLDgaming-setup.jpg | Bin
.../public}/images/OLDhero-portrait.jpg | Bin
.../public}/images/OLDpro-workspace.jpg | Bin
.../public}/images/about-portrait.jpg | Bin
.../public}/images/anime-hero.jpg | Bin
.../public}/images/anime/attack-on-titan.jpg | Bin
.../public}/images/anime/bleach.jpg | Bin
.../public}/images/anime/code-geass.jpg | Bin
.../public}/images/anime/demon-slayer.jpg | Bin
.../anime/frieren-beyond-journeys-end.jpg | Bin
.../images/anime/gundam-all-series.jpg | Bin
.../public}/images/anime/jujutsu-kaisen.jpg | Bin
.../public}/images/anime/kimetsu-no-yaiba.jpg | Bin
.../anime/magi-the-labyrinth-of-magic.jpg | Bin
.../public}/images/anime/my-hero-academia.jpg | Bin
.../public}/images/anime/one-piece.jpg | Bin
...hat-time-i-got-reincarnated-as-a-slime.jpg | Bin
.../public}/images/anime/vinland-saga.jpg | Bin
.../public}/images/boardgames/.gitkeep | 0
.../public}/images/boardgames/wyrmspan.jpg | Bin
{public => legacy/public}/images/cards.jpg | Bin
.../public}/images/deckboxes-example-1.svg | 0
.../public}/images/deckboxes-example-2.svg | 0
.../public}/images/deckboxes.jpg | Bin
.../public}/images/dice_galaxy.jpg | Bin
.../public}/images/dice_golden_moss.jpg | Bin
.../public}/images/dice_scarlet.jpg | Bin
.../public}/images/dice_seastone.jpg | Bin
.../public}/images/fantasy-books.jpg | Bin
{public => legacy/public}/images/fav.png | Bin
.../public}/images/games/.gitkeep | 0
.../public}/images/games/age-of-empires.jpg | Bin
.../images/games/ark-survival-evolved.jpg | Bin
.../public}/images/games/dave-the-diver.jpg | Bin
.../public}/images/games/elden-ring.jpg | Bin
.../public}/images/games/granblue-relink.jpg | Bin
.../public}/images/games/minecraft.jpg | Bin
.../images/games/pok-mon-crystal-version.jpg | Bin
.../public}/images/games/satisfactory.jpg | Bin
.../games/sly-3-honor-among-thieves.jpg | Bin
.../sly-cooper-and-the-thievius-raccoonus.jpg | Bin
.../star-wars-knights-of-the-old-republic.jpg | Bin
.../public}/images/games/stardew-valley.jpg | Bin
.../public}/images/gaming-setup.jpg | Bin
.../public}/images/hero-portrait.jpg | Bin
.../public}/images/kashi_at_work.jpg | Bin
.../public}/images/making-print - Copy.jpg | Bin
.../public}/images/making-print.jpg | Bin
.../public}/images/making-resin.jpg | Bin
{public => legacy/public}/images/minis.jpg | Bin
.../public}/images/movies-hero.jpg | Bin
.../images/movies/avengers-endgame.jpg | Bin
.../images/movies/avengers-infinity-war.jpg | Bin
.../public}/images/movies/contact.jpg | Bin
.../images/movies/howls-moving-castle.jpg | Bin
.../public}/images/movies/jurassic-park.jpg | Bin
.../public}/images/movies/oceans-eleven.jpg | Bin
...wars-episode-v-the-empire-strikes-back.jpg | Bin
.../public}/images/movies/the-matrix.jpg | Bin
.../images/movies/the-princess-bride.jpg | Bin
.../public}/images/movies/transformers.jpg | Bin
.../public}/images/pokemon-cards.jpg | Bin
{public => legacy/public}/images/pro-team.jpg | Bin
.../public}/images/pro-workspace.jpg | Bin
.../public}/images/proxies-example-1.svg | 0
.../public}/images/proxies-example-2.svg | 0
{public => legacy/public}/images/proxies.jpg | Bin
{public => legacy/public}/images/proxies2.jpg | Bin
.../public}/images/reading-nook.jpg | Bin
.../reading/brandon-sanderson-cosmere.jpg | Bin
.../public}/images/reading/desolate-era.jpg | Bin
...uglas-preston-lincoln-child-pendergast.jpg | Bin
.../reading/emily-rodda-deltora-quest.jpg | Bin
.../images/reading/frank-herbert-dune.jpg | Bin
.../j-r-r-tolkien-the-lord-of-the-rings.jpg | Bin
.../reading/omniscient-reader-s-viewpoint.jpg | Bin
.../public}/images/reading/overgeared.jpg | Bin
.../public}/images/reading/solo-leveling.jpg | Bin
.../images/reading/the-king-s-avatar.jpg | Bin
.../public}/images/resin_rings.jpg | Bin
.../store/cards/PXL_20260806_204112875.jpg | Bin
.../store/cards/PXL_20260806_204116184.jpg | Bin
.../store/cards/PXL_20260806_204128368.jpg | Bin
.../store/cards/PXL_20260806_204129979.jpg | Bin
.../public}/images/store/cards/disclosure.jpg | Bin
.../store/cards/foil_cloud_measured.jpg | Bin
.../public}/images/store/cards/ghalta_duo.jpg | Bin
.../public}/images/store/cards/proxies.jpg | Bin
.../public}/images/store/cards/proxies2.jpg | Bin
.../images/store/cards/ravenous_duo.jpg | Bin
.../public}/images/store/cards/sleeved.jpg | Bin
.../images/store/deckboxes/black_front.jpg | Bin
.../images/store/deckboxes/black_side.jpg | Bin
.../images/store/deckboxes/ff_back.jpg | Bin
.../images/store/deckboxes/ff_front.jpg | Bin
.../images/store/deckboxes/ff_open.jpg | Bin
.../images/store/deckboxes/ff_side.jpg | Bin
.../public}/images/tea-gaiwan.jpg | Bin
.../public}/images/ttrpg-boardgame-shelf.jpg | Bin
.../public}/images/ttrpg-dice.jpg | Bin
.../public}/images/ttrpg-mini.jpg | Bin
.../public}/images/ttrpg-table - Copy.jpg | Bin
.../public}/images/ttrpg-table.jpg | Bin
{public => legacy/public}/images/tv-hero.jpg | Bin
.../public}/images/tv/30-rock.jpg | Bin
.../public}/images/tv/arcane.jpg | Bin
.../public}/images/tv/breaking-bad.jpg | Bin
.../public}/images/tv/brooklyn-nine-nine.jpg | Bin
.../public}/images/tv/castlevania.jpg | Bin
.../public}/images/tv/cobra-kai.jpg | Bin
.../public}/images/tv/daredevil.jpg | Bin
.../public}/images/tv/game-of-thrones.jpg | Bin
{public => legacy/public}/images/tv/house.jpg | Bin
.../public}/images/tv/invincible.jpg | Bin
{public => legacy/public}/images/tv/ozark.jpg | Bin
.../images/tv/parks-and-recreation.jpg | Bin
.../public}/images/tv/peaky-blinders.jpg | Bin
.../public}/images/tv/succession.jpg | Bin
{public => legacy/public}/images/tv/suits.jpg | Bin
.../public}/images/tv/the-bear.jpg | Bin
.../public}/images/tv/the-crown.jpg | Bin
.../tv/the-great-british-baking-show.jpg | Bin
.../public}/images/tv/the-mandalorian.jpg | Bin
.../public}/images/tv/the-queens-gambit.jpg | Bin
.../public}/images/tv/the-sopranos.jpg | Bin
.../public}/images/tv/the-wire.jpg | Bin
.../public}/images/tv/wandavision.jpg | Bin
.../public}/images/xmasgifts.jpg | Bin
.../public}/motifs/astrolabe.svg | 0
.../public}/motifs/craft-seal-print-light.svg | 0
.../public}/motifs/kepler-nest-seal-light.svg | 0
.../public}/motifs/layered-seal.svg | 0
.../motifs/squared-circle-seal-light.svg | 0
.../motifs/transmutation-seal-light.svg | 0
.../motifs/tria-prima-array-seal-light.svg | 0
.../public}/motifs/zodiac-seal-light.svg | 0
{public => legacy/public}/robots.txt | 0
{public => legacy/public}/site.webmanifest | 0
.../scripts}/fetch-anime-art.mjs | 0
.../scripts}/fetch-bgg-covers.mjs | 0
.../scripts}/fetch-book-covers.mjs | 0
{scripts => legacy/scripts}/fetch-box-art.mjs | 0
.../scripts}/fetch-tmdb-art.mjs | 0
{src => legacy/src}/components/Icon.astro | 0
.../src}/components/ObfuscatedEmail.astro | 0
.../src}/components/brand/BrandIcon.tsx | 0
.../src}/components/brand/Motif.astro | 0
.../src}/components/brand/PageSeal.astro | 0
.../src}/components/cards/DeckCard.astro | 0
.../src}/components/cards/GameCard.astro | 0
.../src}/components/cards/InterestCard.astro | 0
.../src}/components/cards/MediaCard.astro | 0
.../components/hero/AnimatedBackground.tsx | 0
.../src}/components/layout/Footer.astro | 0
.../src}/components/layout/InterestHero.astro | 0
.../src}/components/layout/Placeholder.astro | 0
.../components/layout/RelatedInterests.astro | 0
.../components/layout/SectionHeader.astro | 0
.../components/layout/SplitTransition.astro | 0
.../src}/components/media/CategoryList.astro | 0
.../src}/components/motion/Reveal.astro | 0
.../components/mystic/AlchemicalDivider.astro | 0
.../components/mystic/CelestialFrame.astro | 0
.../components/mystic/CelestialImage.astro | 0
.../src}/components/mystic/CircuitField.tsx | 0
.../components/mystic/ConstellationField.tsx | 0
.../src}/components/mystic/StarBurst.astro | 0
.../src}/components/mystic/StarField.astro | 0
.../src}/components/nav/MobileMenu.tsx | 0
.../src}/components/nav/NavBar.astro | 0
.../src}/components/nav/ThemeToggle.tsx | 0
.../professional/Certifications.astro | 0
.../components/professional/ContactForm.tsx | 0
.../professional/EducationHistory.astro | 0
.../components/professional/ProjectCard.astro | 0
.../components/professional/SectionNav.astro | 0
.../components/professional/SkillGrid.astro | 0
.../components/professional/Timeline.astro | 0
.../src}/components/store/Carousel.tsx | 0
.../src}/components/store/ColorGrid.astro | 0
.../src}/components/store/OrderForm.tsx | 0
.../src}/components/store/PricingTable.astro | 0
.../src}/components/store/ProductCard.astro | 0
.../src}/components/store/ProductTile.astro | 0
.../components/store/RequirementList.astro | 0
.../src}/components/store/SlotMeter.astro | 0
.../src}/components/store/StoreNav.astro | 0
.../src}/components/ttrpgs/BoardGameShelf.tsx | 0
{src => legacy/src}/content.config.ts | 0
.../src}/content/experience/comcast_1.md | 0
.../src}/content/experience/drexel.md | 0
.../src}/content/experience/implico.md | 0
.../games/hall-of-fame/age-of-empires.md | 0
.../hall-of-fame/ark-survival-evolved.md | 0
.../games/hall-of-fame/dave-the-diver.md | 0
.../content/games/hall-of-fame/elden-ring.md | 0
.../games/hall-of-fame/granblue-relink.md | 0
.../src}/content/games/hall-of-fame/kotor.md | 0
.../content/games/hall-of-fame/minecraft.md | 0
.../games/hall-of-fame/pokemon-crystal.md | 0
.../games/hall-of-fame/satisfactory.md | 0
.../content/games/hall-of-fame/sly-cooper.md | 0
.../games/hall-of-fame/stardew-valley.md | 0
.../src}/content/games/now-playing.yaml | 0
.../src}/content/projects/carrier-connect.md | 0
.../src}/content/projects/cosmos-homelab.md | 0
.../src}/content/projects/driver-app.md | 0
.../content/projects/placeholder-project.md | 0
{src => legacy/src}/data/boardgames.json | 0
{src => legacy/src}/data/media/anime.json | 0
{src => legacy/src}/data/media/movies.json | 0
{src => legacy/src}/data/media/tv.json | 0
{src => legacy/src}/data/reading.json | 0
.../src}/data/store-availability.json | 0
.../src}/data/store-availability.ts | 0
{src => legacy/src}/data/store-products.ts | 0
{src => legacy/src}/env.d.ts | 0
{src => legacy/src}/layouts/BaseLayout.astro | 0
{src => legacy/src}/layouts/StoreLayout.astro | 0
{src => legacy/src}/lib/archidekt.ts | 0
{src => legacy/src}/lib/brand-icons.ts | 0
{src => legacy/src}/lib/env.ts | 0
{src => legacy/src}/lib/media.ts | 0
{src => legacy/src}/lib/motion.ts | 0
{src => legacy/src}/lib/platforms.ts | 0
{src => legacy/src}/lib/site.ts | 0
{src => legacy/src}/middleware.ts | 0
{src => legacy/src}/pages/about.astro | 0
{src => legacy/src}/pages/api/contact.ts | 0
{src => legacy/src}/pages/api/order.ts | 0
{src => legacy/src}/pages/contact.astro | 0
{src => legacy/src}/pages/index.astro | 0
.../src}/pages/interests/anime.astro | 0
.../src}/pages/interests/gaming.astro | 0
.../src}/pages/interests/index.astro | 0
.../src}/pages/interests/making.astro | 0
.../src}/pages/interests/movies.astro | 0
.../src}/pages/interests/pokemon.astro | 0
.../src}/pages/interests/reading.astro | 0
.../src}/pages/interests/tcgs.astro | 0
.../src}/pages/interests/ttrpgs.astro | 0
{src => legacy/src}/pages/interests/tv.astro | 0
.../src}/pages/professional/index.astro | 0
{src => legacy/src}/pages/store/cards.astro | 0
.../src}/pages/store/deck-boxes.astro | 0
{src => legacy/src}/pages/store/index.astro | 0
{src => legacy/src}/pages/store/order.astro | 0
{src => legacy/src}/scripts/reveal-init.ts | 0
{src => legacy/src}/scripts/theme-init.ts | 0
{src => legacy/src}/styles/animations.css | 0
{src => legacy/src}/styles/global.css | 0
{src => legacy/src}/styles/themes.css | 0
tsconfig.json => legacy/tsconfig.json | 0
package-lock.json | 7967 ++--------------
package.json | 56 +-
packages/schema/package.json | 18 +
packages/schema/scripts/emit-json-schema.ts | 24 +
packages/schema/scripts/emit-templates.ts | 9 +
packages/schema/src/fields.ts | 97 +
packages/schema/src/index.ts | 6 +
packages/schema/src/names.ts | 64 +
packages/schema/src/notes.ts | 112 +
packages/schema/src/templates.ts | 60 +
packages/schema/src/types.ts | 508 +
packages/schema/src/vault.ts | 75 +
packages/schema/test/schema.test.ts | 89 +
packages/schema/tsconfig.json | 5 +
tools/migrate/package.json | 15 +
tools/migrate/src/build.ts | 402 +
tools/migrate/src/cli.ts | 73 +
tools/migrate/src/seed.ts | 278 +
tools/migrate/test/migrate.test.ts | 62 +
tools/migrate/tsconfig.json | 4 +
tsconfig.base.json | 15 +
vault-kit/24.01_PersonalWebsite/Site guide.md | 19 +
.../Site/_templates/Board game.md | 14 +
.../Site/_templates/Campaign.md | 13 +
.../Site/_templates/Card game.md | 9 +
.../Site/_templates/Craft.md | 13 +
.../Site/_templates/Deck.md | 11 +
.../Site/_templates/Experience.md | 16 +
.../Site/_templates/Library item.md | 18 +
.../Site/_templates/Make.md | 12 +
.../Site/_templates/Page text.md | 10 +
.../Site/_templates/Post.md | 17 +
.../Site/_templates/Product line.md | 18 +
.../Site/_templates/Station.md | 9 +
.../Site/_templates/Status.md | 11 +
.../Site/_templates/Tabletop system.md | 9 +
.../Site/_templates/Tool or material.md | 9 +
vitest.config.ts | 5 +
324 files changed, 11538 insertions(+), 7556 deletions(-)
create mode 100644 docs/content-model.md
rename .dockerignore => legacy/.dockerignore (100%)
rename .env.example => legacy/.env.example (100%)
create mode 100644 legacy/.gitignore
rename {.zcode => legacy/.zcode}/plans/plan-sess_cfbaea37-9611-497f-804c-b438e5e659b3.md (100%)
rename Dockerfile => legacy/Dockerfile (100%)
create mode 100644 legacy/LEGACY.md
rename Profile_Website_Spec.md => legacy/Profile_Website_Spec.md (100%)
create mode 100644 legacy/README.md
rename astro.config.mjs => legacy/astro.config.mjs (100%)
rename docker-compose.yml => legacy/docker-compose.yml (100%)
rename {docs => legacy/docs}/CONTENT.md (100%)
rename {docs => legacy/docs}/DEPLOYMENT.md (100%)
rename {docs => legacy/docs}/PHOTOS.md (100%)
rename {docs => legacy/docs}/STORE.md (100%)
rename {docs => legacy/docs}/THEMING.md (100%)
create mode 100644 legacy/package-lock.json
create mode 100644 legacy/package.json
rename {public => legacy/public}/.well-known/security.txt (100%)
rename {public => legacy/public}/apple-touch-icon.png (100%)
rename {public => legacy/public}/brand/patiun-seal.svg (100%)
rename {public => legacy/public}/favicon-16.png (100%)
rename {public => legacy/public}/favicon-32.png (100%)
rename {public => legacy/public}/favicon-48.png (100%)
rename {public => legacy/public}/favicon.ico (100%)
rename {public => legacy/public}/favicon.svg (100%)
rename {public => legacy/public}/icon-192.png (100%)
rename {public => legacy/public}/icon-512.png (100%)
rename {public => legacy/public}/icon-maskable-192.png (100%)
rename {public => legacy/public}/icon-maskable-512.png (100%)
rename {public => legacy/public}/icon_2.png (100%)
rename {public => legacy/public}/images/OLDgaming-setup.jpg (100%)
rename {public => legacy/public}/images/OLDhero-portrait.jpg (100%)
rename {public => legacy/public}/images/OLDpro-workspace.jpg (100%)
rename {public => legacy/public}/images/about-portrait.jpg (100%)
rename {public => legacy/public}/images/anime-hero.jpg (100%)
rename {public => legacy/public}/images/anime/attack-on-titan.jpg (100%)
rename {public => legacy/public}/images/anime/bleach.jpg (100%)
rename {public => legacy/public}/images/anime/code-geass.jpg (100%)
rename {public => legacy/public}/images/anime/demon-slayer.jpg (100%)
rename {public => legacy/public}/images/anime/frieren-beyond-journeys-end.jpg (100%)
rename {public => legacy/public}/images/anime/gundam-all-series.jpg (100%)
rename {public => legacy/public}/images/anime/jujutsu-kaisen.jpg (100%)
rename {public => legacy/public}/images/anime/kimetsu-no-yaiba.jpg (100%)
rename {public => legacy/public}/images/anime/magi-the-labyrinth-of-magic.jpg (100%)
rename {public => legacy/public}/images/anime/my-hero-academia.jpg (100%)
rename {public => legacy/public}/images/anime/one-piece.jpg (100%)
rename {public => legacy/public}/images/anime/that-time-i-got-reincarnated-as-a-slime.jpg (100%)
rename {public => legacy/public}/images/anime/vinland-saga.jpg (100%)
rename {public => legacy/public}/images/boardgames/.gitkeep (100%)
rename {public => legacy/public}/images/boardgames/wyrmspan.jpg (100%)
rename {public => legacy/public}/images/cards.jpg (100%)
rename {public => legacy/public}/images/deckboxes-example-1.svg (100%)
rename {public => legacy/public}/images/deckboxes-example-2.svg (100%)
rename {public => legacy/public}/images/deckboxes.jpg (100%)
rename {public => legacy/public}/images/dice_galaxy.jpg (100%)
rename {public => legacy/public}/images/dice_golden_moss.jpg (100%)
rename {public => legacy/public}/images/dice_scarlet.jpg (100%)
rename {public => legacy/public}/images/dice_seastone.jpg (100%)
rename {public => legacy/public}/images/fantasy-books.jpg (100%)
rename {public => legacy/public}/images/fav.png (100%)
rename {public => legacy/public}/images/games/.gitkeep (100%)
rename {public => legacy/public}/images/games/age-of-empires.jpg (100%)
rename {public => legacy/public}/images/games/ark-survival-evolved.jpg (100%)
rename {public => legacy/public}/images/games/dave-the-diver.jpg (100%)
rename {public => legacy/public}/images/games/elden-ring.jpg (100%)
rename {public => legacy/public}/images/games/granblue-relink.jpg (100%)
rename {public => legacy/public}/images/games/minecraft.jpg (100%)
rename {public => legacy/public}/images/games/pok-mon-crystal-version.jpg (100%)
rename {public => legacy/public}/images/games/satisfactory.jpg (100%)
rename {public => legacy/public}/images/games/sly-3-honor-among-thieves.jpg (100%)
rename {public => legacy/public}/images/games/sly-cooper-and-the-thievius-raccoonus.jpg (100%)
rename {public => legacy/public}/images/games/star-wars-knights-of-the-old-republic.jpg (100%)
rename {public => legacy/public}/images/games/stardew-valley.jpg (100%)
rename {public => legacy/public}/images/gaming-setup.jpg (100%)
rename {public => legacy/public}/images/hero-portrait.jpg (100%)
rename {public => legacy/public}/images/kashi_at_work.jpg (100%)
rename {public => legacy/public}/images/making-print - Copy.jpg (100%)
rename {public => legacy/public}/images/making-print.jpg (100%)
rename {public => legacy/public}/images/making-resin.jpg (100%)
rename {public => legacy/public}/images/minis.jpg (100%)
rename {public => legacy/public}/images/movies-hero.jpg (100%)
rename {public => legacy/public}/images/movies/avengers-endgame.jpg (100%)
rename {public => legacy/public}/images/movies/avengers-infinity-war.jpg (100%)
rename {public => legacy/public}/images/movies/contact.jpg (100%)
rename {public => legacy/public}/images/movies/howls-moving-castle.jpg (100%)
rename {public => legacy/public}/images/movies/jurassic-park.jpg (100%)
rename {public => legacy/public}/images/movies/oceans-eleven.jpg (100%)
rename {public => legacy/public}/images/movies/star-wars-episode-v-the-empire-strikes-back.jpg (100%)
rename {public => legacy/public}/images/movies/the-matrix.jpg (100%)
rename {public => legacy/public}/images/movies/the-princess-bride.jpg (100%)
rename {public => legacy/public}/images/movies/transformers.jpg (100%)
rename {public => legacy/public}/images/pokemon-cards.jpg (100%)
rename {public => legacy/public}/images/pro-team.jpg (100%)
rename {public => legacy/public}/images/pro-workspace.jpg (100%)
rename {public => legacy/public}/images/proxies-example-1.svg (100%)
rename {public => legacy/public}/images/proxies-example-2.svg (100%)
rename {public => legacy/public}/images/proxies.jpg (100%)
rename {public => legacy/public}/images/proxies2.jpg (100%)
rename {public => legacy/public}/images/reading-nook.jpg (100%)
rename {public => legacy/public}/images/reading/brandon-sanderson-cosmere.jpg (100%)
rename {public => legacy/public}/images/reading/desolate-era.jpg (100%)
rename {public => legacy/public}/images/reading/douglas-preston-lincoln-child-pendergast.jpg (100%)
rename {public => legacy/public}/images/reading/emily-rodda-deltora-quest.jpg (100%)
rename {public => legacy/public}/images/reading/frank-herbert-dune.jpg (100%)
rename {public => legacy/public}/images/reading/j-r-r-tolkien-the-lord-of-the-rings.jpg (100%)
rename {public => legacy/public}/images/reading/omniscient-reader-s-viewpoint.jpg (100%)
rename {public => legacy/public}/images/reading/overgeared.jpg (100%)
rename {public => legacy/public}/images/reading/solo-leveling.jpg (100%)
rename {public => legacy/public}/images/reading/the-king-s-avatar.jpg (100%)
rename {public => legacy/public}/images/resin_rings.jpg (100%)
rename {public => legacy/public}/images/store/cards/PXL_20260806_204112875.jpg (100%)
rename {public => legacy/public}/images/store/cards/PXL_20260806_204116184.jpg (100%)
rename {public => legacy/public}/images/store/cards/PXL_20260806_204128368.jpg (100%)
rename {public => legacy/public}/images/store/cards/PXL_20260806_204129979.jpg (100%)
rename {public => legacy/public}/images/store/cards/disclosure.jpg (100%)
rename {public => legacy/public}/images/store/cards/foil_cloud_measured.jpg (100%)
rename {public => legacy/public}/images/store/cards/ghalta_duo.jpg (100%)
rename {public => legacy/public}/images/store/cards/proxies.jpg (100%)
rename {public => legacy/public}/images/store/cards/proxies2.jpg (100%)
rename {public => legacy/public}/images/store/cards/ravenous_duo.jpg (100%)
rename {public => legacy/public}/images/store/cards/sleeved.jpg (100%)
rename {public => legacy/public}/images/store/deckboxes/black_front.jpg (100%)
rename {public => legacy/public}/images/store/deckboxes/black_side.jpg (100%)
rename {public => legacy/public}/images/store/deckboxes/ff_back.jpg (100%)
rename {public => legacy/public}/images/store/deckboxes/ff_front.jpg (100%)
rename {public => legacy/public}/images/store/deckboxes/ff_open.jpg (100%)
rename {public => legacy/public}/images/store/deckboxes/ff_side.jpg (100%)
rename {public => legacy/public}/images/tea-gaiwan.jpg (100%)
rename {public => legacy/public}/images/ttrpg-boardgame-shelf.jpg (100%)
rename {public => legacy/public}/images/ttrpg-dice.jpg (100%)
rename {public => legacy/public}/images/ttrpg-mini.jpg (100%)
rename {public => legacy/public}/images/ttrpg-table - Copy.jpg (100%)
rename {public => legacy/public}/images/ttrpg-table.jpg (100%)
rename {public => legacy/public}/images/tv-hero.jpg (100%)
rename {public => legacy/public}/images/tv/30-rock.jpg (100%)
rename {public => legacy/public}/images/tv/arcane.jpg (100%)
rename {public => legacy/public}/images/tv/breaking-bad.jpg (100%)
rename {public => legacy/public}/images/tv/brooklyn-nine-nine.jpg (100%)
rename {public => legacy/public}/images/tv/castlevania.jpg (100%)
rename {public => legacy/public}/images/tv/cobra-kai.jpg (100%)
rename {public => legacy/public}/images/tv/daredevil.jpg (100%)
rename {public => legacy/public}/images/tv/game-of-thrones.jpg (100%)
rename {public => legacy/public}/images/tv/house.jpg (100%)
rename {public => legacy/public}/images/tv/invincible.jpg (100%)
rename {public => legacy/public}/images/tv/ozark.jpg (100%)
rename {public => legacy/public}/images/tv/parks-and-recreation.jpg (100%)
rename {public => legacy/public}/images/tv/peaky-blinders.jpg (100%)
rename {public => legacy/public}/images/tv/succession.jpg (100%)
rename {public => legacy/public}/images/tv/suits.jpg (100%)
rename {public => legacy/public}/images/tv/the-bear.jpg (100%)
rename {public => legacy/public}/images/tv/the-crown.jpg (100%)
rename {public => legacy/public}/images/tv/the-great-british-baking-show.jpg (100%)
rename {public => legacy/public}/images/tv/the-mandalorian.jpg (100%)
rename {public => legacy/public}/images/tv/the-queens-gambit.jpg (100%)
rename {public => legacy/public}/images/tv/the-sopranos.jpg (100%)
rename {public => legacy/public}/images/tv/the-wire.jpg (100%)
rename {public => legacy/public}/images/tv/wandavision.jpg (100%)
rename {public => legacy/public}/images/xmasgifts.jpg (100%)
rename {public => legacy/public}/motifs/astrolabe.svg (100%)
rename {public => legacy/public}/motifs/craft-seal-print-light.svg (100%)
rename {public => legacy/public}/motifs/kepler-nest-seal-light.svg (100%)
rename {public => legacy/public}/motifs/layered-seal.svg (100%)
rename {public => legacy/public}/motifs/squared-circle-seal-light.svg (100%)
rename {public => legacy/public}/motifs/transmutation-seal-light.svg (100%)
rename {public => legacy/public}/motifs/tria-prima-array-seal-light.svg (100%)
rename {public => legacy/public}/motifs/zodiac-seal-light.svg (100%)
rename {public => legacy/public}/robots.txt (100%)
rename {public => legacy/public}/site.webmanifest (100%)
rename {scripts => legacy/scripts}/fetch-anime-art.mjs (100%)
rename {scripts => legacy/scripts}/fetch-bgg-covers.mjs (100%)
rename {scripts => legacy/scripts}/fetch-book-covers.mjs (100%)
rename {scripts => legacy/scripts}/fetch-box-art.mjs (100%)
rename {scripts => legacy/scripts}/fetch-tmdb-art.mjs (100%)
rename {src => legacy/src}/components/Icon.astro (100%)
rename {src => legacy/src}/components/ObfuscatedEmail.astro (100%)
rename {src => legacy/src}/components/brand/BrandIcon.tsx (100%)
rename {src => legacy/src}/components/brand/Motif.astro (100%)
rename {src => legacy/src}/components/brand/PageSeal.astro (100%)
rename {src => legacy/src}/components/cards/DeckCard.astro (100%)
rename {src => legacy/src}/components/cards/GameCard.astro (100%)
rename {src => legacy/src}/components/cards/InterestCard.astro (100%)
rename {src => legacy/src}/components/cards/MediaCard.astro (100%)
rename {src => legacy/src}/components/hero/AnimatedBackground.tsx (100%)
rename {src => legacy/src}/components/layout/Footer.astro (100%)
rename {src => legacy/src}/components/layout/InterestHero.astro (100%)
rename {src => legacy/src}/components/layout/Placeholder.astro (100%)
rename {src => legacy/src}/components/layout/RelatedInterests.astro (100%)
rename {src => legacy/src}/components/layout/SectionHeader.astro (100%)
rename {src => legacy/src}/components/layout/SplitTransition.astro (100%)
rename {src => legacy/src}/components/media/CategoryList.astro (100%)
rename {src => legacy/src}/components/motion/Reveal.astro (100%)
rename {src => legacy/src}/components/mystic/AlchemicalDivider.astro (100%)
rename {src => legacy/src}/components/mystic/CelestialFrame.astro (100%)
rename {src => legacy/src}/components/mystic/CelestialImage.astro (100%)
rename {src => legacy/src}/components/mystic/CircuitField.tsx (100%)
rename {src => legacy/src}/components/mystic/ConstellationField.tsx (100%)
rename {src => legacy/src}/components/mystic/StarBurst.astro (100%)
rename {src => legacy/src}/components/mystic/StarField.astro (100%)
rename {src => legacy/src}/components/nav/MobileMenu.tsx (100%)
rename {src => legacy/src}/components/nav/NavBar.astro (100%)
rename {src => legacy/src}/components/nav/ThemeToggle.tsx (100%)
rename {src => legacy/src}/components/professional/Certifications.astro (100%)
rename {src => legacy/src}/components/professional/ContactForm.tsx (100%)
rename {src => legacy/src}/components/professional/EducationHistory.astro (100%)
rename {src => legacy/src}/components/professional/ProjectCard.astro (100%)
rename {src => legacy/src}/components/professional/SectionNav.astro (100%)
rename {src => legacy/src}/components/professional/SkillGrid.astro (100%)
rename {src => legacy/src}/components/professional/Timeline.astro (100%)
rename {src => legacy/src}/components/store/Carousel.tsx (100%)
rename {src => legacy/src}/components/store/ColorGrid.astro (100%)
rename {src => legacy/src}/components/store/OrderForm.tsx (100%)
rename {src => legacy/src}/components/store/PricingTable.astro (100%)
rename {src => legacy/src}/components/store/ProductCard.astro (100%)
rename {src => legacy/src}/components/store/ProductTile.astro (100%)
rename {src => legacy/src}/components/store/RequirementList.astro (100%)
rename {src => legacy/src}/components/store/SlotMeter.astro (100%)
rename {src => legacy/src}/components/store/StoreNav.astro (100%)
rename {src => legacy/src}/components/ttrpgs/BoardGameShelf.tsx (100%)
rename {src => legacy/src}/content.config.ts (100%)
rename {src => legacy/src}/content/experience/comcast_1.md (100%)
rename {src => legacy/src}/content/experience/drexel.md (100%)
rename {src => legacy/src}/content/experience/implico.md (100%)
rename {src => legacy/src}/content/games/hall-of-fame/age-of-empires.md (100%)
rename {src => legacy/src}/content/games/hall-of-fame/ark-survival-evolved.md (100%)
rename {src => legacy/src}/content/games/hall-of-fame/dave-the-diver.md (100%)
rename {src => legacy/src}/content/games/hall-of-fame/elden-ring.md (100%)
rename {src => legacy/src}/content/games/hall-of-fame/granblue-relink.md (100%)
rename {src => legacy/src}/content/games/hall-of-fame/kotor.md (100%)
rename {src => legacy/src}/content/games/hall-of-fame/minecraft.md (100%)
rename {src => legacy/src}/content/games/hall-of-fame/pokemon-crystal.md (100%)
rename {src => legacy/src}/content/games/hall-of-fame/satisfactory.md (100%)
rename {src => legacy/src}/content/games/hall-of-fame/sly-cooper.md (100%)
rename {src => legacy/src}/content/games/hall-of-fame/stardew-valley.md (100%)
rename {src => legacy/src}/content/games/now-playing.yaml (100%)
rename {src => legacy/src}/content/projects/carrier-connect.md (100%)
rename {src => legacy/src}/content/projects/cosmos-homelab.md (100%)
rename {src => legacy/src}/content/projects/driver-app.md (100%)
rename {src => legacy/src}/content/projects/placeholder-project.md (100%)
rename {src => legacy/src}/data/boardgames.json (100%)
rename {src => legacy/src}/data/media/anime.json (100%)
rename {src => legacy/src}/data/media/movies.json (100%)
rename {src => legacy/src}/data/media/tv.json (100%)
rename {src => legacy/src}/data/reading.json (100%)
rename {src => legacy/src}/data/store-availability.json (100%)
rename {src => legacy/src}/data/store-availability.ts (100%)
rename {src => legacy/src}/data/store-products.ts (100%)
rename {src => legacy/src}/env.d.ts (100%)
rename {src => legacy/src}/layouts/BaseLayout.astro (100%)
rename {src => legacy/src}/layouts/StoreLayout.astro (100%)
rename {src => legacy/src}/lib/archidekt.ts (100%)
rename {src => legacy/src}/lib/brand-icons.ts (100%)
rename {src => legacy/src}/lib/env.ts (100%)
rename {src => legacy/src}/lib/media.ts (100%)
rename {src => legacy/src}/lib/motion.ts (100%)
rename {src => legacy/src}/lib/platforms.ts (100%)
rename {src => legacy/src}/lib/site.ts (100%)
rename {src => legacy/src}/middleware.ts (100%)
rename {src => legacy/src}/pages/about.astro (100%)
rename {src => legacy/src}/pages/api/contact.ts (100%)
rename {src => legacy/src}/pages/api/order.ts (100%)
rename {src => legacy/src}/pages/contact.astro (100%)
rename {src => legacy/src}/pages/index.astro (100%)
rename {src => legacy/src}/pages/interests/anime.astro (100%)
rename {src => legacy/src}/pages/interests/gaming.astro (100%)
rename {src => legacy/src}/pages/interests/index.astro (100%)
rename {src => legacy/src}/pages/interests/making.astro (100%)
rename {src => legacy/src}/pages/interests/movies.astro (100%)
rename {src => legacy/src}/pages/interests/pokemon.astro (100%)
rename {src => legacy/src}/pages/interests/reading.astro (100%)
rename {src => legacy/src}/pages/interests/tcgs.astro (100%)
rename {src => legacy/src}/pages/interests/ttrpgs.astro (100%)
rename {src => legacy/src}/pages/interests/tv.astro (100%)
rename {src => legacy/src}/pages/professional/index.astro (100%)
rename {src => legacy/src}/pages/store/cards.astro (100%)
rename {src => legacy/src}/pages/store/deck-boxes.astro (100%)
rename {src => legacy/src}/pages/store/index.astro (100%)
rename {src => legacy/src}/pages/store/order.astro (100%)
rename {src => legacy/src}/scripts/reveal-init.ts (100%)
rename {src => legacy/src}/scripts/theme-init.ts (100%)
rename {src => legacy/src}/styles/animations.css (100%)
rename {src => legacy/src}/styles/global.css (100%)
rename {src => legacy/src}/styles/themes.css (100%)
rename tsconfig.json => legacy/tsconfig.json (100%)
create mode 100644 packages/schema/package.json
create mode 100644 packages/schema/scripts/emit-json-schema.ts
create mode 100644 packages/schema/scripts/emit-templates.ts
create mode 100644 packages/schema/src/fields.ts
create mode 100644 packages/schema/src/index.ts
create mode 100644 packages/schema/src/names.ts
create mode 100644 packages/schema/src/notes.ts
create mode 100644 packages/schema/src/templates.ts
create mode 100644 packages/schema/src/types.ts
create mode 100644 packages/schema/src/vault.ts
create mode 100644 packages/schema/test/schema.test.ts
create mode 100644 packages/schema/tsconfig.json
create mode 100644 tools/migrate/package.json
create mode 100644 tools/migrate/src/build.ts
create mode 100644 tools/migrate/src/cli.ts
create mode 100644 tools/migrate/src/seed.ts
create mode 100644 tools/migrate/test/migrate.test.ts
create mode 100644 tools/migrate/tsconfig.json
create mode 100644 tsconfig.base.json
create mode 100644 vault-kit/24.01_PersonalWebsite/Site guide.md
create mode 100644 vault-kit/24.01_PersonalWebsite/Site/_templates/Board game.md
create mode 100644 vault-kit/24.01_PersonalWebsite/Site/_templates/Campaign.md
create mode 100644 vault-kit/24.01_PersonalWebsite/Site/_templates/Card game.md
create mode 100644 vault-kit/24.01_PersonalWebsite/Site/_templates/Craft.md
create mode 100644 vault-kit/24.01_PersonalWebsite/Site/_templates/Deck.md
create mode 100644 vault-kit/24.01_PersonalWebsite/Site/_templates/Experience.md
create mode 100644 vault-kit/24.01_PersonalWebsite/Site/_templates/Library item.md
create mode 100644 vault-kit/24.01_PersonalWebsite/Site/_templates/Make.md
create mode 100644 vault-kit/24.01_PersonalWebsite/Site/_templates/Page text.md
create mode 100644 vault-kit/24.01_PersonalWebsite/Site/_templates/Post.md
create mode 100644 vault-kit/24.01_PersonalWebsite/Site/_templates/Product line.md
create mode 100644 vault-kit/24.01_PersonalWebsite/Site/_templates/Station.md
create mode 100644 vault-kit/24.01_PersonalWebsite/Site/_templates/Status.md
create mode 100644 vault-kit/24.01_PersonalWebsite/Site/_templates/Tabletop system.md
create mode 100644 vault-kit/24.01_PersonalWebsite/Site/_templates/Tool or material.md
create mode 100644 vitest.config.ts
diff --git a/.gitignore b/.gitignore
index 5c155e1..226df75 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,30 +1,21 @@
-# build output
-dist/
-.output/
-
# dependencies
node_modules/
-# generated types
-.astro/
+# migration output (it goes into the vault, not the repo)
+out/
+
+# generated from the schema
+packages/schema/json/
# environment
.env
.env.*
!.env.example
-# logs
+# logs and editor files
npm-debug.log*
-yarn-debug.log*
-yarn-error.log*
-pnpm-debug.log*
-
-# macOS / editor
.DS_Store
.vscode/*
!.vscode/extensions.json
!.vscode/settings.json
.idea/
-
-# misc
-*.local
diff --git a/README.md b/README.md
index 3cdaf10..bccf14d 100644
--- a/README.md
+++ b/README.md
@@ -1,156 +1,41 @@
-# Greg Kilmer — Profile & Portfolio Site
+# GregKilmer.com
-A responsive, content-driven personal site for Greg Kilmer, built as **two wings
-of the same house**: a polished professional resume/portfolio on one side, and a
-warm, personality-driven interests hub on the other.
+The site, rebuilt around Greg's Obsidian vault. Notes in the vault's Site
+folder are the content; a content service checks every change and keeps the
+last good version; the site serves pages from that service. The full plan,
+with the decisions behind it, is the
+[Functional Design Plan](https://claude.ai/code/artifact/2a4e8339-7887-4919-a51d-2c11547b80b3).
-Built with **Astro 7 + React Islands + TypeScript**, served by a **Node server**
-with API routes, ready to deploy as a container on a homelab Cosmos system.
+## What is here
----
+| Path | What it is |
+| --- | --- |
+| `packages/schema` | The one definition of every note type: fields, folders, file names, and the checks for one note and for the whole folder. The migration, the content service, the MCP server, the site and any later editing tool all use it. |
+| `vault-kit` | What goes into the vault: the Obsidian templates (generated from the schema) and the Site guide. |
+| `tools/migrate` | The one-time migration from the old site's data into Site folder notes. |
+| `docs` | How the pieces fit. Start with [docs/content-model.md](docs/content-model.md). |
+| `legacy` | The old site, kept for reference only. Nothing outside it imports from it except the migration. |
-## Highlights
+Coming in later phases: `services/content` (the content service and its MCP
+server), the admin page, and the new site.
-- **Astro 7** static-first core with React Islands for interactivity (mobile
- menu, theme toggle, animated hero background, contact form).
-- **Node backend** (`@astrojs/node`, standalone) with a typed `/api/contact`
- endpoint (SMTP via nodemailer + webhook fallback, Zod validation, rate limit).
-- **Section-aware theming**: the Professional wing reads dark/polished, the
- Interests wing reads warm/inviting, plus a global light/dark toggle.
-- **Rich motion**: animated mesh-gradient hero with an optional canvas particle
- field, IntersectionObserver scroll reveals, View Transitions page swaps,
- scroll-triggered nav blur. All `prefers-reduced-motion` aware.
-- **Content Collections** (typed): add projects and experience by dropping
- markdown/MDX files.
-- **mdi icons everywhere** (no emojis, per the spec), inlined at build time from
- `@iconify-json/mdi` for zero runtime cost.
-- **Self-hosted variable fonts** (Inter, Plus Jakarta Sans, JetBrains Mono).
-- **Accessible**: semantic landmarks, skip link, keyboard-operable menu,
- focus-visible rings, ARIA on interactive bits, WCAG-AA contrast targets.
-- **Deploy artifacts**: multi-stage `Dockerfile` + `docker-compose.yml`.
+## Commands
----
+Node 22 or newer.
-## Quickstart
-
-```bash
-# 1. Install dependencies (Node 20+)
+```sh
npm install
-
-# 2. Start the dev server with hot reload
-npm run dev
-# -> http://localhost:4321
-
-# 3. Production build (runs astro check + build)
-npm run build
-
-# 4. Run the built Node server
-npm run serve
-# -> http://localhost:4321
+npm run check # typecheck and tests
+npm run migrate # writes out/24.01_PersonalWebsite and out/MIGRATION-REPORT.md
+npm run templates # regenerates vault-kit templates after a schema change
+npm run schema:json # writes a JSON Schema per note type to packages/schema/json/
```
-Environment is optional for local dev. To enable contact-form delivery, copy
-`.env.example` to `.env` and fill in SMTP or webhook vars (see
-[docs/DEPLOYMENT.md](docs/DEPLOYMENT.md)).
+## The old site
----
-
-## Project structure
+The live site still runs from the old code until cutover. It builds the same
+way as before, from inside `legacy/`:
+```sh
+cd legacy && docker compose up -d --build
```
-src/
-├── components/
-│ ├── hero/ AnimatedBackground.tsx (gradient + canvas particles)
-│ ├── motion/ Reveal.astro (IntersectionObserver scroll reveals)
-│ ├── nav/ NavBar.astro, MobileMenu.tsx, ThemeToggle.tsx
-│ ├── cards/ InterestCard.astro
-│ ├── professional/ Timeline, SkillGrid, ProjectCard, SectionNav, ContactForm
-│ ├── layout/ Footer, SectionHeader, SplitTransition, InterestHero,
-│ │ RelatedInterests, Placeholder
-│ └── Icon.astro Inline mdi icon renderer (build-time)
-├── content/
-│ ├── projects/ Project collection entries (markdown/MDX)
-│ └── experience/ Experience collection entries
-├── layouts/
-│ └── BaseLayout.astro shell, head, fonts, theme init, nav, footer
-├── pages/
-│ ├── index.astro Home (hero + snapshot grid)
-│ ├── about.astro About + split transition fork
-│ ├── professional/index.astro Single long page: Summary, Experience,
-│ │ Skills, Projects, Education, Contact
-│ ├── interests/ Hub + 5 sub-pages
-│ │ ├── index.astro
-│ │ ├── ttrpgs / gaming / reading / making / pokemon .astro
-│ ├── contact.astro Two-column connect page + contact form
-│ └── api/contact.ts POST endpoint -> SMTP / webhook
-├── content.config.ts Collection schemas (projects, experience)
-├── lib/ site.ts (nav/socials), motion.ts, content helpers
-├── styles/ global.css, themes.css, animations.css
-└── scripts/theme-init.ts No-FOUC theme boot (inlined in )
-```
-
----
-
-## The two wings (theming model)
-
-Theming has two independent axes (see [docs/THEMING.md](docs/THEMING.md)):
-
-1. **Global light/dark** on ``, set before paint by an inline
- script, persisted to `localStorage`, toggleable via the nav button.
-2. **Section tone** via `data-surface` on ``:
- - `neutral` (Home/About/Contact) — balanced middle ground
- - `professional` — dark slate, polished
- - `interest` — warm cream/amber, inviting
-
-Components only reference semantic CSS variables (`--surface`, `--text`,
-`--accent`...), so changing either axis restyles everything automatically.
-
----
-
-## Adding content
-
-- **Projects / Experience:** drop a markdown or MDX file in
- `src/content/projects/` or `src/content/experience/`. Schemas and field
- reference are in [docs/CONTENT.md](docs/CONTENT.md).
-- **Interest pages:** hand-built `.astro` files under `src/pages/interests/`,
- each following the spec's template (Header, Overview, Highlights, Currently,
- Related). Edit copy in place.
-- **Placeholders:** every `[Greg to fill]` gap from the spec is a styled
- `Placeholder` slot with a TODO badge so you can find and replace them.
-
----
-
-## Deployment (Cosmos homelab)
-
-The site runs as a Node server inside a container. Full walkthrough in
-[docs/DEPLOYMENT.md](docs/DEPLOYMENT.md):
-
-```bash
-docker compose up -d --build
-# Then point your reverse proxy at port 4321 with TLS for gregkilmer.com.
-```
-
----
-
-## Scripts
-
-| Script | Description |
-| ----------------- | ----------------------------------------------------- |
-| `npm run dev` | Dev server with hot reload |
-| `npm run build` | Type-check (`astro check`) then build the Node server |
-| `npm run serve` | Run the built server (`dist/server/entry.mjs`) |
-| `npm run preview` | Preview the build locally |
-| `npm run check` | Type-check only |
-
----
-
-## Tech stack
-
-Astro 7, React 19, TypeScript 6, Tailwind CSS v4, Framer Motion, nodemailer,
-Zod, `@iconify-json/mdi`, Fontsource variable fonts, `@astrojs/node` standalone.
-
----
-
-## License
-
-Personal site for Greg Kilmer. All rights reserved.
diff --git a/docs/content-model.md b/docs/content-model.md
new file mode 100644
index 0000000..63edde7
--- /dev/null
+++ b/docs/content-model.md
@@ -0,0 +1,48 @@
+# Content model
+
+Everything the site shows is a Markdown note with YAML frontmatter in the
+vault at `20-29_Makerspace/24_Homelab_Infrastructure/24.01_PersonalWebsite/Site/`.
+`24.02_PatiunWebsite` is reserved for the Patiun site later.
+
+## Rules every note follows
+
+- A note's `type` field says what it is. The types, their fields and their
+ folders are defined once, in `packages/schema/src/types.ts`.
+- A note is live only when `publish: true`. Missing means false.
+- Links between notes are Obsidian wikilinks, `[[Note name]]`, in frontmatter
+ or in the body. A link resolves by file name, so file names are unique across
+ the whole Site folder.
+- Images live in `Site/_attachments/` and are linked the same way,
+ `[[galaxy-dice.jpg]]`, or are full https URLs.
+- Folders starting with `_` hold templates and attachments, never notes.
+ Files starting with `_` are single settings notes: `_site.md`,
+ `Now/_kinds.md`, `Store/_settings.md`.
+- Slugs come from the title unless a note sets `slug`. They are unique within
+ a type.
+
+## Note types
+
+| Type | Folder | Notes |
+| --- | --- | --- |
+| `post` | `Posts/` | Blog posts. `postType: case study` also appears on the Work page. |
+| `library-item` | `Library//` | Books, light novels, webnovels, manga, manhwa, video games, anime, TV, movies. |
+| `board-game` | `Shelf/` | The Shelf. Players and play time sync from BoardGameGeek by `bggId`. |
+| `craft`, `station`, `tool`, `make` | `Workshop/…` | The Workshop. |
+| `tabletop-system`, `campaign`, `card-game`, `deck` | `Tabletop/…` | Tabletop. Magic decks sync from Archidekt. |
+| `experience`, `skills`, `education` | `Resume/…` | The resume page and its PDF. |
+| `status`, `status-kinds` | `Now/` | The status log behind Now, the Landing strip and Now chips. |
+| `product-line`, `store-settings` | `Store/…` | The Store. |
+| `site-settings` | `_site.md` | Name, tagline, location and social links. |
+| `page` | `Pages/` | The written introduction for a page. |
+
+Each field's title and description live in the schema too, so the
+JSON Schema export (`npm run schema:json`) can drive a form or explain a field
+to an agent.
+
+## Designed for editing tools
+
+The content service will be the only writer apart from Obsidian. It will
+expose one write API, built on the schema's `checkNote`, `writeNote` and
+`checkVault`. The MCP server for agents is one client of that API; the admin
+page is another; any editing tool built later is a third. None of them define
+fields or rules of their own.
diff --git a/.dockerignore b/legacy/.dockerignore
similarity index 100%
rename from .dockerignore
rename to legacy/.dockerignore
diff --git a/.env.example b/legacy/.env.example
similarity index 100%
rename from .env.example
rename to legacy/.env.example
diff --git a/legacy/.gitignore b/legacy/.gitignore
new file mode 100644
index 0000000..5c155e1
--- /dev/null
+++ b/legacy/.gitignore
@@ -0,0 +1,30 @@
+# build output
+dist/
+.output/
+
+# dependencies
+node_modules/
+
+# generated types
+.astro/
+
+# environment
+.env
+.env.*
+!.env.example
+
+# logs
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+pnpm-debug.log*
+
+# macOS / editor
+.DS_Store
+.vscode/*
+!.vscode/extensions.json
+!.vscode/settings.json
+.idea/
+
+# misc
+*.local
diff --git a/.zcode/plans/plan-sess_cfbaea37-9611-497f-804c-b438e5e659b3.md b/legacy/.zcode/plans/plan-sess_cfbaea37-9611-497f-804c-b438e5e659b3.md
similarity index 100%
rename from .zcode/plans/plan-sess_cfbaea37-9611-497f-804c-b438e5e659b3.md
rename to legacy/.zcode/plans/plan-sess_cfbaea37-9611-497f-804c-b438e5e659b3.md
diff --git a/Dockerfile b/legacy/Dockerfile
similarity index 100%
rename from Dockerfile
rename to legacy/Dockerfile
diff --git a/legacy/LEGACY.md b/legacy/LEGACY.md
new file mode 100644
index 0000000..a002c22
--- /dev/null
+++ b/legacy/LEGACY.md
@@ -0,0 +1,7 @@
+# Legacy site
+
+The GregKilmer.com site as it was before the 2026 rebuild, kept for reference.
+It still runs the live site until cutover, built from this folder.
+
+Do not add features here. Its data was migrated into vault notes by
+`tools/migrate`; new content goes into the vault.
diff --git a/Profile_Website_Spec.md b/legacy/Profile_Website_Spec.md
similarity index 100%
rename from Profile_Website_Spec.md
rename to legacy/Profile_Website_Spec.md
diff --git a/legacy/README.md b/legacy/README.md
new file mode 100644
index 0000000..3cdaf10
--- /dev/null
+++ b/legacy/README.md
@@ -0,0 +1,156 @@
+# Greg Kilmer — Profile & Portfolio Site
+
+A responsive, content-driven personal site for Greg Kilmer, built as **two wings
+of the same house**: a polished professional resume/portfolio on one side, and a
+warm, personality-driven interests hub on the other.
+
+Built with **Astro 7 + React Islands + TypeScript**, served by a **Node server**
+with API routes, ready to deploy as a container on a homelab Cosmos system.
+
+---
+
+## Highlights
+
+- **Astro 7** static-first core with React Islands for interactivity (mobile
+ menu, theme toggle, animated hero background, contact form).
+- **Node backend** (`@astrojs/node`, standalone) with a typed `/api/contact`
+ endpoint (SMTP via nodemailer + webhook fallback, Zod validation, rate limit).
+- **Section-aware theming**: the Professional wing reads dark/polished, the
+ Interests wing reads warm/inviting, plus a global light/dark toggle.
+- **Rich motion**: animated mesh-gradient hero with an optional canvas particle
+ field, IntersectionObserver scroll reveals, View Transitions page swaps,
+ scroll-triggered nav blur. All `prefers-reduced-motion` aware.
+- **Content Collections** (typed): add projects and experience by dropping
+ markdown/MDX files.
+- **mdi icons everywhere** (no emojis, per the spec), inlined at build time from
+ `@iconify-json/mdi` for zero runtime cost.
+- **Self-hosted variable fonts** (Inter, Plus Jakarta Sans, JetBrains Mono).
+- **Accessible**: semantic landmarks, skip link, keyboard-operable menu,
+ focus-visible rings, ARIA on interactive bits, WCAG-AA contrast targets.
+- **Deploy artifacts**: multi-stage `Dockerfile` + `docker-compose.yml`.
+
+---
+
+## Quickstart
+
+```bash
+# 1. Install dependencies (Node 20+)
+npm install
+
+# 2. Start the dev server with hot reload
+npm run dev
+# -> http://localhost:4321
+
+# 3. Production build (runs astro check + build)
+npm run build
+
+# 4. Run the built Node server
+npm run serve
+# -> http://localhost:4321
+```
+
+Environment is optional for local dev. To enable contact-form delivery, copy
+`.env.example` to `.env` and fill in SMTP or webhook vars (see
+[docs/DEPLOYMENT.md](docs/DEPLOYMENT.md)).
+
+---
+
+## Project structure
+
+```
+src/
+├── components/
+│ ├── hero/ AnimatedBackground.tsx (gradient + canvas particles)
+│ ├── motion/ Reveal.astro (IntersectionObserver scroll reveals)
+│ ├── nav/ NavBar.astro, MobileMenu.tsx, ThemeToggle.tsx
+│ ├── cards/ InterestCard.astro
+│ ├── professional/ Timeline, SkillGrid, ProjectCard, SectionNav, ContactForm
+│ ├── layout/ Footer, SectionHeader, SplitTransition, InterestHero,
+│ │ RelatedInterests, Placeholder
+│ └── Icon.astro Inline mdi icon renderer (build-time)
+├── content/
+│ ├── projects/ Project collection entries (markdown/MDX)
+│ └── experience/ Experience collection entries
+├── layouts/
+│ └── BaseLayout.astro shell, head, fonts, theme init, nav, footer
+├── pages/
+│ ├── index.astro Home (hero + snapshot grid)
+│ ├── about.astro About + split transition fork
+│ ├── professional/index.astro Single long page: Summary, Experience,
+│ │ Skills, Projects, Education, Contact
+│ ├── interests/ Hub + 5 sub-pages
+│ │ ├── index.astro
+│ │ ├── ttrpgs / gaming / reading / making / pokemon .astro
+│ ├── contact.astro Two-column connect page + contact form
+│ └── api/contact.ts POST endpoint -> SMTP / webhook
+├── content.config.ts Collection schemas (projects, experience)
+├── lib/ site.ts (nav/socials), motion.ts, content helpers
+├── styles/ global.css, themes.css, animations.css
+└── scripts/theme-init.ts No-FOUC theme boot (inlined in )
+```
+
+---
+
+## The two wings (theming model)
+
+Theming has two independent axes (see [docs/THEMING.md](docs/THEMING.md)):
+
+1. **Global light/dark** on ``, set before paint by an inline
+ script, persisted to `localStorage`, toggleable via the nav button.
+2. **Section tone** via `data-surface` on ``:
+ - `neutral` (Home/About/Contact) — balanced middle ground
+ - `professional` — dark slate, polished
+ - `interest` — warm cream/amber, inviting
+
+Components only reference semantic CSS variables (`--surface`, `--text`,
+`--accent`...), so changing either axis restyles everything automatically.
+
+---
+
+## Adding content
+
+- **Projects / Experience:** drop a markdown or MDX file in
+ `src/content/projects/` or `src/content/experience/`. Schemas and field
+ reference are in [docs/CONTENT.md](docs/CONTENT.md).
+- **Interest pages:** hand-built `.astro` files under `src/pages/interests/`,
+ each following the spec's template (Header, Overview, Highlights, Currently,
+ Related). Edit copy in place.
+- **Placeholders:** every `[Greg to fill]` gap from the spec is a styled
+ `Placeholder` slot with a TODO badge so you can find and replace them.
+
+---
+
+## Deployment (Cosmos homelab)
+
+The site runs as a Node server inside a container. Full walkthrough in
+[docs/DEPLOYMENT.md](docs/DEPLOYMENT.md):
+
+```bash
+docker compose up -d --build
+# Then point your reverse proxy at port 4321 with TLS for gregkilmer.com.
+```
+
+---
+
+## Scripts
+
+| Script | Description |
+| ----------------- | ----------------------------------------------------- |
+| `npm run dev` | Dev server with hot reload |
+| `npm run build` | Type-check (`astro check`) then build the Node server |
+| `npm run serve` | Run the built server (`dist/server/entry.mjs`) |
+| `npm run preview` | Preview the build locally |
+| `npm run check` | Type-check only |
+
+---
+
+## Tech stack
+
+Astro 7, React 19, TypeScript 6, Tailwind CSS v4, Framer Motion, nodemailer,
+Zod, `@iconify-json/mdi`, Fontsource variable fonts, `@astrojs/node` standalone.
+
+---
+
+## License
+
+Personal site for Greg Kilmer. All rights reserved.
diff --git a/astro.config.mjs b/legacy/astro.config.mjs
similarity index 100%
rename from astro.config.mjs
rename to legacy/astro.config.mjs
diff --git a/docker-compose.yml b/legacy/docker-compose.yml
similarity index 100%
rename from docker-compose.yml
rename to legacy/docker-compose.yml
diff --git a/docs/CONTENT.md b/legacy/docs/CONTENT.md
similarity index 100%
rename from docs/CONTENT.md
rename to legacy/docs/CONTENT.md
diff --git a/docs/DEPLOYMENT.md b/legacy/docs/DEPLOYMENT.md
similarity index 100%
rename from docs/DEPLOYMENT.md
rename to legacy/docs/DEPLOYMENT.md
diff --git a/docs/PHOTOS.md b/legacy/docs/PHOTOS.md
similarity index 100%
rename from docs/PHOTOS.md
rename to legacy/docs/PHOTOS.md
diff --git a/docs/STORE.md b/legacy/docs/STORE.md
similarity index 100%
rename from docs/STORE.md
rename to legacy/docs/STORE.md
diff --git a/docs/THEMING.md b/legacy/docs/THEMING.md
similarity index 100%
rename from docs/THEMING.md
rename to legacy/docs/THEMING.md
diff --git a/legacy/package-lock.json b/legacy/package-lock.json
new file mode 100644
index 0000000..11d5bf2
--- /dev/null
+++ b/legacy/package-lock.json
@@ -0,0 +1,8470 @@
+{
+ "name": "greg-kilmer-profile",
+ "version": "1.0.0",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "greg-kilmer-profile",
+ "version": "1.0.0",
+ "dependencies": {
+ "@astrojs/check": "^0.9.9",
+ "@astrojs/mdx": "^7.0.2",
+ "@astrojs/node": "^11.0.2",
+ "@astrojs/react": "^6.0.2",
+ "@fontsource-variable/cinzel": "^5.3.0",
+ "@fontsource-variable/jetbrains-mono": "^5.2.8",
+ "@fontsource-variable/montserrat": "^5.3.0",
+ "@iconify-json/mdi": "^1.2.3",
+ "@tailwindcss/vite": "^4.3.2",
+ "@types/nodemailer": "^8.0.1",
+ "@types/react": "^19.2.17",
+ "@types/react-dom": "^19.2.3",
+ "astro": "^7.2.0",
+ "framer-motion": "^12.42.2",
+ "nodemailer": "^9.0.3",
+ "react": "^19.2.7",
+ "react-dom": "^19.2.7",
+ "tailwindcss": "^4.3.2",
+ "typescript": "^6.0.3",
+ "zod": "^4.4.3"
+ }
+ },
+ "node_modules/@astrojs/check": {
+ "version": "0.9.10",
+ "resolved": "https://registry.npmjs.org/@astrojs/check/-/check-0.9.10.tgz",
+ "integrity": "sha512-zgx/UQMozdjOa3bOxjgeCFdtpE3c9rRX6xHwa+2QXvy8z8Akifu2AtubHyv/zzC2znO8dl8fFWL4K+Ba9kS8HQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@astrojs/language-server": "^2.16.7",
+ "chokidar": "^4.0.3",
+ "kleur": "^4.1.5",
+ "yargs": "^18.0.0"
+ },
+ "bin": {
+ "astro-check": "bin/astro-check.js"
+ },
+ "peerDependencies": {
+ "typescript": "^5.0.0 || ^6.0.0"
+ }
+ },
+ "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/compiler-binding": {
+ "version": "0.3.2",
+ "resolved": "https://registry.npmjs.org/@astrojs/compiler-binding/-/compiler-binding-0.3.2.tgz",
+ "integrity": "sha512-8w/9CWmYrAJJ8N0SY3O43ws2BgxoW6u3QsD8u2mE140lMYAlwh+tlNoUeSBq22wVheFuiBbR212l6ixZ2IIgCQ==",
+ "license": "MIT",
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ },
+ "optionalDependencies": {
+ "@astrojs/compiler-binding-darwin-arm64": "0.3.2",
+ "@astrojs/compiler-binding-darwin-x64": "0.3.2",
+ "@astrojs/compiler-binding-linux-arm64-gnu": "0.3.2",
+ "@astrojs/compiler-binding-linux-arm64-musl": "0.3.2",
+ "@astrojs/compiler-binding-linux-x64-gnu": "0.3.2",
+ "@astrojs/compiler-binding-linux-x64-musl": "0.3.2",
+ "@astrojs/compiler-binding-wasm32-wasi": "0.3.2",
+ "@astrojs/compiler-binding-win32-arm64-msvc": "0.3.2",
+ "@astrojs/compiler-binding-win32-x64-msvc": "0.3.2"
+ }
+ },
+ "node_modules/@astrojs/compiler-binding-darwin-arm64": {
+ "version": "0.3.2",
+ "resolved": "https://registry.npmjs.org/@astrojs/compiler-binding-darwin-arm64/-/compiler-binding-darwin-arm64-0.3.2.tgz",
+ "integrity": "sha512-MM8tn8CSimcfytaOla4b6acN8mKWiL/rlAA1fpT3/Wl7dNGSE4y8FjTN/zJVNnb63CsLWG5zZwCt01TXtDKh9g==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@astrojs/compiler-binding-darwin-x64": {
+ "version": "0.3.2",
+ "resolved": "https://registry.npmjs.org/@astrojs/compiler-binding-darwin-x64/-/compiler-binding-darwin-x64-0.3.2.tgz",
+ "integrity": "sha512-2lXOlzf8xb7jLomRsf/aswh61/NnGusynB2OwFkK6k4pmOtpfXMYnG0PLfXrEvxXYj69NdCnmUYXtHDd+JOOag==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@astrojs/compiler-binding-linux-arm64-gnu": {
+ "version": "0.3.2",
+ "resolved": "https://registry.npmjs.org/@astrojs/compiler-binding-linux-arm64-gnu/-/compiler-binding-linux-arm64-gnu-0.3.2.tgz",
+ "integrity": "sha512-BmU3kWj7qnLrd4vzm49zFEPJ5oFnn1tCT4Vt9hZbqdU5Cmb8GZl7fn6VFsnNfe7B18a2gIFtVzbLINtYl5kBjQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@astrojs/compiler-binding-linux-arm64-musl": {
+ "version": "0.3.2",
+ "resolved": "https://registry.npmjs.org/@astrojs/compiler-binding-linux-arm64-musl/-/compiler-binding-linux-arm64-musl-0.3.2.tgz",
+ "integrity": "sha512-f0heT9ZZEseSu5bHCeb80eL2DH07ArE6U9xi1WT/PEusNjzPmEr3GJsjG1tRLo5VYUUYX7h3ScaqGmGrMOVGmw==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@astrojs/compiler-binding-linux-x64-gnu": {
+ "version": "0.3.2",
+ "resolved": "https://registry.npmjs.org/@astrojs/compiler-binding-linux-x64-gnu/-/compiler-binding-linux-x64-gnu-0.3.2.tgz",
+ "integrity": "sha512-M8fOUt0itRpqiGyoEA/ij184s8O+hqbCz3+YozRusOOM3osgGljpDThhbKAJjqh82wOo6FioQ4w8PBvU1XMD5Q==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@astrojs/compiler-binding-linux-x64-musl": {
+ "version": "0.3.2",
+ "resolved": "https://registry.npmjs.org/@astrojs/compiler-binding-linux-x64-musl/-/compiler-binding-linux-x64-musl-0.3.2.tgz",
+ "integrity": "sha512-/Kebk8sO6HnLeSd691JkaAPfN7CqR9/KEXmWvyNPkaKNGmj8rTZ/lf2uXtnPu92Lan84UrKdIKVPy1fSo2encQ==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@astrojs/compiler-binding-wasm32-wasi": {
+ "version": "0.3.2",
+ "resolved": "https://registry.npmjs.org/@astrojs/compiler-binding-wasm32-wasi/-/compiler-binding-wasm32-wasi-0.3.2.tgz",
+ "integrity": "sha512-pUA6xbcOSB7DhfzIArB8BCAkFfAIqriiR7zl5zOStd6oU2G0kIKj+GUdGnyXyhfiv881Hffyk5tC0mR18sDjDw==",
+ "cpu": [
+ "wasm32"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "@napi-rs/wasm-runtime": "^1.2.0"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@astrojs/compiler-binding-win32-arm64-msvc": {
+ "version": "0.3.2",
+ "resolved": "https://registry.npmjs.org/@astrojs/compiler-binding-win32-arm64-msvc/-/compiler-binding-win32-arm64-msvc-0.3.2.tgz",
+ "integrity": "sha512-ESruf+6Qkl1trHUFxI6GSf6t52j8yN2kCNSzMWdzt7V/T09tFHrYzrVaJQohb2C9bJUH76pNvX6Zb51+xCQc9Q==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@astrojs/compiler-binding-win32-x64-msvc": {
+ "version": "0.3.2",
+ "resolved": "https://registry.npmjs.org/@astrojs/compiler-binding-win32-x64-msvc/-/compiler-binding-win32-x64-msvc-0.3.2.tgz",
+ "integrity": "sha512-wzzVrEbOwbsLWOdEbocskjMRx2aZPxJ7ZbmL+jnpBamFwmigm+2M/wzuM6JWncocgYwLic1csSpalBh96kQKXA==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@astrojs/compiler-rs": {
+ "version": "0.3.2",
+ "resolved": "https://registry.npmjs.org/@astrojs/compiler-rs/-/compiler-rs-0.3.2.tgz",
+ "integrity": "sha512-xlx/T7JovIKduu4ucbTQUxQ5+Q8wxkHxhLjnZk3VlJbhbQ9RLvvuDk1p2YYFYFQ5y14dVm3FGGO4isQXa4F+Tg==",
+ "license": "MIT",
+ "dependencies": {
+ "@astrojs/compiler-binding": "0.3.2"
+ },
+ "engines": {
+ "node": ">=22.12.0"
+ }
+ },
+ "node_modules/@astrojs/internal-helpers": {
+ "version": "0.10.2",
+ "resolved": "https://registry.npmjs.org/@astrojs/internal-helpers/-/internal-helpers-0.10.2.tgz",
+ "integrity": "sha512-yt7fMgPYqSM4Tmr+taTW6Per+hjJ8Pk6lA1PAcDyqzOt8HzJ6Kje5WzCxA2Sd+9wsUW7uhkLeoTMK0cXPwH9rQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/hast": "^3.0.4",
+ "@types/mdast": "^4.0.4",
+ "js-yaml": "^4.3.0",
+ "picomatch": "^4.0.4",
+ "retext-smartypants": "^6.2.0",
+ "shiki": "^4.0.2",
+ "smol-toml": "^1.6.0",
+ "unified": "^11.0.5"
+ }
+ },
+ "node_modules/@astrojs/language-server": {
+ "version": "2.16.13",
+ "resolved": "https://registry.npmjs.org/@astrojs/language-server/-/language-server-2.16.13.tgz",
+ "integrity": "sha512-ekOa+CYprEq5n4EJC1qTIAhLk49HZIUQuFwrEuF+3JK/pdMaYnWoREFUI2A0KEPOJiFA2kamBzKzbYljDvUxLg==",
+ "license": "MIT",
+ "dependencies": {
+ "@astrojs/compiler": "^2.13.1",
+ "@astrojs/yaml2ts": "^0.2.4",
+ "@jridgewell/sourcemap-codec": "^1.5.5",
+ "@volar/kit": "~2.4.28",
+ "@volar/language-core": "~2.4.28",
+ "@volar/language-server": "~2.4.28",
+ "@volar/language-service": "~2.4.28",
+ "muggle-string": "^0.4.1",
+ "tinyglobby": "^0.2.16",
+ "volar-service-css": "0.0.71",
+ "volar-service-emmet": "0.0.71",
+ "volar-service-html": "0.0.71",
+ "volar-service-prettier": "0.0.71",
+ "volar-service-typescript": "0.0.71",
+ "volar-service-typescript-twoslash-queries": "0.0.71",
+ "volar-service-yaml": "0.0.71",
+ "vscode-html-languageservice": "^5.6.2",
+ "vscode-uri": "^3.1.0"
+ },
+ "bin": {
+ "astro-ls": "bin/nodeServer.js"
+ },
+ "peerDependencies": {
+ "prettier": "^3.0.0",
+ "prettier-plugin-astro": ">=0.11.0"
+ },
+ "peerDependenciesMeta": {
+ "prettier": {
+ "optional": true
+ },
+ "prettier-plugin-astro": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@astrojs/markdown-remark": {
+ "version": "7.2.2",
+ "resolved": "https://registry.npmjs.org/@astrojs/markdown-remark/-/markdown-remark-7.2.2.tgz",
+ "integrity": "sha512-FGfmK84zSNcrsBd0dl1gXE9JvZYElp8EXQa2jpHVAxG4deGKAp43wspxFupjADJX7MSsMRHwYCnfT6EyVmgeFQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@astrojs/internal-helpers": "0.10.2",
+ "@astrojs/prism": "4.0.2",
+ "github-slugger": "^2.0.0",
+ "hast-util-from-html": "^2.0.3",
+ "hast-util-to-text": "^4.0.2",
+ "mdast-util-definitions": "^6.0.0",
+ "rehype-raw": "^7.0.0",
+ "rehype-stringify": "^10.0.1",
+ "remark-gfm": "^4.0.1",
+ "remark-parse": "^11.0.0",
+ "remark-rehype": "^11.1.2",
+ "remark-smartypants": "^3.0.2",
+ "unified": "^11.0.5",
+ "unist-util-remove-position": "^5.0.0",
+ "unist-util-visit": "^5.1.0",
+ "unist-util-visit-parents": "^6.0.2",
+ "vfile": "^6.0.3"
+ }
+ },
+ "node_modules/@astrojs/markdown-satteri": {
+ "version": "0.3.5",
+ "resolved": "https://registry.npmjs.org/@astrojs/markdown-satteri/-/markdown-satteri-0.3.5.tgz",
+ "integrity": "sha512-CvWVEFAbay7YO+i9SaqDJubipA5ckiVB89QWoMJ5XC0m5CtFg8JwZ7Kau6X9sYY7FZURH0w2l03ISH2jOS/RDQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@astrojs/internal-helpers": "0.10.2",
+ "@astrojs/prism": "4.0.2",
+ "github-slugger": "^2.0.0",
+ "hast-util-from-html": "^2.0.3",
+ "satteri": "^0.9.1"
+ }
+ },
+ "node_modules/@astrojs/mdx": {
+ "version": "7.0.5",
+ "resolved": "https://registry.npmjs.org/@astrojs/mdx/-/mdx-7.0.5.tgz",
+ "integrity": "sha512-wEM/HH1RiEntyPVagdiF+yArzfcYLKBB0C1RZspVidKZ97rRMbaqP1Nbl/GR0sJs8zwaceqxRymw8aOKKJRdYw==",
+ "license": "MIT",
+ "dependencies": {
+ "@astrojs/internal-helpers": "0.10.2",
+ "@astrojs/markdown-remark": "7.2.2",
+ "@mdx-js/mdx": "^3.1.1",
+ "acorn": "^8.16.0",
+ "es-module-lexer": "^2.0.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.1.0",
+ "vfile": "^6.0.3"
+ },
+ "engines": {
+ "node": ">=22.12.0"
+ },
+ "peerDependencies": {
+ "@astrojs/markdown-satteri": "^0.3.1",
+ "astro": "^7.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@astrojs/markdown-satteri": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@astrojs/node": {
+ "version": "11.1.0",
+ "resolved": "https://registry.npmjs.org/@astrojs/node/-/node-11.1.0.tgz",
+ "integrity": "sha512-utAqBE1eIcAhyIkBUnNIWrzj4vyTiOlbbqcjUSE2Yz5XrMu+9sQdBwp/UmZXfJ9wjaEIHf26HInNLUUmTOFeEw==",
+ "license": "MIT",
+ "dependencies": {
+ "@astrojs/internal-helpers": "0.10.2",
+ "send": "^1.2.1",
+ "server-destroy": "^1.0.1"
+ },
+ "peerDependencies": {
+ "astro": "^7.0.0"
+ }
+ },
+ "node_modules/@astrojs/prism": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/@astrojs/prism/-/prism-4.0.2.tgz",
+ "integrity": "sha512-KTivpmnz6lDsC6o9H4+DNm2SrE/GHzw8cNAvEJwAvUT+eoaEnn/4NtbDNfRRaxaJHdp15gf+tfHAWiXR4wB3BA==",
+ "license": "MIT",
+ "dependencies": {
+ "prismjs": "^1.30.0"
+ },
+ "engines": {
+ "node": ">=22.12.0"
+ }
+ },
+ "node_modules/@astrojs/react": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/@astrojs/react/-/react-6.0.2.tgz",
+ "integrity": "sha512-Fvv2UqS7ajFL2Yjd5s4s5PcCBCSSfks4r04rbCHZO5z4pUPwCygV2ketI+iC+1P/sdHANLV3tgDL1wjzty4cJg==",
+ "license": "MIT",
+ "dependencies": {
+ "@astrojs/internal-helpers": "0.10.2",
+ "@vitejs/plugin-react": "^5.2.0",
+ "devalue": "^5.8.1",
+ "ultrahtml": "^1.6.0",
+ "vite": "^8.0.13"
+ },
+ "engines": {
+ "node": ">=22.12.0"
+ },
+ "peerDependencies": {
+ "@types/react": "^17.0.50 || ^18.0.21 || ^19.0.0",
+ "@types/react-dom": "^17.0.17 || ^18.0.6 || ^19.0.0",
+ "react": "^17.0.2 || ^18.0.0 || ^19.0.0",
+ "react-dom": "^17.0.2 || ^18.0.0 || ^19.0.0"
+ }
+ },
+ "node_modules/@astrojs/telemetry": {
+ "version": "3.3.3",
+ "resolved": "https://registry.npmjs.org/@astrojs/telemetry/-/telemetry-3.3.3.tgz",
+ "integrity": "sha512-C1TLn5sPJr0x4vk56piHWKbnqlEB8BKyte5Y45V02U+D7BGO5eMqZDH5aPjnkXQWJggvmsTXxH03QMZ9NgWLzQ==",
+ "license": "MIT",
+ "dependencies": {
+ "ci-info": "^4.4.0",
+ "dset": "^3.1.4",
+ "is-docker": "^4.0.0",
+ "package-manager-detector": "^1.6.0"
+ },
+ "engines": {
+ "node": "18.20.8 || ^20.3.0 || >=22.0.0"
+ }
+ },
+ "node_modules/@astrojs/yaml2ts": {
+ "version": "0.2.4",
+ "resolved": "https://registry.npmjs.org/@astrojs/yaml2ts/-/yaml2ts-0.2.4.tgz",
+ "integrity": "sha512-8oddpOae35pJsXPQXhTkM0ypfKPskVsh2bCxRtbf7e+/Epw2nReakFYpLKjZMEr75CsoF203PMnCocpfz0s69A==",
+ "license": "MIT",
+ "dependencies": {
+ "yaml": "^2.8.3"
+ }
+ },
+ "node_modules/@babel/code-frame": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz",
+ "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-validator-identifier": "^7.29.7",
+ "js-tokens": "^4.0.0",
+ "picocolors": "^1.1.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/compat-data": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.7.tgz",
+ "integrity": "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/core": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.7.tgz",
+ "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.29.7",
+ "@babel/generator": "^7.29.7",
+ "@babel/helper-compilation-targets": "^7.29.7",
+ "@babel/helper-module-transforms": "^7.29.7",
+ "@babel/helpers": "^7.29.7",
+ "@babel/parser": "^7.29.7",
+ "@babel/template": "^7.29.7",
+ "@babel/traverse": "^7.29.7",
+ "@babel/types": "^7.29.7",
+ "@jridgewell/remapping": "^2.3.5",
+ "convert-source-map": "^2.0.0",
+ "debug": "^4.1.0",
+ "gensync": "^1.0.0-beta.2",
+ "json5": "^2.2.3",
+ "semver": "^6.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/babel"
+ }
+ },
+ "node_modules/@babel/generator": {
+ "version": "7.29.8",
+ "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.8.tgz",
+ "integrity": "sha512-gZbepsdh3WDtgZKWL+vTPh71LSBrm/Y4/QDZBVCcYfmeTEEuoOYwlSy+G1StfJg+/Zy550u/3TATbm7qDbbMtg==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.29.8",
+ "@babel/types": "^7.29.8",
+ "@jridgewell/gen-mapping": "^0.3.12",
+ "@jridgewell/trace-mapping": "^0.3.28",
+ "jsesc": "^3.0.2"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-compilation-targets": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz",
+ "integrity": "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/compat-data": "^7.29.7",
+ "@babel/helper-validator-option": "^7.29.7",
+ "browserslist": "^4.24.0",
+ "lru-cache": "^5.1.1",
+ "semver": "^6.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-globals": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz",
+ "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-module-imports": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz",
+ "integrity": "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/traverse": "^7.29.7",
+ "@babel/types": "^7.29.7"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-module-transforms": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz",
+ "integrity": "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-module-imports": "^7.29.7",
+ "@babel/helper-validator-identifier": "^7.29.7",
+ "@babel/traverse": "^7.29.7"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0"
+ }
+ },
+ "node_modules/@babel/helper-plugin-utils": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.29.7.tgz",
+ "integrity": "sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-string-parser": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz",
+ "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-validator-identifier": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz",
+ "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-validator-option": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz",
+ "integrity": "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helpers": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.7.tgz",
+ "integrity": "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/template": "^7.29.7",
+ "@babel/types": "^7.29.7"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/parser": {
+ "version": "7.29.8",
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.8.tgz",
+ "integrity": "sha512-E8lTAYNB1KW+FH+VGJuZM1ioAx2E6oVlvQFRrf5P8ZZmsiJXYAD9vTFV7yyEURNzgh1dFqMZuO6tUwcARbqFCA==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.29.8"
+ },
+ "bin": {
+ "parser": "bin/babel-parser.js"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-react-jsx-self": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.29.7.tgz",
+ "integrity": "sha512-TL0hMc9xzy86VD31nUiwzd5otRAcyEPcsegCxolO0PvcXuH1v0kECe/UIznYFihpkvU5wg/jk4v0TTEFfm53fw==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.29.7"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-react-jsx-source": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.29.7.tgz",
+ "integrity": "sha512-06IyK09H3wi4cGbhDBwp5gUGo0IKtnYa8tyTiephirPCK6fbobVGiXMMI5zLQ4aKEYP3wZ3ArU44o+8KMrSG/Q==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.29.7"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/template": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz",
+ "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.29.7",
+ "@babel/parser": "^7.29.7",
+ "@babel/types": "^7.29.7"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/traverse": {
+ "version": "7.29.8",
+ "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.8.tgz",
+ "integrity": "sha512-I5z7H3bf/41ktsNVLtpN0wAa336HkqIHQ5BuPLEhTkt1jVSyZpeNKIzTgEWmlxjdg81R0IgUCcaE+Ok3NvrfZg==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.29.7",
+ "@babel/generator": "^7.29.8",
+ "@babel/helper-globals": "^7.29.7",
+ "@babel/parser": "^7.29.8",
+ "@babel/template": "^7.29.7",
+ "@babel/types": "^7.29.8",
+ "debug": "^4.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/types": {
+ "version": "7.29.8",
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.8.tgz",
+ "integrity": "sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-string-parser": "^7.29.7",
+ "@babel/helper-validator-identifier": "^7.29.7"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@bruits/satteri-darwin-arm64": {
+ "version": "0.9.5",
+ "resolved": "https://registry.npmjs.org/@bruits/satteri-darwin-arm64/-/satteri-darwin-arm64-0.9.5.tgz",
+ "integrity": "sha512-iw4nZgx9v30lWo/MTngQqi1pI78KI0DnkSm+lVJGYdmPLgAyDNJigVhpG42/Iq55A6c1Ll8q66ljyyRiQUxwow==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@bruits/satteri-darwin-x64": {
+ "version": "0.9.5",
+ "resolved": "https://registry.npmjs.org/@bruits/satteri-darwin-x64/-/satteri-darwin-x64-0.9.5.tgz",
+ "integrity": "sha512-6T26Z5Kf3cFW2PSlk9p7zT7yVxvuBSiJvYyz9u8KjYwMTqZyIDOj2wDyNpxKV4+6yUVG7rddq2QwvG/8LJA2+Q==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@bruits/satteri-linux-arm64-gnu": {
+ "version": "0.9.5",
+ "resolved": "https://registry.npmjs.org/@bruits/satteri-linux-arm64-gnu/-/satteri-linux-arm64-gnu-0.9.5.tgz",
+ "integrity": "sha512-u51id17uJwNEMK9nBlICsq6U31c+XVqQueVBkwRIzZG+gMpS8TOJctt5h5Wz33Z8xnMdTd+adtACVz0yHgGuOA==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@bruits/satteri-linux-arm64-musl": {
+ "version": "0.9.5",
+ "resolved": "https://registry.npmjs.org/@bruits/satteri-linux-arm64-musl/-/satteri-linux-arm64-musl-0.9.5.tgz",
+ "integrity": "sha512-v39HxiwGC5Rqm01HksP6+5Y+xKLPlsuVFgIgpEAo+SiQ22c+mJVhS3u7Z6ePAKdhL5NJoK1xq70kLz3L13AhpQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@bruits/satteri-linux-x64-gnu": {
+ "version": "0.9.5",
+ "resolved": "https://registry.npmjs.org/@bruits/satteri-linux-x64-gnu/-/satteri-linux-x64-gnu-0.9.5.tgz",
+ "integrity": "sha512-F3uO8uFp3pAP5ZGXttwvh57GS7s0lL953tnNdyI2gRyP4kOOkp6pyGojNJzCjkDvWI2Cvb9iNrKok3aqQPauAw==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@bruits/satteri-linux-x64-musl": {
+ "version": "0.9.5",
+ "resolved": "https://registry.npmjs.org/@bruits/satteri-linux-x64-musl/-/satteri-linux-x64-musl-0.9.5.tgz",
+ "integrity": "sha512-bicEqglLlz++mWyADaZoP0JY20s4vDfLjaPYgQqC+NI4zZLTOOg1T4GB8aqtc822Pqji8SQBmSrTb7CrP8i08Q==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@bruits/satteri-wasm32-wasi": {
+ "version": "0.9.5",
+ "resolved": "https://registry.npmjs.org/@bruits/satteri-wasm32-wasi/-/satteri-wasm32-wasi-0.9.5.tgz",
+ "integrity": "sha512-zauAuMwfPnKPUkd4AFixRFpXdgKwP2mKgxrIIo2gJzW0/ZneF9dbHnLkojSpaBnCCp7VUL1hIi5WWZvB1CqmAQ==",
+ "cpu": [
+ "wasm32"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "@emnapi/core": "1.11.1",
+ "@emnapi/runtime": "1.11.1",
+ "@napi-rs/wasm-runtime": "^1.1.6"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@bruits/satteri-wasm32-wasi/node_modules/@emnapi/core": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.11.1.tgz",
+ "integrity": "sha512-RSvbQmHzdKzNsLYa/wHrbc3KN4sYLKAdPZxqiM2HATqv/SBk2/ENSHpvXGaLOMcsAyz0poEGqkmmKYG3OWiJEQ==",
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "@emnapi/wasi-threads": "1.2.2",
+ "tslib": "^2.4.0"
+ }
+ },
+ "node_modules/@bruits/satteri-wasm32-wasi/node_modules/@emnapi/runtime": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.1.tgz",
+ "integrity": "sha512-vgj7R3y3Wgx24IQaGPA/R6YFXLHVMOZ0uVEyIQPaWs+rd1AzfEMXlAC22FYwO1XkKR6NPsq7mUandH8oIRdZFw==",
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "tslib": "^2.4.0"
+ }
+ },
+ "node_modules/@bruits/satteri-wasm32-wasi/node_modules/@emnapi/wasi-threads": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.2.tgz",
+ "integrity": "sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA==",
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "tslib": "^2.4.0"
+ }
+ },
+ "node_modules/@bruits/satteri-win32-arm64-msvc": {
+ "version": "0.9.5",
+ "resolved": "https://registry.npmjs.org/@bruits/satteri-win32-arm64-msvc/-/satteri-win32-arm64-msvc-0.9.5.tgz",
+ "integrity": "sha512-SrfE7NEsgZjBvU3c+RR6oQRu0ToXY5uVJEbieXEF0YTctIV2zAVlbaMjWLts074QCgh3a+XHWkR/lWh2VH2LUg==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@bruits/satteri-win32-x64-msvc": {
+ "version": "0.9.5",
+ "resolved": "https://registry.npmjs.org/@bruits/satteri-win32-x64-msvc/-/satteri-win32-x64-msvc-0.9.5.tgz",
+ "integrity": "sha512-5Kw9ZAtTGS8WHizyn+CJhjjfIQrw+7jcZodpmpXJjefnO15M8UexIi6JR2E5thyvsmHyhL6ZDDMUNR4bKJPd4g==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@capsizecss/unpack": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/@capsizecss/unpack/-/unpack-4.0.1.tgz",
+ "integrity": "sha512-CuNiSqg7+e1cO/GjffyMOm5Tt2jUF9CWHHnvQ/UkqvtkGfHdgwEC0wpmq7fkN3gxwpRnrAN0WzO3vREKmNolMQ==",
+ "license": "MIT",
+ "dependencies": {
+ "fontkitten": "^1.0.3"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@clack/core": {
+ "version": "1.4.3",
+ "resolved": "https://registry.npmjs.org/@clack/core/-/core-1.4.3.tgz",
+ "integrity": "sha512-/kr3UWNtdJfxZtPgDqUOmG2pvwlmcLGheex5yiZKdwbzZJxhV+HMNR9QNmyY5cGwTNV6LrR7Jtp+KjhUAP1qBQ==",
+ "license": "MIT",
+ "dependencies": {
+ "fast-wrap-ansi": "^0.2.0",
+ "sisteransi": "^1.0.5"
+ },
+ "engines": {
+ "node": ">= 20.12.0"
+ }
+ },
+ "node_modules/@clack/prompts": {
+ "version": "1.7.0",
+ "resolved": "https://registry.npmjs.org/@clack/prompts/-/prompts-1.7.0.tgz",
+ "integrity": "sha512-y7/yvZ2TPAnR9+jnc00klvNNLkJiXFFrQA/hlLCcxA9a2A4zQIOimyFQ9XfwYKiGD1fb5GY8vbKIIgO8d5Tb2A==",
+ "license": "MIT",
+ "dependencies": {
+ "@clack/core": "1.4.3",
+ "fast-string-width": "^3.0.2",
+ "fast-wrap-ansi": "^0.2.0",
+ "sisteransi": "^1.0.5"
+ },
+ "engines": {
+ "node": ">= 20.12.0"
+ }
+ },
+ "node_modules/@emmetio/abbreviation": {
+ "version": "2.3.3",
+ "resolved": "https://registry.npmjs.org/@emmetio/abbreviation/-/abbreviation-2.3.3.tgz",
+ "integrity": "sha512-mgv58UrU3rh4YgbE/TzgLQwJ3pFsHHhCLqY20aJq+9comytTXUDNGG/SMtSeMJdkpxgXSXunBGLD8Boka3JyVA==",
+ "license": "MIT",
+ "dependencies": {
+ "@emmetio/scanner": "^1.0.4"
+ }
+ },
+ "node_modules/@emmetio/css-abbreviation": {
+ "version": "2.1.8",
+ "resolved": "https://registry.npmjs.org/@emmetio/css-abbreviation/-/css-abbreviation-2.1.8.tgz",
+ "integrity": "sha512-s9yjhJ6saOO/uk1V74eifykk2CBYi01STTK3WlXWGOepyKa23ymJ053+DNQjpFcy1ingpaO7AxCcwLvHFY9tuw==",
+ "license": "MIT",
+ "dependencies": {
+ "@emmetio/scanner": "^1.0.4"
+ }
+ },
+ "node_modules/@emmetio/css-parser": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/@emmetio/css-parser/-/css-parser-0.4.1.tgz",
+ "integrity": "sha512-2bC6m0MV/voF4CTZiAbG5MWKbq5EBmDPKu9Sb7s7nVcEzNQlrZP6mFFFlIaISM8X6514H9shWMme1fCm8cWAfQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@emmetio/stream-reader": "^2.2.0",
+ "@emmetio/stream-reader-utils": "^0.1.0"
+ }
+ },
+ "node_modules/@emmetio/html-matcher": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/@emmetio/html-matcher/-/html-matcher-1.3.0.tgz",
+ "integrity": "sha512-NTbsvppE5eVyBMuyGfVu2CRrLvo7J4YHb6t9sBFLyY03WYhXET37qA4zOYUjBWFCRHO7pS1B9khERtY0f5JXPQ==",
+ "license": "ISC",
+ "dependencies": {
+ "@emmetio/scanner": "^1.0.0"
+ }
+ },
+ "node_modules/@emmetio/scanner": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/@emmetio/scanner/-/scanner-1.0.4.tgz",
+ "integrity": "sha512-IqRuJtQff7YHHBk4G8YZ45uB9BaAGcwQeVzgj/zj8/UdOhtQpEIupUhSk8dys6spFIWVZVeK20CzGEnqR5SbqA==",
+ "license": "MIT"
+ },
+ "node_modules/@emmetio/stream-reader": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/@emmetio/stream-reader/-/stream-reader-2.2.0.tgz",
+ "integrity": "sha512-fXVXEyFA5Yv3M3n8sUGT7+fvecGrZP4k6FnWWMSZVQf69kAq0LLpaBQLGcPR30m3zMmKYhECP4k/ZkzvhEW5kw==",
+ "license": "MIT"
+ },
+ "node_modules/@emmetio/stream-reader-utils": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/@emmetio/stream-reader-utils/-/stream-reader-utils-0.1.0.tgz",
+ "integrity": "sha512-ZsZ2I9Vzso3Ho/pjZFsmmZ++FWeEd/txqybHTm4OgaZzdS8V9V/YYWQwg5TC38Z7uLWUV1vavpLLbjJtKubR1A==",
+ "license": "MIT"
+ },
+ "node_modules/@emnapi/core": {
+ "version": "1.11.3",
+ "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.11.3.tgz",
+ "integrity": "sha512-zLpS5asjEb7lq8jYLq37N6XKaE41DIexlY1rF/z4/tIl3wo13Sqm28fRyfIsKZD+NZ8mM5RoKkpW/rBcuoSZSg==",
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "@emnapi/wasi-threads": "1.2.3",
+ "tslib": "^2.4.0"
+ }
+ },
+ "node_modules/@emnapi/runtime": {
+ "version": "1.11.3",
+ "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.3.tgz",
+ "integrity": "sha512-Xz4Tpyki7XyrpbUK1jR1AhdAdaXyhhY4lZ3neLodmhpuWfy2PAQN5B46sAiU4liOXGLkHypn/qU+jvfWSCYYLA==",
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "tslib": "^2.4.0"
+ }
+ },
+ "node_modules/@emnapi/wasi-threads": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.3.tgz",
+ "integrity": "sha512-ELEBe8PsLvvJ6QMr0zLt8ffvOHW/dc1m3CEzNMg7aJUv3bMaoDtw2TXyDAwkYBuroxxuHEwhRTLJSe5sya547g==",
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "tslib": "^2.4.0"
+ }
+ },
+ "node_modules/@esbuild/aix-ppc64": {
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.1.tgz",
+ "integrity": "sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ==",
+ "cpu": [
+ "ppc64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "aix"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-arm": {
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.1.tgz",
+ "integrity": "sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ==",
+ "cpu": [
+ "arm"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-arm64": {
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.1.tgz",
+ "integrity": "sha512-34EGEbCIAgosYz6goLcopX6Mo7NyGv9tfwEM2/7Ce2VcVRk568iSvniGWcUXIy7wEDR1wzolcxcriFVrWYcwBg==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-x64": {
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.1.tgz",
+ "integrity": "sha512-dbwY7ltSMDWsRatcRpCnES4F+im88OCUgGZjy52shC7GqHRE/cYlxNbB4Z4UpJswpcc4Qxd2oE/ufM0p61IKng==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/darwin-arm64": {
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.1.tgz",
+ "integrity": "sha512-TZbWkQY7kvTAXbXUT7uVACR5cMHsDiSz9z7ZKAX/RTq/WJEk3QyRr0wZpNhBDX+/0CtdqUIJlOiodQcta6tY3Q==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/darwin-x64": {
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.1.tgz",
+ "integrity": "sha512-zfdzgK9ACBNZLI/CyHTOx81SyNbM6YXn7rxSgX97VjyiPl9W1i4Ka4fgKECEoFCKGpvBj5qArWIGgQjOwkgskQ==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/freebsd-arm64": {
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.1.tgz",
+ "integrity": "sha512-wG2EA8ENdEI0qhkSZMjfqrdY+ziCYCPMmtZjjIwOmXFjmyzEHn+UUxk5of+SYsjtfs3VpnlC7QLzSI5hY/rOAw==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/freebsd-x64": {
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.1.tgz",
+ "integrity": "sha512-i7dZ9vQgnvSCzi/rYCXNgtF/U+eKZNJBzu3eTQbRgHnM7tNSizLOkRFAl3qzVc/Op/u5YkHHa4pf/3DOYHthLQ==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-arm": {
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.1.tgz",
+ "integrity": "sha512-qVXBOHQS+d5Y722GwJzJUtOLlX7km3CraOaGormF1pDtPd2C/l1SHRPgjLunLGe51Sh5YYWKMFDyV4SxgMQYTQ==",
+ "cpu": [
+ "arm"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-arm64": {
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.1.tgz",
+ "integrity": "sha512-yHs+0uc8+nvEAfAfxrWQKK5peSNzBc4PegcMO0EJ2hT71uA7vB8Ihg2e77R2P7SG5uYjPbHlLLmve4LLLRCf0g==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-ia32": {
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.1.tgz",
+ "integrity": "sha512-d1z4ZuP0ajrfz/FhGT4vv278rX8KnPPJx8i5+AtK7TYbx9Le9F1hyzurZpkEyjkGa9dUGhQow4C1NmeGvqxN2w==",
+ "cpu": [
+ "ia32"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-loong64": {
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.1.tgz",
+ "integrity": "sha512-M5sRjUVZrkm1OAPR3dlOYzNmN+loZKGVi1VUQGrwuqLcbR6qeAz+famMhjASeH3YVKvZz+zT1jlh/keC3Rj/lg==",
+ "cpu": [
+ "loong64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-mips64el": {
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.1.tgz",
+ "integrity": "sha512-mRObBZeHh2OxcBFPWE/FjylkRgZdYuiTR3vaTozquCGOH14iP9oN4x4Ge81CoIDYQrXmIxpFumJBu5MtZpnQJQ==",
+ "cpu": [
+ "mips64el"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-ppc64": {
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.1.tgz",
+ "integrity": "sha512-slScBsMAb3GFDcdrCgLwZtPYRoH2H/youv10QiZyRjmsP48fznoveWytSgCI/R0ZcUgpc0ZhIUEx6LHts8yrfQ==",
+ "cpu": [
+ "ppc64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-riscv64": {
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.1.tgz",
+ "integrity": "sha512-kw0owk1o0GFETUJyW0jc0G4Yzs0BHZn0JDZ8JRT088vjJYX777BAs1fDGxAC+q831qOs2DTC96mNsG2opdfyyQ==",
+ "cpu": [
+ "riscv64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-s390x": {
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.1.tgz",
+ "integrity": "sha512-/lAIjX8aYFRByhh6L5rYtPEDRqa9de/4V/juOXcta5frjvzXO4/sqEtyytse0g3zZFuWu5cDN0MkLz2qRDD2Ag==",
+ "cpu": [
+ "s390x"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-x64": {
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.1.tgz",
+ "integrity": "sha512-u/anNYF2mmVOEDwLtnQ1wOr3EZ9sTNGLWrsYGYwHWzGA3Si84IOkHXlbWTD1NB+9/1lcnweYKO54uhxZydNzfA==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/netbsd-arm64": {
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.1.tgz",
+ "integrity": "sha512-oks0DYbLwWMmaakTsCb+zL4E+aHRVLom9IJZOAthMQEPiQmydXHkziYEsGYRx0uNV/IjEKGAV941JzH02pflqw==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/netbsd-x64": {
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.1.tgz",
+ "integrity": "sha512-aeL6lAnN89Hz43Mlh1G8ARasbuoYvSITDEx0tHh5b7jJnHcssqgjy9Yx430GDpmCa6OyrKoS0aNRjKundRizGg==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openbsd-arm64": {
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.1.tgz",
+ "integrity": "sha512-MEFJe5C3R8pwXdZ5Y21oo6m7ePiS0d9pWucn99O/wvyJZChoIQKrQDxKrGeW8F5+T0okTHesAmDeiHDTIq0V/Q==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openbsd-x64": {
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.1.tgz",
+ "integrity": "sha512-i/ZLIOafE0Z8cI/XANJAixoJL/uRAoS2xOA3rb0xN+KK0K177cMAsQYkzHtBrtMXAKuAc7HGgcWiZ/sRC1Nxgw==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openharmony-arm64": {
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.1.tgz",
+ "integrity": "sha512-ge+Z7EXFNt2BO1oAMsVpiQ8EwndV9i1xXerAeTIK7AtPs3bKFXQM7nlRxDSIUIMeueR1CNXxqztLzdNeReKBJg==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openharmony"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/sunos-x64": {
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.1.tgz",
+ "integrity": "sha512-BEjgtECkL3vY+SaSQ6nzVfiALUeFxpawyp8Jmf5PtYhf1Ug40N1h/hxlhts+f1FvSvarEigdxS3BlSMI2PJLcQ==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "sunos"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-arm64": {
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.1.tgz",
+ "integrity": "sha512-lCv9eK/H6ZJWbE7bh2nw54CZ9M2nupBxJcTsdk/QQnWkdSjKGuxmmH8/GWrlT1eMmZfn4dGcCjRte397WqfQXA==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-ia32": {
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.1.tgz",
+ "integrity": "sha512-zvb/mB2bSCoJOpoCBgYKKpX6YM6mJBlBUVUtVj41DlZJVEB6/0CKlRYxP5wWl1C1ILiCoAU5wZZ4q1P3qeS6Eg==",
+ "cpu": [
+ "ia32"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-x64": {
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.1.tgz",
+ "integrity": "sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@fontsource-variable/cinzel": {
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/@fontsource-variable/cinzel/-/cinzel-5.3.0.tgz",
+ "integrity": "sha512-eFcU0TwaAP5QESF2pV8VoOPJJXJ6sSGm/6mXMwqTdMBPAJMaLhfa6oISHuOtFeddsoHP9RYm4Io0j1j3mNEz2Q==",
+ "license": "OFL-1.1",
+ "funding": {
+ "url": "https://github.com/sponsors/ayuhito"
+ }
+ },
+ "node_modules/@fontsource-variable/jetbrains-mono": {
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/@fontsource-variable/jetbrains-mono/-/jetbrains-mono-5.3.0.tgz",
+ "integrity": "sha512-F32xpS2NsGYoQi2ADSkKTgpJj7ozajsGgDJ8woTnqjmIB+dxDIqImjl4pXZVEExu8UFZ2ndhmX18EBS/hdz3Lw==",
+ "license": "OFL-1.1",
+ "funding": {
+ "url": "https://github.com/sponsors/ayuhito"
+ }
+ },
+ "node_modules/@fontsource-variable/montserrat": {
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/@fontsource-variable/montserrat/-/montserrat-5.3.0.tgz",
+ "integrity": "sha512-7PaZoxaxrWLAyrhO46v65An9LhUhfkTExWLhfbywYZCnZEgg/W1rEHnlNmZKjNZ3nJTVYyicqxlJt10z/26yTA==",
+ "license": "OFL-1.1",
+ "funding": {
+ "url": "https://github.com/sponsors/ayuhito"
+ }
+ },
+ "node_modules/@iconify-json/mdi": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/@iconify-json/mdi/-/mdi-1.2.3.tgz",
+ "integrity": "sha512-O3cLwbDOK7NNDf2ihaQOH5F9JglnulNDFV7WprU2dSoZu3h3cWH//h74uQAB87brHmvFVxIOkuBX2sZSzYhScg==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@iconify/types": "*"
+ }
+ },
+ "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"
+ },
+ "node_modules/@img/colour": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.1.0.tgz",
+ "integrity": "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==",
+ "license": "MIT",
+ "optional": true,
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@img/sharp-darwin-arm64": {
+ "version": "0.35.3",
+ "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.35.3.tgz",
+ "integrity": "sha512-RMnFX7YQsMoh7lWfcM4NEHHymBX/rLuKNPVM84XE9ONPcaSCDgE7CHIHpSgPcO2xcRthgBy1HfNO319mwhIAkg==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=20.9.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-darwin-arm64": "1.3.2"
+ }
+ },
+ "node_modules/@img/sharp-darwin-x64": {
+ "version": "0.35.3",
+ "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.35.3.tgz",
+ "integrity": "sha512-Xo+5uFBtLN0BKqieTxiFzFPQAUlBbbH5iBKyRX/z1JrbnYsHTfKJnUfL8+p2TPXr1pXqao4eeL4Rl144uDpK9w==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=20.9.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-darwin-x64": "1.3.2"
+ }
+ },
+ "node_modules/@img/sharp-freebsd-wasm32": {
+ "version": "0.35.3",
+ "resolved": "https://registry.npmjs.org/@img/sharp-freebsd-wasm32/-/sharp-freebsd-wasm32-0.35.3.tgz",
+ "integrity": "sha512-lUxcqWIj2wMQ9BrwNjngcr1gWUr5xgaGThBRqPPalIC2n67Cqj1uPh8NnA/ZhAg8hUbKl+kVHKwgUIwe6ZYPrg==",
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "dependencies": {
+ "@img/sharp-wasm32": "0.35.3"
+ },
+ "engines": {
+ "node": ">=20.9.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-libvips-darwin-arm64": {
+ "version": "1.3.2",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.3.2.tgz",
+ "integrity": "sha512-9J6ypZFpQBj4YnePGoq/S38w6nz+vqg5WZLrLGY4YuSemdMq47GMLBPO42MzwdGwpg/agZ7xzZcFHa48xlywfg==",
+ "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.3.2",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.3.2.tgz",
+ "integrity": "sha512-m2pW1n6cns9VaubNwsZ+c3CRYjxNQWgJ5gPlnL1nbBcpkBvFm6SCFN5o0psFHI8w9n11NKhFkeEDns98tiqbEw==",
+ "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.3.2",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.3.2.tgz",
+ "integrity": "sha512-1eMLzy92I4J6rmi4mAT8yC3HxOtniyGELlzGbNMLLeqe052ahFQ0h6LFq+lh5DsDIdYViIDst08abvSbcEdLXQ==",
+ "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.3.2",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.3.2.tgz",
+ "integrity": "sha512-dqVSFynCox4C/J8kT16V7SIFAns0IjgLwkvYT7p8LQVmJ5OS5b6tI9IGflxTeuBS//zXeFIUbwt5dwxyZ17cnA==",
+ "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.3.2",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.3.2.tgz",
+ "integrity": "sha512-3z0NHDxD6n5I9gc05U1eW1AyRm+Gznzq3naMrthPNqE6oYykcogW0l/jfpJdjYnuNl8R7yI9pNbE1XiUeyq0Aw==",
+ "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.3.2",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.3.2.tgz",
+ "integrity": "sha512-bsb4rI+NldGOsXuej2r8OdSS8+zXDVaCWxyWrcv6kneTOlgAHtZABRzBBCwdsPiD90J4myNJuHpg6kA20ImW/w==",
+ "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.3.2",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.3.2.tgz",
+ "integrity": "sha512-/ABshyj8gCpyIrNXnHn4LorDJ0HHm1VhXPBlxZ8zAtfVPAaSafXPGn+sUSIRiwaSBy0mmFjSjiXI5mkcwdChKQ==",
+ "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.3.2",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.3.2.tgz",
+ "integrity": "sha512-ITPEtgffGJ0S6G9dRyw/366tJQqFRcHWPHhC+Stpg3Z8AEMrDrTr2lhdz4f/Y/HMbRh//7Z5mBzEpVdi62Oc3w==",
+ "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.3.2",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.3.2.tgz",
+ "integrity": "sha512-zE9EdiUzUmg5mDT5a1rk5fYJ6GWPloTwWBYDS14naqHsL+EaMpDj1AWnpLgh3u0YCORv2Tt50wrcrpYqkP97Kw==",
+ "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.3.2",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.3.2.tgz",
+ "integrity": "sha512-m0lrLiUt+lBYnCFr8qV/65yMR4E/c7/wf78I5eKTdkEakFAlZ9QlzEM3QIhhAwVeUhLAHLcCq7a7Vszq/oFNZQ==",
+ "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.35.3",
+ "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.35.3.tgz",
+ "integrity": "sha512-affVWCTLooy8TSxbDx2qkzuDeaWLNVBA+P//FNBirHsXpP2fuBhk5AuboYUnrDnzoXes8GFjpTx0SBFOCRg+FA==",
+ "cpu": [
+ "arm"
+ ],
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=20.9.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-linux-arm": "1.3.2"
+ }
+ },
+ "node_modules/@img/sharp-linux-arm64": {
+ "version": "0.35.3",
+ "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.35.3.tgz",
+ "integrity": "sha512-QgKDspHPnrU+GQ55XPhGwyhC8acLVOOSyAvo1oVfFmrIXLkDNmGWzAfDZ4xK8oSA1qBQrALcHX0G5UZni/SuFQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=20.9.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-linux-arm64": "1.3.2"
+ }
+ },
+ "node_modules/@img/sharp-linux-ppc64": {
+ "version": "0.35.3",
+ "resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.35.3.tgz",
+ "integrity": "sha512-sMd8rDxmpLOwv/7N44klFjOD5DUO7FLdjiXDI0hoxYaf7Ar262dQIEkosE98bps+5HPLtp/EvNqeqQtOycP/IA==",
+ "cpu": [
+ "ppc64"
+ ],
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=20.9.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-linux-ppc64": "1.3.2"
+ }
+ },
+ "node_modules/@img/sharp-linux-riscv64": {
+ "version": "0.35.3",
+ "resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.35.3.tgz",
+ "integrity": "sha512-0Eob78yjlYPfL5vMNWAW55l3R9Y6BQS/gOfe0ZcP9mEz9ohhKSt4im1hayiknXgf8AWrFqMvJcKIdmLmEe7yeQ==",
+ "cpu": [
+ "riscv64"
+ ],
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=20.9.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-linux-riscv64": "1.3.2"
+ }
+ },
+ "node_modules/@img/sharp-linux-s390x": {
+ "version": "0.35.3",
+ "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.35.3.tgz",
+ "integrity": "sha512-KgAxQ0DxpNOq1rG2t5cgTgShJFGSuU7XO45cqC+1NVOuZnP6tlgZRuSYOfNupGkHID0o3cJOsw4DVeJpMovcGw==",
+ "cpu": [
+ "s390x"
+ ],
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=20.9.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-linux-s390x": "1.3.2"
+ }
+ },
+ "node_modules/@img/sharp-linux-x64": {
+ "version": "0.35.3",
+ "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.35.3.tgz",
+ "integrity": "sha512-8pqvxubL2PGdhlPy6GLqzDYMUjyRmKAwKHYKixpdJYBUK7PJ0C029XdsnpFIdgRZG68fZiGdHVWcKPvtiPB4cA==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=20.9.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-linux-x64": "1.3.2"
+ }
+ },
+ "node_modules/@img/sharp-linuxmusl-arm64": {
+ "version": "0.35.3",
+ "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.35.3.tgz",
+ "integrity": "sha512-Vz0iQjzzcSX3HCbfwFfCSG/9SCIqyO0mH2sXyiHaAYfBk0cRsCWXRyQYX0ovCK/PAQBbTzQ0dsPQHh5MAFL59w==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=20.9.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-linuxmusl-arm64": "1.3.2"
+ }
+ },
+ "node_modules/@img/sharp-linuxmusl-x64": {
+ "version": "0.35.3",
+ "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.35.3.tgz",
+ "integrity": "sha512-6O1NPKcDVj9QEdg7Hx549EX8U0rp6yXQERqru6yRN7fGBn32UvIRJUlWnk+8xDCiG76hXVBbX82NZ/ZKr0euIg==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=20.9.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-linuxmusl-x64": "1.3.2"
+ }
+ },
+ "node_modules/@img/sharp-wasm32": {
+ "version": "0.35.3",
+ "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.35.3.tgz",
+ "integrity": "sha512-cZ0XkcYGpHZkqW6iCkqTcmUC0CD9DhD5d/qeZlZkfRBn6GnHniZXLUo5+9xw8Iv76YE6LQFN9YNBlKREcCG76w==",
+ "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT",
+ "optional": true,
+ "dependencies": {
+ "@emnapi/runtime": "^1.11.1"
+ },
+ "engines": {
+ "node": ">=20.9.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-webcontainers-wasm32": {
+ "version": "0.35.3",
+ "resolved": "https://registry.npmjs.org/@img/sharp-webcontainers-wasm32/-/sharp-webcontainers-wasm32-0.35.3.tgz",
+ "integrity": "sha512-2rnq7bX3NzeR2T4YWgz8qiG4h3TSdMe+vN1iQXpJleSJ3SM5zQ8Fy2SyyXAWlbxpEZ2Y+Z4u1BePgJEYbSy80Q==",
+ "cpu": [
+ "wasm32"
+ ],
+ "license": "Apache-2.0",
+ "optional": true,
+ "dependencies": {
+ "@img/sharp-wasm32": "0.35.3"
+ },
+ "engines": {
+ "node": ">=20.9.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-win32-arm64": {
+ "version": "0.35.3",
+ "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.35.3.tgz",
+ "integrity": "sha512-4bPwFdMbeC4JQ8L8LOyWp6nsHcboP5fxkp6iPOXz2Vg49R42TuMs2whkJ5OAP4/Ul035qOzy0AecOF9VOscn4w==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "Apache-2.0 AND LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=20.9.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-win32-ia32": {
+ "version": "0.35.3",
+ "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.35.3.tgz",
+ "integrity": "sha512-r53mXsBN6lFUDiST764SvgwUdHAqM4rPAiDzAmf4fLoB6X/rkfyTrLCg6+g17wJJiCmB3JYgHuUldCWUIRFSXw==",
+ "cpu": [
+ "ia32"
+ ],
+ "license": "Apache-2.0 AND LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": "^20.9.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-win32-x64": {
+ "version": "0.35.3",
+ "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.35.3.tgz",
+ "integrity": "sha512-D4y1vNeZrIIJCN+uHaWVtH86B+aCrdMYYjicy9pXHvbGZeGYLLSd3wdVuC37FxVXlU1ARsk84eKWfWMXGYEqvA==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "Apache-2.0 AND LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=20.9.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@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==",
+ "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==",
+ "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==",
+ "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==",
+ "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/@napi-rs/wasm-runtime": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.2.2.tgz",
+ "integrity": "sha512-JfB4kuJQjaoHuCTseIINHtHWeJnvgEcxjwA5t/Y00ZgaOO1Crz3fjT/p8kT28zA/Caz7oiUMn3d6H2yOVCVwuw==",
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "@tybys/wasm-util": "^0.10.3"
+ },
+ "engines": {
+ "node": "^20.19.0 || ^22.13.0 || >=23.5.0"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/Brooooooklyn"
+ },
+ "peerDependencies": {
+ "@emnapi/core": "^1.7.1 || ^2.0.0-alpha.3",
+ "@emnapi/runtime": "^1.7.1 || ^2.0.0-alpha.3"
+ }
+ },
+ "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/@oxc-project/types": {
+ "version": "0.143.0",
+ "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.143.0.tgz",
+ "integrity": "sha512-u6JZdLBTLotrNC9Vd6vPssINdzcCzleKAH6EJKImQb7GtYvX5keN2dxkoK44stCc4tffE6QQRtZTXVSzsLUlWA==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/Boshen"
+ }
+ },
+ "node_modules/@rolldown/binding-android-arm64": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.2.3.tgz",
+ "integrity": "sha512-zrJtHDcaZJ1Fp7xf4hNl+7seH9Cn/N5TwLYkhgXREtBwAd/jaqW3uqeHxpDugJLVICWg4eW44kOQEGJ1r6jCGw==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-darwin-arm64": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.2.3.tgz",
+ "integrity": "sha512-ieIiibVCp0tX7TLu2cafoNPv8wJyYi01ekXpbf8q2j7F4rGAhhXb/eQh7ge9DRBY78GwmRQtvjZDux7EDbA8kA==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-darwin-x64": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.2.3.tgz",
+ "integrity": "sha512-Zh9tCon19eDXJoihx0rqKhMUlMYqzwj3aPsSuHmI4RWZh62dWUL+DJN4C5YQya5TcQBJU/Fe8+rY0jhXTQITqA==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-freebsd-x64": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.2.3.tgz",
+ "integrity": "sha512-nGbJWewA1wrXXZiQhjAT5rhibGfns5ZNkDVqxsO6zJ3f3YvpoDNNmGMSbbhLuXKjNScaBJVOAboztAWVespQMg==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-linux-arm-gnueabihf": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.2.3.tgz",
+ "integrity": "sha512-QNniJr5Kml0kDEB98jiDOJjXNroxIIi0IXIbdYzY26Xt1pVbeP62+KnoIZLwirOymX/0jDk/2gI/bNUv7A7OIw==",
+ "cpu": [
+ "arm"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-linux-arm64-gnu": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.2.3.tgz",
+ "integrity": "sha512-TkqEAcmmvH3I/q4114NB4RVt6241Dao48pF45uLcFGrwAaIn0iITgTAKP/dLjbN0R4buJjGb91+UHSoFmpgIWw==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-linux-arm64-musl": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.2.3.tgz",
+ "integrity": "sha512-NHqjnxpsndf4MPymxteFAWHHfkTL8HjWh1KB7z23ofZ6QO2euONuxDXjat69dKZRALnGypg8k8SsK8vZJoXv1Q==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-linux-ppc64-gnu": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.2.3.tgz",
+ "integrity": "sha512-6tbrbwfz5GB9DQ4Jwo6hy9v+vR31xZlvzZ6n5Xut6Hhx5PvrA9q/HsK8KMaYQp063iqZGXwNvZtYNLD7EM/x0w==",
+ "cpu": [
+ "ppc64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-linux-s390x-gnu": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.2.3.tgz",
+ "integrity": "sha512-oyuXxXmoZHjXC917IAPFAAv4wWAa0cM9afk8nx1+9/jNNOX1uPf8yDA6p7G0RypOfw/X0PQt5IfoquY1um+zSg==",
+ "cpu": [
+ "s390x"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-linux-x64-gnu": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.2.3.tgz",
+ "integrity": "sha512-TytMwF2KVGqP2tgd0I1OY0PAv78dZRAYcF5ssDzjM34SUXCED3uXvSd5+lHoC0bTD6eEdFz7LdQNCO1y0oVk9w==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-linux-x64-musl": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.2.3.tgz",
+ "integrity": "sha512-/E9m3qstrJFVPoULV25mVQblSNExY2+kBsYe4sy0Tn0yOOgJ8wZbZt3KnRbF/XeU2Gl1STKUQnDNTqhIE5MD4A==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-openharmony-arm64": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.2.3.tgz",
+ "integrity": "sha512-Kr0OcsoQI816i6HOl3vFHpd1K0eZyh76zgfj4c1nTyaTsd5r2Mj1lwM4R90y/qaCfmTn9eHy0SKwi98eitRxug==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openharmony"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-win32-arm64-msvc": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.2.3.tgz",
+ "integrity": "sha512-hOtMwTqnME+/gJcH/PCZ0wn0zPUjiWOgkHpxbSJpfGKMezHltx1S7/k1SitzVa7Ww2cqrDDaFbZEhcJZO8o+Jw==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-win32-x64-msvc": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.2.3.tgz",
+ "integrity": "sha512-ekcqMMkI2PlhYnfzQnB/cEdYUVVJViWvoUyLrbzgDoi3Snfc1mVBwdnc306ufA5ejy8JSPjT2RlW1nQSjW7efg==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/pluginutils": {
+ "version": "1.0.0-rc.3",
+ "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.3.tgz",
+ "integrity": "sha512-eybk3TjzzzV97Dlj5c+XrBFW57eTNhzod66y9HrBlzJ6NsCrWCp/2kaPS3K9wJmurBC0Tdw4yPjXKZqlznim3Q==",
+ "license": "MIT"
+ },
+ "node_modules/@shikijs/core": {
+ "version": "4.4.2",
+ "resolved": "https://registry.npmjs.org/@shikijs/core/-/core-4.4.2.tgz",
+ "integrity": "sha512-StyzbAyxg2/tBGf78gwbBkGyeQ73lf8UiJArFaQhTQIDqQOCKPCQFanvrs4/Yv3Yfyc+ONInJM6K+FMIf+P+kA==",
+ "license": "MIT",
+ "dependencies": {
+ "@shikijs/primitive": "4.4.2",
+ "@shikijs/types": "4.4.2",
+ "@shikijs/vscode-textmate": "^10.0.2",
+ "@types/hast": "^3.0.5",
+ "hast-util-to-html": "^9.0.5"
+ },
+ "engines": {
+ "node": ">=20"
+ }
+ },
+ "node_modules/@shikijs/engine-javascript": {
+ "version": "4.4.2",
+ "resolved": "https://registry.npmjs.org/@shikijs/engine-javascript/-/engine-javascript-4.4.2.tgz",
+ "integrity": "sha512-MnIkeqWdVPUWsxlx8gKLVCJFTsqrQJgpTPBPpQwaFeJ56lOnJxj5aN2LUFnfxUEcvOQuNocmbaMnVrCEln6rkw==",
+ "license": "MIT",
+ "dependencies": {
+ "@shikijs/types": "4.4.2",
+ "@shikijs/vscode-textmate": "^10.0.2",
+ "oniguruma-to-es": "^4.3.6"
+ },
+ "engines": {
+ "node": ">=20"
+ }
+ },
+ "node_modules/@shikijs/engine-oniguruma": {
+ "version": "4.4.2",
+ "resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-4.4.2.tgz",
+ "integrity": "sha512-GLhowz1+jixjz+wiZ3wMnOn1jTxiFCGl2PkXufivbnwPHKuyw1AYqu5/hbWhZZ2oAb0NP05WUJhYeigY14drnw==",
+ "license": "MIT",
+ "dependencies": {
+ "@shikijs/types": "4.4.2",
+ "@shikijs/vscode-textmate": "^10.0.2"
+ },
+ "engines": {
+ "node": ">=20"
+ }
+ },
+ "node_modules/@shikijs/langs": {
+ "version": "4.4.2",
+ "resolved": "https://registry.npmjs.org/@shikijs/langs/-/langs-4.4.2.tgz",
+ "integrity": "sha512-8DfeusD+Zdv/eYIDdXyJTUnSMHt+aAWjAOCXV20HNGAHRlInXpG8wh421v6B91WOm9TFwRLN+b/LG5F2NAIojg==",
+ "license": "MIT",
+ "dependencies": {
+ "@shikijs/types": "4.4.2"
+ },
+ "engines": {
+ "node": ">=20"
+ }
+ },
+ "node_modules/@shikijs/primitive": {
+ "version": "4.4.2",
+ "resolved": "https://registry.npmjs.org/@shikijs/primitive/-/primitive-4.4.2.tgz",
+ "integrity": "sha512-l6fQQKsOMlz72n38fztmSgZ76MO6KSWuw8o+GJ+FhmqrpC9pIOJNQNXGgbb5yX2AwpzlEHwsaLPnk/8o4Fm+rA==",
+ "license": "MIT",
+ "dependencies": {
+ "@shikijs/types": "4.4.2",
+ "@shikijs/vscode-textmate": "^10.0.2",
+ "@types/hast": "^3.0.5"
+ },
+ "engines": {
+ "node": ">=20"
+ }
+ },
+ "node_modules/@shikijs/themes": {
+ "version": "4.4.2",
+ "resolved": "https://registry.npmjs.org/@shikijs/themes/-/themes-4.4.2.tgz",
+ "integrity": "sha512-H0CFoL07ddDC2Dd6EdrPYNkRhUR6YCkJlnuYFceYYUJJA5TIm2b5B33qqiDYryBExgbKMndFJPb2u1gTuqO37g==",
+ "license": "MIT",
+ "dependencies": {
+ "@shikijs/types": "4.4.2"
+ },
+ "engines": {
+ "node": ">=20"
+ }
+ },
+ "node_modules/@shikijs/types": {
+ "version": "4.4.2",
+ "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-4.4.2.tgz",
+ "integrity": "sha512-PFYitV4vpDr/iPCIhnHp+Q4ftic5N5VeNJ3KQ1O8gn3h2ar8qgwMAXF7tq4m1CWaMS60fV4VqF6vfnWH4F7vqQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@shikijs/vscode-textmate": "^10.0.2",
+ "@types/hast": "^3.0.5"
+ },
+ "engines": {
+ "node": ">=20"
+ }
+ },
+ "node_modules/@shikijs/vscode-textmate": {
+ "version": "10.0.2",
+ "resolved": "https://registry.npmjs.org/@shikijs/vscode-textmate/-/vscode-textmate-10.0.2.tgz",
+ "integrity": "sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==",
+ "license": "MIT"
+ },
+ "node_modules/@tailwindcss/node": {
+ "version": "4.3.3",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.3.3.tgz",
+ "integrity": "sha512-/T8IKEsf9VTU6tLjgC7+sv2mOPtQxzE2jMw7u4Tt40Tx+QSZxpzh95/H6cMKoja9XuW7iMdLJYBB0o9G1CaAgg==",
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/remapping": "^2.3.5",
+ "enhanced-resolve": "^5.24.1",
+ "jiti": "^2.7.0",
+ "lightningcss": "1.32.0",
+ "magic-string": "^0.30.21",
+ "source-map-js": "^1.2.1",
+ "tailwindcss": "4.3.3"
+ }
+ },
+ "node_modules/@tailwindcss/oxide": {
+ "version": "4.3.3",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.3.3.tgz",
+ "integrity": "sha512-krXjAikiaFSPaK/FkAQT5UTx3VormQaiZ5hBFlJZ9UFQGB/rwg1MZIhHAG9smMQRTdyJxP6Qt5MwMtdyU5FWrA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 20"
+ },
+ "optionalDependencies": {
+ "@tailwindcss/oxide-android-arm64": "4.3.3",
+ "@tailwindcss/oxide-darwin-arm64": "4.3.3",
+ "@tailwindcss/oxide-darwin-x64": "4.3.3",
+ "@tailwindcss/oxide-freebsd-x64": "4.3.3",
+ "@tailwindcss/oxide-linux-arm-gnueabihf": "4.3.3",
+ "@tailwindcss/oxide-linux-arm64-gnu": "4.3.3",
+ "@tailwindcss/oxide-linux-arm64-musl": "4.3.3",
+ "@tailwindcss/oxide-linux-x64-gnu": "4.3.3",
+ "@tailwindcss/oxide-linux-x64-musl": "4.3.3",
+ "@tailwindcss/oxide-wasm32-wasi": "4.3.3",
+ "@tailwindcss/oxide-win32-arm64-msvc": "4.3.3",
+ "@tailwindcss/oxide-win32-x64-msvc": "4.3.3"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-android-arm64": {
+ "version": "4.3.3",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.3.3.tgz",
+ "integrity": "sha512-Y85A2gmPSkl5Ve5qR86GL4HT509cFqQh1aes9p3sSkyTPwt0Pppf3GkwGe4JPACcRYjgJIEhQgM6dBClnr0NYw==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">= 20"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-darwin-arm64": {
+ "version": "4.3.3",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.3.3.tgz",
+ "integrity": "sha512-BiaWatpBcERQFDlOjRDpIVXuFK5PJez5SA4JMg6VYZdBYU+qKfV/vqjcIs+IYmtitf1xYQZTwXvU/8y4lfZUGw==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 20"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-darwin-x64": {
+ "version": "4.3.3",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.3.3.tgz",
+ "integrity": "sha512-fAeUqfV5ndhxRwai8cXGzdLvul9utWOmeTkv69unv4ZXixjn61Z+p9lCWdwOwA3TYboG3BwdVuN/RDjhBRl0mw==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 20"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-freebsd-x64": {
+ "version": "4.3.3",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.3.3.tgz",
+ "integrity": "sha512-iyf5bV6+wnAlflVeEy7R25dupxTNECZN5QMI0qNT6eT+EgaGdZcKhGkr5SdoaWiLJ3spLqIY9VCeSGrwmtg4kw==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">= 20"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": {
+ "version": "4.3.3",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.3.3.tgz",
+ "integrity": "sha512-aAYUprJAJQWWbRrPvtjdroZ56Md+JM8pMiopS6xGEwDfLhqj+2ver2p4nU4Mb3CRqcMmNBjo8KkUgcxhkzVQGQ==",
+ "cpu": [
+ "arm"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 20"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-linux-arm64-gnu": {
+ "version": "4.3.3",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.3.3.tgz",
+ "integrity": "sha512-nDxldcEENOxZRzC2uu9jrutZdAAQtb+8WWDCSnWL1zvBk1+FN+x6MtDViPB5AJMfttVCUhehGWus3XBPgatM/w==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 20"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-linux-arm64-musl": {
+ "version": "4.3.3",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.3.3.tgz",
+ "integrity": "sha512-Md44bD6veX/PC5iyF8cDVnw4HBIANZepRZZ7a8DQOvkfo5WUBwcp6iAuCUz23u+4SUkhJlD3eL7hNdW8ezd/kA==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 20"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-linux-x64-gnu": {
+ "version": "4.3.3",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.3.3.tgz",
+ "integrity": "sha512-tx7us1muwOKAKWao2v/GaafFeQboE6aj88vC6ziN2NCGcRm8gWUhwjzg+YdVB1e4boAtdtma4L43onunI6NS4w==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 20"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-linux-x64-musl": {
+ "version": "4.3.3",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.3.3.tgz",
+ "integrity": "sha512-SJxX60smvHgasZoBy11dX6YRjXJFovwWBoedhbQPOBzgFWBHGB+TVPWB9BxzR7TTxU8FQZAI2AyiNCMzFm8Img==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 20"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-wasm32-wasi": {
+ "version": "4.3.3",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.3.3.tgz",
+ "integrity": "sha512-jx1+rPhY/5Ympkktd656HBWEBLxP7dH06losBLjjf5vgCODXvi9KhtftWcMIwTFIDqBr7cRnQkdLnAG+IOlGvQ==",
+ "bundleDependencies": [
+ "@napi-rs/wasm-runtime",
+ "@emnapi/core",
+ "@emnapi/runtime",
+ "@tybys/wasm-util",
+ "@emnapi/wasi-threads",
+ "tslib"
+ ],
+ "cpu": [
+ "wasm32"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "@emnapi/core": "^1.11.1",
+ "@emnapi/runtime": "^1.11.1",
+ "@emnapi/wasi-threads": "^1.2.2",
+ "@napi-rs/wasm-runtime": "^1.1.4",
+ "@tybys/wasm-util": "^0.10.2",
+ "tslib": "^2.8.1"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-win32-arm64-msvc": {
+ "version": "4.3.3",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.3.3.tgz",
+ "integrity": "sha512-3rc292Ca2ceK6Ulcc/bAVnTs/3nDtoPhyEKlgPv+yQJQi/JS/AMJlqzxvlDacL1nekbrcf6bTqp/jV4qgnPxNQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 20"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-win32-x64-msvc": {
+ "version": "4.3.3",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.3.3.tgz",
+ "integrity": "sha512-yJ0pwIVc/nYeGoV02WtsN8KYyLQv7kyI2wDnkezyJlGGjkd4QLwDGAwl47YpPJeuI0M0ObaXGSPjvWDPeTPggw==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 20"
+ }
+ },
+ "node_modules/@tailwindcss/vite": {
+ "version": "4.3.3",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.3.3.tgz",
+ "integrity": "sha512-yYU8cogLeSh/ms2jh8Fj7jaba/EWa7Ja6GoUqYZaraEuCI5YS6ms6ObZgjjedm+jm6XZjdNRWBpPP6Z86oOxcw==",
+ "license": "MIT",
+ "dependencies": {
+ "@tailwindcss/node": "4.3.3",
+ "@tailwindcss/oxide": "4.3.3",
+ "tailwindcss": "4.3.3"
+ },
+ "peerDependencies": {
+ "vite": "^5.2.0 || ^6 || ^7 || ^8"
+ }
+ },
+ "node_modules/@tybys/wasm-util": {
+ "version": "0.10.3",
+ "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.3.tgz",
+ "integrity": "sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg==",
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "tslib": "^2.4.0"
+ }
+ },
+ "node_modules/@types/babel__core": {
+ "version": "7.20.5",
+ "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
+ "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.20.7",
+ "@babel/types": "^7.20.7",
+ "@types/babel__generator": "*",
+ "@types/babel__template": "*",
+ "@types/babel__traverse": "*"
+ }
+ },
+ "node_modules/@types/babel__generator": {
+ "version": "7.27.0",
+ "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz",
+ "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.0.0"
+ }
+ },
+ "node_modules/@types/babel__template": {
+ "version": "7.4.4",
+ "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz",
+ "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.1.0",
+ "@babel/types": "^7.0.0"
+ }
+ },
+ "node_modules/@types/babel__traverse": {
+ "version": "7.28.0",
+ "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz",
+ "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.28.2"
+ }
+ },
+ "node_modules/@types/debug": {
+ "version": "4.1.13",
+ "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.13.tgz",
+ "integrity": "sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/ms": "*"
+ }
+ },
+ "node_modules/@types/estree": {
+ "version": "1.0.9",
+ "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz",
+ "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==",
+ "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/hast": {
+ "version": "3.0.5",
+ "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.5.tgz",
+ "integrity": "sha512-rp/ezSWaD1m44dPKICGhiskI13nVr7qTloFwDa/IYkhhf5nzwP+zIQcIJh3WIFSBOy/H1PzB40jPjMDksN4F+g==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/unist": "*"
+ }
+ },
+ "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.14",
+ "resolved": "https://registry.npmjs.org/@types/mdx/-/mdx-2.0.14.tgz",
+ "integrity": "sha512-T48PeuJtvLosNTPVhfnIp3i/n3a4g4Bad7YCq5k64D4u7NwDrAotikQ+5+sjtUvBmxCMlbo3dVL+C2dP0rWHzg==",
+ "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": "26.1.2",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-26.1.2.tgz",
+ "integrity": "sha512-Vu4a5UFA9rIIFJ7rB/Vaafh9lrCQszopTCx6KjFboXTGQbPNasehVR5TEiithSDGyd1DEiUByggTZsg8jukeIg==",
+ "license": "MIT",
+ "dependencies": {
+ "undici-types": "~8.3.0"
+ }
+ },
+ "node_modules/@types/nodemailer": {
+ "version": "8.0.1",
+ "resolved": "https://registry.npmjs.org/@types/nodemailer/-/nodemailer-8.0.1.tgz",
+ "integrity": "sha512-PxpaInm8V1JQDd4j0ds5HfvWQk8JupS1C0Picb96QJsrrRDjBH+DlK7L4ZdNSqNULhiZRQHc40nLVShaGxXAMw==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/node": "*"
+ }
+ },
+ "node_modules/@types/react": {
+ "version": "19.2.18",
+ "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.18.tgz",
+ "integrity": "sha512-AnzbBERsrLKtk2XSfTbYRLjQPdy116Sty4q+T+Bp3IC4l6jNBvreVPAHmpq9qhXQM7CXZPjLVmGMw9sy+hxQ3w==",
+ "license": "MIT",
+ "dependencies": {
+ "csstype": "^3.2.2"
+ }
+ },
+ "node_modules/@types/react-dom": {
+ "version": "19.2.4",
+ "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.4.tgz",
+ "integrity": "sha512-Bsc+QHgp+P/F02XDzNCY9jnZNCUuLki36KT7VKrTXXLdHf+vHMNZnW1rVu5DNW/rCK+fya3DATySbLM4yhtKUw==",
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/react": "^19.2.0"
+ }
+ },
+ "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.3",
+ "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.3.tgz",
+ "integrity": "sha512-60YRaenCQcVjYEKOcG824+DRGGIQ3VKErcBoAEDJZz5bKIs2ZG+X/H9Nk+Q6EVkwJk5QNApxbrc5QtBSwtrXAg==",
+ "license": "ISC"
+ },
+ "node_modules/@vitejs/plugin-react": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-5.2.0.tgz",
+ "integrity": "sha512-YmKkfhOAi3wsB1PhJq5Scj3GXMn3WvtQ/JC0xoopuHoXSdmtdStOpFrYaT1kie2YgFBcIe64ROzMYRjCrYOdYw==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/core": "^7.29.0",
+ "@babel/plugin-transform-react-jsx-self": "^7.27.1",
+ "@babel/plugin-transform-react-jsx-source": "^7.27.1",
+ "@rolldown/pluginutils": "1.0.0-rc.3",
+ "@types/babel__core": "^7.20.5",
+ "react-refresh": "^0.18.0"
+ },
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ },
+ "peerDependencies": {
+ "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0"
+ }
+ },
+ "node_modules/@volar/kit": {
+ "version": "2.4.28",
+ "resolved": "https://registry.npmjs.org/@volar/kit/-/kit-2.4.28.tgz",
+ "integrity": "sha512-cKX4vK9dtZvDRaAzeoUdaAJEew6IdxHNCRrdp5Kvcl6zZOqb6jTOfk3kXkIkG3T7oTFXguEMt5+9ptyqYR84Pg==",
+ "license": "MIT",
+ "dependencies": {
+ "@volar/language-service": "2.4.28",
+ "@volar/typescript": "2.4.28",
+ "typesafe-path": "^0.2.2",
+ "vscode-languageserver-textdocument": "^1.0.11",
+ "vscode-uri": "^3.0.8"
+ },
+ "peerDependencies": {
+ "typescript": "*"
+ }
+ },
+ "node_modules/@volar/language-core": {
+ "version": "2.4.28",
+ "resolved": "https://registry.npmjs.org/@volar/language-core/-/language-core-2.4.28.tgz",
+ "integrity": "sha512-w4qhIJ8ZSitgLAkVay6AbcnC7gP3glYM3fYwKV3srj8m494E3xtrCv6E+bWviiK/8hs6e6t1ij1s2Endql7vzQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@volar/source-map": "2.4.28"
+ }
+ },
+ "node_modules/@volar/language-server": {
+ "version": "2.4.28",
+ "resolved": "https://registry.npmjs.org/@volar/language-server/-/language-server-2.4.28.tgz",
+ "integrity": "sha512-NqcLnE5gERKuS4PUFwlhMxf6vqYo7hXtbMFbViXcbVkbZ905AIVWhnSo0ZNBC2V127H1/2zP7RvVOVnyITFfBw==",
+ "license": "MIT",
+ "dependencies": {
+ "@volar/language-core": "2.4.28",
+ "@volar/language-service": "2.4.28",
+ "@volar/typescript": "2.4.28",
+ "path-browserify": "^1.0.1",
+ "request-light": "^0.7.0",
+ "vscode-languageserver": "^9.0.1",
+ "vscode-languageserver-protocol": "^3.17.5",
+ "vscode-languageserver-textdocument": "^1.0.11",
+ "vscode-uri": "^3.0.8"
+ }
+ },
+ "node_modules/@volar/language-service": {
+ "version": "2.4.28",
+ "resolved": "https://registry.npmjs.org/@volar/language-service/-/language-service-2.4.28.tgz",
+ "integrity": "sha512-Rh/wYCZJrI5vCwMk9xyw/Z+MsWxlJY1rmMZPsxUoJKfzIRjS/NF1NmnuEcrMbEVGja00aVpCsInJfixQTMdvLw==",
+ "license": "MIT",
+ "dependencies": {
+ "@volar/language-core": "2.4.28",
+ "vscode-languageserver-protocol": "^3.17.5",
+ "vscode-languageserver-textdocument": "^1.0.11",
+ "vscode-uri": "^3.0.8"
+ }
+ },
+ "node_modules/@volar/source-map": {
+ "version": "2.4.28",
+ "resolved": "https://registry.npmjs.org/@volar/source-map/-/source-map-2.4.28.tgz",
+ "integrity": "sha512-yX2BDBqJkRXfKw8my8VarTyjv48QwxdJtvRgUpNE5erCsgEUdI2DsLbpa+rOQVAJYshY99szEcRDmyHbF10ggQ==",
+ "license": "MIT"
+ },
+ "node_modules/@volar/typescript": {
+ "version": "2.4.28",
+ "resolved": "https://registry.npmjs.org/@volar/typescript/-/typescript-2.4.28.tgz",
+ "integrity": "sha512-Ja6yvWrbis2QtN4ClAKreeUZPVYMARDYZl9LMEv1iQ1QdepB6wn0jTRxA9MftYmYa4DQ4k/DaSZpFPUfxl8giw==",
+ "license": "MIT",
+ "dependencies": {
+ "@volar/language-core": "2.4.28",
+ "path-browserify": "^1.0.1",
+ "vscode-uri": "^3.0.8"
+ }
+ },
+ "node_modules/@vscode/emmet-helper": {
+ "version": "2.11.0",
+ "resolved": "https://registry.npmjs.org/@vscode/emmet-helper/-/emmet-helper-2.11.0.tgz",
+ "integrity": "sha512-QLxjQR3imPZPQltfbWRnHU6JecWTF1QSWhx3GAKQpslx7y3Dp6sIIXhKjiUJ/BR9FX8PVthjr9PD6pNwOJfAzw==",
+ "license": "MIT",
+ "dependencies": {
+ "emmet": "^2.4.3",
+ "jsonc-parser": "^2.3.0",
+ "vscode-languageserver-textdocument": "^1.0.1",
+ "vscode-languageserver-types": "^3.15.1",
+ "vscode-uri": "^3.0.8"
+ }
+ },
+ "node_modules/@vscode/emmet-helper/node_modules/jsonc-parser": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-2.3.1.tgz",
+ "integrity": "sha512-H8jvkz1O50L3dMZCsLqiuB2tA7muqbSg1AtGEkN0leAqGjsUzDJir3Zwr02BhqdcITPg3ei3mZ+HjMocAknhhg==",
+ "license": "MIT"
+ },
+ "node_modules/@vscode/l10n": {
+ "version": "0.0.18",
+ "resolved": "https://registry.npmjs.org/@vscode/l10n/-/l10n-0.0.18.tgz",
+ "integrity": "sha512-KYSIHVmslkaCDyw013pphY+d7x1qV8IZupYfeIfzNA+nsaWHbn5uPuQRvdRFsa9zFzGeudPuoGoZ1Op4jrJXIQ==",
+ "license": "MIT"
+ },
+ "node_modules/acorn": {
+ "version": "8.18.0",
+ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.18.0.tgz",
+ "integrity": "sha512-lGq+9yr1/GuAWaVYIHRjvvySG5/4VfKIvC8EWxStPdcDh/Ka7FG3twP6v4d5BkravUilhIAsG4Qj83t02LWUPQ==",
+ "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/ajv": {
+ "version": "8.20.0",
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz",
+ "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==",
+ "license": "MIT",
+ "dependencies": {
+ "fast-deep-equal": "^3.1.3",
+ "fast-uri": "^3.0.1",
+ "json-schema-traverse": "^1.0.0",
+ "require-from-string": "^2.0.2"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/epoberezkin"
+ }
+ },
+ "node_modules/ajv-draft-04": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/ajv-draft-04/-/ajv-draft-04-1.0.0.tgz",
+ "integrity": "sha512-mv00Te6nmYbRp5DCwclxtt7yV/joXJPGS7nM+97GdxvuttCOfgI3K4U25zboyeX0O+myI8ERluxQe5wljMmVIw==",
+ "license": "MIT",
+ "peerDependencies": {
+ "ajv": "^8.5.0"
+ },
+ "peerDependenciesMeta": {
+ "ajv": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/ajv-i18n": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/ajv-i18n/-/ajv-i18n-4.2.0.tgz",
+ "integrity": "sha512-v/ei2UkCEeuKNXh8RToiFsUclmU+G57LO1Oo22OagNMENIw+Yb8eMwvHu7Vn9fmkjJyv6XclhJ8TbuigSglPkg==",
+ "license": "MIT",
+ "peerDependencies": {
+ "ajv": "^8.0.0-beta.0"
+ }
+ },
+ "node_modules/am-i-vibing": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/am-i-vibing/-/am-i-vibing-0.4.0.tgz",
+ "integrity": "sha512-MxT4XZL7pzLHpuvhDKdMaQHMGGkJDLluKBLsbstn+8wv9sWcFT6h+0ve9qkml95amVTZtZV83gQe2hY+ojgHLg==",
+ "license": "MIT",
+ "dependencies": {
+ "process-ancestry": "^0.1.0"
+ },
+ "bin": {
+ "am-i-vibing": "dist/cli.mjs"
+ }
+ },
+ "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.2",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz",
+ "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=8.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "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": "7.2.0",
+ "resolved": "https://registry.npmjs.org/astro/-/astro-7.2.0.tgz",
+ "integrity": "sha512-lLTYzx3fOvCmtwD3JVBLQcbORbIOW1/j0R+3IvJx/XKwMGrk7mFnF0BYSOeRiNw1qHUR5mdA6+hRnyvyDfqrWQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@astrojs/compiler-rs": "^0.3.2",
+ "@astrojs/internal-helpers": "0.10.2",
+ "@astrojs/markdown-satteri": "0.3.5",
+ "@astrojs/telemetry": "3.3.3",
+ "@capsizecss/unpack": "^4.0.0",
+ "@clack/prompts": "^1.1.0",
+ "@oslojs/encoding": "^1.1.0",
+ "am-i-vibing": "^0.4.0",
+ "aria-query": "^5.3.2",
+ "axobject-query": "^4.1.0",
+ "ci-info": "^4.4.0",
+ "clsx": "^2.1.1",
+ "common-ancestor-path": "^2.0.0",
+ "cookie": "^2.0.1",
+ "devalue": "^5.8.1",
+ "diff": "^8.0.3",
+ "dset": "^3.1.4",
+ "es-module-lexer": "^2.0.0",
+ "esbuild": "^0.28.0",
+ "flattie": "^1.1.1",
+ "fontace": "~0.4.1",
+ "get-tsconfig": "5.0.0-beta.4",
+ "github-slugger": "^2.0.0",
+ "html-escaper": "3.0.3",
+ "http-cache-semantics": "^4.2.0",
+ "js-yaml": "^4.3.0",
+ "jsonc-parser": "^3.3.1",
+ "magic-string": "^1.0.0",
+ "magicast": "^0.5.2",
+ "mrmime": "^2.0.1",
+ "neotraverse": "^1.0.1",
+ "obug": "^2.1.1",
+ "p-limit": "^7.3.0",
+ "p-queue": "^9.1.0",
+ "package-manager-detector": "^1.6.0",
+ "piccolore": "^0.1.3",
+ "picomatch": "^4.0.4",
+ "semver": "^7.7.4",
+ "shiki": "^4.0.2",
+ "smol-toml": "^1.6.0",
+ "svgo": "^4.0.1",
+ "tinyclip": "^0.1.12",
+ "tinyexec": "^1.0.4",
+ "tinyglobby": "^0.2.15",
+ "ultrahtml": "^1.6.0",
+ "unifont": "~0.7.4",
+ "unstorage": "^1.17.5",
+ "vite": "^8.0.13",
+ "vitefu": "^1.1.2",
+ "xxhash-wasm": "^1.1.0",
+ "yargs-parser": "^22.0.0",
+ "zod": "^4.3.6"
+ },
+ "bin": {
+ "astro": "bin/astro.mjs"
+ },
+ "engines": {
+ "node": ">=22.12.0",
+ "npm": ">=9.6.5",
+ "pnpm": ">=7.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/astrodotbuild"
+ },
+ "optionalDependencies": {
+ "sharp": "^0.34.0 || ^0.35.0"
+ },
+ "peerDependencies": {
+ "@astrojs/markdown-remark": "7.2.2"
+ },
+ "peerDependenciesMeta": {
+ "@astrojs/markdown-remark": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/astro/node_modules/magic-string": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-1.1.0.tgz",
+ "integrity": "sha512-kS3VHe0nEPST2saQV4Rbkchcd3UBRkVTQHo1D3h/ZTwFDhai/mfKkmtPAtD129EOI7K3HlHIsFOt0WrI2/oU9g==",
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/sourcemap-codec": "^1.5.5"
+ }
+ },
+ "node_modules/astro/node_modules/semver": {
+ "version": "7.8.5",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz",
+ "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==",
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "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/baseline-browser-mapping": {
+ "version": "2.11.12",
+ "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.12.tgz",
+ "integrity": "sha512-r7WnVImvVCeFpf2DOXfy41aPWzeNg3H/A2X4dKmy1QL0MSyyk/e7z8ihJ3N6Nn2PsdhkVlqnEfnUE4a05P2aTA==",
+ "license": "Apache-2.0",
+ "bin": {
+ "baseline-browser-mapping": "dist/cli.cjs"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/boolbase": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz",
+ "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==",
+ "license": "ISC"
+ },
+ "node_modules/browserslist": {
+ "version": "4.28.7",
+ "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.7.tgz",
+ "integrity": "sha512-JxV13hNrFxqjOc8alRbq9dK1MM79NEXYpma2B2J4wAtpWS5zIEIKqWPGCl7N4o7Uc7B7itylh7SuDujATRyyTw==",
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/browserslist"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "baseline-browser-mapping": "^2.10.44",
+ "caniuse-lite": "^1.0.30001806",
+ "electron-to-chromium": "^1.5.393",
+ "node-releases": "^2.0.51",
+ "update-browserslist-db": "^1.2.3"
+ },
+ "bin": {
+ "browserslist": "cli.js"
+ },
+ "engines": {
+ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
+ }
+ },
+ "node_modules/caniuse-lite": {
+ "version": "1.0.30001807",
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001807.tgz",
+ "integrity": "sha512-daRXJ9EB/rdRgu7kV+TTl1YUKtlsMWblPl2sLnpg9DZae16QCegol6A1SmCE31Lm9mXC1sRWGt/krouH+/dl7Q==",
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/caniuse-lite"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "CC-BY-4.0"
+ },
+ "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/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/chokidar": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz",
+ "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==",
+ "license": "MIT",
+ "dependencies": {
+ "readdirp": "^4.0.1"
+ },
+ "engines": {
+ "node": ">= 14.16.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/cliui": {
+ "version": "9.0.1",
+ "resolved": "https://registry.npmjs.org/cliui/-/cliui-9.0.1.tgz",
+ "integrity": "sha512-k7ndgKhwoQveBL+/1tqGJYNz097I7WOvwbmmU2AR5+magtbjPWQTS1C5vzGkBC8Ym8UWRzfKUzUUqFLypY4Q+w==",
+ "license": "ISC",
+ "dependencies": {
+ "string-width": "^7.2.0",
+ "strip-ansi": "^7.1.0",
+ "wrap-ansi": "^9.0.0"
+ },
+ "engines": {
+ "node": ">=20"
+ }
+ },
+ "node_modules/cliui/node_modules/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/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/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": "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/common-ancestor-path": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/common-ancestor-path/-/common-ancestor-path-2.0.0.tgz",
+ "integrity": "sha512-dnN3ibLeoRf2HNC+OlCiNc5d2zxbLJXOtiZUudNFSXZrNSydxcCsSpRzXwfu7BBWCIfHPw+xTayeBvJCP/D8Ng==",
+ "license": "BlueOak-1.0.0",
+ "engines": {
+ "node": ">= 18"
+ }
+ },
+ "node_modules/convert-source-map": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
+ "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
+ "license": "MIT"
+ },
+ "node_modules/cookie": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-2.0.1.tgz",
+ "integrity": "sha512-yuToqVvRrj6pfDXREyQAAv8SkAEk/8GS3jQRTiUMm66TVtBYmqQeoEjL2Lmq8Rpo6271vH76InTChTitEAm65w==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=22"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/cookie-es": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/cookie-es/-/cookie-es-1.2.3.tgz",
+ "integrity": "sha512-lXVyvUvrNXblMqzIRrxHb57UUVmqsSWlxqt3XIjCkUP0wDAf6uicO6KMbEgYrMNtEvWgWHwe42CKxPu9MYAnWw==",
+ "license": "MIT"
+ },
+ "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-tree": {
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.2.1.tgz",
+ "integrity": "sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA==",
+ "license": "MIT",
+ "dependencies": {
+ "mdn-data": "2.27.1",
+ "source-map-js": "^1.2.1"
+ },
+ "engines": {
+ "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0"
+ }
+ },
+ "node_modules/css-what": {
+ "version": "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/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/csstype": {
+ "version": "3.2.3",
+ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
+ "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
+ "license": "MIT"
+ },
+ "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.7",
+ "resolved": "https://registry.npmjs.org/defu/-/defu-6.1.7.tgz",
+ "integrity": "sha512-7z22QmUWiQ/2d0KkdYmANbRUVABpZ9SNYyH5vx6PZ+nE5bcC0l7uFvEfHlyld/HcGBFTL536ClDt3DEcSlEJAQ==",
+ "license": "MIT"
+ },
+ "node_modules/depd": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
+ "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "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/devalue": {
+ "version": "5.9.0",
+ "resolved": "https://registry.npmjs.org/devalue/-/devalue-5.9.0.tgz",
+ "integrity": "sha512-RWrqdArjvPbsATEhOPUo6Wndc/iWnkWKlhIrdlF3zMMYo/c3CVtoaVAyLtWxz5h8nSlkHzxnzV2uLydPXmtF+A==",
+ "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.4",
+ "resolved": "https://registry.npmjs.org/diff/-/diff-8.0.4.tgz",
+ "integrity": "sha512-DPi0FmjiSU5EvQV0++GFDOJ9ASQUVFh5kD+OzOnYdi7n3Wpm9hWWGfB/O2blfHcMVTL5WkQXSnRiK9makhrcnw==",
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=0.3.1"
+ }
+ },
+ "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/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/ee-first": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
+ "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==",
+ "license": "MIT"
+ },
+ "node_modules/electron-to-chromium": {
+ "version": "1.5.402",
+ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.402.tgz",
+ "integrity": "sha512-/oOpMaPT6Yg+6/1XQhyIPlzgj7Ye9zf+nNM2Uh6OcE2G2oNptWazFa+qB2Pdqqbsc9KnIDzgAntoYN0dbwOXwA==",
+ "license": "ISC"
+ },
+ "node_modules/emmet": {
+ "version": "2.4.11",
+ "resolved": "https://registry.npmjs.org/emmet/-/emmet-2.4.11.tgz",
+ "integrity": "sha512-23QPJB3moh/U9sT4rQzGgeyyGIrcM+GH5uVYg2C6wZIxAIJq7Ng3QLT79tl8FUwDXhyq9SusfknOrofAKqvgyQ==",
+ "license": "MIT",
+ "workspaces": [
+ "./packages/scanner",
+ "./packages/abbreviation",
+ "./packages/css-abbreviation",
+ "./"
+ ],
+ "dependencies": {
+ "@emmetio/abbreviation": "^2.3.3",
+ "@emmetio/css-abbreviation": "^2.1.8"
+ }
+ },
+ "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/encodeurl": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
+ "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/enhanced-resolve": {
+ "version": "5.24.5",
+ "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.24.5.tgz",
+ "integrity": "sha512-L1l8TNvomm6UVW5B253AGxQagSQr+vGwhMlrrfRS2qmhx46AMpMVJKQYLvWYbysTMY8VoicOvzHzoHMbyzB+4A==",
+ "license": "MIT",
+ "dependencies": {
+ "graceful-fs": "^4.2.4",
+ "tapable": "^2.3.3"
+ },
+ "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": "2.3.1",
+ "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.3.1.tgz",
+ "integrity": "sha512-shc1dbU90Yl/xq1QrC7QRtfcwURZuVRfPhZbDoldJ1cn1gzDvBaBWlv0eFolj5+0znnPJz5TXLxsN77X/12KTA==",
+ "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.28.1",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.1.tgz",
+ "integrity": "sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw==",
+ "hasInstallScript": true,
+ "license": "MIT",
+ "bin": {
+ "esbuild": "bin/esbuild"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "optionalDependencies": {
+ "@esbuild/aix-ppc64": "0.28.1",
+ "@esbuild/android-arm": "0.28.1",
+ "@esbuild/android-arm64": "0.28.1",
+ "@esbuild/android-x64": "0.28.1",
+ "@esbuild/darwin-arm64": "0.28.1",
+ "@esbuild/darwin-x64": "0.28.1",
+ "@esbuild/freebsd-arm64": "0.28.1",
+ "@esbuild/freebsd-x64": "0.28.1",
+ "@esbuild/linux-arm": "0.28.1",
+ "@esbuild/linux-arm64": "0.28.1",
+ "@esbuild/linux-ia32": "0.28.1",
+ "@esbuild/linux-loong64": "0.28.1",
+ "@esbuild/linux-mips64el": "0.28.1",
+ "@esbuild/linux-ppc64": "0.28.1",
+ "@esbuild/linux-riscv64": "0.28.1",
+ "@esbuild/linux-s390x": "0.28.1",
+ "@esbuild/linux-x64": "0.28.1",
+ "@esbuild/netbsd-arm64": "0.28.1",
+ "@esbuild/netbsd-x64": "0.28.1",
+ "@esbuild/openbsd-arm64": "0.28.1",
+ "@esbuild/openbsd-x64": "0.28.1",
+ "@esbuild/openharmony-arm64": "0.28.1",
+ "@esbuild/sunos-x64": "0.28.1",
+ "@esbuild/win32-arm64": "0.28.1",
+ "@esbuild/win32-ia32": "0.28.1",
+ "@esbuild/win32-x64": "0.28.1"
+ }
+ },
+ "node_modules/escalade": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
+ "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/escape-html": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
+ "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==",
+ "license": "MIT"
+ },
+ "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/etag": {
+ "version": "1.8.1",
+ "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
+ "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "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/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/fast-deep-equal": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
+ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
+ "license": "MIT"
+ },
+ "node_modules/fast-string-truncated-width": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/fast-string-truncated-width/-/fast-string-truncated-width-3.0.3.tgz",
+ "integrity": "sha512-0jjjIEL6+0jag3l2XWWizO64/aZVtpiGE3t0Zgqxv0DPuxiMjvB3M24fCyhZUO4KomJQPj3LTSUnDP3GpdwC0g==",
+ "license": "MIT"
+ },
+ "node_modules/fast-string-width": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/fast-string-width/-/fast-string-width-3.0.2.tgz",
+ "integrity": "sha512-gX8LrtNEI5hq8DVUfRQMbr5lpaS4nMIWV+7XEbXk2b8kiQIizgnlr12B4dA3ZEx3308ze0O4Q1R+cHts8kyUJg==",
+ "license": "MIT",
+ "dependencies": {
+ "fast-string-truncated-width": "^3.0.2"
+ }
+ },
+ "node_modules/fast-uri": {
+ "version": "3.1.5",
+ "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.5.tgz",
+ "integrity": "sha512-gHwA1O9LDIcKunMKhObS/HimwtehO1nPUECKAu5TpKgaO19fcWEl4bliWe1jWxVFvIXztJjjQ4L8XQ1EU9f7Jw==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/fastify"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/fastify"
+ }
+ ],
+ "license": "BSD-3-Clause"
+ },
+ "node_modules/fast-wrap-ansi": {
+ "version": "0.2.2",
+ "resolved": "https://registry.npmjs.org/fast-wrap-ansi/-/fast-wrap-ansi-0.2.2.tgz",
+ "integrity": "sha512-7F2Fl+TjRSenLqlU3UjSH0iyqopqoZIu7eZVpEirP2g1GtWa2G/ecEmBdgz31+Mxr+ELclgg6sokpSFIQiZ02Q==",
+ "license": "MIT",
+ "dependencies": {
+ "fast-string-width": "^3.0.2"
+ }
+ },
+ "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.3",
+ "resolved": "https://registry.npmjs.org/fontkitten/-/fontkitten-1.0.3.tgz",
+ "integrity": "sha512-Wp1zXWPVUPBmfoa3Cqc9ctaKuzKAV6uLstRqlR56kSjplf5uAce+qeyYym7F+PHbGTk+tCEdkCW6RD7DX/gBZw==",
+ "license": "MIT",
+ "dependencies": {
+ "tiny-inflate": "^1.0.3"
+ },
+ "engines": {
+ "node": ">=20"
+ }
+ },
+ "node_modules/framer-motion": {
+ "version": "12.43.0",
+ "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.43.0.tgz",
+ "integrity": "sha512-1eaL3RvR/kAlbG7UYcpMptEyzPoENO0c6w7ZnB3/hh2vSAz/6uGAFn6fdoqTBguNstf3MsFhJHsD/0DHiclG+g==",
+ "license": "MIT",
+ "dependencies": {
+ "motion-dom": "^12.43.0",
+ "motion-utils": "^12.39.0",
+ "tslib": "^2.4.0"
+ },
+ "peerDependencies": {
+ "@emotion/is-prop-valid": "*",
+ "react": "^18.0.0 || ^19.0.0",
+ "react-dom": "^18.0.0 || ^19.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@emotion/is-prop-valid": {
+ "optional": true
+ },
+ "react": {
+ "optional": true
+ },
+ "react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/fresh": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz",
+ "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "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/gensync": {
+ "version": "1.0.0-beta.2",
+ "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
+ "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/get-caller-file": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
+ "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
+ "license": "ISC",
+ "engines": {
+ "node": "6.* || 8.* || >= 10.*"
+ }
+ },
+ "node_modules/get-east-asian-width": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.6.0.tgz",
+ "integrity": "sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/get-tsconfig": {
+ "version": "5.0.0-beta.4",
+ "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-5.0.0-beta.4.tgz",
+ "integrity": "sha512-7nF7C9fIPFEMHgEMEfgIlO9wDdZ8CyHw27rWciFZfHvHDReIiPhsYuzPRXsfvBCqFy1l8RRyyWV7QLM+ZhUJsQ==",
+ "license": "MIT",
+ "dependencies": {
+ "resolve-pkg-maps": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=20.20.0"
+ },
+ "funding": {
+ "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1"
+ }
+ },
+ "node_modules/github-slugger": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/github-slugger/-/github-slugger-2.0.0.tgz",
+ "integrity": "sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw==",
+ "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==",
+ "license": "ISC"
+ },
+ "node_modules/h3": {
+ "version": "1.15.11",
+ "resolved": "https://registry.npmjs.org/h3/-/h3-1.15.11.tgz",
+ "integrity": "sha512-L3THSe2MPeBwgIZVSH5zLdBBU90TOxarvhK9d04IDY2AmVS8j2Jz2LIWtwsGOU3lu2I5jCN7FNvVfY2+XyF+mg==",
+ "license": "MIT",
+ "dependencies": {
+ "cookie-es": "^1.2.3",
+ "crossws": "^0.3.5",
+ "defu": "^6.1.6",
+ "destr": "^2.0.5",
+ "iron-webcrypto": "^1.2.1",
+ "node-mock-http": "^1.0.4",
+ "radix3": "^1.1.2",
+ "ufo": "^1.6.3",
+ "uncrypto": "^0.1.3"
+ }
+ },
+ "node_modules/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-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-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-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-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-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-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/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/http-errors": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz",
+ "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==",
+ "license": "MIT",
+ "dependencies": {
+ "depd": "~2.0.0",
+ "inherits": "~2.0.4",
+ "setprototypeof": "~1.2.0",
+ "statuses": "~2.0.2",
+ "toidentifier": "~1.0.1"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/inherits": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
+ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
+ "license": "ISC"
+ },
+ "node_modules/inline-style-parser": {
+ "version": "0.2.7",
+ "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.2.7.tgz",
+ "integrity": "sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA==",
+ "license": "MIT"
+ },
+ "node_modules/iron-webcrypto": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/iron-webcrypto/-/iron-webcrypto-1.2.1.tgz",
+ "integrity": "sha512-feOM6FaSr6rEABp/eDfVseKyTMDt+KGpeB35SkVn9Tyn0CqvVsY3EwI0v5i8nMHyJnzCIQf7nsy3p41TPkJZhg==",
+ "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-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": "4.0.0",
+ "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-4.0.0.tgz",
+ "integrity": "sha512-LHE+wROyG/Y/0ZnbktRCoTix2c1RhgWaZraMZ8o1Q7zCh0VSrICJQO5oqIIISrcSBtrXv0o233w1IYwsWCjTzA==",
+ "license": "MIT",
+ "bin": {
+ "is-docker": "cli.js"
+ },
+ "engines": {
+ "node": ">=20"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "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-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/jiti": {
+ "version": "2.7.0",
+ "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.7.0.tgz",
+ "integrity": "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==",
+ "license": "MIT",
+ "bin": {
+ "jiti": "lib/jiti-cli.mjs"
+ }
+ },
+ "node_modules/js-tokens": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
+ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
+ "license": "MIT"
+ },
+ "node_modules/js-yaml": {
+ "version": "4.3.1",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.1.tgz",
+ "integrity": "sha512-CY6crGq313MX8GkwvB7tzgp99vjQxY1++5y10/BKN/GUfHqWaOGQMNZkBvqSzsZKWk/ijwHlWzzkLulsGHhjWQ==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/puzrin"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/nodeca"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "argparse": "^2.0.1"
+ },
+ "bin": {
+ "js-yaml": "bin/js-yaml.js"
+ }
+ },
+ "node_modules/jsesc": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz",
+ "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==",
+ "license": "MIT",
+ "bin": {
+ "jsesc": "bin/jsesc"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/json-schema-traverse": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
+ "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
+ "license": "MIT"
+ },
+ "node_modules/json5": {
+ "version": "2.2.3",
+ "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
+ "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
+ "license": "MIT",
+ "bin": {
+ "json5": "lib/cli.js"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/jsonc-parser": {
+ "version": "3.3.1",
+ "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.3.1.tgz",
+ "integrity": "sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==",
+ "license": "MIT"
+ },
+ "node_modules/kleur": {
+ "version": "4.1.5",
+ "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz",
+ "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/lightningcss": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz",
+ "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==",
+ "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.32.0",
+ "lightningcss-darwin-arm64": "1.32.0",
+ "lightningcss-darwin-x64": "1.32.0",
+ "lightningcss-freebsd-x64": "1.32.0",
+ "lightningcss-linux-arm-gnueabihf": "1.32.0",
+ "lightningcss-linux-arm64-gnu": "1.32.0",
+ "lightningcss-linux-arm64-musl": "1.32.0",
+ "lightningcss-linux-x64-gnu": "1.32.0",
+ "lightningcss-linux-x64-musl": "1.32.0",
+ "lightningcss-win32-arm64-msvc": "1.32.0",
+ "lightningcss-win32-x64-msvc": "1.32.0"
+ }
+ },
+ "node_modules/lightningcss-android-arm64": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz",
+ "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==",
+ "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.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz",
+ "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==",
+ "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.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz",
+ "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==",
+ "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.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz",
+ "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==",
+ "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.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz",
+ "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==",
+ "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.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz",
+ "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==",
+ "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.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz",
+ "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==",
+ "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.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz",
+ "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==",
+ "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.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz",
+ "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==",
+ "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.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz",
+ "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==",
+ "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.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz",
+ "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==",
+ "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/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": "5.1.1",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
+ "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
+ "license": "ISC",
+ "dependencies": {
+ "yallist": "^3.0.2"
+ }
+ },
+ "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.4",
+ "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.5.4.tgz",
+ "integrity": "sha512-llBEhWm1SacoRwgHUoQJYtwp4PBLF4faQi5TCpIGyGs9n4y5+juI0tDgyKIfpqxckRHaHzouUEph3THklWh03w==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.29.7",
+ "@babel/types": "^7.29.7",
+ "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/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-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.3",
+ "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.3.tgz",
+ "integrity": "sha512-W4mAWTvSlKvf8L6J+VN9yLSqQ9AOAAvHuoDAmPkz4dHf553m5gVj2ejadHJhoJmcmxEnOv6Pa8XJhpxE93kb8Q==",
+ "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-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.27.1",
+ "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.27.1.tgz",
+ "integrity": "sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==",
+ "license": "CC0-1.0"
+ },
+ "node_modules/micromark": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.2.tgz",
+ "integrity": "sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==",
+ "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-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-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/mime-db": {
+ "version": "1.54.0",
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz",
+ "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/mime-types": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz",
+ "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==",
+ "license": "MIT",
+ "dependencies": {
+ "mime-db": "^1.54.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/motion-dom": {
+ "version": "12.43.0",
+ "resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.43.0.tgz",
+ "integrity": "sha512-azKON4d9S65PEoFUiQTMTgPheEmzf2QngdRc50AKfJp9Q9mmcBVw22c8eMq9k8kxOFHdL7+WZY7N/5F/lwiDag==",
+ "license": "MIT",
+ "dependencies": {
+ "motion-utils": "^12.39.0"
+ }
+ },
+ "node_modules/motion-utils": {
+ "version": "12.39.0",
+ "resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-12.39.0.tgz",
+ "integrity": "sha512-8nadJAJjTtqRkmRF36FoJTrywK9nnFmnPwnSMyxaOCU7GDjN9RTMJIxx9De8ErM+vpPhMccr/6fo5WciyQLnMQ==",
+ "license": "MIT"
+ },
+ "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/muggle-string": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/muggle-string/-/muggle-string-0.4.1.tgz",
+ "integrity": "sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==",
+ "license": "MIT"
+ },
+ "node_modules/nanoid": {
+ "version": "3.3.17",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.17.tgz",
+ "integrity": "sha512-xQLf0A3HOMlgHq0n247/LRuAOYmB7dXJ/DvAxGvsSBij45XtBSmQycu+F8ODbHwns/XyFZagyL1+J0Offw1E0g==",
+ "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": "1.0.1",
+ "resolved": "https://registry.npmjs.org/neotraverse/-/neotraverse-1.0.1.tgz",
+ "integrity": "sha512-WmmLty1YWwJl9yZi77v2dVIV6X2kuYV8YYBI/G3LWGKdGHmHUvL1z7FW0iDvEvGAwNEoc5x1tOOOyDnf5jJw/w==",
+ "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.5",
+ "resolved": "https://registry.npmjs.org/node-mock-http/-/node-mock-http-1.0.5.tgz",
+ "integrity": "sha512-KQyt/wLjG3TAc7DOUhpqWzgd4ERxR80JOlTK5VE5R1S12IaPVN5qkj4klBce9HPG1Njuup4Sb5bljaT34lIyjw==",
+ "license": "MIT"
+ },
+ "node_modules/node-releases": {
+ "version": "2.0.53",
+ "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.53.tgz",
+ "integrity": "sha512-D9UOmYG3UH1V+ENW56t5QXBwJw1YEY18ruVeus89Rw+SyIgjPkCO84bRzO3uNIYosJbNwiabWVn48o3uJLjxFQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/nodemailer": {
+ "version": "9.0.4",
+ "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-9.0.4.tgz",
+ "integrity": "sha512-LmJNRVRtfSCULxcZpy0Cpg4WWenlUZ9+zbmTO+S7v9wD6XreYLjXRFtDjtV/4F0HT5p1GyZfA0Ux/myxHb18CQ==",
+ "license": "MIT-0",
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "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/obug": {
+ "version": "2.1.4",
+ "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.4.tgz",
+ "integrity": "sha512-4a+OsYv9UktOJKE+l1A4OufDgdRF9PifWj+tJnHURo/P+WOxpG4GzUFL9qCalmWauao6ogiG+QvnCovwPoyAWA==",
+ "funding": [
+ "https://github.com/sponsors/sxzz",
+ "https://opencollective.com/debug"
+ ],
+ "license": "MIT",
+ "engines": {
+ "node": ">=12.20.0"
+ }
+ },
+ "node_modules/ofetch": {
+ "version": "1.5.1",
+ "resolved": "https://registry.npmjs.org/ofetch/-/ofetch-1.5.1.tgz",
+ "integrity": "sha512-2W4oUZlVaqAPAil6FUg/difl6YhqhUR7x2eZY4bQCko22UXg3hptq9KLQdqFClV+Wu85UX7hNtdGTngi/1BxcA==",
+ "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/on-finished": {
+ "version": "2.4.1",
+ "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
+ "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==",
+ "license": "MIT",
+ "dependencies": {
+ "ee-first": "1.1.1"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/oniguruma-parser": {
+ "version": "0.12.2",
+ "resolved": "https://registry.npmjs.org/oniguruma-parser/-/oniguruma-parser-0.12.2.tgz",
+ "integrity": "sha512-6HVa5oIrgMC6aA6WF6XyyqbhRPJrKR02L20+2+zpDtO5QAzGHAUGw5TKQvwi5vctNnRHkJYmjAhRVQF2EKdTQw==",
+ "license": "MIT"
+ },
+ "node_modules/oniguruma-to-es": {
+ "version": "4.3.6",
+ "resolved": "https://registry.npmjs.org/oniguruma-to-es/-/oniguruma-to-es-4.3.6.tgz",
+ "integrity": "sha512-csuQ9x3Yr0cEIs/Zgx/OEt9iBw9vqIunAPQkx19R/fiMq2oGVTgcMqO/V3Ybqefr1TBvosI6jU539ksaBULJyA==",
+ "license": "MIT",
+ "dependencies": {
+ "oniguruma-parser": "^0.12.2",
+ "regex": "^6.1.0",
+ "regex-recursion": "^6.0.2"
+ }
+ },
+ "node_modules/p-limit": {
+ "version": "7.3.1",
+ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-7.3.1.tgz",
+ "integrity": "sha512-0trZaiG7Y7kN/Egy9a8j47t9osC0Tch4PaIWd9yGF6bvmlk7muExRvGNYb8sXBwEKMoNKsbNN9P8EefuQekE4Q==",
+ "license": "MIT",
+ "dependencies": {
+ "yocto-queue": "^1.2.1"
+ },
+ "engines": {
+ "node": ">=20"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/p-queue": {
+ "version": "9.3.3",
+ "resolved": "https://registry.npmjs.org/p-queue/-/p-queue-9.3.3.tgz",
+ "integrity": "sha512-NXAOdnEe5FsZJfT4oK84lE1Y5cFFdWlRuOo5tww8DyNMxyRXwn39fIkUtNLKppcPC+UYU/bXujNCUGDv01y7CA==",
+ "license": "MIT",
+ "dependencies": {
+ "eventemitter3": "^5.0.4",
+ "p-timeout": "^7.0.0"
+ },
+ "engines": {
+ "node": ">=20"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/p-timeout": {
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-7.0.1.tgz",
+ "integrity": "sha512-AxTM2wDGORHGEkPCt8yqxOTMgpfbEHqF51f/5fJCmwFC3C/zNcGT63SymH2ttOAaiIws2zVg4+izQCjrakcwHg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=20"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/package-manager-detector": {
+ "version": "1.8.0",
+ "resolved": "https://registry.npmjs.org/package-manager-detector/-/package-manager-detector-1.8.0.tgz",
+ "integrity": "sha512-yQA4H19AmPEoMUeavPMDIe1higySl/gH/yaQrkT/s07Qp+7pp2hYz30N3z2l5BkjVkF9Ow6o0wjJamm2y7Sn0A==",
+ "license": "MIT"
+ },
+ "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-browserify": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz",
+ "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==",
+ "license": "MIT"
+ },
+ "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.5",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz",
+ "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/postcss": {
+ "version": "8.5.26",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.26.tgz",
+ "integrity": "sha512-u82N74LFzG8ca+dD8puPnplTXoGH4fTPpVGuIbt36G3qvNlkvfD0lEAZSxaly3KX8TS/L1A1gsCEmvKmBcVbkQ==",
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/postcss"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "nanoid": "^3.3.17",
+ "picocolors": "^1.1.1",
+ "source-map-js": "^1.2.1"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >=14"
+ }
+ },
+ "node_modules/prettier": {
+ "version": "3.9.6",
+ "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.9.6.tgz",
+ "integrity": "sha512-OpN0zzVdiaiAhxpuuj5efpIS4sY9j7bY6uR5mnj5yPzGkdkjNKSJeUThPb60Jw29QuAZgA4o+/iB49kFiaBX6g==",
+ "license": "MIT",
+ "bin": {
+ "prettier": "bin/prettier.cjs"
+ },
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/prettier/prettier?sponsor=1"
+ }
+ },
+ "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/process-ancestry": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/process-ancestry/-/process-ancestry-0.1.0.tgz",
+ "integrity": "sha512-tGqJW/UnclpYASFcM6Xh8D8l/BMtaQ9+CSG0vlJSJTcdMM4lDRv4c6H0Pdcsfted+bVczdYSfk2fdukg2gQkZg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18.0.0"
+ }
+ },
+ "node_modules/property-information": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/property-information/-/property-information-7.2.0.tgz",
+ "integrity": "sha512-IAtzIB6sUiWaJYrX9smp3V46pBGbBeLFRGdh25kg1334VcBlD8HzhPeNIWQH9zhGmo2itIe25EHt9dQP7G5hmg==",
+ "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/range-parser": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.3.0.tgz",
+ "integrity": "sha512-hek2mFQpPuI4E1BBKrSto+BU3e3x4xuarsbiwr3+lf7p44juvFMV0XFWQAP3xUyqXA4RrXLIoaSUGbSt056ZMw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/react": {
+ "version": "19.2.8",
+ "resolved": "https://registry.npmjs.org/react/-/react-19.2.8.tgz",
+ "integrity": "sha512-PWaYA1L/q9u2u7xYQi+Y3L3Yfnie7XyLeaJICV1MGD6LprsBxcAqGjYyr0eY3p+QdsA+x/Irkt4Qif8D63+Sbw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/react-dom": {
+ "version": "19.2.8",
+ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.8.tgz",
+ "integrity": "sha512-rVprimfGBG3DR+Tq0IQG2DT5PxKth1WIGDmj5yPmlzr4YBe7uyE+Du4oVqTDXZSHGGGXRtTJEGSSePyQCMBglQ==",
+ "license": "MIT",
+ "dependencies": {
+ "scheduler": "^0.27.0"
+ },
+ "peerDependencies": {
+ "react": "^19.2.8"
+ }
+ },
+ "node_modules/react-refresh": {
+ "version": "0.18.0",
+ "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.18.0.tgz",
+ "integrity": "sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/readdirp": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz",
+ "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 14.18.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-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-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-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.3",
+ "resolved": "https://registry.npmjs.org/remark-smartypants/-/remark-smartypants-3.0.3.tgz",
+ "integrity": "sha512-gCaK+ndZ0hYezlqFegHFCVh2CQemsi0Npdh1qVM9bxlUFknjkbP6VmojWhddOCrbK0PbbacmYLWfTULRiT1eWA==",
+ "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/request-light": {
+ "version": "0.7.0",
+ "resolved": "https://registry.npmjs.org/request-light/-/request-light-0.7.0.tgz",
+ "integrity": "sha512-lMbBMrDoxgsyO+yB3sDcrDuX85yYt7sS8BfQd11jtbW/z5ZWgLZRcEGLsLoYw7I0WSUGQBs8CC8ScIxkTX1+6Q==",
+ "license": "MIT"
+ },
+ "node_modules/require-from-string": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz",
+ "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/resolve-pkg-maps": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz",
+ "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1"
+ }
+ },
+ "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/rolldown": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.2.3.tgz",
+ "integrity": "sha512-rn9wpmxplLf7NLNyCk9FyWh3FM43DbY8jOzCdEPzH7uflhTftRbCEpqi6Ly2osgoU8OwObtmavMbWLaWy4LX7A==",
+ "license": "MIT",
+ "dependencies": {
+ "@oxc-project/types": "=0.143.0",
+ "@rolldown/pluginutils": "^1.0.0"
+ },
+ "bin": {
+ "rolldown": "bin/cli.mjs"
+ },
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ },
+ "optionalDependencies": {
+ "@rolldown/binding-android-arm64": "1.2.3",
+ "@rolldown/binding-darwin-arm64": "1.2.3",
+ "@rolldown/binding-darwin-x64": "1.2.3",
+ "@rolldown/binding-freebsd-x64": "1.2.3",
+ "@rolldown/binding-linux-arm-gnueabihf": "1.2.3",
+ "@rolldown/binding-linux-arm64-gnu": "1.2.3",
+ "@rolldown/binding-linux-arm64-musl": "1.2.3",
+ "@rolldown/binding-linux-ppc64-gnu": "1.2.3",
+ "@rolldown/binding-linux-s390x-gnu": "1.2.3",
+ "@rolldown/binding-linux-x64-gnu": "1.2.3",
+ "@rolldown/binding-linux-x64-musl": "1.2.3",
+ "@rolldown/binding-openharmony-arm64": "1.2.3",
+ "@rolldown/binding-win32-arm64-msvc": "1.2.3",
+ "@rolldown/binding-win32-x64-msvc": "1.2.3"
+ }
+ },
+ "node_modules/rolldown/node_modules/@rolldown/pluginutils": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz",
+ "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==",
+ "license": "MIT"
+ },
+ "node_modules/satteri": {
+ "version": "0.9.5",
+ "resolved": "https://registry.npmjs.org/satteri/-/satteri-0.9.5.tgz",
+ "integrity": "sha512-ZuWVl+vnM64y+/TtX8Kosv2c00W+hLQiiwnEL6H0UKVVrxFqMw4D2CJHHQaouVd89OAhtBBfjWLqhKi3TVUV4w==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/estree-jsx": "^1.0.5",
+ "@types/hast": "^3.0.4",
+ "@types/mdast": "^4.0.4",
+ "@types/unist": "^3.0.3"
+ },
+ "optionalDependencies": {
+ "@bruits/satteri-darwin-arm64": "0.9.5",
+ "@bruits/satteri-darwin-x64": "0.9.5",
+ "@bruits/satteri-linux-arm64-gnu": "0.9.5",
+ "@bruits/satteri-linux-arm64-musl": "0.9.5",
+ "@bruits/satteri-linux-x64-gnu": "0.9.5",
+ "@bruits/satteri-linux-x64-musl": "0.9.5",
+ "@bruits/satteri-wasm32-wasi": "0.9.5",
+ "@bruits/satteri-win32-arm64-msvc": "0.9.5",
+ "@bruits/satteri-win32-x64-msvc": "0.9.5"
+ }
+ },
+ "node_modules/sax": {
+ "version": "1.6.1",
+ "resolved": "https://registry.npmjs.org/sax/-/sax-1.6.1.tgz",
+ "integrity": "sha512-42tBVwLWnaQvW5zc4HbZrTuWccECCZfBi92FDuwtqxasH+JbPB3/FOKb1m222K42R4WxuxzzMsTswfzgtSu64Q==",
+ "license": "BlueOak-1.0.0",
+ "engines": {
+ "node": ">=11.0.0"
+ }
+ },
+ "node_modules/scheduler": {
+ "version": "0.27.0",
+ "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz",
+ "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==",
+ "license": "MIT"
+ },
+ "node_modules/semver": {
+ "version": "6.3.1",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
+ "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ }
+ },
+ "node_modules/send": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz",
+ "integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==",
+ "license": "MIT",
+ "dependencies": {
+ "debug": "^4.4.3",
+ "encodeurl": "^2.0.0",
+ "escape-html": "^1.0.3",
+ "etag": "^1.8.1",
+ "fresh": "^2.0.0",
+ "http-errors": "^2.0.1",
+ "mime-types": "^3.0.2",
+ "ms": "^2.1.3",
+ "on-finished": "^2.4.1",
+ "range-parser": "^1.2.1",
+ "statuses": "^2.0.2"
+ },
+ "engines": {
+ "node": ">= 18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/server-destroy": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/server-destroy/-/server-destroy-1.0.1.tgz",
+ "integrity": "sha512-rb+9B5YBIEzYcD6x2VKidaa+cqYBJQKnU4oe4E3ANwRRN56yk/ua1YCJT1n21NTS8w6CcOclAKNP3PhdCXKYtQ==",
+ "license": "ISC"
+ },
+ "node_modules/setprototypeof": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
+ "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==",
+ "license": "ISC"
+ },
+ "node_modules/sharp": {
+ "version": "0.35.3",
+ "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.35.3.tgz",
+ "integrity": "sha512-ej0zVHuZGHCiABXcNxeYhpRnPNPAcvbG8RMdBAhDAxLKkCRVSpK3Iyu7qbqw3JMzoj0REeM6f3tJLtVwl0023Q==",
+ "license": "Apache-2.0",
+ "optional": true,
+ "dependencies": {
+ "@img/colour": "^1.1.0",
+ "detect-libc": "^2.1.2",
+ "semver": "^7.8.5"
+ },
+ "engines": {
+ "node": ">=20.9.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-darwin-arm64": "0.35.3",
+ "@img/sharp-darwin-x64": "0.35.3",
+ "@img/sharp-freebsd-wasm32": "0.35.3",
+ "@img/sharp-libvips-darwin-arm64": "1.3.2",
+ "@img/sharp-libvips-darwin-x64": "1.3.2",
+ "@img/sharp-libvips-linux-arm": "1.3.2",
+ "@img/sharp-libvips-linux-arm64": "1.3.2",
+ "@img/sharp-libvips-linux-ppc64": "1.3.2",
+ "@img/sharp-libvips-linux-riscv64": "1.3.2",
+ "@img/sharp-libvips-linux-s390x": "1.3.2",
+ "@img/sharp-libvips-linux-x64": "1.3.2",
+ "@img/sharp-libvips-linuxmusl-arm64": "1.3.2",
+ "@img/sharp-libvips-linuxmusl-x64": "1.3.2",
+ "@img/sharp-linux-arm": "0.35.3",
+ "@img/sharp-linux-arm64": "0.35.3",
+ "@img/sharp-linux-ppc64": "0.35.3",
+ "@img/sharp-linux-riscv64": "0.35.3",
+ "@img/sharp-linux-s390x": "0.35.3",
+ "@img/sharp-linux-x64": "0.35.3",
+ "@img/sharp-linuxmusl-arm64": "0.35.3",
+ "@img/sharp-linuxmusl-x64": "0.35.3",
+ "@img/sharp-webcontainers-wasm32": "0.35.3",
+ "@img/sharp-win32-arm64": "0.35.3",
+ "@img/sharp-win32-ia32": "0.35.3",
+ "@img/sharp-win32-x64": "0.35.3"
+ },
+ "peerDependenciesMeta": {
+ "@types/node": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/sharp/node_modules/semver": {
+ "version": "7.8.5",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz",
+ "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==",
+ "license": "ISC",
+ "optional": true,
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/shiki": {
+ "version": "4.4.2",
+ "resolved": "https://registry.npmjs.org/shiki/-/shiki-4.4.2.tgz",
+ "integrity": "sha512-P8F/dFhRevaw2uSdeIYlq/5SXZNY85DPtmXQ947gD1Zj2JqO5AkNvVVBar0Me9JkFx3uzVud/qOtP5ek9NEQGA==",
+ "license": "MIT",
+ "dependencies": {
+ "@shikijs/core": "4.4.2",
+ "@shikijs/engine-javascript": "4.4.2",
+ "@shikijs/engine-oniguruma": "4.4.2",
+ "@shikijs/langs": "4.4.2",
+ "@shikijs/themes": "4.4.2",
+ "@shikijs/types": "4.4.2",
+ "@shikijs/vscode-textmate": "^10.0.2",
+ "@types/hast": "^3.0.5"
+ },
+ "engines": {
+ "node": ">=20"
+ }
+ },
+ "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/smol-toml": {
+ "version": "1.7.1",
+ "resolved": "https://registry.npmjs.org/smol-toml/-/smol-toml-1.7.1.tgz",
+ "integrity": "sha512-PPlsspAZ4jbMBu5DMFhfUGDQLu/vrL4SyBROVS37x8ynnVmFIs1VPBz1Co8Xks3TvpIaZXmU85y4DrQ+UyVFoQ==",
+ "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/statuses": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz",
+ "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/string-width": {
+ "version": "8.2.2",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-8.2.2.tgz",
+ "integrity": "sha512-GaPUh5gfdrYzqeVNZvUfT23vYYxXzKYidUcnMtJg/3rxRV63EFZy3k6xfKlmfeJD0176lnUV/Usr3XcwSvFzpg==",
+ "license": "MIT",
+ "dependencies": {
+ "get-east-asian-width": "^1.5.0",
+ "strip-ansi": "^7.1.2"
+ },
+ "engines": {
+ "node": ">=20"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/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.2.0",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz",
+ "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==",
+ "license": "MIT",
+ "dependencies": {
+ "ansi-regex": "^6.2.2"
+ },
+ "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/svgo": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/svgo/-/svgo-4.0.2.tgz",
+ "integrity": "sha512-ekx94z1rRc5LDi6oSUaeRnYhd0UOJxdtQCL2rF8xpWxD3TPAsISWOrxezqGovqS38GRZOdpDfvQe3ts6F7nsng==",
+ "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.5.0"
+ },
+ "bin": {
+ "svgo": "bin/svgo.js"
+ },
+ "engines": {
+ "node": ">=16"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/svgo"
+ }
+ },
+ "node_modules/tailwindcss": {
+ "version": "4.3.3",
+ "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.3.3.tgz",
+ "integrity": "sha512-gOhV3P7ufE62QDGg1zVaTgCR+EtPv92k2nIhVcVKcLmxT1sUBsQGhnZj175j+MqRt4zLF7ic+sCYjfhxMxj7YQ==",
+ "license": "MIT"
+ },
+ "node_modules/tapable": {
+ "version": "2.3.3",
+ "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.3.tgz",
+ "integrity": "sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==",
+ "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/tinyclip": {
+ "version": "0.1.15",
+ "resolved": "https://registry.npmjs.org/tinyclip/-/tinyclip-0.1.15.tgz",
+ "integrity": "sha512-uo33abH+Ays0xYaDysoBt494Hb3hsEczMpcC0MwFl773pazORx4fmvKhclhR1wonUbB6vvpRsvVMwnhfqeMc+A==",
+ "license": "MIT",
+ "engines": {
+ "node": "^16.14.0 || >= 17.3.0"
+ }
+ },
+ "node_modules/tinyexec": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.3.0.tgz",
+ "integrity": "sha512-QKAl9m8gWWGHV8jZcPeym6j+XULi6tOf1mT83WYJ4Lk2ytW/uwAWkrP0uFsdoYMdueVJ0qs26wZ+23xeB4ibNQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/tinyglobby": {
+ "version": "0.2.17",
+ "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz",
+ "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==",
+ "license": "MIT",
+ "dependencies": {
+ "fdir": "^6.5.0",
+ "picomatch": "^4.0.4"
+ },
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/SuperchupuDev"
+ }
+ },
+ "node_modules/toidentifier": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
+ "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.6"
+ }
+ },
+ "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/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"
+ },
+ "node_modules/typesafe-path": {
+ "version": "0.2.2",
+ "resolved": "https://registry.npmjs.org/typesafe-path/-/typesafe-path-0.2.2.tgz",
+ "integrity": "sha512-OJabfkAg1WLZSqJAJ0Z6Sdt3utnbzr/jh+NAHoyWHJe8CMSy79Gm085094M9nvTPy22KzTVn5Zq5mbapCI/hPA==",
+ "license": "MIT"
+ },
+ "node_modules/typescript": {
+ "version": "6.0.3",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz",
+ "integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==",
+ "license": "Apache-2.0",
+ "bin": {
+ "tsc": "bin/tsc",
+ "tsserver": "bin/tsserver"
+ },
+ "engines": {
+ "node": ">=14.17"
+ }
+ },
+ "node_modules/typescript-auto-import-cache": {
+ "version": "0.3.6",
+ "resolved": "https://registry.npmjs.org/typescript-auto-import-cache/-/typescript-auto-import-cache-0.3.6.tgz",
+ "integrity": "sha512-RpuHXrknHdVdK7wv/8ug3Fr0WNsNi5l5aB8MYYuXhq2UH5lnEB1htJ1smhtD5VeCsGr2p8mUDtd83LCQDFVgjQ==",
+ "license": "MIT",
+ "dependencies": {
+ "semver": "^7.3.8"
+ }
+ },
+ "node_modules/typescript-auto-import-cache/node_modules/semver": {
+ "version": "7.8.5",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz",
+ "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==",
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/ufo": {
+ "version": "1.6.4",
+ "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.4.tgz",
+ "integrity": "sha512-JFNbkD1Svwe0KvGi8GOeLcP4kAWQ609twvCdcHxq1oSL8svv39ZuSvajcD8B+5D0eL4+s1Is2D/O6KN3qcTeRA==",
+ "license": "MIT"
+ },
+ "node_modules/ultrahtml": {
+ "version": "1.7.0",
+ "resolved": "https://registry.npmjs.org/ultrahtml/-/ultrahtml-1.7.0.tgz",
+ "integrity": "sha512-2xRd0VHoAQE4M+vF/DvFFB7pUV0ZxTW1TLi7lHQWnF/Sb5TPeEUV/l+hxcNnGO00ZXGnR0voCMmYRKQf+rvJ2g==",
+ "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": "8.3.0",
+ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-8.3.0.tgz",
+ "integrity": "sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ==",
+ "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.5",
+ "resolved": "https://registry.npmjs.org/unstorage/-/unstorage-1.17.5.tgz",
+ "integrity": "sha512-0i3iqvRfx29hkNntHyQvJTpf5W9dQ9ZadSoRU8+xVlhVtT7jAX57fazYO9EHvcRCfBCyi5YRya7XCDOsbTgkPg==",
+ "license": "MIT",
+ "dependencies": {
+ "anymatch": "^3.1.3",
+ "chokidar": "^5.0.0",
+ "destr": "^2.0.5",
+ "h3": "^1.15.10",
+ "lru-cache": "^11.2.7",
+ "node-fetch-native": "^1.6.7",
+ "ofetch": "^1.5.1",
+ "ufo": "^1.6.3"
+ },
+ "peerDependencies": {
+ "@azure/app-configuration": "^1.8.0",
+ "@azure/cosmos": "^4.2.0",
+ "@azure/data-tables": "^13.3.0",
+ "@azure/identity": "^4.6.0",
+ "@azure/keyvault-secrets": "^4.9.0",
+ "@azure/storage-blob": "^12.26.0",
+ "@capacitor/preferences": "^6 || ^7 || ^8",
+ "@deno/kv": ">=0.9.0",
+ "@netlify/blobs": "^6.5.0 || ^7.0.0 || ^8.1.0 || ^9.0.0 || ^10.0.0",
+ "@planetscale/database": "^1.19.0",
+ "@upstash/redis": "^1.34.3",
+ "@vercel/blob": ">=0.27.1",
+ "@vercel/functions": "^2.2.12 || ^3.0.0",
+ "@vercel/kv": "^1 || ^2 || ^3",
+ "aws4fetch": "^1.0.20",
+ "db0": ">=0.2.1",
+ "idb-keyval": "^6.2.1",
+ "ioredis": "^5.4.2",
+ "uploadthing": "^7.4.4"
+ },
+ "peerDependenciesMeta": {
+ "@azure/app-configuration": {
+ "optional": true
+ },
+ "@azure/cosmos": {
+ "optional": true
+ },
+ "@azure/data-tables": {
+ "optional": true
+ },
+ "@azure/identity": {
+ "optional": true
+ },
+ "@azure/keyvault-secrets": {
+ "optional": true
+ },
+ "@azure/storage-blob": {
+ "optional": true
+ },
+ "@capacitor/preferences": {
+ "optional": true
+ },
+ "@deno/kv": {
+ "optional": true
+ },
+ "@netlify/blobs": {
+ "optional": true
+ },
+ "@planetscale/database": {
+ "optional": true
+ },
+ "@upstash/redis": {
+ "optional": true
+ },
+ "@vercel/blob": {
+ "optional": true
+ },
+ "@vercel/functions": {
+ "optional": true
+ },
+ "@vercel/kv": {
+ "optional": true
+ },
+ "aws4fetch": {
+ "optional": true
+ },
+ "db0": {
+ "optional": true
+ },
+ "idb-keyval": {
+ "optional": true
+ },
+ "ioredis": {
+ "optional": true
+ },
+ "uploadthing": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/unstorage/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/unstorage/node_modules/lru-cache": {
+ "version": "11.5.2",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.2.tgz",
+ "integrity": "sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g==",
+ "license": "BlueOak-1.0.0",
+ "engines": {
+ "node": "20 || >=22"
+ }
+ },
+ "node_modules/unstorage/node_modules/readdirp": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-5.1.1.tgz",
+ "integrity": "sha512-Kko+Y5XQ6fM+Ce3dq3m9YGxnacYZYl9cA1wZjaF3Vbry2L3i1qVg8+CAgNPsXRArPMUMCaOR7oa9Nqntc43JKA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 20.19.0"
+ },
+ "funding": {
+ "type": "individual",
+ "url": "https://paulmillr.com/funding/"
+ }
+ },
+ "node_modules/update-browserslist-db": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz",
+ "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==",
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/browserslist"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "escalade": "^3.2.0",
+ "picocolors": "^1.1.1"
+ },
+ "bin": {
+ "update-browserslist-db": "cli.js"
+ },
+ "peerDependencies": {
+ "browserslist": ">= 4.21.0"
+ }
+ },
+ "node_modules/vfile": {
+ "version": "6.0.3",
+ "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz",
+ "integrity": "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==",
+ "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": "8.2.1",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-8.2.1.tgz",
+ "integrity": "sha512-EU/eS7BH3XROHh2YnBefjM6DBKA6ZeMZEYQbj7NLWg5wHYlhB8B/Mayd5XsgWq+NFYccDOTemRpdETWR6Ka/lw==",
+ "license": "MIT",
+ "dependencies": {
+ "lightningcss": "^1.33.0",
+ "picomatch": "^4.0.5",
+ "postcss": "^8.5.25",
+ "rolldown": "~1.2.1",
+ "tinyglobby": "^0.2.17"
+ },
+ "bin": {
+ "vite": "bin/vite.js"
+ },
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ },
+ "funding": {
+ "url": "https://github.com/vitejs/vite?sponsor=1"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.3"
+ },
+ "peerDependencies": {
+ "@types/node": "^20.19.0 || >=22.12.0",
+ "@vitejs/devtools": "^0.4.0",
+ "esbuild": "^0.27.0 || ^0.28.0",
+ "jiti": ">=1.21.0",
+ "less": "^4.0.0",
+ "sass": "^1.70.0",
+ "sass-embedded": "^1.70.0",
+ "stylus": ">=0.54.8",
+ "sugarss": "^5.0.0",
+ "terser": "^5.16.0",
+ "tsx": "^4.8.1",
+ "yaml": "^2.4.2"
+ },
+ "peerDependenciesMeta": {
+ "@types/node": {
+ "optional": true
+ },
+ "@vitejs/devtools": {
+ "optional": true
+ },
+ "esbuild": {
+ "optional": true
+ },
+ "jiti": {
+ "optional": true
+ },
+ "less": {
+ "optional": true
+ },
+ "sass": {
+ "optional": true
+ },
+ "sass-embedded": {
+ "optional": true
+ },
+ "stylus": {
+ "optional": true
+ },
+ "sugarss": {
+ "optional": true
+ },
+ "terser": {
+ "optional": true
+ },
+ "tsx": {
+ "optional": true
+ },
+ "yaml": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/vite/node_modules/lightningcss": {
+ "version": "1.33.0",
+ "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.33.0.tgz",
+ "integrity": "sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA==",
+ "license": "MPL-2.0",
+ "dependencies": {
+ "detect-libc": "^2.0.3"
+ },
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ },
+ "optionalDependencies": {
+ "lightningcss-android-arm64": "1.33.0",
+ "lightningcss-darwin-arm64": "1.33.0",
+ "lightningcss-darwin-x64": "1.33.0",
+ "lightningcss-freebsd-x64": "1.33.0",
+ "lightningcss-linux-arm-gnueabihf": "1.33.0",
+ "lightningcss-linux-arm64-gnu": "1.33.0",
+ "lightningcss-linux-arm64-musl": "1.33.0",
+ "lightningcss-linux-x64-gnu": "1.33.0",
+ "lightningcss-linux-x64-musl": "1.33.0",
+ "lightningcss-win32-arm64-msvc": "1.33.0",
+ "lightningcss-win32-x64-msvc": "1.33.0"
+ }
+ },
+ "node_modules/vite/node_modules/lightningcss-android-arm64": {
+ "version": "1.33.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.33.0.tgz",
+ "integrity": "sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/vite/node_modules/lightningcss-darwin-arm64": {
+ "version": "1.33.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.33.0.tgz",
+ "integrity": "sha512-Sciaz8eenNTKn9b3t7+xr0ipTp9YxKQY4npwQ3mrRuL0BAVHBLyZxofhaKBAVtzmtRZ/zTyo0/to4B1uWG/Djg==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/vite/node_modules/lightningcss-darwin-x64": {
+ "version": "1.33.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.33.0.tgz",
+ "integrity": "sha512-Z5UPAxzrjlWNNyGy6i65cJzzvgJ5D3T6wMvs+gWpY9d7qRhANrxqAp6LhxIgZhWEw18RfJTGcRxjuLIBr+m8XQ==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/vite/node_modules/lightningcss-freebsd-x64": {
+ "version": "1.33.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.33.0.tgz",
+ "integrity": "sha512-QQM/Ti/hQajJwCY+RiWuCZ9sdtI/XQk7nDK5vC8kkdwixezOlDgvDx7+RT+QjK6FcFT4MpsuoBnHIo/O3StRRg==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/vite/node_modules/lightningcss-linux-arm-gnueabihf": {
+ "version": "1.33.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.33.0.tgz",
+ "integrity": "sha512-N7FVBe6iS24MlM6R/4RBTxGhQheZGs7tiQ9U32UtF75NzP5Q7xWPRqLBCKxlRQRk3rY1jCIPLzx7WzOhuUIRLQ==",
+ "cpu": [
+ "arm"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/vite/node_modules/lightningcss-linux-arm64-gnu": {
+ "version": "1.33.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.33.0.tgz",
+ "integrity": "sha512-j2v/itmy4HlNxlc6voKXYgBqNi0Ng2LShg4z7GufpEgs05P+2suBVyi9I6YHq5uoVFx9ETin3eCEhLVyXGQnKg==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/vite/node_modules/lightningcss-linux-arm64-musl": {
+ "version": "1.33.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.33.0.tgz",
+ "integrity": "sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/vite/node_modules/lightningcss-linux-x64-gnu": {
+ "version": "1.33.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.33.0.tgz",
+ "integrity": "sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/vite/node_modules/lightningcss-linux-x64-musl": {
+ "version": "1.33.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.33.0.tgz",
+ "integrity": "sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/vite/node_modules/lightningcss-win32-arm64-msvc": {
+ "version": "1.33.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.33.0.tgz",
+ "integrity": "sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/vite/node_modules/lightningcss-win32-x64-msvc": {
+ "version": "1.33.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.33.0.tgz",
+ "integrity": "sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/vitefu": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/vitefu/-/vitefu-1.1.3.tgz",
+ "integrity": "sha512-ub4okH7Z5KLjb6hDyjqrGXqWtWvoYdU3IGm/NorpgHncKoLTCfRIbvlhBm7r0YstIaQRYlp4yEbFqDcKSzXSSg==",
+ "license": "MIT",
+ "workspaces": [
+ "tests/deps/*",
+ "tests/projects/*",
+ "tests/projects/workspace/packages/*"
+ ],
+ "peerDependencies": {
+ "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0"
+ },
+ "peerDependenciesMeta": {
+ "vite": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/volar-service-css": {
+ "version": "0.0.71",
+ "resolved": "https://registry.npmjs.org/volar-service-css/-/volar-service-css-0.0.71.tgz",
+ "integrity": "sha512-wRRFt9BpjMKCazcgOh67MSjUjiWUCAh99DyYSDIOTuxaRjEtDC7PpB0k1Y1wbJIW/pVtMUSVbpPo3UGSm0Byxw==",
+ "license": "MIT",
+ "dependencies": {
+ "vscode-css-languageservice": "^6.3.0",
+ "vscode-languageserver-textdocument": "^1.0.11",
+ "vscode-uri": "^3.0.8"
+ },
+ "peerDependencies": {
+ "@volar/language-service": "~2.4.0"
+ },
+ "peerDependenciesMeta": {
+ "@volar/language-service": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/volar-service-emmet": {
+ "version": "0.0.71",
+ "resolved": "https://registry.npmjs.org/volar-service-emmet/-/volar-service-emmet-0.0.71.tgz",
+ "integrity": "sha512-zqjzt6bN95e3CUstBm0PBFAJnrfz0ZAARka87fart46/gNCLLuP3Vujy8V/J8HEziTFLnfkgIASLFYPUhonJcA==",
+ "license": "MIT",
+ "dependencies": {
+ "@emmetio/css-parser": "^0.4.1",
+ "@emmetio/html-matcher": "^1.3.0",
+ "@vscode/emmet-helper": "^2.9.3",
+ "vscode-uri": "^3.0.8"
+ },
+ "peerDependencies": {
+ "@volar/language-service": "~2.4.0"
+ },
+ "peerDependenciesMeta": {
+ "@volar/language-service": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/volar-service-html": {
+ "version": "0.0.71",
+ "resolved": "https://registry.npmjs.org/volar-service-html/-/volar-service-html-0.0.71.tgz",
+ "integrity": "sha512-e8tHPhgQ7ooLfudAEIku+kgd9pWkq3SSz8RbnQDI1+Eb8wbenkLGHqoirLqz5ORLV6wIMr2Iv08RWBG5eOcgpw==",
+ "license": "MIT",
+ "dependencies": {
+ "vscode-html-languageservice": "^5.3.0",
+ "vscode-languageserver-textdocument": "^1.0.11",
+ "vscode-uri": "^3.0.8"
+ },
+ "peerDependencies": {
+ "@volar/language-service": "~2.4.0"
+ },
+ "peerDependenciesMeta": {
+ "@volar/language-service": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/volar-service-prettier": {
+ "version": "0.0.71",
+ "resolved": "https://registry.npmjs.org/volar-service-prettier/-/volar-service-prettier-0.0.71.tgz",
+ "integrity": "sha512-Rz7JVH3qD108UCdmIEiZvOBNljMt2nLFdbN8AXcDfn7xD9F5I2aCIsDVqBbXw21PsnxG0b7MfwtNF+zPS/NKUg==",
+ "license": "MIT",
+ "dependencies": {
+ "vscode-uri": "^3.0.8"
+ },
+ "peerDependencies": {
+ "@volar/language-service": "~2.4.0",
+ "prettier": "^2.2 || ^3.0"
+ },
+ "peerDependenciesMeta": {
+ "@volar/language-service": {
+ "optional": true
+ },
+ "prettier": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/volar-service-typescript": {
+ "version": "0.0.71",
+ "resolved": "https://registry.npmjs.org/volar-service-typescript/-/volar-service-typescript-0.0.71.tgz",
+ "integrity": "sha512-yTtM/BVT6hoyEYnDtaCyAtNhdNeS/mhTTABlBOdw3NNiRBUin3IznFJpgfjer4c6RYopiPjjQjc9VFhxVl1mLw==",
+ "license": "MIT",
+ "dependencies": {
+ "path-browserify": "^1.0.1",
+ "semver": "^7.6.2",
+ "typescript-auto-import-cache": "^0.3.5",
+ "vscode-languageserver-textdocument": "^1.0.11",
+ "vscode-nls": "^5.2.0",
+ "vscode-uri": "^3.0.8"
+ },
+ "peerDependencies": {
+ "@volar/language-service": "~2.4.0"
+ },
+ "peerDependenciesMeta": {
+ "@volar/language-service": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/volar-service-typescript-twoslash-queries": {
+ "version": "0.0.71",
+ "resolved": "https://registry.npmjs.org/volar-service-typescript-twoslash-queries/-/volar-service-typescript-twoslash-queries-0.0.71.tgz",
+ "integrity": "sha512-9K2k72s4n7rV9s4bX0MyjbX9iBribvKZbBJKuEmTCZfeWJXs6Yh7bGpY4eoc7UufAjvpheBqwyZCOIPBvxCv0A==",
+ "license": "MIT",
+ "dependencies": {
+ "vscode-uri": "^3.0.8"
+ },
+ "peerDependencies": {
+ "@volar/language-service": "~2.4.0"
+ },
+ "peerDependenciesMeta": {
+ "@volar/language-service": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/volar-service-typescript/node_modules/semver": {
+ "version": "7.8.5",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz",
+ "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==",
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/volar-service-yaml": {
+ "version": "0.0.71",
+ "resolved": "https://registry.npmjs.org/volar-service-yaml/-/volar-service-yaml-0.0.71.tgz",
+ "integrity": "sha512-qYGWGuVpUTnZGu5P/CR4KLK4aIR8RrcVnmfZ2eRcj9q/I8VZCoC5yy9FtEvfNvnDp4MU17yhdJcvpQPIqhJS2Q==",
+ "license": "MIT",
+ "dependencies": {
+ "vscode-uri": "^3.0.8",
+ "yaml-language-server": "~1.23.0"
+ },
+ "peerDependencies": {
+ "@volar/language-service": "~2.4.0"
+ },
+ "peerDependenciesMeta": {
+ "@volar/language-service": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/vscode-css-languageservice": {
+ "version": "6.3.10",
+ "resolved": "https://registry.npmjs.org/vscode-css-languageservice/-/vscode-css-languageservice-6.3.10.tgz",
+ "integrity": "sha512-eq5N9Er3fC4vA9zd9EFhyBG90wtCCuXgRSpAndaOgXMh1Wgep5lBgRIeDgjZBW9pa+332yC9+49cZMW8jcL3MA==",
+ "license": "MIT",
+ "dependencies": {
+ "@vscode/l10n": "^0.0.18",
+ "vscode-languageserver-textdocument": "^1.0.12",
+ "vscode-languageserver-types": "3.17.5",
+ "vscode-uri": "^3.1.0"
+ }
+ },
+ "node_modules/vscode-css-languageservice/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"
+ },
+ "node_modules/vscode-html-languageservice": {
+ "version": "5.6.2",
+ "resolved": "https://registry.npmjs.org/vscode-html-languageservice/-/vscode-html-languageservice-5.6.2.tgz",
+ "integrity": "sha512-ulCrSnFnfQ16YzvwnYUgEbUEl/ZG7u2eV27YhvLObSHKkb8fw1Z9cgsnUwjTEeDIdJDoTDTDpxuhQwoenoLNMg==",
+ "license": "MIT",
+ "dependencies": {
+ "@vscode/l10n": "^0.0.18",
+ "vscode-languageserver-textdocument": "^1.0.12",
+ "vscode-languageserver-types": "^3.17.5",
+ "vscode-uri": "^3.1.0"
+ }
+ },
+ "node_modules/vscode-json-languageservice": {
+ "version": "4.1.8",
+ "resolved": "https://registry.npmjs.org/vscode-json-languageservice/-/vscode-json-languageservice-4.1.8.tgz",
+ "integrity": "sha512-0vSpg6Xd9hfV+eZAaYN63xVVMOTmJ4GgHxXnkLCh+9RsQBkWKIghzLhW2B9ebfG+LQQg8uLtsQ2aUKjTgE+QOg==",
+ "license": "MIT",
+ "dependencies": {
+ "jsonc-parser": "^3.0.0",
+ "vscode-languageserver-textdocument": "^1.0.1",
+ "vscode-languageserver-types": "^3.16.0",
+ "vscode-nls": "^5.0.0",
+ "vscode-uri": "^3.0.2"
+ },
+ "engines": {
+ "npm": ">=7.0.0"
+ }
+ },
+ "node_modules/vscode-jsonrpc": {
+ "version": "9.0.1",
+ "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-9.0.1.tgz",
+ "integrity": "sha512-rfuA6T75H6m5EkbhtEPzre9pT0HPcDI2MMy4+nPFIBks5J8JBAUHD4tRYSgaBOijIEC7SRkC1kKyXTLqbmh9jw==",
+ "license": "MIT",
+ "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",
+ "dependencies": {
+ "vscode-languageserver-protocol": "3.17.5"
+ },
+ "bin": {
+ "installServerIntoExtension": "bin/installServerIntoExtension"
+ }
+ },
+ "node_modules/vscode-languageserver-protocol": {
+ "version": "3.18.2",
+ "resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.18.2.tgz",
+ "integrity": "sha512-XRyDbT0Pp3sSNti3JmxVEUMySWCSi1hhM+/KUlCy1hV1zmrqpM1OwO12EAki8blhmLuIMpaJrYbo0OzGVfK2Qg==",
+ "license": "MIT",
+ "dependencies": {
+ "vscode-jsonrpc": "9.0.1",
+ "vscode-languageserver-types": "3.18.0"
+ }
+ },
+ "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"
+ },
+ "node_modules/vscode-languageserver-types": {
+ "version": "3.18.0",
+ "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.18.0.tgz",
+ "integrity": "sha512-8TsGPNMIMiiBdkORgRSvLjuiEIiAFtO+KssmYWxQ+uSVvlf7RjK8YKCOjPzZ+YA04jXEV7+7LvkSmHkhpNS99g==",
+ "license": "MIT"
+ },
+ "node_modules/vscode-languageserver/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",
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/vscode-languageserver/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",
+ "dependencies": {
+ "vscode-jsonrpc": "8.2.0",
+ "vscode-languageserver-types": "3.17.5"
+ }
+ },
+ "node_modules/vscode-languageserver/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"
+ },
+ "node_modules/vscode-nls": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/vscode-nls/-/vscode-nls-5.2.0.tgz",
+ "integrity": "sha512-RAaHx7B14ZU04EU31pT+rKz2/zSl7xMsfIZuo8pd+KZO6PXtQmpevpq3vxvWNcrGbdmhM/rr5Uw5Mz+NBfhVng==",
+ "license": "MIT"
+ },
+ "node_modules/vscode-uri": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.1.0.tgz",
+ "integrity": "sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==",
+ "license": "MIT"
+ },
+ "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/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/wrap-ansi/node_modules/string-width": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz",
+ "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==",
+ "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/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/y18n": {
+ "version": "5.0.8",
+ "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
+ "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==",
+ "license": "ISC",
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/yallist": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
+ "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
+ "license": "ISC"
+ },
+ "node_modules/yaml": {
+ "version": "2.9.0",
+ "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.0.tgz",
+ "integrity": "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==",
+ "license": "ISC",
+ "bin": {
+ "yaml": "bin.mjs"
+ },
+ "engines": {
+ "node": ">= 14.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/eemeli"
+ }
+ },
+ "node_modules/yaml-language-server": {
+ "version": "1.23.0",
+ "resolved": "https://registry.npmjs.org/yaml-language-server/-/yaml-language-server-1.23.0.tgz",
+ "integrity": "sha512-3qVyCOexLCWw06PQa5kRPwvMWMZ/eZeCRWUvgD6a0OkqL/4iCnxy2WumbWifa937Uo5xhyWJ0uxlU39ljhNh7A==",
+ "license": "MIT",
+ "dependencies": {
+ "@vscode/l10n": "^0.0.18",
+ "ajv": "^8.17.1",
+ "ajv-draft-04": "^1.0.0",
+ "ajv-i18n": "^4.2.0",
+ "prettier": "^3.8.1",
+ "request-light": "^0.5.7",
+ "vscode-json-languageservice": "4.1.8",
+ "vscode-languageserver": "^9.0.0",
+ "vscode-languageserver-textdocument": "^1.0.1",
+ "vscode-languageserver-types": "^3.16.0",
+ "vscode-uri": "^3.0.2",
+ "yaml": "2.8.3"
+ },
+ "bin": {
+ "yaml-language-server": "bin/yaml-language-server"
+ }
+ },
+ "node_modules/yaml-language-server/node_modules/request-light": {
+ "version": "0.5.8",
+ "resolved": "https://registry.npmjs.org/request-light/-/request-light-0.5.8.tgz",
+ "integrity": "sha512-3Zjgh+8b5fhRJBQZoy+zbVKpAQGLyka0MPgW3zruTF4dFFJ8Fqcfu9YsAvi/rvdcaTeWG3MkbZv4WKxAn/84Lg==",
+ "license": "MIT"
+ },
+ "node_modules/yaml-language-server/node_modules/yaml": {
+ "version": "2.8.3",
+ "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.3.tgz",
+ "integrity": "sha512-AvbaCLOO2Otw/lW5bmh9d/WEdcDFdQp2Z2ZUH3pX9U2ihyUY0nvLv7J6TrWowklRGPYbB/IuIMfYgxaCPg5Bpg==",
+ "license": "ISC",
+ "bin": {
+ "yaml": "bin.mjs"
+ },
+ "engines": {
+ "node": ">= 14.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/eemeli"
+ }
+ },
+ "node_modules/yargs": {
+ "version": "18.1.0",
+ "resolved": "https://registry.npmjs.org/yargs/-/yargs-18.1.0.tgz",
+ "integrity": "sha512-2rAgRKu54VsHkqI0/tYkmluGXHD4KW7yZoycuqDQ15QOTnc2VVfy0nN/1eMhnQLO00A+dwtK20xuCnc1YGeUyg==",
+ "license": "MIT",
+ "dependencies": {
+ "cliui": "^9.0.1",
+ "escalade": "^3.1.1",
+ "get-caller-file": "^2.0.5",
+ "string-width": "^8.2.1",
+ "y18n": "^5.0.5",
+ "yargs-parser": "^22.0.0"
+ },
+ "engines": {
+ "node": "^20.19.0 || ^22.12.0 || >=23"
+ }
+ },
+ "node_modules/yargs-parser": {
+ "version": "22.0.0",
+ "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-22.0.0.tgz",
+ "integrity": "sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw==",
+ "license": "ISC",
+ "engines": {
+ "node": "^20.19.0 || ^22.12.0 || >=23"
+ }
+ },
+ "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/zod": {
+ "version": "4.4.3",
+ "resolved": "https://registry.npmjs.org/zod/-/zod-4.4.3.tgz",
+ "integrity": "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/colinhacks"
+ }
+ },
+ "node_modules/zwitch": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz",
+ "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ }
+ }
+}
diff --git a/legacy/package.json b/legacy/package.json
new file mode 100644
index 0000000..f432565
--- /dev/null
+++ b/legacy/package.json
@@ -0,0 +1,39 @@
+{
+ "name": "greg-kilmer-profile",
+ "version": "1.0.0",
+ "type": "module",
+ "private": true,
+ "description": "Personal profile / portfolio website for Greg Kilmer. Astro + React Islands + Node server.",
+ "scripts": {
+ "dev": "astro dev",
+ "start": "astro dev",
+ "build": "astro check && astro build",
+ "preview": "astro preview",
+ "serve": "node ./dist/server/entry.mjs",
+ "astro": "astro",
+ "check": "astro check",
+ "typecheck": "astro check && tsc --noEmit"
+ },
+ "dependencies": {
+ "@astrojs/check": "^0.9.9",
+ "@astrojs/mdx": "^7.0.2",
+ "@astrojs/node": "^11.0.2",
+ "@astrojs/react": "^6.0.2",
+ "@fontsource-variable/cinzel": "^5.3.0",
+ "@fontsource-variable/jetbrains-mono": "^5.2.8",
+ "@fontsource-variable/montserrat": "^5.3.0",
+ "@iconify-json/mdi": "^1.2.3",
+ "@tailwindcss/vite": "^4.3.2",
+ "@types/nodemailer": "^8.0.1",
+ "@types/react": "^19.2.17",
+ "@types/react-dom": "^19.2.3",
+ "astro": "^7.2.0",
+ "framer-motion": "^12.42.2",
+ "nodemailer": "^9.0.3",
+ "react": "^19.2.7",
+ "react-dom": "^19.2.7",
+ "tailwindcss": "^4.3.2",
+ "typescript": "^6.0.3",
+ "zod": "^4.4.3"
+ }
+}
diff --git a/public/.well-known/security.txt b/legacy/public/.well-known/security.txt
similarity index 100%
rename from public/.well-known/security.txt
rename to legacy/public/.well-known/security.txt
diff --git a/public/apple-touch-icon.png b/legacy/public/apple-touch-icon.png
similarity index 100%
rename from public/apple-touch-icon.png
rename to legacy/public/apple-touch-icon.png
diff --git a/public/brand/patiun-seal.svg b/legacy/public/brand/patiun-seal.svg
similarity index 100%
rename from public/brand/patiun-seal.svg
rename to legacy/public/brand/patiun-seal.svg
diff --git a/public/favicon-16.png b/legacy/public/favicon-16.png
similarity index 100%
rename from public/favicon-16.png
rename to legacy/public/favicon-16.png
diff --git a/public/favicon-32.png b/legacy/public/favicon-32.png
similarity index 100%
rename from public/favicon-32.png
rename to legacy/public/favicon-32.png
diff --git a/public/favicon-48.png b/legacy/public/favicon-48.png
similarity index 100%
rename from public/favicon-48.png
rename to legacy/public/favicon-48.png
diff --git a/public/favicon.ico b/legacy/public/favicon.ico
similarity index 100%
rename from public/favicon.ico
rename to legacy/public/favicon.ico
diff --git a/public/favicon.svg b/legacy/public/favicon.svg
similarity index 100%
rename from public/favicon.svg
rename to legacy/public/favicon.svg
diff --git a/public/icon-192.png b/legacy/public/icon-192.png
similarity index 100%
rename from public/icon-192.png
rename to legacy/public/icon-192.png
diff --git a/public/icon-512.png b/legacy/public/icon-512.png
similarity index 100%
rename from public/icon-512.png
rename to legacy/public/icon-512.png
diff --git a/public/icon-maskable-192.png b/legacy/public/icon-maskable-192.png
similarity index 100%
rename from public/icon-maskable-192.png
rename to legacy/public/icon-maskable-192.png
diff --git a/public/icon-maskable-512.png b/legacy/public/icon-maskable-512.png
similarity index 100%
rename from public/icon-maskable-512.png
rename to legacy/public/icon-maskable-512.png
diff --git a/public/icon_2.png b/legacy/public/icon_2.png
similarity index 100%
rename from public/icon_2.png
rename to legacy/public/icon_2.png
diff --git a/public/images/OLDgaming-setup.jpg b/legacy/public/images/OLDgaming-setup.jpg
similarity index 100%
rename from public/images/OLDgaming-setup.jpg
rename to legacy/public/images/OLDgaming-setup.jpg
diff --git a/public/images/OLDhero-portrait.jpg b/legacy/public/images/OLDhero-portrait.jpg
similarity index 100%
rename from public/images/OLDhero-portrait.jpg
rename to legacy/public/images/OLDhero-portrait.jpg
diff --git a/public/images/OLDpro-workspace.jpg b/legacy/public/images/OLDpro-workspace.jpg
similarity index 100%
rename from public/images/OLDpro-workspace.jpg
rename to legacy/public/images/OLDpro-workspace.jpg
diff --git a/public/images/about-portrait.jpg b/legacy/public/images/about-portrait.jpg
similarity index 100%
rename from public/images/about-portrait.jpg
rename to legacy/public/images/about-portrait.jpg
diff --git a/public/images/anime-hero.jpg b/legacy/public/images/anime-hero.jpg
similarity index 100%
rename from public/images/anime-hero.jpg
rename to legacy/public/images/anime-hero.jpg
diff --git a/public/images/anime/attack-on-titan.jpg b/legacy/public/images/anime/attack-on-titan.jpg
similarity index 100%
rename from public/images/anime/attack-on-titan.jpg
rename to legacy/public/images/anime/attack-on-titan.jpg
diff --git a/public/images/anime/bleach.jpg b/legacy/public/images/anime/bleach.jpg
similarity index 100%
rename from public/images/anime/bleach.jpg
rename to legacy/public/images/anime/bleach.jpg
diff --git a/public/images/anime/code-geass.jpg b/legacy/public/images/anime/code-geass.jpg
similarity index 100%
rename from public/images/anime/code-geass.jpg
rename to legacy/public/images/anime/code-geass.jpg
diff --git a/public/images/anime/demon-slayer.jpg b/legacy/public/images/anime/demon-slayer.jpg
similarity index 100%
rename from public/images/anime/demon-slayer.jpg
rename to legacy/public/images/anime/demon-slayer.jpg
diff --git a/public/images/anime/frieren-beyond-journeys-end.jpg b/legacy/public/images/anime/frieren-beyond-journeys-end.jpg
similarity index 100%
rename from public/images/anime/frieren-beyond-journeys-end.jpg
rename to legacy/public/images/anime/frieren-beyond-journeys-end.jpg
diff --git a/public/images/anime/gundam-all-series.jpg b/legacy/public/images/anime/gundam-all-series.jpg
similarity index 100%
rename from public/images/anime/gundam-all-series.jpg
rename to legacy/public/images/anime/gundam-all-series.jpg
diff --git a/public/images/anime/jujutsu-kaisen.jpg b/legacy/public/images/anime/jujutsu-kaisen.jpg
similarity index 100%
rename from public/images/anime/jujutsu-kaisen.jpg
rename to legacy/public/images/anime/jujutsu-kaisen.jpg
diff --git a/public/images/anime/kimetsu-no-yaiba.jpg b/legacy/public/images/anime/kimetsu-no-yaiba.jpg
similarity index 100%
rename from public/images/anime/kimetsu-no-yaiba.jpg
rename to legacy/public/images/anime/kimetsu-no-yaiba.jpg
diff --git a/public/images/anime/magi-the-labyrinth-of-magic.jpg b/legacy/public/images/anime/magi-the-labyrinth-of-magic.jpg
similarity index 100%
rename from public/images/anime/magi-the-labyrinth-of-magic.jpg
rename to legacy/public/images/anime/magi-the-labyrinth-of-magic.jpg
diff --git a/public/images/anime/my-hero-academia.jpg b/legacy/public/images/anime/my-hero-academia.jpg
similarity index 100%
rename from public/images/anime/my-hero-academia.jpg
rename to legacy/public/images/anime/my-hero-academia.jpg
diff --git a/public/images/anime/one-piece.jpg b/legacy/public/images/anime/one-piece.jpg
similarity index 100%
rename from public/images/anime/one-piece.jpg
rename to legacy/public/images/anime/one-piece.jpg
diff --git a/public/images/anime/that-time-i-got-reincarnated-as-a-slime.jpg b/legacy/public/images/anime/that-time-i-got-reincarnated-as-a-slime.jpg
similarity index 100%
rename from public/images/anime/that-time-i-got-reincarnated-as-a-slime.jpg
rename to legacy/public/images/anime/that-time-i-got-reincarnated-as-a-slime.jpg
diff --git a/public/images/anime/vinland-saga.jpg b/legacy/public/images/anime/vinland-saga.jpg
similarity index 100%
rename from public/images/anime/vinland-saga.jpg
rename to legacy/public/images/anime/vinland-saga.jpg
diff --git a/public/images/boardgames/.gitkeep b/legacy/public/images/boardgames/.gitkeep
similarity index 100%
rename from public/images/boardgames/.gitkeep
rename to legacy/public/images/boardgames/.gitkeep
diff --git a/public/images/boardgames/wyrmspan.jpg b/legacy/public/images/boardgames/wyrmspan.jpg
similarity index 100%
rename from public/images/boardgames/wyrmspan.jpg
rename to legacy/public/images/boardgames/wyrmspan.jpg
diff --git a/public/images/cards.jpg b/legacy/public/images/cards.jpg
similarity index 100%
rename from public/images/cards.jpg
rename to legacy/public/images/cards.jpg
diff --git a/public/images/deckboxes-example-1.svg b/legacy/public/images/deckboxes-example-1.svg
similarity index 100%
rename from public/images/deckboxes-example-1.svg
rename to legacy/public/images/deckboxes-example-1.svg
diff --git a/public/images/deckboxes-example-2.svg b/legacy/public/images/deckboxes-example-2.svg
similarity index 100%
rename from public/images/deckboxes-example-2.svg
rename to legacy/public/images/deckboxes-example-2.svg
diff --git a/public/images/deckboxes.jpg b/legacy/public/images/deckboxes.jpg
similarity index 100%
rename from public/images/deckboxes.jpg
rename to legacy/public/images/deckboxes.jpg
diff --git a/public/images/dice_galaxy.jpg b/legacy/public/images/dice_galaxy.jpg
similarity index 100%
rename from public/images/dice_galaxy.jpg
rename to legacy/public/images/dice_galaxy.jpg
diff --git a/public/images/dice_golden_moss.jpg b/legacy/public/images/dice_golden_moss.jpg
similarity index 100%
rename from public/images/dice_golden_moss.jpg
rename to legacy/public/images/dice_golden_moss.jpg
diff --git a/public/images/dice_scarlet.jpg b/legacy/public/images/dice_scarlet.jpg
similarity index 100%
rename from public/images/dice_scarlet.jpg
rename to legacy/public/images/dice_scarlet.jpg
diff --git a/public/images/dice_seastone.jpg b/legacy/public/images/dice_seastone.jpg
similarity index 100%
rename from public/images/dice_seastone.jpg
rename to legacy/public/images/dice_seastone.jpg
diff --git a/public/images/fantasy-books.jpg b/legacy/public/images/fantasy-books.jpg
similarity index 100%
rename from public/images/fantasy-books.jpg
rename to legacy/public/images/fantasy-books.jpg
diff --git a/public/images/fav.png b/legacy/public/images/fav.png
similarity index 100%
rename from public/images/fav.png
rename to legacy/public/images/fav.png
diff --git a/public/images/games/.gitkeep b/legacy/public/images/games/.gitkeep
similarity index 100%
rename from public/images/games/.gitkeep
rename to legacy/public/images/games/.gitkeep
diff --git a/public/images/games/age-of-empires.jpg b/legacy/public/images/games/age-of-empires.jpg
similarity index 100%
rename from public/images/games/age-of-empires.jpg
rename to legacy/public/images/games/age-of-empires.jpg
diff --git a/public/images/games/ark-survival-evolved.jpg b/legacy/public/images/games/ark-survival-evolved.jpg
similarity index 100%
rename from public/images/games/ark-survival-evolved.jpg
rename to legacy/public/images/games/ark-survival-evolved.jpg
diff --git a/public/images/games/dave-the-diver.jpg b/legacy/public/images/games/dave-the-diver.jpg
similarity index 100%
rename from public/images/games/dave-the-diver.jpg
rename to legacy/public/images/games/dave-the-diver.jpg
diff --git a/public/images/games/elden-ring.jpg b/legacy/public/images/games/elden-ring.jpg
similarity index 100%
rename from public/images/games/elden-ring.jpg
rename to legacy/public/images/games/elden-ring.jpg
diff --git a/public/images/games/granblue-relink.jpg b/legacy/public/images/games/granblue-relink.jpg
similarity index 100%
rename from public/images/games/granblue-relink.jpg
rename to legacy/public/images/games/granblue-relink.jpg
diff --git a/public/images/games/minecraft.jpg b/legacy/public/images/games/minecraft.jpg
similarity index 100%
rename from public/images/games/minecraft.jpg
rename to legacy/public/images/games/minecraft.jpg
diff --git a/public/images/games/pok-mon-crystal-version.jpg b/legacy/public/images/games/pok-mon-crystal-version.jpg
similarity index 100%
rename from public/images/games/pok-mon-crystal-version.jpg
rename to legacy/public/images/games/pok-mon-crystal-version.jpg
diff --git a/public/images/games/satisfactory.jpg b/legacy/public/images/games/satisfactory.jpg
similarity index 100%
rename from public/images/games/satisfactory.jpg
rename to legacy/public/images/games/satisfactory.jpg
diff --git a/public/images/games/sly-3-honor-among-thieves.jpg b/legacy/public/images/games/sly-3-honor-among-thieves.jpg
similarity index 100%
rename from public/images/games/sly-3-honor-among-thieves.jpg
rename to legacy/public/images/games/sly-3-honor-among-thieves.jpg
diff --git a/public/images/games/sly-cooper-and-the-thievius-raccoonus.jpg b/legacy/public/images/games/sly-cooper-and-the-thievius-raccoonus.jpg
similarity index 100%
rename from public/images/games/sly-cooper-and-the-thievius-raccoonus.jpg
rename to legacy/public/images/games/sly-cooper-and-the-thievius-raccoonus.jpg
diff --git a/public/images/games/star-wars-knights-of-the-old-republic.jpg b/legacy/public/images/games/star-wars-knights-of-the-old-republic.jpg
similarity index 100%
rename from public/images/games/star-wars-knights-of-the-old-republic.jpg
rename to legacy/public/images/games/star-wars-knights-of-the-old-republic.jpg
diff --git a/public/images/games/stardew-valley.jpg b/legacy/public/images/games/stardew-valley.jpg
similarity index 100%
rename from public/images/games/stardew-valley.jpg
rename to legacy/public/images/games/stardew-valley.jpg
diff --git a/public/images/gaming-setup.jpg b/legacy/public/images/gaming-setup.jpg
similarity index 100%
rename from public/images/gaming-setup.jpg
rename to legacy/public/images/gaming-setup.jpg
diff --git a/public/images/hero-portrait.jpg b/legacy/public/images/hero-portrait.jpg
similarity index 100%
rename from public/images/hero-portrait.jpg
rename to legacy/public/images/hero-portrait.jpg
diff --git a/public/images/kashi_at_work.jpg b/legacy/public/images/kashi_at_work.jpg
similarity index 100%
rename from public/images/kashi_at_work.jpg
rename to legacy/public/images/kashi_at_work.jpg
diff --git a/public/images/making-print - Copy.jpg b/legacy/public/images/making-print - Copy.jpg
similarity index 100%
rename from public/images/making-print - Copy.jpg
rename to legacy/public/images/making-print - Copy.jpg
diff --git a/public/images/making-print.jpg b/legacy/public/images/making-print.jpg
similarity index 100%
rename from public/images/making-print.jpg
rename to legacy/public/images/making-print.jpg
diff --git a/public/images/making-resin.jpg b/legacy/public/images/making-resin.jpg
similarity index 100%
rename from public/images/making-resin.jpg
rename to legacy/public/images/making-resin.jpg
diff --git a/public/images/minis.jpg b/legacy/public/images/minis.jpg
similarity index 100%
rename from public/images/minis.jpg
rename to legacy/public/images/minis.jpg
diff --git a/public/images/movies-hero.jpg b/legacy/public/images/movies-hero.jpg
similarity index 100%
rename from public/images/movies-hero.jpg
rename to legacy/public/images/movies-hero.jpg
diff --git a/public/images/movies/avengers-endgame.jpg b/legacy/public/images/movies/avengers-endgame.jpg
similarity index 100%
rename from public/images/movies/avengers-endgame.jpg
rename to legacy/public/images/movies/avengers-endgame.jpg
diff --git a/public/images/movies/avengers-infinity-war.jpg b/legacy/public/images/movies/avengers-infinity-war.jpg
similarity index 100%
rename from public/images/movies/avengers-infinity-war.jpg
rename to legacy/public/images/movies/avengers-infinity-war.jpg
diff --git a/public/images/movies/contact.jpg b/legacy/public/images/movies/contact.jpg
similarity index 100%
rename from public/images/movies/contact.jpg
rename to legacy/public/images/movies/contact.jpg
diff --git a/public/images/movies/howls-moving-castle.jpg b/legacy/public/images/movies/howls-moving-castle.jpg
similarity index 100%
rename from public/images/movies/howls-moving-castle.jpg
rename to legacy/public/images/movies/howls-moving-castle.jpg
diff --git a/public/images/movies/jurassic-park.jpg b/legacy/public/images/movies/jurassic-park.jpg
similarity index 100%
rename from public/images/movies/jurassic-park.jpg
rename to legacy/public/images/movies/jurassic-park.jpg
diff --git a/public/images/movies/oceans-eleven.jpg b/legacy/public/images/movies/oceans-eleven.jpg
similarity index 100%
rename from public/images/movies/oceans-eleven.jpg
rename to legacy/public/images/movies/oceans-eleven.jpg
diff --git a/public/images/movies/star-wars-episode-v-the-empire-strikes-back.jpg b/legacy/public/images/movies/star-wars-episode-v-the-empire-strikes-back.jpg
similarity index 100%
rename from public/images/movies/star-wars-episode-v-the-empire-strikes-back.jpg
rename to legacy/public/images/movies/star-wars-episode-v-the-empire-strikes-back.jpg
diff --git a/public/images/movies/the-matrix.jpg b/legacy/public/images/movies/the-matrix.jpg
similarity index 100%
rename from public/images/movies/the-matrix.jpg
rename to legacy/public/images/movies/the-matrix.jpg
diff --git a/public/images/movies/the-princess-bride.jpg b/legacy/public/images/movies/the-princess-bride.jpg
similarity index 100%
rename from public/images/movies/the-princess-bride.jpg
rename to legacy/public/images/movies/the-princess-bride.jpg
diff --git a/public/images/movies/transformers.jpg b/legacy/public/images/movies/transformers.jpg
similarity index 100%
rename from public/images/movies/transformers.jpg
rename to legacy/public/images/movies/transformers.jpg
diff --git a/public/images/pokemon-cards.jpg b/legacy/public/images/pokemon-cards.jpg
similarity index 100%
rename from public/images/pokemon-cards.jpg
rename to legacy/public/images/pokemon-cards.jpg
diff --git a/public/images/pro-team.jpg b/legacy/public/images/pro-team.jpg
similarity index 100%
rename from public/images/pro-team.jpg
rename to legacy/public/images/pro-team.jpg
diff --git a/public/images/pro-workspace.jpg b/legacy/public/images/pro-workspace.jpg
similarity index 100%
rename from public/images/pro-workspace.jpg
rename to legacy/public/images/pro-workspace.jpg
diff --git a/public/images/proxies-example-1.svg b/legacy/public/images/proxies-example-1.svg
similarity index 100%
rename from public/images/proxies-example-1.svg
rename to legacy/public/images/proxies-example-1.svg
diff --git a/public/images/proxies-example-2.svg b/legacy/public/images/proxies-example-2.svg
similarity index 100%
rename from public/images/proxies-example-2.svg
rename to legacy/public/images/proxies-example-2.svg
diff --git a/public/images/proxies.jpg b/legacy/public/images/proxies.jpg
similarity index 100%
rename from public/images/proxies.jpg
rename to legacy/public/images/proxies.jpg
diff --git a/public/images/proxies2.jpg b/legacy/public/images/proxies2.jpg
similarity index 100%
rename from public/images/proxies2.jpg
rename to legacy/public/images/proxies2.jpg
diff --git a/public/images/reading-nook.jpg b/legacy/public/images/reading-nook.jpg
similarity index 100%
rename from public/images/reading-nook.jpg
rename to legacy/public/images/reading-nook.jpg
diff --git a/public/images/reading/brandon-sanderson-cosmere.jpg b/legacy/public/images/reading/brandon-sanderson-cosmere.jpg
similarity index 100%
rename from public/images/reading/brandon-sanderson-cosmere.jpg
rename to legacy/public/images/reading/brandon-sanderson-cosmere.jpg
diff --git a/public/images/reading/desolate-era.jpg b/legacy/public/images/reading/desolate-era.jpg
similarity index 100%
rename from public/images/reading/desolate-era.jpg
rename to legacy/public/images/reading/desolate-era.jpg
diff --git a/public/images/reading/douglas-preston-lincoln-child-pendergast.jpg b/legacy/public/images/reading/douglas-preston-lincoln-child-pendergast.jpg
similarity index 100%
rename from public/images/reading/douglas-preston-lincoln-child-pendergast.jpg
rename to legacy/public/images/reading/douglas-preston-lincoln-child-pendergast.jpg
diff --git a/public/images/reading/emily-rodda-deltora-quest.jpg b/legacy/public/images/reading/emily-rodda-deltora-quest.jpg
similarity index 100%
rename from public/images/reading/emily-rodda-deltora-quest.jpg
rename to legacy/public/images/reading/emily-rodda-deltora-quest.jpg
diff --git a/public/images/reading/frank-herbert-dune.jpg b/legacy/public/images/reading/frank-herbert-dune.jpg
similarity index 100%
rename from public/images/reading/frank-herbert-dune.jpg
rename to legacy/public/images/reading/frank-herbert-dune.jpg
diff --git a/public/images/reading/j-r-r-tolkien-the-lord-of-the-rings.jpg b/legacy/public/images/reading/j-r-r-tolkien-the-lord-of-the-rings.jpg
similarity index 100%
rename from public/images/reading/j-r-r-tolkien-the-lord-of-the-rings.jpg
rename to legacy/public/images/reading/j-r-r-tolkien-the-lord-of-the-rings.jpg
diff --git a/public/images/reading/omniscient-reader-s-viewpoint.jpg b/legacy/public/images/reading/omniscient-reader-s-viewpoint.jpg
similarity index 100%
rename from public/images/reading/omniscient-reader-s-viewpoint.jpg
rename to legacy/public/images/reading/omniscient-reader-s-viewpoint.jpg
diff --git a/public/images/reading/overgeared.jpg b/legacy/public/images/reading/overgeared.jpg
similarity index 100%
rename from public/images/reading/overgeared.jpg
rename to legacy/public/images/reading/overgeared.jpg
diff --git a/public/images/reading/solo-leveling.jpg b/legacy/public/images/reading/solo-leveling.jpg
similarity index 100%
rename from public/images/reading/solo-leveling.jpg
rename to legacy/public/images/reading/solo-leveling.jpg
diff --git a/public/images/reading/the-king-s-avatar.jpg b/legacy/public/images/reading/the-king-s-avatar.jpg
similarity index 100%
rename from public/images/reading/the-king-s-avatar.jpg
rename to legacy/public/images/reading/the-king-s-avatar.jpg
diff --git a/public/images/resin_rings.jpg b/legacy/public/images/resin_rings.jpg
similarity index 100%
rename from public/images/resin_rings.jpg
rename to legacy/public/images/resin_rings.jpg
diff --git a/public/images/store/cards/PXL_20260806_204112875.jpg b/legacy/public/images/store/cards/PXL_20260806_204112875.jpg
similarity index 100%
rename from public/images/store/cards/PXL_20260806_204112875.jpg
rename to legacy/public/images/store/cards/PXL_20260806_204112875.jpg
diff --git a/public/images/store/cards/PXL_20260806_204116184.jpg b/legacy/public/images/store/cards/PXL_20260806_204116184.jpg
similarity index 100%
rename from public/images/store/cards/PXL_20260806_204116184.jpg
rename to legacy/public/images/store/cards/PXL_20260806_204116184.jpg
diff --git a/public/images/store/cards/PXL_20260806_204128368.jpg b/legacy/public/images/store/cards/PXL_20260806_204128368.jpg
similarity index 100%
rename from public/images/store/cards/PXL_20260806_204128368.jpg
rename to legacy/public/images/store/cards/PXL_20260806_204128368.jpg
diff --git a/public/images/store/cards/PXL_20260806_204129979.jpg b/legacy/public/images/store/cards/PXL_20260806_204129979.jpg
similarity index 100%
rename from public/images/store/cards/PXL_20260806_204129979.jpg
rename to legacy/public/images/store/cards/PXL_20260806_204129979.jpg
diff --git a/public/images/store/cards/disclosure.jpg b/legacy/public/images/store/cards/disclosure.jpg
similarity index 100%
rename from public/images/store/cards/disclosure.jpg
rename to legacy/public/images/store/cards/disclosure.jpg
diff --git a/public/images/store/cards/foil_cloud_measured.jpg b/legacy/public/images/store/cards/foil_cloud_measured.jpg
similarity index 100%
rename from public/images/store/cards/foil_cloud_measured.jpg
rename to legacy/public/images/store/cards/foil_cloud_measured.jpg
diff --git a/public/images/store/cards/ghalta_duo.jpg b/legacy/public/images/store/cards/ghalta_duo.jpg
similarity index 100%
rename from public/images/store/cards/ghalta_duo.jpg
rename to legacy/public/images/store/cards/ghalta_duo.jpg
diff --git a/public/images/store/cards/proxies.jpg b/legacy/public/images/store/cards/proxies.jpg
similarity index 100%
rename from public/images/store/cards/proxies.jpg
rename to legacy/public/images/store/cards/proxies.jpg
diff --git a/public/images/store/cards/proxies2.jpg b/legacy/public/images/store/cards/proxies2.jpg
similarity index 100%
rename from public/images/store/cards/proxies2.jpg
rename to legacy/public/images/store/cards/proxies2.jpg
diff --git a/public/images/store/cards/ravenous_duo.jpg b/legacy/public/images/store/cards/ravenous_duo.jpg
similarity index 100%
rename from public/images/store/cards/ravenous_duo.jpg
rename to legacy/public/images/store/cards/ravenous_duo.jpg
diff --git a/public/images/store/cards/sleeved.jpg b/legacy/public/images/store/cards/sleeved.jpg
similarity index 100%
rename from public/images/store/cards/sleeved.jpg
rename to legacy/public/images/store/cards/sleeved.jpg
diff --git a/public/images/store/deckboxes/black_front.jpg b/legacy/public/images/store/deckboxes/black_front.jpg
similarity index 100%
rename from public/images/store/deckboxes/black_front.jpg
rename to legacy/public/images/store/deckboxes/black_front.jpg
diff --git a/public/images/store/deckboxes/black_side.jpg b/legacy/public/images/store/deckboxes/black_side.jpg
similarity index 100%
rename from public/images/store/deckboxes/black_side.jpg
rename to legacy/public/images/store/deckboxes/black_side.jpg
diff --git a/public/images/store/deckboxes/ff_back.jpg b/legacy/public/images/store/deckboxes/ff_back.jpg
similarity index 100%
rename from public/images/store/deckboxes/ff_back.jpg
rename to legacy/public/images/store/deckboxes/ff_back.jpg
diff --git a/public/images/store/deckboxes/ff_front.jpg b/legacy/public/images/store/deckboxes/ff_front.jpg
similarity index 100%
rename from public/images/store/deckboxes/ff_front.jpg
rename to legacy/public/images/store/deckboxes/ff_front.jpg
diff --git a/public/images/store/deckboxes/ff_open.jpg b/legacy/public/images/store/deckboxes/ff_open.jpg
similarity index 100%
rename from public/images/store/deckboxes/ff_open.jpg
rename to legacy/public/images/store/deckboxes/ff_open.jpg
diff --git a/public/images/store/deckboxes/ff_side.jpg b/legacy/public/images/store/deckboxes/ff_side.jpg
similarity index 100%
rename from public/images/store/deckboxes/ff_side.jpg
rename to legacy/public/images/store/deckboxes/ff_side.jpg
diff --git a/public/images/tea-gaiwan.jpg b/legacy/public/images/tea-gaiwan.jpg
similarity index 100%
rename from public/images/tea-gaiwan.jpg
rename to legacy/public/images/tea-gaiwan.jpg
diff --git a/public/images/ttrpg-boardgame-shelf.jpg b/legacy/public/images/ttrpg-boardgame-shelf.jpg
similarity index 100%
rename from public/images/ttrpg-boardgame-shelf.jpg
rename to legacy/public/images/ttrpg-boardgame-shelf.jpg
diff --git a/public/images/ttrpg-dice.jpg b/legacy/public/images/ttrpg-dice.jpg
similarity index 100%
rename from public/images/ttrpg-dice.jpg
rename to legacy/public/images/ttrpg-dice.jpg
diff --git a/public/images/ttrpg-mini.jpg b/legacy/public/images/ttrpg-mini.jpg
similarity index 100%
rename from public/images/ttrpg-mini.jpg
rename to legacy/public/images/ttrpg-mini.jpg
diff --git a/public/images/ttrpg-table - Copy.jpg b/legacy/public/images/ttrpg-table - Copy.jpg
similarity index 100%
rename from public/images/ttrpg-table - Copy.jpg
rename to legacy/public/images/ttrpg-table - Copy.jpg
diff --git a/public/images/ttrpg-table.jpg b/legacy/public/images/ttrpg-table.jpg
similarity index 100%
rename from public/images/ttrpg-table.jpg
rename to legacy/public/images/ttrpg-table.jpg
diff --git a/public/images/tv-hero.jpg b/legacy/public/images/tv-hero.jpg
similarity index 100%
rename from public/images/tv-hero.jpg
rename to legacy/public/images/tv-hero.jpg
diff --git a/public/images/tv/30-rock.jpg b/legacy/public/images/tv/30-rock.jpg
similarity index 100%
rename from public/images/tv/30-rock.jpg
rename to legacy/public/images/tv/30-rock.jpg
diff --git a/public/images/tv/arcane.jpg b/legacy/public/images/tv/arcane.jpg
similarity index 100%
rename from public/images/tv/arcane.jpg
rename to legacy/public/images/tv/arcane.jpg
diff --git a/public/images/tv/breaking-bad.jpg b/legacy/public/images/tv/breaking-bad.jpg
similarity index 100%
rename from public/images/tv/breaking-bad.jpg
rename to legacy/public/images/tv/breaking-bad.jpg
diff --git a/public/images/tv/brooklyn-nine-nine.jpg b/legacy/public/images/tv/brooklyn-nine-nine.jpg
similarity index 100%
rename from public/images/tv/brooklyn-nine-nine.jpg
rename to legacy/public/images/tv/brooklyn-nine-nine.jpg
diff --git a/public/images/tv/castlevania.jpg b/legacy/public/images/tv/castlevania.jpg
similarity index 100%
rename from public/images/tv/castlevania.jpg
rename to legacy/public/images/tv/castlevania.jpg
diff --git a/public/images/tv/cobra-kai.jpg b/legacy/public/images/tv/cobra-kai.jpg
similarity index 100%
rename from public/images/tv/cobra-kai.jpg
rename to legacy/public/images/tv/cobra-kai.jpg
diff --git a/public/images/tv/daredevil.jpg b/legacy/public/images/tv/daredevil.jpg
similarity index 100%
rename from public/images/tv/daredevil.jpg
rename to legacy/public/images/tv/daredevil.jpg
diff --git a/public/images/tv/game-of-thrones.jpg b/legacy/public/images/tv/game-of-thrones.jpg
similarity index 100%
rename from public/images/tv/game-of-thrones.jpg
rename to legacy/public/images/tv/game-of-thrones.jpg
diff --git a/public/images/tv/house.jpg b/legacy/public/images/tv/house.jpg
similarity index 100%
rename from public/images/tv/house.jpg
rename to legacy/public/images/tv/house.jpg
diff --git a/public/images/tv/invincible.jpg b/legacy/public/images/tv/invincible.jpg
similarity index 100%
rename from public/images/tv/invincible.jpg
rename to legacy/public/images/tv/invincible.jpg
diff --git a/public/images/tv/ozark.jpg b/legacy/public/images/tv/ozark.jpg
similarity index 100%
rename from public/images/tv/ozark.jpg
rename to legacy/public/images/tv/ozark.jpg
diff --git a/public/images/tv/parks-and-recreation.jpg b/legacy/public/images/tv/parks-and-recreation.jpg
similarity index 100%
rename from public/images/tv/parks-and-recreation.jpg
rename to legacy/public/images/tv/parks-and-recreation.jpg
diff --git a/public/images/tv/peaky-blinders.jpg b/legacy/public/images/tv/peaky-blinders.jpg
similarity index 100%
rename from public/images/tv/peaky-blinders.jpg
rename to legacy/public/images/tv/peaky-blinders.jpg
diff --git a/public/images/tv/succession.jpg b/legacy/public/images/tv/succession.jpg
similarity index 100%
rename from public/images/tv/succession.jpg
rename to legacy/public/images/tv/succession.jpg
diff --git a/public/images/tv/suits.jpg b/legacy/public/images/tv/suits.jpg
similarity index 100%
rename from public/images/tv/suits.jpg
rename to legacy/public/images/tv/suits.jpg
diff --git a/public/images/tv/the-bear.jpg b/legacy/public/images/tv/the-bear.jpg
similarity index 100%
rename from public/images/tv/the-bear.jpg
rename to legacy/public/images/tv/the-bear.jpg
diff --git a/public/images/tv/the-crown.jpg b/legacy/public/images/tv/the-crown.jpg
similarity index 100%
rename from public/images/tv/the-crown.jpg
rename to legacy/public/images/tv/the-crown.jpg
diff --git a/public/images/tv/the-great-british-baking-show.jpg b/legacy/public/images/tv/the-great-british-baking-show.jpg
similarity index 100%
rename from public/images/tv/the-great-british-baking-show.jpg
rename to legacy/public/images/tv/the-great-british-baking-show.jpg
diff --git a/public/images/tv/the-mandalorian.jpg b/legacy/public/images/tv/the-mandalorian.jpg
similarity index 100%
rename from public/images/tv/the-mandalorian.jpg
rename to legacy/public/images/tv/the-mandalorian.jpg
diff --git a/public/images/tv/the-queens-gambit.jpg b/legacy/public/images/tv/the-queens-gambit.jpg
similarity index 100%
rename from public/images/tv/the-queens-gambit.jpg
rename to legacy/public/images/tv/the-queens-gambit.jpg
diff --git a/public/images/tv/the-sopranos.jpg b/legacy/public/images/tv/the-sopranos.jpg
similarity index 100%
rename from public/images/tv/the-sopranos.jpg
rename to legacy/public/images/tv/the-sopranos.jpg
diff --git a/public/images/tv/the-wire.jpg b/legacy/public/images/tv/the-wire.jpg
similarity index 100%
rename from public/images/tv/the-wire.jpg
rename to legacy/public/images/tv/the-wire.jpg
diff --git a/public/images/tv/wandavision.jpg b/legacy/public/images/tv/wandavision.jpg
similarity index 100%
rename from public/images/tv/wandavision.jpg
rename to legacy/public/images/tv/wandavision.jpg
diff --git a/public/images/xmasgifts.jpg b/legacy/public/images/xmasgifts.jpg
similarity index 100%
rename from public/images/xmasgifts.jpg
rename to legacy/public/images/xmasgifts.jpg
diff --git a/public/motifs/astrolabe.svg b/legacy/public/motifs/astrolabe.svg
similarity index 100%
rename from public/motifs/astrolabe.svg
rename to legacy/public/motifs/astrolabe.svg
diff --git a/public/motifs/craft-seal-print-light.svg b/legacy/public/motifs/craft-seal-print-light.svg
similarity index 100%
rename from public/motifs/craft-seal-print-light.svg
rename to legacy/public/motifs/craft-seal-print-light.svg
diff --git a/public/motifs/kepler-nest-seal-light.svg b/legacy/public/motifs/kepler-nest-seal-light.svg
similarity index 100%
rename from public/motifs/kepler-nest-seal-light.svg
rename to legacy/public/motifs/kepler-nest-seal-light.svg
diff --git a/public/motifs/layered-seal.svg b/legacy/public/motifs/layered-seal.svg
similarity index 100%
rename from public/motifs/layered-seal.svg
rename to legacy/public/motifs/layered-seal.svg
diff --git a/public/motifs/squared-circle-seal-light.svg b/legacy/public/motifs/squared-circle-seal-light.svg
similarity index 100%
rename from public/motifs/squared-circle-seal-light.svg
rename to legacy/public/motifs/squared-circle-seal-light.svg
diff --git a/public/motifs/transmutation-seal-light.svg b/legacy/public/motifs/transmutation-seal-light.svg
similarity index 100%
rename from public/motifs/transmutation-seal-light.svg
rename to legacy/public/motifs/transmutation-seal-light.svg
diff --git a/public/motifs/tria-prima-array-seal-light.svg b/legacy/public/motifs/tria-prima-array-seal-light.svg
similarity index 100%
rename from public/motifs/tria-prima-array-seal-light.svg
rename to legacy/public/motifs/tria-prima-array-seal-light.svg
diff --git a/public/motifs/zodiac-seal-light.svg b/legacy/public/motifs/zodiac-seal-light.svg
similarity index 100%
rename from public/motifs/zodiac-seal-light.svg
rename to legacy/public/motifs/zodiac-seal-light.svg
diff --git a/public/robots.txt b/legacy/public/robots.txt
similarity index 100%
rename from public/robots.txt
rename to legacy/public/robots.txt
diff --git a/public/site.webmanifest b/legacy/public/site.webmanifest
similarity index 100%
rename from public/site.webmanifest
rename to legacy/public/site.webmanifest
diff --git a/scripts/fetch-anime-art.mjs b/legacy/scripts/fetch-anime-art.mjs
similarity index 100%
rename from scripts/fetch-anime-art.mjs
rename to legacy/scripts/fetch-anime-art.mjs
diff --git a/scripts/fetch-bgg-covers.mjs b/legacy/scripts/fetch-bgg-covers.mjs
similarity index 100%
rename from scripts/fetch-bgg-covers.mjs
rename to legacy/scripts/fetch-bgg-covers.mjs
diff --git a/scripts/fetch-book-covers.mjs b/legacy/scripts/fetch-book-covers.mjs
similarity index 100%
rename from scripts/fetch-book-covers.mjs
rename to legacy/scripts/fetch-book-covers.mjs
diff --git a/scripts/fetch-box-art.mjs b/legacy/scripts/fetch-box-art.mjs
similarity index 100%
rename from scripts/fetch-box-art.mjs
rename to legacy/scripts/fetch-box-art.mjs
diff --git a/scripts/fetch-tmdb-art.mjs b/legacy/scripts/fetch-tmdb-art.mjs
similarity index 100%
rename from scripts/fetch-tmdb-art.mjs
rename to legacy/scripts/fetch-tmdb-art.mjs
diff --git a/src/components/Icon.astro b/legacy/src/components/Icon.astro
similarity index 100%
rename from src/components/Icon.astro
rename to legacy/src/components/Icon.astro
diff --git a/src/components/ObfuscatedEmail.astro b/legacy/src/components/ObfuscatedEmail.astro
similarity index 100%
rename from src/components/ObfuscatedEmail.astro
rename to legacy/src/components/ObfuscatedEmail.astro
diff --git a/src/components/brand/BrandIcon.tsx b/legacy/src/components/brand/BrandIcon.tsx
similarity index 100%
rename from src/components/brand/BrandIcon.tsx
rename to legacy/src/components/brand/BrandIcon.tsx
diff --git a/src/components/brand/Motif.astro b/legacy/src/components/brand/Motif.astro
similarity index 100%
rename from src/components/brand/Motif.astro
rename to legacy/src/components/brand/Motif.astro
diff --git a/src/components/brand/PageSeal.astro b/legacy/src/components/brand/PageSeal.astro
similarity index 100%
rename from src/components/brand/PageSeal.astro
rename to legacy/src/components/brand/PageSeal.astro
diff --git a/src/components/cards/DeckCard.astro b/legacy/src/components/cards/DeckCard.astro
similarity index 100%
rename from src/components/cards/DeckCard.astro
rename to legacy/src/components/cards/DeckCard.astro
diff --git a/src/components/cards/GameCard.astro b/legacy/src/components/cards/GameCard.astro
similarity index 100%
rename from src/components/cards/GameCard.astro
rename to legacy/src/components/cards/GameCard.astro
diff --git a/src/components/cards/InterestCard.astro b/legacy/src/components/cards/InterestCard.astro
similarity index 100%
rename from src/components/cards/InterestCard.astro
rename to legacy/src/components/cards/InterestCard.astro
diff --git a/src/components/cards/MediaCard.astro b/legacy/src/components/cards/MediaCard.astro
similarity index 100%
rename from src/components/cards/MediaCard.astro
rename to legacy/src/components/cards/MediaCard.astro
diff --git a/src/components/hero/AnimatedBackground.tsx b/legacy/src/components/hero/AnimatedBackground.tsx
similarity index 100%
rename from src/components/hero/AnimatedBackground.tsx
rename to legacy/src/components/hero/AnimatedBackground.tsx
diff --git a/src/components/layout/Footer.astro b/legacy/src/components/layout/Footer.astro
similarity index 100%
rename from src/components/layout/Footer.astro
rename to legacy/src/components/layout/Footer.astro
diff --git a/src/components/layout/InterestHero.astro b/legacy/src/components/layout/InterestHero.astro
similarity index 100%
rename from src/components/layout/InterestHero.astro
rename to legacy/src/components/layout/InterestHero.astro
diff --git a/src/components/layout/Placeholder.astro b/legacy/src/components/layout/Placeholder.astro
similarity index 100%
rename from src/components/layout/Placeholder.astro
rename to legacy/src/components/layout/Placeholder.astro
diff --git a/src/components/layout/RelatedInterests.astro b/legacy/src/components/layout/RelatedInterests.astro
similarity index 100%
rename from src/components/layout/RelatedInterests.astro
rename to legacy/src/components/layout/RelatedInterests.astro
diff --git a/src/components/layout/SectionHeader.astro b/legacy/src/components/layout/SectionHeader.astro
similarity index 100%
rename from src/components/layout/SectionHeader.astro
rename to legacy/src/components/layout/SectionHeader.astro
diff --git a/src/components/layout/SplitTransition.astro b/legacy/src/components/layout/SplitTransition.astro
similarity index 100%
rename from src/components/layout/SplitTransition.astro
rename to legacy/src/components/layout/SplitTransition.astro
diff --git a/src/components/media/CategoryList.astro b/legacy/src/components/media/CategoryList.astro
similarity index 100%
rename from src/components/media/CategoryList.astro
rename to legacy/src/components/media/CategoryList.astro
diff --git a/src/components/motion/Reveal.astro b/legacy/src/components/motion/Reveal.astro
similarity index 100%
rename from src/components/motion/Reveal.astro
rename to legacy/src/components/motion/Reveal.astro
diff --git a/src/components/mystic/AlchemicalDivider.astro b/legacy/src/components/mystic/AlchemicalDivider.astro
similarity index 100%
rename from src/components/mystic/AlchemicalDivider.astro
rename to legacy/src/components/mystic/AlchemicalDivider.astro
diff --git a/src/components/mystic/CelestialFrame.astro b/legacy/src/components/mystic/CelestialFrame.astro
similarity index 100%
rename from src/components/mystic/CelestialFrame.astro
rename to legacy/src/components/mystic/CelestialFrame.astro
diff --git a/src/components/mystic/CelestialImage.astro b/legacy/src/components/mystic/CelestialImage.astro
similarity index 100%
rename from src/components/mystic/CelestialImage.astro
rename to legacy/src/components/mystic/CelestialImage.astro
diff --git a/src/components/mystic/CircuitField.tsx b/legacy/src/components/mystic/CircuitField.tsx
similarity index 100%
rename from src/components/mystic/CircuitField.tsx
rename to legacy/src/components/mystic/CircuitField.tsx
diff --git a/src/components/mystic/ConstellationField.tsx b/legacy/src/components/mystic/ConstellationField.tsx
similarity index 100%
rename from src/components/mystic/ConstellationField.tsx
rename to legacy/src/components/mystic/ConstellationField.tsx
diff --git a/src/components/mystic/StarBurst.astro b/legacy/src/components/mystic/StarBurst.astro
similarity index 100%
rename from src/components/mystic/StarBurst.astro
rename to legacy/src/components/mystic/StarBurst.astro
diff --git a/src/components/mystic/StarField.astro b/legacy/src/components/mystic/StarField.astro
similarity index 100%
rename from src/components/mystic/StarField.astro
rename to legacy/src/components/mystic/StarField.astro
diff --git a/src/components/nav/MobileMenu.tsx b/legacy/src/components/nav/MobileMenu.tsx
similarity index 100%
rename from src/components/nav/MobileMenu.tsx
rename to legacy/src/components/nav/MobileMenu.tsx
diff --git a/src/components/nav/NavBar.astro b/legacy/src/components/nav/NavBar.astro
similarity index 100%
rename from src/components/nav/NavBar.astro
rename to legacy/src/components/nav/NavBar.astro
diff --git a/src/components/nav/ThemeToggle.tsx b/legacy/src/components/nav/ThemeToggle.tsx
similarity index 100%
rename from src/components/nav/ThemeToggle.tsx
rename to legacy/src/components/nav/ThemeToggle.tsx
diff --git a/src/components/professional/Certifications.astro b/legacy/src/components/professional/Certifications.astro
similarity index 100%
rename from src/components/professional/Certifications.astro
rename to legacy/src/components/professional/Certifications.astro
diff --git a/src/components/professional/ContactForm.tsx b/legacy/src/components/professional/ContactForm.tsx
similarity index 100%
rename from src/components/professional/ContactForm.tsx
rename to legacy/src/components/professional/ContactForm.tsx
diff --git a/src/components/professional/EducationHistory.astro b/legacy/src/components/professional/EducationHistory.astro
similarity index 100%
rename from src/components/professional/EducationHistory.astro
rename to legacy/src/components/professional/EducationHistory.astro
diff --git a/src/components/professional/ProjectCard.astro b/legacy/src/components/professional/ProjectCard.astro
similarity index 100%
rename from src/components/professional/ProjectCard.astro
rename to legacy/src/components/professional/ProjectCard.astro
diff --git a/src/components/professional/SectionNav.astro b/legacy/src/components/professional/SectionNav.astro
similarity index 100%
rename from src/components/professional/SectionNav.astro
rename to legacy/src/components/professional/SectionNav.astro
diff --git a/src/components/professional/SkillGrid.astro b/legacy/src/components/professional/SkillGrid.astro
similarity index 100%
rename from src/components/professional/SkillGrid.astro
rename to legacy/src/components/professional/SkillGrid.astro
diff --git a/src/components/professional/Timeline.astro b/legacy/src/components/professional/Timeline.astro
similarity index 100%
rename from src/components/professional/Timeline.astro
rename to legacy/src/components/professional/Timeline.astro
diff --git a/src/components/store/Carousel.tsx b/legacy/src/components/store/Carousel.tsx
similarity index 100%
rename from src/components/store/Carousel.tsx
rename to legacy/src/components/store/Carousel.tsx
diff --git a/src/components/store/ColorGrid.astro b/legacy/src/components/store/ColorGrid.astro
similarity index 100%
rename from src/components/store/ColorGrid.astro
rename to legacy/src/components/store/ColorGrid.astro
diff --git a/src/components/store/OrderForm.tsx b/legacy/src/components/store/OrderForm.tsx
similarity index 100%
rename from src/components/store/OrderForm.tsx
rename to legacy/src/components/store/OrderForm.tsx
diff --git a/src/components/store/PricingTable.astro b/legacy/src/components/store/PricingTable.astro
similarity index 100%
rename from src/components/store/PricingTable.astro
rename to legacy/src/components/store/PricingTable.astro
diff --git a/src/components/store/ProductCard.astro b/legacy/src/components/store/ProductCard.astro
similarity index 100%
rename from src/components/store/ProductCard.astro
rename to legacy/src/components/store/ProductCard.astro
diff --git a/src/components/store/ProductTile.astro b/legacy/src/components/store/ProductTile.astro
similarity index 100%
rename from src/components/store/ProductTile.astro
rename to legacy/src/components/store/ProductTile.astro
diff --git a/src/components/store/RequirementList.astro b/legacy/src/components/store/RequirementList.astro
similarity index 100%
rename from src/components/store/RequirementList.astro
rename to legacy/src/components/store/RequirementList.astro
diff --git a/src/components/store/SlotMeter.astro b/legacy/src/components/store/SlotMeter.astro
similarity index 100%
rename from src/components/store/SlotMeter.astro
rename to legacy/src/components/store/SlotMeter.astro
diff --git a/src/components/store/StoreNav.astro b/legacy/src/components/store/StoreNav.astro
similarity index 100%
rename from src/components/store/StoreNav.astro
rename to legacy/src/components/store/StoreNav.astro
diff --git a/src/components/ttrpgs/BoardGameShelf.tsx b/legacy/src/components/ttrpgs/BoardGameShelf.tsx
similarity index 100%
rename from src/components/ttrpgs/BoardGameShelf.tsx
rename to legacy/src/components/ttrpgs/BoardGameShelf.tsx
diff --git a/src/content.config.ts b/legacy/src/content.config.ts
similarity index 100%
rename from src/content.config.ts
rename to legacy/src/content.config.ts
diff --git a/src/content/experience/comcast_1.md b/legacy/src/content/experience/comcast_1.md
similarity index 100%
rename from src/content/experience/comcast_1.md
rename to legacy/src/content/experience/comcast_1.md
diff --git a/src/content/experience/drexel.md b/legacy/src/content/experience/drexel.md
similarity index 100%
rename from src/content/experience/drexel.md
rename to legacy/src/content/experience/drexel.md
diff --git a/src/content/experience/implico.md b/legacy/src/content/experience/implico.md
similarity index 100%
rename from src/content/experience/implico.md
rename to legacy/src/content/experience/implico.md
diff --git a/src/content/games/hall-of-fame/age-of-empires.md b/legacy/src/content/games/hall-of-fame/age-of-empires.md
similarity index 100%
rename from src/content/games/hall-of-fame/age-of-empires.md
rename to legacy/src/content/games/hall-of-fame/age-of-empires.md
diff --git a/src/content/games/hall-of-fame/ark-survival-evolved.md b/legacy/src/content/games/hall-of-fame/ark-survival-evolved.md
similarity index 100%
rename from src/content/games/hall-of-fame/ark-survival-evolved.md
rename to legacy/src/content/games/hall-of-fame/ark-survival-evolved.md
diff --git a/src/content/games/hall-of-fame/dave-the-diver.md b/legacy/src/content/games/hall-of-fame/dave-the-diver.md
similarity index 100%
rename from src/content/games/hall-of-fame/dave-the-diver.md
rename to legacy/src/content/games/hall-of-fame/dave-the-diver.md
diff --git a/src/content/games/hall-of-fame/elden-ring.md b/legacy/src/content/games/hall-of-fame/elden-ring.md
similarity index 100%
rename from src/content/games/hall-of-fame/elden-ring.md
rename to legacy/src/content/games/hall-of-fame/elden-ring.md
diff --git a/src/content/games/hall-of-fame/granblue-relink.md b/legacy/src/content/games/hall-of-fame/granblue-relink.md
similarity index 100%
rename from src/content/games/hall-of-fame/granblue-relink.md
rename to legacy/src/content/games/hall-of-fame/granblue-relink.md
diff --git a/src/content/games/hall-of-fame/kotor.md b/legacy/src/content/games/hall-of-fame/kotor.md
similarity index 100%
rename from src/content/games/hall-of-fame/kotor.md
rename to legacy/src/content/games/hall-of-fame/kotor.md
diff --git a/src/content/games/hall-of-fame/minecraft.md b/legacy/src/content/games/hall-of-fame/minecraft.md
similarity index 100%
rename from src/content/games/hall-of-fame/minecraft.md
rename to legacy/src/content/games/hall-of-fame/minecraft.md
diff --git a/src/content/games/hall-of-fame/pokemon-crystal.md b/legacy/src/content/games/hall-of-fame/pokemon-crystal.md
similarity index 100%
rename from src/content/games/hall-of-fame/pokemon-crystal.md
rename to legacy/src/content/games/hall-of-fame/pokemon-crystal.md
diff --git a/src/content/games/hall-of-fame/satisfactory.md b/legacy/src/content/games/hall-of-fame/satisfactory.md
similarity index 100%
rename from src/content/games/hall-of-fame/satisfactory.md
rename to legacy/src/content/games/hall-of-fame/satisfactory.md
diff --git a/src/content/games/hall-of-fame/sly-cooper.md b/legacy/src/content/games/hall-of-fame/sly-cooper.md
similarity index 100%
rename from src/content/games/hall-of-fame/sly-cooper.md
rename to legacy/src/content/games/hall-of-fame/sly-cooper.md
diff --git a/src/content/games/hall-of-fame/stardew-valley.md b/legacy/src/content/games/hall-of-fame/stardew-valley.md
similarity index 100%
rename from src/content/games/hall-of-fame/stardew-valley.md
rename to legacy/src/content/games/hall-of-fame/stardew-valley.md
diff --git a/src/content/games/now-playing.yaml b/legacy/src/content/games/now-playing.yaml
similarity index 100%
rename from src/content/games/now-playing.yaml
rename to legacy/src/content/games/now-playing.yaml
diff --git a/src/content/projects/carrier-connect.md b/legacy/src/content/projects/carrier-connect.md
similarity index 100%
rename from src/content/projects/carrier-connect.md
rename to legacy/src/content/projects/carrier-connect.md
diff --git a/src/content/projects/cosmos-homelab.md b/legacy/src/content/projects/cosmos-homelab.md
similarity index 100%
rename from src/content/projects/cosmos-homelab.md
rename to legacy/src/content/projects/cosmos-homelab.md
diff --git a/src/content/projects/driver-app.md b/legacy/src/content/projects/driver-app.md
similarity index 100%
rename from src/content/projects/driver-app.md
rename to legacy/src/content/projects/driver-app.md
diff --git a/src/content/projects/placeholder-project.md b/legacy/src/content/projects/placeholder-project.md
similarity index 100%
rename from src/content/projects/placeholder-project.md
rename to legacy/src/content/projects/placeholder-project.md
diff --git a/src/data/boardgames.json b/legacy/src/data/boardgames.json
similarity index 100%
rename from src/data/boardgames.json
rename to legacy/src/data/boardgames.json
diff --git a/src/data/media/anime.json b/legacy/src/data/media/anime.json
similarity index 100%
rename from src/data/media/anime.json
rename to legacy/src/data/media/anime.json
diff --git a/src/data/media/movies.json b/legacy/src/data/media/movies.json
similarity index 100%
rename from src/data/media/movies.json
rename to legacy/src/data/media/movies.json
diff --git a/src/data/media/tv.json b/legacy/src/data/media/tv.json
similarity index 100%
rename from src/data/media/tv.json
rename to legacy/src/data/media/tv.json
diff --git a/src/data/reading.json b/legacy/src/data/reading.json
similarity index 100%
rename from src/data/reading.json
rename to legacy/src/data/reading.json
diff --git a/src/data/store-availability.json b/legacy/src/data/store-availability.json
similarity index 100%
rename from src/data/store-availability.json
rename to legacy/src/data/store-availability.json
diff --git a/src/data/store-availability.ts b/legacy/src/data/store-availability.ts
similarity index 100%
rename from src/data/store-availability.ts
rename to legacy/src/data/store-availability.ts
diff --git a/src/data/store-products.ts b/legacy/src/data/store-products.ts
similarity index 100%
rename from src/data/store-products.ts
rename to legacy/src/data/store-products.ts
diff --git a/src/env.d.ts b/legacy/src/env.d.ts
similarity index 100%
rename from src/env.d.ts
rename to legacy/src/env.d.ts
diff --git a/src/layouts/BaseLayout.astro b/legacy/src/layouts/BaseLayout.astro
similarity index 100%
rename from src/layouts/BaseLayout.astro
rename to legacy/src/layouts/BaseLayout.astro
diff --git a/src/layouts/StoreLayout.astro b/legacy/src/layouts/StoreLayout.astro
similarity index 100%
rename from src/layouts/StoreLayout.astro
rename to legacy/src/layouts/StoreLayout.astro
diff --git a/src/lib/archidekt.ts b/legacy/src/lib/archidekt.ts
similarity index 100%
rename from src/lib/archidekt.ts
rename to legacy/src/lib/archidekt.ts
diff --git a/src/lib/brand-icons.ts b/legacy/src/lib/brand-icons.ts
similarity index 100%
rename from src/lib/brand-icons.ts
rename to legacy/src/lib/brand-icons.ts
diff --git a/src/lib/env.ts b/legacy/src/lib/env.ts
similarity index 100%
rename from src/lib/env.ts
rename to legacy/src/lib/env.ts
diff --git a/src/lib/media.ts b/legacy/src/lib/media.ts
similarity index 100%
rename from src/lib/media.ts
rename to legacy/src/lib/media.ts
diff --git a/src/lib/motion.ts b/legacy/src/lib/motion.ts
similarity index 100%
rename from src/lib/motion.ts
rename to legacy/src/lib/motion.ts
diff --git a/src/lib/platforms.ts b/legacy/src/lib/platforms.ts
similarity index 100%
rename from src/lib/platforms.ts
rename to legacy/src/lib/platforms.ts
diff --git a/src/lib/site.ts b/legacy/src/lib/site.ts
similarity index 100%
rename from src/lib/site.ts
rename to legacy/src/lib/site.ts
diff --git a/src/middleware.ts b/legacy/src/middleware.ts
similarity index 100%
rename from src/middleware.ts
rename to legacy/src/middleware.ts
diff --git a/src/pages/about.astro b/legacy/src/pages/about.astro
similarity index 100%
rename from src/pages/about.astro
rename to legacy/src/pages/about.astro
diff --git a/src/pages/api/contact.ts b/legacy/src/pages/api/contact.ts
similarity index 100%
rename from src/pages/api/contact.ts
rename to legacy/src/pages/api/contact.ts
diff --git a/src/pages/api/order.ts b/legacy/src/pages/api/order.ts
similarity index 100%
rename from src/pages/api/order.ts
rename to legacy/src/pages/api/order.ts
diff --git a/src/pages/contact.astro b/legacy/src/pages/contact.astro
similarity index 100%
rename from src/pages/contact.astro
rename to legacy/src/pages/contact.astro
diff --git a/src/pages/index.astro b/legacy/src/pages/index.astro
similarity index 100%
rename from src/pages/index.astro
rename to legacy/src/pages/index.astro
diff --git a/src/pages/interests/anime.astro b/legacy/src/pages/interests/anime.astro
similarity index 100%
rename from src/pages/interests/anime.astro
rename to legacy/src/pages/interests/anime.astro
diff --git a/src/pages/interests/gaming.astro b/legacy/src/pages/interests/gaming.astro
similarity index 100%
rename from src/pages/interests/gaming.astro
rename to legacy/src/pages/interests/gaming.astro
diff --git a/src/pages/interests/index.astro b/legacy/src/pages/interests/index.astro
similarity index 100%
rename from src/pages/interests/index.astro
rename to legacy/src/pages/interests/index.astro
diff --git a/src/pages/interests/making.astro b/legacy/src/pages/interests/making.astro
similarity index 100%
rename from src/pages/interests/making.astro
rename to legacy/src/pages/interests/making.astro
diff --git a/src/pages/interests/movies.astro b/legacy/src/pages/interests/movies.astro
similarity index 100%
rename from src/pages/interests/movies.astro
rename to legacy/src/pages/interests/movies.astro
diff --git a/src/pages/interests/pokemon.astro b/legacy/src/pages/interests/pokemon.astro
similarity index 100%
rename from src/pages/interests/pokemon.astro
rename to legacy/src/pages/interests/pokemon.astro
diff --git a/src/pages/interests/reading.astro b/legacy/src/pages/interests/reading.astro
similarity index 100%
rename from src/pages/interests/reading.astro
rename to legacy/src/pages/interests/reading.astro
diff --git a/src/pages/interests/tcgs.astro b/legacy/src/pages/interests/tcgs.astro
similarity index 100%
rename from src/pages/interests/tcgs.astro
rename to legacy/src/pages/interests/tcgs.astro
diff --git a/src/pages/interests/ttrpgs.astro b/legacy/src/pages/interests/ttrpgs.astro
similarity index 100%
rename from src/pages/interests/ttrpgs.astro
rename to legacy/src/pages/interests/ttrpgs.astro
diff --git a/src/pages/interests/tv.astro b/legacy/src/pages/interests/tv.astro
similarity index 100%
rename from src/pages/interests/tv.astro
rename to legacy/src/pages/interests/tv.astro
diff --git a/src/pages/professional/index.astro b/legacy/src/pages/professional/index.astro
similarity index 100%
rename from src/pages/professional/index.astro
rename to legacy/src/pages/professional/index.astro
diff --git a/src/pages/store/cards.astro b/legacy/src/pages/store/cards.astro
similarity index 100%
rename from src/pages/store/cards.astro
rename to legacy/src/pages/store/cards.astro
diff --git a/src/pages/store/deck-boxes.astro b/legacy/src/pages/store/deck-boxes.astro
similarity index 100%
rename from src/pages/store/deck-boxes.astro
rename to legacy/src/pages/store/deck-boxes.astro
diff --git a/src/pages/store/index.astro b/legacy/src/pages/store/index.astro
similarity index 100%
rename from src/pages/store/index.astro
rename to legacy/src/pages/store/index.astro
diff --git a/src/pages/store/order.astro b/legacy/src/pages/store/order.astro
similarity index 100%
rename from src/pages/store/order.astro
rename to legacy/src/pages/store/order.astro
diff --git a/src/scripts/reveal-init.ts b/legacy/src/scripts/reveal-init.ts
similarity index 100%
rename from src/scripts/reveal-init.ts
rename to legacy/src/scripts/reveal-init.ts
diff --git a/src/scripts/theme-init.ts b/legacy/src/scripts/theme-init.ts
similarity index 100%
rename from src/scripts/theme-init.ts
rename to legacy/src/scripts/theme-init.ts
diff --git a/src/styles/animations.css b/legacy/src/styles/animations.css
similarity index 100%
rename from src/styles/animations.css
rename to legacy/src/styles/animations.css
diff --git a/src/styles/global.css b/legacy/src/styles/global.css
similarity index 100%
rename from src/styles/global.css
rename to legacy/src/styles/global.css
diff --git a/src/styles/themes.css b/legacy/src/styles/themes.css
similarity index 100%
rename from src/styles/themes.css
rename to legacy/src/styles/themes.css
diff --git a/tsconfig.json b/legacy/tsconfig.json
similarity index 100%
rename from tsconfig.json
rename to legacy/tsconfig.json
diff --git a/package-lock.json b/package-lock.json
index 11d5bf2..f3fcd09 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,985 +1,32 @@
{
- "name": "greg-kilmer-profile",
- "version": "1.0.0",
+ "name": "gregkilmer-com",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
- "name": "greg-kilmer-profile",
- "version": "1.0.0",
- "dependencies": {
- "@astrojs/check": "^0.9.9",
- "@astrojs/mdx": "^7.0.2",
- "@astrojs/node": "^11.0.2",
- "@astrojs/react": "^6.0.2",
- "@fontsource-variable/cinzel": "^5.3.0",
- "@fontsource-variable/jetbrains-mono": "^5.2.8",
- "@fontsource-variable/montserrat": "^5.3.0",
- "@iconify-json/mdi": "^1.2.3",
- "@tailwindcss/vite": "^4.3.2",
- "@types/nodemailer": "^8.0.1",
- "@types/react": "^19.2.17",
- "@types/react-dom": "^19.2.3",
- "astro": "^7.2.0",
- "framer-motion": "^12.42.2",
- "nodemailer": "^9.0.3",
- "react": "^19.2.7",
- "react-dom": "^19.2.7",
- "tailwindcss": "^4.3.2",
- "typescript": "^6.0.3",
- "zod": "^4.4.3"
- }
- },
- "node_modules/@astrojs/check": {
- "version": "0.9.10",
- "resolved": "https://registry.npmjs.org/@astrojs/check/-/check-0.9.10.tgz",
- "integrity": "sha512-zgx/UQMozdjOa3bOxjgeCFdtpE3c9rRX6xHwa+2QXvy8z8Akifu2AtubHyv/zzC2znO8dl8fFWL4K+Ba9kS8HQ==",
- "license": "MIT",
- "dependencies": {
- "@astrojs/language-server": "^2.16.7",
- "chokidar": "^4.0.3",
- "kleur": "^4.1.5",
- "yargs": "^18.0.0"
- },
- "bin": {
- "astro-check": "bin/astro-check.js"
- },
- "peerDependencies": {
- "typescript": "^5.0.0 || ^6.0.0"
- }
- },
- "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/compiler-binding": {
- "version": "0.3.2",
- "resolved": "https://registry.npmjs.org/@astrojs/compiler-binding/-/compiler-binding-0.3.2.tgz",
- "integrity": "sha512-8w/9CWmYrAJJ8N0SY3O43ws2BgxoW6u3QsD8u2mE140lMYAlwh+tlNoUeSBq22wVheFuiBbR212l6ixZ2IIgCQ==",
- "license": "MIT",
- "engines": {
- "node": "^20.19.0 || >=22.12.0"
- },
- "optionalDependencies": {
- "@astrojs/compiler-binding-darwin-arm64": "0.3.2",
- "@astrojs/compiler-binding-darwin-x64": "0.3.2",
- "@astrojs/compiler-binding-linux-arm64-gnu": "0.3.2",
- "@astrojs/compiler-binding-linux-arm64-musl": "0.3.2",
- "@astrojs/compiler-binding-linux-x64-gnu": "0.3.2",
- "@astrojs/compiler-binding-linux-x64-musl": "0.3.2",
- "@astrojs/compiler-binding-wasm32-wasi": "0.3.2",
- "@astrojs/compiler-binding-win32-arm64-msvc": "0.3.2",
- "@astrojs/compiler-binding-win32-x64-msvc": "0.3.2"
- }
- },
- "node_modules/@astrojs/compiler-binding-darwin-arm64": {
- "version": "0.3.2",
- "resolved": "https://registry.npmjs.org/@astrojs/compiler-binding-darwin-arm64/-/compiler-binding-darwin-arm64-0.3.2.tgz",
- "integrity": "sha512-MM8tn8CSimcfytaOla4b6acN8mKWiL/rlAA1fpT3/Wl7dNGSE4y8FjTN/zJVNnb63CsLWG5zZwCt01TXtDKh9g==",
- "cpu": [
- "arm64"
+ "name": "gregkilmer-com",
+ "workspaces": [
+ "packages/*",
+ "tools/*"
],
- "license": "MIT",
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": "^20.19.0 || >=22.12.0"
- }
- },
- "node_modules/@astrojs/compiler-binding-darwin-x64": {
- "version": "0.3.2",
- "resolved": "https://registry.npmjs.org/@astrojs/compiler-binding-darwin-x64/-/compiler-binding-darwin-x64-0.3.2.tgz",
- "integrity": "sha512-2lXOlzf8xb7jLomRsf/aswh61/NnGusynB2OwFkK6k4pmOtpfXMYnG0PLfXrEvxXYj69NdCnmUYXtHDd+JOOag==",
- "cpu": [
- "x64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": "^20.19.0 || >=22.12.0"
- }
- },
- "node_modules/@astrojs/compiler-binding-linux-arm64-gnu": {
- "version": "0.3.2",
- "resolved": "https://registry.npmjs.org/@astrojs/compiler-binding-linux-arm64-gnu/-/compiler-binding-linux-arm64-gnu-0.3.2.tgz",
- "integrity": "sha512-BmU3kWj7qnLrd4vzm49zFEPJ5oFnn1tCT4Vt9hZbqdU5Cmb8GZl7fn6VFsnNfe7B18a2gIFtVzbLINtYl5kBjQ==",
- "cpu": [
- "arm64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": "^20.19.0 || >=22.12.0"
- }
- },
- "node_modules/@astrojs/compiler-binding-linux-arm64-musl": {
- "version": "0.3.2",
- "resolved": "https://registry.npmjs.org/@astrojs/compiler-binding-linux-arm64-musl/-/compiler-binding-linux-arm64-musl-0.3.2.tgz",
- "integrity": "sha512-f0heT9ZZEseSu5bHCeb80eL2DH07ArE6U9xi1WT/PEusNjzPmEr3GJsjG1tRLo5VYUUYX7h3ScaqGmGrMOVGmw==",
- "cpu": [
- "arm64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": "^20.19.0 || >=22.12.0"
- }
- },
- "node_modules/@astrojs/compiler-binding-linux-x64-gnu": {
- "version": "0.3.2",
- "resolved": "https://registry.npmjs.org/@astrojs/compiler-binding-linux-x64-gnu/-/compiler-binding-linux-x64-gnu-0.3.2.tgz",
- "integrity": "sha512-M8fOUt0itRpqiGyoEA/ij184s8O+hqbCz3+YozRusOOM3osgGljpDThhbKAJjqh82wOo6FioQ4w8PBvU1XMD5Q==",
- "cpu": [
- "x64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": "^20.19.0 || >=22.12.0"
- }
- },
- "node_modules/@astrojs/compiler-binding-linux-x64-musl": {
- "version": "0.3.2",
- "resolved": "https://registry.npmjs.org/@astrojs/compiler-binding-linux-x64-musl/-/compiler-binding-linux-x64-musl-0.3.2.tgz",
- "integrity": "sha512-/Kebk8sO6HnLeSd691JkaAPfN7CqR9/KEXmWvyNPkaKNGmj8rTZ/lf2uXtnPu92Lan84UrKdIKVPy1fSo2encQ==",
- "cpu": [
- "x64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": "^20.19.0 || >=22.12.0"
- }
- },
- "node_modules/@astrojs/compiler-binding-wasm32-wasi": {
- "version": "0.3.2",
- "resolved": "https://registry.npmjs.org/@astrojs/compiler-binding-wasm32-wasi/-/compiler-binding-wasm32-wasi-0.3.2.tgz",
- "integrity": "sha512-pUA6xbcOSB7DhfzIArB8BCAkFfAIqriiR7zl5zOStd6oU2G0kIKj+GUdGnyXyhfiv881Hffyk5tC0mR18sDjDw==",
- "cpu": [
- "wasm32"
- ],
- "license": "MIT",
- "optional": true,
- "dependencies": {
- "@napi-rs/wasm-runtime": "^1.2.0"
+ "devDependencies": {
+ "@types/node": "^22.10.0",
+ "tsx": "^4.19.0",
+ "typescript": "^5.7.0",
+ "vitest": "^5.0.3"
},
"engines": {
- "node": ">=14.0.0"
- }
- },
- "node_modules/@astrojs/compiler-binding-win32-arm64-msvc": {
- "version": "0.3.2",
- "resolved": "https://registry.npmjs.org/@astrojs/compiler-binding-win32-arm64-msvc/-/compiler-binding-win32-arm64-msvc-0.3.2.tgz",
- "integrity": "sha512-ESruf+6Qkl1trHUFxI6GSf6t52j8yN2kCNSzMWdzt7V/T09tFHrYzrVaJQohb2C9bJUH76pNvX6Zb51+xCQc9Q==",
- "cpu": [
- "arm64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": "^20.19.0 || >=22.12.0"
- }
- },
- "node_modules/@astrojs/compiler-binding-win32-x64-msvc": {
- "version": "0.3.2",
- "resolved": "https://registry.npmjs.org/@astrojs/compiler-binding-win32-x64-msvc/-/compiler-binding-win32-x64-msvc-0.3.2.tgz",
- "integrity": "sha512-wzzVrEbOwbsLWOdEbocskjMRx2aZPxJ7ZbmL+jnpBamFwmigm+2M/wzuM6JWncocgYwLic1csSpalBh96kQKXA==",
- "cpu": [
- "x64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": "^20.19.0 || >=22.12.0"
- }
- },
- "node_modules/@astrojs/compiler-rs": {
- "version": "0.3.2",
- "resolved": "https://registry.npmjs.org/@astrojs/compiler-rs/-/compiler-rs-0.3.2.tgz",
- "integrity": "sha512-xlx/T7JovIKduu4ucbTQUxQ5+Q8wxkHxhLjnZk3VlJbhbQ9RLvvuDk1p2YYFYFQ5y14dVm3FGGO4isQXa4F+Tg==",
- "license": "MIT",
- "dependencies": {
- "@astrojs/compiler-binding": "0.3.2"
- },
- "engines": {
- "node": ">=22.12.0"
- }
- },
- "node_modules/@astrojs/internal-helpers": {
- "version": "0.10.2",
- "resolved": "https://registry.npmjs.org/@astrojs/internal-helpers/-/internal-helpers-0.10.2.tgz",
- "integrity": "sha512-yt7fMgPYqSM4Tmr+taTW6Per+hjJ8Pk6lA1PAcDyqzOt8HzJ6Kje5WzCxA2Sd+9wsUW7uhkLeoTMK0cXPwH9rQ==",
- "license": "MIT",
- "dependencies": {
- "@types/hast": "^3.0.4",
- "@types/mdast": "^4.0.4",
- "js-yaml": "^4.3.0",
- "picomatch": "^4.0.4",
- "retext-smartypants": "^6.2.0",
- "shiki": "^4.0.2",
- "smol-toml": "^1.6.0",
- "unified": "^11.0.5"
- }
- },
- "node_modules/@astrojs/language-server": {
- "version": "2.16.13",
- "resolved": "https://registry.npmjs.org/@astrojs/language-server/-/language-server-2.16.13.tgz",
- "integrity": "sha512-ekOa+CYprEq5n4EJC1qTIAhLk49HZIUQuFwrEuF+3JK/pdMaYnWoREFUI2A0KEPOJiFA2kamBzKzbYljDvUxLg==",
- "license": "MIT",
- "dependencies": {
- "@astrojs/compiler": "^2.13.1",
- "@astrojs/yaml2ts": "^0.2.4",
- "@jridgewell/sourcemap-codec": "^1.5.5",
- "@volar/kit": "~2.4.28",
- "@volar/language-core": "~2.4.28",
- "@volar/language-server": "~2.4.28",
- "@volar/language-service": "~2.4.28",
- "muggle-string": "^0.4.1",
- "tinyglobby": "^0.2.16",
- "volar-service-css": "0.0.71",
- "volar-service-emmet": "0.0.71",
- "volar-service-html": "0.0.71",
- "volar-service-prettier": "0.0.71",
- "volar-service-typescript": "0.0.71",
- "volar-service-typescript-twoslash-queries": "0.0.71",
- "volar-service-yaml": "0.0.71",
- "vscode-html-languageservice": "^5.6.2",
- "vscode-uri": "^3.1.0"
- },
- "bin": {
- "astro-ls": "bin/nodeServer.js"
- },
- "peerDependencies": {
- "prettier": "^3.0.0",
- "prettier-plugin-astro": ">=0.11.0"
- },
- "peerDependenciesMeta": {
- "prettier": {
- "optional": true
- },
- "prettier-plugin-astro": {
- "optional": true
- }
- }
- },
- "node_modules/@astrojs/markdown-remark": {
- "version": "7.2.2",
- "resolved": "https://registry.npmjs.org/@astrojs/markdown-remark/-/markdown-remark-7.2.2.tgz",
- "integrity": "sha512-FGfmK84zSNcrsBd0dl1gXE9JvZYElp8EXQa2jpHVAxG4deGKAp43wspxFupjADJX7MSsMRHwYCnfT6EyVmgeFQ==",
- "license": "MIT",
- "dependencies": {
- "@astrojs/internal-helpers": "0.10.2",
- "@astrojs/prism": "4.0.2",
- "github-slugger": "^2.0.0",
- "hast-util-from-html": "^2.0.3",
- "hast-util-to-text": "^4.0.2",
- "mdast-util-definitions": "^6.0.0",
- "rehype-raw": "^7.0.0",
- "rehype-stringify": "^10.0.1",
- "remark-gfm": "^4.0.1",
- "remark-parse": "^11.0.0",
- "remark-rehype": "^11.1.2",
- "remark-smartypants": "^3.0.2",
- "unified": "^11.0.5",
- "unist-util-remove-position": "^5.0.0",
- "unist-util-visit": "^5.1.0",
- "unist-util-visit-parents": "^6.0.2",
- "vfile": "^6.0.3"
- }
- },
- "node_modules/@astrojs/markdown-satteri": {
- "version": "0.3.5",
- "resolved": "https://registry.npmjs.org/@astrojs/markdown-satteri/-/markdown-satteri-0.3.5.tgz",
- "integrity": "sha512-CvWVEFAbay7YO+i9SaqDJubipA5ckiVB89QWoMJ5XC0m5CtFg8JwZ7Kau6X9sYY7FZURH0w2l03ISH2jOS/RDQ==",
- "license": "MIT",
- "dependencies": {
- "@astrojs/internal-helpers": "0.10.2",
- "@astrojs/prism": "4.0.2",
- "github-slugger": "^2.0.0",
- "hast-util-from-html": "^2.0.3",
- "satteri": "^0.9.1"
- }
- },
- "node_modules/@astrojs/mdx": {
- "version": "7.0.5",
- "resolved": "https://registry.npmjs.org/@astrojs/mdx/-/mdx-7.0.5.tgz",
- "integrity": "sha512-wEM/HH1RiEntyPVagdiF+yArzfcYLKBB0C1RZspVidKZ97rRMbaqP1Nbl/GR0sJs8zwaceqxRymw8aOKKJRdYw==",
- "license": "MIT",
- "dependencies": {
- "@astrojs/internal-helpers": "0.10.2",
- "@astrojs/markdown-remark": "7.2.2",
- "@mdx-js/mdx": "^3.1.1",
- "acorn": "^8.16.0",
- "es-module-lexer": "^2.0.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.1.0",
- "vfile": "^6.0.3"
- },
- "engines": {
- "node": ">=22.12.0"
- },
- "peerDependencies": {
- "@astrojs/markdown-satteri": "^0.3.1",
- "astro": "^7.0.0"
- },
- "peerDependenciesMeta": {
- "@astrojs/markdown-satteri": {
- "optional": true
- }
- }
- },
- "node_modules/@astrojs/node": {
- "version": "11.1.0",
- "resolved": "https://registry.npmjs.org/@astrojs/node/-/node-11.1.0.tgz",
- "integrity": "sha512-utAqBE1eIcAhyIkBUnNIWrzj4vyTiOlbbqcjUSE2Yz5XrMu+9sQdBwp/UmZXfJ9wjaEIHf26HInNLUUmTOFeEw==",
- "license": "MIT",
- "dependencies": {
- "@astrojs/internal-helpers": "0.10.2",
- "send": "^1.2.1",
- "server-destroy": "^1.0.1"
- },
- "peerDependencies": {
- "astro": "^7.0.0"
- }
- },
- "node_modules/@astrojs/prism": {
- "version": "4.0.2",
- "resolved": "https://registry.npmjs.org/@astrojs/prism/-/prism-4.0.2.tgz",
- "integrity": "sha512-KTivpmnz6lDsC6o9H4+DNm2SrE/GHzw8cNAvEJwAvUT+eoaEnn/4NtbDNfRRaxaJHdp15gf+tfHAWiXR4wB3BA==",
- "license": "MIT",
- "dependencies": {
- "prismjs": "^1.30.0"
- },
- "engines": {
- "node": ">=22.12.0"
- }
- },
- "node_modules/@astrojs/react": {
- "version": "6.0.2",
- "resolved": "https://registry.npmjs.org/@astrojs/react/-/react-6.0.2.tgz",
- "integrity": "sha512-Fvv2UqS7ajFL2Yjd5s4s5PcCBCSSfks4r04rbCHZO5z4pUPwCygV2ketI+iC+1P/sdHANLV3tgDL1wjzty4cJg==",
- "license": "MIT",
- "dependencies": {
- "@astrojs/internal-helpers": "0.10.2",
- "@vitejs/plugin-react": "^5.2.0",
- "devalue": "^5.8.1",
- "ultrahtml": "^1.6.0",
- "vite": "^8.0.13"
- },
- "engines": {
- "node": ">=22.12.0"
- },
- "peerDependencies": {
- "@types/react": "^17.0.50 || ^18.0.21 || ^19.0.0",
- "@types/react-dom": "^17.0.17 || ^18.0.6 || ^19.0.0",
- "react": "^17.0.2 || ^18.0.0 || ^19.0.0",
- "react-dom": "^17.0.2 || ^18.0.0 || ^19.0.0"
- }
- },
- "node_modules/@astrojs/telemetry": {
- "version": "3.3.3",
- "resolved": "https://registry.npmjs.org/@astrojs/telemetry/-/telemetry-3.3.3.tgz",
- "integrity": "sha512-C1TLn5sPJr0x4vk56piHWKbnqlEB8BKyte5Y45V02U+D7BGO5eMqZDH5aPjnkXQWJggvmsTXxH03QMZ9NgWLzQ==",
- "license": "MIT",
- "dependencies": {
- "ci-info": "^4.4.0",
- "dset": "^3.1.4",
- "is-docker": "^4.0.0",
- "package-manager-detector": "^1.6.0"
- },
- "engines": {
- "node": "18.20.8 || ^20.3.0 || >=22.0.0"
- }
- },
- "node_modules/@astrojs/yaml2ts": {
- "version": "0.2.4",
- "resolved": "https://registry.npmjs.org/@astrojs/yaml2ts/-/yaml2ts-0.2.4.tgz",
- "integrity": "sha512-8oddpOae35pJsXPQXhTkM0ypfKPskVsh2bCxRtbf7e+/Epw2nReakFYpLKjZMEr75CsoF203PMnCocpfz0s69A==",
- "license": "MIT",
- "dependencies": {
- "yaml": "^2.8.3"
- }
- },
- "node_modules/@babel/code-frame": {
- "version": "7.29.7",
- "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz",
- "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==",
- "license": "MIT",
- "dependencies": {
- "@babel/helper-validator-identifier": "^7.29.7",
- "js-tokens": "^4.0.0",
- "picocolors": "^1.1.1"
- },
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/compat-data": {
- "version": "7.29.7",
- "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.7.tgz",
- "integrity": "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==",
- "license": "MIT",
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/core": {
- "version": "7.29.7",
- "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.7.tgz",
- "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==",
- "license": "MIT",
- "dependencies": {
- "@babel/code-frame": "^7.29.7",
- "@babel/generator": "^7.29.7",
- "@babel/helper-compilation-targets": "^7.29.7",
- "@babel/helper-module-transforms": "^7.29.7",
- "@babel/helpers": "^7.29.7",
- "@babel/parser": "^7.29.7",
- "@babel/template": "^7.29.7",
- "@babel/traverse": "^7.29.7",
- "@babel/types": "^7.29.7",
- "@jridgewell/remapping": "^2.3.5",
- "convert-source-map": "^2.0.0",
- "debug": "^4.1.0",
- "gensync": "^1.0.0-beta.2",
- "json5": "^2.2.3",
- "semver": "^6.3.1"
- },
- "engines": {
- "node": ">=6.9.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/babel"
- }
- },
- "node_modules/@babel/generator": {
- "version": "7.29.8",
- "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.8.tgz",
- "integrity": "sha512-gZbepsdh3WDtgZKWL+vTPh71LSBrm/Y4/QDZBVCcYfmeTEEuoOYwlSy+G1StfJg+/Zy550u/3TATbm7qDbbMtg==",
- "license": "MIT",
- "dependencies": {
- "@babel/parser": "^7.29.8",
- "@babel/types": "^7.29.8",
- "@jridgewell/gen-mapping": "^0.3.12",
- "@jridgewell/trace-mapping": "^0.3.28",
- "jsesc": "^3.0.2"
- },
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/helper-compilation-targets": {
- "version": "7.29.7",
- "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz",
- "integrity": "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==",
- "license": "MIT",
- "dependencies": {
- "@babel/compat-data": "^7.29.7",
- "@babel/helper-validator-option": "^7.29.7",
- "browserslist": "^4.24.0",
- "lru-cache": "^5.1.1",
- "semver": "^6.3.1"
- },
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/helper-globals": {
- "version": "7.29.7",
- "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz",
- "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==",
- "license": "MIT",
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/helper-module-imports": {
- "version": "7.29.7",
- "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz",
- "integrity": "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==",
- "license": "MIT",
- "dependencies": {
- "@babel/traverse": "^7.29.7",
- "@babel/types": "^7.29.7"
- },
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/helper-module-transforms": {
- "version": "7.29.7",
- "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz",
- "integrity": "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==",
- "license": "MIT",
- "dependencies": {
- "@babel/helper-module-imports": "^7.29.7",
- "@babel/helper-validator-identifier": "^7.29.7",
- "@babel/traverse": "^7.29.7"
- },
- "engines": {
- "node": ">=6.9.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0"
- }
- },
- "node_modules/@babel/helper-plugin-utils": {
- "version": "7.29.7",
- "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.29.7.tgz",
- "integrity": "sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw==",
- "license": "MIT",
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/helper-string-parser": {
- "version": "7.29.7",
- "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz",
- "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==",
- "license": "MIT",
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/helper-validator-identifier": {
- "version": "7.29.7",
- "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz",
- "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==",
- "license": "MIT",
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/helper-validator-option": {
- "version": "7.29.7",
- "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz",
- "integrity": "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==",
- "license": "MIT",
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/helpers": {
- "version": "7.29.7",
- "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.7.tgz",
- "integrity": "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==",
- "license": "MIT",
- "dependencies": {
- "@babel/template": "^7.29.7",
- "@babel/types": "^7.29.7"
- },
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/parser": {
- "version": "7.29.8",
- "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.8.tgz",
- "integrity": "sha512-E8lTAYNB1KW+FH+VGJuZM1ioAx2E6oVlvQFRrf5P8ZZmsiJXYAD9vTFV7yyEURNzgh1dFqMZuO6tUwcARbqFCA==",
- "license": "MIT",
- "dependencies": {
- "@babel/types": "^7.29.8"
- },
- "bin": {
- "parser": "bin/babel-parser.js"
- },
- "engines": {
- "node": ">=6.0.0"
- }
- },
- "node_modules/@babel/plugin-transform-react-jsx-self": {
- "version": "7.29.7",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.29.7.tgz",
- "integrity": "sha512-TL0hMc9xzy86VD31nUiwzd5otRAcyEPcsegCxolO0PvcXuH1v0kECe/UIznYFihpkvU5wg/jk4v0TTEFfm53fw==",
- "license": "MIT",
- "dependencies": {
- "@babel/helper-plugin-utils": "^7.29.7"
- },
- "engines": {
- "node": ">=6.9.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0-0"
- }
- },
- "node_modules/@babel/plugin-transform-react-jsx-source": {
- "version": "7.29.7",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.29.7.tgz",
- "integrity": "sha512-06IyK09H3wi4cGbhDBwp5gUGo0IKtnYa8tyTiephirPCK6fbobVGiXMMI5zLQ4aKEYP3wZ3ArU44o+8KMrSG/Q==",
- "license": "MIT",
- "dependencies": {
- "@babel/helper-plugin-utils": "^7.29.7"
- },
- "engines": {
- "node": ">=6.9.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0-0"
- }
- },
- "node_modules/@babel/template": {
- "version": "7.29.7",
- "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz",
- "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==",
- "license": "MIT",
- "dependencies": {
- "@babel/code-frame": "^7.29.7",
- "@babel/parser": "^7.29.7",
- "@babel/types": "^7.29.7"
- },
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/traverse": {
- "version": "7.29.8",
- "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.8.tgz",
- "integrity": "sha512-I5z7H3bf/41ktsNVLtpN0wAa336HkqIHQ5BuPLEhTkt1jVSyZpeNKIzTgEWmlxjdg81R0IgUCcaE+Ok3NvrfZg==",
- "license": "MIT",
- "dependencies": {
- "@babel/code-frame": "^7.29.7",
- "@babel/generator": "^7.29.8",
- "@babel/helper-globals": "^7.29.7",
- "@babel/parser": "^7.29.8",
- "@babel/template": "^7.29.7",
- "@babel/types": "^7.29.8",
- "debug": "^4.3.1"
- },
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/types": {
- "version": "7.29.8",
- "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.8.tgz",
- "integrity": "sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==",
- "license": "MIT",
- "dependencies": {
- "@babel/helper-string-parser": "^7.29.7",
- "@babel/helper-validator-identifier": "^7.29.7"
- },
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@bruits/satteri-darwin-arm64": {
- "version": "0.9.5",
- "resolved": "https://registry.npmjs.org/@bruits/satteri-darwin-arm64/-/satteri-darwin-arm64-0.9.5.tgz",
- "integrity": "sha512-iw4nZgx9v30lWo/MTngQqi1pI78KI0DnkSm+lVJGYdmPLgAyDNJigVhpG42/Iq55A6c1Ll8q66ljyyRiQUxwow==",
- "cpu": [
- "arm64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "darwin"
- ]
- },
- "node_modules/@bruits/satteri-darwin-x64": {
- "version": "0.9.5",
- "resolved": "https://registry.npmjs.org/@bruits/satteri-darwin-x64/-/satteri-darwin-x64-0.9.5.tgz",
- "integrity": "sha512-6T26Z5Kf3cFW2PSlk9p7zT7yVxvuBSiJvYyz9u8KjYwMTqZyIDOj2wDyNpxKV4+6yUVG7rddq2QwvG/8LJA2+Q==",
- "cpu": [
- "x64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "darwin"
- ]
- },
- "node_modules/@bruits/satteri-linux-arm64-gnu": {
- "version": "0.9.5",
- "resolved": "https://registry.npmjs.org/@bruits/satteri-linux-arm64-gnu/-/satteri-linux-arm64-gnu-0.9.5.tgz",
- "integrity": "sha512-u51id17uJwNEMK9nBlICsq6U31c+XVqQueVBkwRIzZG+gMpS8TOJctt5h5Wz33Z8xnMdTd+adtACVz0yHgGuOA==",
- "cpu": [
- "arm64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@bruits/satteri-linux-arm64-musl": {
- "version": "0.9.5",
- "resolved": "https://registry.npmjs.org/@bruits/satteri-linux-arm64-musl/-/satteri-linux-arm64-musl-0.9.5.tgz",
- "integrity": "sha512-v39HxiwGC5Rqm01HksP6+5Y+xKLPlsuVFgIgpEAo+SiQ22c+mJVhS3u7Z6ePAKdhL5NJoK1xq70kLz3L13AhpQ==",
- "cpu": [
- "arm64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@bruits/satteri-linux-x64-gnu": {
- "version": "0.9.5",
- "resolved": "https://registry.npmjs.org/@bruits/satteri-linux-x64-gnu/-/satteri-linux-x64-gnu-0.9.5.tgz",
- "integrity": "sha512-F3uO8uFp3pAP5ZGXttwvh57GS7s0lL953tnNdyI2gRyP4kOOkp6pyGojNJzCjkDvWI2Cvb9iNrKok3aqQPauAw==",
- "cpu": [
- "x64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@bruits/satteri-linux-x64-musl": {
- "version": "0.9.5",
- "resolved": "https://registry.npmjs.org/@bruits/satteri-linux-x64-musl/-/satteri-linux-x64-musl-0.9.5.tgz",
- "integrity": "sha512-bicEqglLlz++mWyADaZoP0JY20s4vDfLjaPYgQqC+NI4zZLTOOg1T4GB8aqtc822Pqji8SQBmSrTb7CrP8i08Q==",
- "cpu": [
- "x64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@bruits/satteri-wasm32-wasi": {
- "version": "0.9.5",
- "resolved": "https://registry.npmjs.org/@bruits/satteri-wasm32-wasi/-/satteri-wasm32-wasi-0.9.5.tgz",
- "integrity": "sha512-zauAuMwfPnKPUkd4AFixRFpXdgKwP2mKgxrIIo2gJzW0/ZneF9dbHnLkojSpaBnCCp7VUL1hIi5WWZvB1CqmAQ==",
- "cpu": [
- "wasm32"
- ],
- "license": "MIT",
- "optional": true,
- "dependencies": {
- "@emnapi/core": "1.11.1",
- "@emnapi/runtime": "1.11.1",
- "@napi-rs/wasm-runtime": "^1.1.6"
- },
- "engines": {
- "node": ">=14.0.0"
- }
- },
- "node_modules/@bruits/satteri-wasm32-wasi/node_modules/@emnapi/core": {
- "version": "1.11.1",
- "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.11.1.tgz",
- "integrity": "sha512-RSvbQmHzdKzNsLYa/wHrbc3KN4sYLKAdPZxqiM2HATqv/SBk2/ENSHpvXGaLOMcsAyz0poEGqkmmKYG3OWiJEQ==",
- "license": "MIT",
- "optional": true,
- "dependencies": {
- "@emnapi/wasi-threads": "1.2.2",
- "tslib": "^2.4.0"
- }
- },
- "node_modules/@bruits/satteri-wasm32-wasi/node_modules/@emnapi/runtime": {
- "version": "1.11.1",
- "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.1.tgz",
- "integrity": "sha512-vgj7R3y3Wgx24IQaGPA/R6YFXLHVMOZ0uVEyIQPaWs+rd1AzfEMXlAC22FYwO1XkKR6NPsq7mUandH8oIRdZFw==",
- "license": "MIT",
- "optional": true,
- "dependencies": {
- "tslib": "^2.4.0"
- }
- },
- "node_modules/@bruits/satteri-wasm32-wasi/node_modules/@emnapi/wasi-threads": {
- "version": "1.2.2",
- "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.2.tgz",
- "integrity": "sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA==",
- "license": "MIT",
- "optional": true,
- "dependencies": {
- "tslib": "^2.4.0"
- }
- },
- "node_modules/@bruits/satteri-win32-arm64-msvc": {
- "version": "0.9.5",
- "resolved": "https://registry.npmjs.org/@bruits/satteri-win32-arm64-msvc/-/satteri-win32-arm64-msvc-0.9.5.tgz",
- "integrity": "sha512-SrfE7NEsgZjBvU3c+RR6oQRu0ToXY5uVJEbieXEF0YTctIV2zAVlbaMjWLts074QCgh3a+XHWkR/lWh2VH2LUg==",
- "cpu": [
- "arm64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "win32"
- ]
- },
- "node_modules/@bruits/satteri-win32-x64-msvc": {
- "version": "0.9.5",
- "resolved": "https://registry.npmjs.org/@bruits/satteri-win32-x64-msvc/-/satteri-win32-x64-msvc-0.9.5.tgz",
- "integrity": "sha512-5Kw9ZAtTGS8WHizyn+CJhjjfIQrw+7jcZodpmpXJjefnO15M8UexIi6JR2E5thyvsmHyhL6ZDDMUNR4bKJPd4g==",
- "cpu": [
- "x64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "win32"
- ]
- },
- "node_modules/@capsizecss/unpack": {
- "version": "4.0.1",
- "resolved": "https://registry.npmjs.org/@capsizecss/unpack/-/unpack-4.0.1.tgz",
- "integrity": "sha512-CuNiSqg7+e1cO/GjffyMOm5Tt2jUF9CWHHnvQ/UkqvtkGfHdgwEC0wpmq7fkN3gxwpRnrAN0WzO3vREKmNolMQ==",
- "license": "MIT",
- "dependencies": {
- "fontkitten": "^1.0.3"
- },
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@clack/core": {
- "version": "1.4.3",
- "resolved": "https://registry.npmjs.org/@clack/core/-/core-1.4.3.tgz",
- "integrity": "sha512-/kr3UWNtdJfxZtPgDqUOmG2pvwlmcLGheex5yiZKdwbzZJxhV+HMNR9QNmyY5cGwTNV6LrR7Jtp+KjhUAP1qBQ==",
- "license": "MIT",
- "dependencies": {
- "fast-wrap-ansi": "^0.2.0",
- "sisteransi": "^1.0.5"
- },
- "engines": {
- "node": ">= 20.12.0"
- }
- },
- "node_modules/@clack/prompts": {
- "version": "1.7.0",
- "resolved": "https://registry.npmjs.org/@clack/prompts/-/prompts-1.7.0.tgz",
- "integrity": "sha512-y7/yvZ2TPAnR9+jnc00klvNNLkJiXFFrQA/hlLCcxA9a2A4zQIOimyFQ9XfwYKiGD1fb5GY8vbKIIgO8d5Tb2A==",
- "license": "MIT",
- "dependencies": {
- "@clack/core": "1.4.3",
- "fast-string-width": "^3.0.2",
- "fast-wrap-ansi": "^0.2.0",
- "sisteransi": "^1.0.5"
- },
- "engines": {
- "node": ">= 20.12.0"
- }
- },
- "node_modules/@emmetio/abbreviation": {
- "version": "2.3.3",
- "resolved": "https://registry.npmjs.org/@emmetio/abbreviation/-/abbreviation-2.3.3.tgz",
- "integrity": "sha512-mgv58UrU3rh4YgbE/TzgLQwJ3pFsHHhCLqY20aJq+9comytTXUDNGG/SMtSeMJdkpxgXSXunBGLD8Boka3JyVA==",
- "license": "MIT",
- "dependencies": {
- "@emmetio/scanner": "^1.0.4"
- }
- },
- "node_modules/@emmetio/css-abbreviation": {
- "version": "2.1.8",
- "resolved": "https://registry.npmjs.org/@emmetio/css-abbreviation/-/css-abbreviation-2.1.8.tgz",
- "integrity": "sha512-s9yjhJ6saOO/uk1V74eifykk2CBYi01STTK3WlXWGOepyKa23ymJ053+DNQjpFcy1ingpaO7AxCcwLvHFY9tuw==",
- "license": "MIT",
- "dependencies": {
- "@emmetio/scanner": "^1.0.4"
- }
- },
- "node_modules/@emmetio/css-parser": {
- "version": "0.4.1",
- "resolved": "https://registry.npmjs.org/@emmetio/css-parser/-/css-parser-0.4.1.tgz",
- "integrity": "sha512-2bC6m0MV/voF4CTZiAbG5MWKbq5EBmDPKu9Sb7s7nVcEzNQlrZP6mFFFlIaISM8X6514H9shWMme1fCm8cWAfQ==",
- "license": "MIT",
- "dependencies": {
- "@emmetio/stream-reader": "^2.2.0",
- "@emmetio/stream-reader-utils": "^0.1.0"
- }
- },
- "node_modules/@emmetio/html-matcher": {
- "version": "1.3.0",
- "resolved": "https://registry.npmjs.org/@emmetio/html-matcher/-/html-matcher-1.3.0.tgz",
- "integrity": "sha512-NTbsvppE5eVyBMuyGfVu2CRrLvo7J4YHb6t9sBFLyY03WYhXET37qA4zOYUjBWFCRHO7pS1B9khERtY0f5JXPQ==",
- "license": "ISC",
- "dependencies": {
- "@emmetio/scanner": "^1.0.0"
- }
- },
- "node_modules/@emmetio/scanner": {
- "version": "1.0.4",
- "resolved": "https://registry.npmjs.org/@emmetio/scanner/-/scanner-1.0.4.tgz",
- "integrity": "sha512-IqRuJtQff7YHHBk4G8YZ45uB9BaAGcwQeVzgj/zj8/UdOhtQpEIupUhSk8dys6spFIWVZVeK20CzGEnqR5SbqA==",
- "license": "MIT"
- },
- "node_modules/@emmetio/stream-reader": {
- "version": "2.2.0",
- "resolved": "https://registry.npmjs.org/@emmetio/stream-reader/-/stream-reader-2.2.0.tgz",
- "integrity": "sha512-fXVXEyFA5Yv3M3n8sUGT7+fvecGrZP4k6FnWWMSZVQf69kAq0LLpaBQLGcPR30m3zMmKYhECP4k/ZkzvhEW5kw==",
- "license": "MIT"
- },
- "node_modules/@emmetio/stream-reader-utils": {
- "version": "0.1.0",
- "resolved": "https://registry.npmjs.org/@emmetio/stream-reader-utils/-/stream-reader-utils-0.1.0.tgz",
- "integrity": "sha512-ZsZ2I9Vzso3Ho/pjZFsmmZ++FWeEd/txqybHTm4OgaZzdS8V9V/YYWQwg5TC38Z7uLWUV1vavpLLbjJtKubR1A==",
- "license": "MIT"
- },
- "node_modules/@emnapi/core": {
- "version": "1.11.3",
- "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.11.3.tgz",
- "integrity": "sha512-zLpS5asjEb7lq8jYLq37N6XKaE41DIexlY1rF/z4/tIl3wo13Sqm28fRyfIsKZD+NZ8mM5RoKkpW/rBcuoSZSg==",
- "license": "MIT",
- "optional": true,
- "dependencies": {
- "@emnapi/wasi-threads": "1.2.3",
- "tslib": "^2.4.0"
- }
- },
- "node_modules/@emnapi/runtime": {
- "version": "1.11.3",
- "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.3.tgz",
- "integrity": "sha512-Xz4Tpyki7XyrpbUK1jR1AhdAdaXyhhY4lZ3neLodmhpuWfy2PAQN5B46sAiU4liOXGLkHypn/qU+jvfWSCYYLA==",
- "license": "MIT",
- "optional": true,
- "dependencies": {
- "tslib": "^2.4.0"
- }
- },
- "node_modules/@emnapi/wasi-threads": {
- "version": "1.2.3",
- "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.3.tgz",
- "integrity": "sha512-ELEBe8PsLvvJ6QMr0zLt8ffvOHW/dc1m3CEzNMg7aJUv3bMaoDtw2TXyDAwkYBuroxxuHEwhRTLJSe5sya547g==",
- "license": "MIT",
- "optional": true,
- "dependencies": {
- "tslib": "^2.4.0"
+ "node": ">=22"
}
},
"node_modules/@esbuild/aix-ppc64": {
- "version": "0.28.1",
- "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.1.tgz",
- "integrity": "sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ==",
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.2.tgz",
+ "integrity": "sha512-XExcO+dvLKvVtNTibSTBej1NCAbaGhWn9Ww1ZPx80qsahhPFe/8jgWP0IchNe0F3HwkU7n8ejhH8bjonqht8mQ==",
"cpu": [
"ppc64"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -990,12 +37,13 @@
}
},
"node_modules/@esbuild/android-arm": {
- "version": "0.28.1",
- "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.1.tgz",
- "integrity": "sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ==",
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.2.tgz",
+ "integrity": "sha512-kXXoiPVVGQcnIYGOeaovwOURpniDBpSq4A03qkQ+BMQqtGG6HYap3xne9C1O1yo4TR3qxlCX5IqqmX6fFo2Lqg==",
"cpu": [
"arm"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -1006,12 +54,13 @@
}
},
"node_modules/@esbuild/android-arm64": {
- "version": "0.28.1",
- "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.1.tgz",
- "integrity": "sha512-34EGEbCIAgosYz6goLcopX6Mo7NyGv9tfwEM2/7Ce2VcVRk568iSvniGWcUXIy7wEDR1wzolcxcriFVrWYcwBg==",
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.2.tgz",
+ "integrity": "sha512-5YfKeeI8qWfBZIX+u2xZC3Zlb3Os/gLS2sbEKM+I4ZOcsWmHS2WLysCcQZDAFRslDUU5Oiq44gf6PYN1vGwG5A==",
"cpu": [
"arm64"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -1022,12 +71,13 @@
}
},
"node_modules/@esbuild/android-x64": {
- "version": "0.28.1",
- "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.1.tgz",
- "integrity": "sha512-dbwY7ltSMDWsRatcRpCnES4F+im88OCUgGZjy52shC7GqHRE/cYlxNbB4Z4UpJswpcc4Qxd2oE/ufM0p61IKng==",
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.2.tgz",
+ "integrity": "sha512-O387ite7SzUyCcy3JQX4P4bLtEA7bLLkx+esve5JHnyYfNTxcVpXZo9jhdB0lTKN44gztELTdU7nS8Nr16Fs1Q==",
"cpu": [
"x64"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -1038,12 +88,13 @@
}
},
"node_modules/@esbuild/darwin-arm64": {
- "version": "0.28.1",
- "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.1.tgz",
- "integrity": "sha512-TZbWkQY7kvTAXbXUT7uVACR5cMHsDiSz9z7ZKAX/RTq/WJEk3QyRr0wZpNhBDX+/0CtdqUIJlOiodQcta6tY3Q==",
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.2.tgz",
+ "integrity": "sha512-n4KqkOQrraxHJcgjM1RvwbigfQKIKJVpM7xp+KsxiyUSrRdIXnt73VhrPAx0fV44hgfmIVKjxMN9J1t5jySVkw==",
"cpu": [
"arm64"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -1054,12 +105,13 @@
}
},
"node_modules/@esbuild/darwin-x64": {
- "version": "0.28.1",
- "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.1.tgz",
- "integrity": "sha512-zfdzgK9ACBNZLI/CyHTOx81SyNbM6YXn7rxSgX97VjyiPl9W1i4Ka4fgKECEoFCKGpvBj5qArWIGgQjOwkgskQ==",
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.2.tgz",
+ "integrity": "sha512-uq6suIWYP37qzGddBKPw5QEQPi6HiLGsO7UmkpfyaYNQ3D+rN6w6WfwH+nuqcGXWvawGwxOEroO4YGnFh95azw==",
"cpu": [
"x64"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -1070,12 +122,13 @@
}
},
"node_modules/@esbuild/freebsd-arm64": {
- "version": "0.28.1",
- "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.1.tgz",
- "integrity": "sha512-wG2EA8ENdEI0qhkSZMjfqrdY+ziCYCPMmtZjjIwOmXFjmyzEHn+UUxk5of+SYsjtfs3VpnlC7QLzSI5hY/rOAw==",
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.2.tgz",
+ "integrity": "sha512-n+I0BTSRIoy+d6RPKnEVwql5UwBJolytvY4mAOIEJorKlqgPII8ix6slVVrfZ5Tnj7glIZvloylbB/EJPMWEXw==",
"cpu": [
"arm64"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -1086,12 +139,13 @@
}
},
"node_modules/@esbuild/freebsd-x64": {
- "version": "0.28.1",
- "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.1.tgz",
- "integrity": "sha512-i7dZ9vQgnvSCzi/rYCXNgtF/U+eKZNJBzu3eTQbRgHnM7tNSizLOkRFAl3qzVc/Op/u5YkHHa4pf/3DOYHthLQ==",
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.2.tgz",
+ "integrity": "sha512-78XJTJkvPs0kz2w61301PJjXl4g7q3JqiYMZ/M/yVI73EHBrCRTgkhu9oqG7vPqq+a/yadEW8aD+agKlk5xrmg==",
"cpu": [
"x64"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -1102,12 +156,13 @@
}
},
"node_modules/@esbuild/linux-arm": {
- "version": "0.28.1",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.1.tgz",
- "integrity": "sha512-qVXBOHQS+d5Y722GwJzJUtOLlX7km3CraOaGormF1pDtPd2C/l1SHRPgjLunLGe51Sh5YYWKMFDyV4SxgMQYTQ==",
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.2.tgz",
+ "integrity": "sha512-XlDnu2q5yoqems+xay6wSAcg9DDD7K9RLKZEBOMZm3ckNpJBvOX20tSfby8KfrrhINDyv9V2YVZKY/SpoGJI8w==",
"cpu": [
"arm"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -1118,12 +173,13 @@
}
},
"node_modules/@esbuild/linux-arm64": {
- "version": "0.28.1",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.1.tgz",
- "integrity": "sha512-yHs+0uc8+nvEAfAfxrWQKK5peSNzBc4PegcMO0EJ2hT71uA7vB8Ihg2e77R2P7SG5uYjPbHlLLmve4LLLRCf0g==",
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.2.tgz",
+ "integrity": "sha512-pW4AC0P3it8c7do9MVM4p51FzHzdM/TZrerurgRcHJ2WTa1VQ1CIq18xncfpBJw4ojkiZZrKW2yIBWBP92j6Ug==",
"cpu": [
"arm64"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -1134,12 +190,13 @@
}
},
"node_modules/@esbuild/linux-ia32": {
- "version": "0.28.1",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.1.tgz",
- "integrity": "sha512-d1z4ZuP0ajrfz/FhGT4vv278rX8KnPPJx8i5+AtK7TYbx9Le9F1hyzurZpkEyjkGa9dUGhQow4C1NmeGvqxN2w==",
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.2.tgz",
+ "integrity": "sha512-CYbnj78HsIeA+DhgUKgFCfvNsTHFhMMrinUrMZpDXJXKN8T3XViTZ/+wtHeVxEWY8ewSzTFN+nRmSwO2tZaLUQ==",
"cpu": [
"ia32"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -1150,12 +207,13 @@
}
},
"node_modules/@esbuild/linux-loong64": {
- "version": "0.28.1",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.1.tgz",
- "integrity": "sha512-M5sRjUVZrkm1OAPR3dlOYzNmN+loZKGVi1VUQGrwuqLcbR6qeAz+famMhjASeH3YVKvZz+zT1jlh/keC3Rj/lg==",
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.2.tgz",
+ "integrity": "sha512-buwkd8nsph4R+ajRvw0qM5Hja/TXQow3ptzWO2EbG/cqcIkHloRrdlBtQlshyYGTNFvfkfJ5tpPLVkY4DtsPfQ==",
"cpu": [
"loong64"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -1166,12 +224,13 @@
}
},
"node_modules/@esbuild/linux-mips64el": {
- "version": "0.28.1",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.1.tgz",
- "integrity": "sha512-mRObBZeHh2OxcBFPWE/FjylkRgZdYuiTR3vaTozquCGOH14iP9oN4x4Ge81CoIDYQrXmIxpFumJBu5MtZpnQJQ==",
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.2.tgz",
+ "integrity": "sha512-ZVykbDyk7519VwiNb9Lcj9m8XM6v5V9uKPvrEMkkEedVewf+0itkhahp4HDpgERXhwLRpWFypsGbG/J8s0QjJA==",
"cpu": [
"mips64el"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -1182,12 +241,13 @@
}
},
"node_modules/@esbuild/linux-ppc64": {
- "version": "0.28.1",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.1.tgz",
- "integrity": "sha512-slScBsMAb3GFDcdrCgLwZtPYRoH2H/youv10QiZyRjmsP48fznoveWytSgCI/R0ZcUgpc0ZhIUEx6LHts8yrfQ==",
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.2.tgz",
+ "integrity": "sha512-CAXl+Dtd9UUuJd8pKKdwh6MLm3MUMiqMPmhZ3tTSXPqfyQ3vDl6R5hZdZ/kYojK4ofXtdfSv1tFq8XzWx3heNQ==",
"cpu": [
"ppc64"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -1198,12 +258,13 @@
}
},
"node_modules/@esbuild/linux-riscv64": {
- "version": "0.28.1",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.1.tgz",
- "integrity": "sha512-kw0owk1o0GFETUJyW0jc0G4Yzs0BHZn0JDZ8JRT088vjJYX777BAs1fDGxAC+q831qOs2DTC96mNsG2opdfyyQ==",
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.2.tgz",
+ "integrity": "sha512-GeXCej4IQtU1B+QlDV8W/RRvbzI3O/Stss+/bCXv4lZls5WGRtu2a+3JkA3i4qIUlMXpcHebWpF8AkJhATowuA==",
"cpu": [
"riscv64"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -1214,12 +275,13 @@
}
},
"node_modules/@esbuild/linux-s390x": {
- "version": "0.28.1",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.1.tgz",
- "integrity": "sha512-/lAIjX8aYFRByhh6L5rYtPEDRqa9de/4V/juOXcta5frjvzXO4/sqEtyytse0g3zZFuWu5cDN0MkLz2qRDD2Ag==",
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.2.tgz",
+ "integrity": "sha512-3H1weTYZPxt/WOhByszQZybS9w5lKzUn1FDMsgEChbHWQwHYQQRfBxgCcZvPhjHfKyJjIievvMmEUawJrdY9Dg==",
"cpu": [
"s390x"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -1230,12 +292,13 @@
}
},
"node_modules/@esbuild/linux-x64": {
- "version": "0.28.1",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.1.tgz",
- "integrity": "sha512-u/anNYF2mmVOEDwLtnQ1wOr3EZ9sTNGLWrsYGYwHWzGA3Si84IOkHXlbWTD1NB+9/1lcnweYKO54uhxZydNzfA==",
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.2.tgz",
+ "integrity": "sha512-4xTZr1FUmSoQW4XIWmit3tzQrUTZM+N3P0XV8xROKYF50XfI7xeO90+1bZvNwxIufQ9hDQVRJH5YhgPVF8A/HQ==",
"cpu": [
"x64"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -1246,12 +309,13 @@
}
},
"node_modules/@esbuild/netbsd-arm64": {
- "version": "0.28.1",
- "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.1.tgz",
- "integrity": "sha512-oks0DYbLwWMmaakTsCb+zL4E+aHRVLom9IJZOAthMQEPiQmydXHkziYEsGYRx0uNV/IjEKGAV941JzH02pflqw==",
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.2.tgz",
+ "integrity": "sha512-sSATRjPeDBg3pdgHoQfoYBob11Kk1FGa9lui5RIHZCoCkJa9QKlvl3/vKz2usCmYYjs7ymJR/2Nnsqe+Hjt5nw==",
"cpu": [
"arm64"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -1262,12 +326,13 @@
}
},
"node_modules/@esbuild/netbsd-x64": {
- "version": "0.28.1",
- "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.1.tgz",
- "integrity": "sha512-aeL6lAnN89Hz43Mlh1G8ARasbuoYvSITDEx0tHh5b7jJnHcssqgjy9Yx430GDpmCa6OyrKoS0aNRjKundRizGg==",
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.2.tgz",
+ "integrity": "sha512-lqnzCV+mM0gIADaKihiCg6ifgfU2L3h5E33rNQBN1Y4MaVGnzryzmvvf7UHxprpQdE8hpqLolJ9Rl+SkIRDpyw==",
"cpu": [
"x64"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -1278,12 +343,13 @@
}
},
"node_modules/@esbuild/openbsd-arm64": {
- "version": "0.28.1",
- "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.1.tgz",
- "integrity": "sha512-MEFJe5C3R8pwXdZ5Y21oo6m7ePiS0d9pWucn99O/wvyJZChoIQKrQDxKrGeW8F5+T0okTHesAmDeiHDTIq0V/Q==",
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.2.tgz",
+ "integrity": "sha512-AL2qJILH7lNjrDmCQDvdxMfAUIv8KMNZOvrwAQ8i8//ntL9FflhOyMJ8OZSMBb8/AWXe3/5v5S20y3zCoZWKoQ==",
"cpu": [
"arm64"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -1294,12 +360,13 @@
}
},
"node_modules/@esbuild/openbsd-x64": {
- "version": "0.28.1",
- "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.1.tgz",
- "integrity": "sha512-i/ZLIOafE0Z8cI/XANJAixoJL/uRAoS2xOA3rb0xN+KK0K177cMAsQYkzHtBrtMXAKuAc7HGgcWiZ/sRC1Nxgw==",
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.2.tgz",
+ "integrity": "sha512-QtiuPytchRyC4rwUKhexJdQKvDuZ6hWloi3igqPQNUJCS1/v9EiO3UTOXR6A3FoMo4fnAKbWJdqaIwhOzh8qEw==",
"cpu": [
"x64"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -1310,12 +377,13 @@
}
},
"node_modules/@esbuild/openharmony-arm64": {
- "version": "0.28.1",
- "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.1.tgz",
- "integrity": "sha512-ge+Z7EXFNt2BO1oAMsVpiQ8EwndV9i1xXerAeTIK7AtPs3bKFXQM7nlRxDSIUIMeueR1CNXxqztLzdNeReKBJg==",
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.2.tgz",
+ "integrity": "sha512-WkhYDmpTjLvGlScA1rwjRUmhl4k8oXR3cIbtqWmELgU/dFeHHlEllxDvdWcNJV9rbzCexB5vz8gtNewWLgCT7Q==",
"cpu": [
"arm64"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -1326,12 +394,13 @@
}
},
"node_modules/@esbuild/sunos-x64": {
- "version": "0.28.1",
- "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.1.tgz",
- "integrity": "sha512-BEjgtECkL3vY+SaSQ6nzVfiALUeFxpawyp8Jmf5PtYhf1Ug40N1h/hxlhts+f1FvSvarEigdxS3BlSMI2PJLcQ==",
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.2.tgz",
+ "integrity": "sha512-GPMSkTOtMnv2U2F8gxe4Io6qmVs+YKyp832Etqqxr0hFngmXQ3rzwytelm3GIn7T4VviRUlf3sOgBOiTdvaf7g==",
"cpu": [
"x64"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -1342,12 +411,13 @@
}
},
"node_modules/@esbuild/win32-arm64": {
- "version": "0.28.1",
- "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.1.tgz",
- "integrity": "sha512-lCv9eK/H6ZJWbE7bh2nw54CZ9M2nupBxJcTsdk/QQnWkdSjKGuxmmH8/GWrlT1eMmZfn4dGcCjRte397WqfQXA==",
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.2.tgz",
+ "integrity": "sha512-PIhhEkE9uPBleRBrQEJpUn7MBnibZzbGzYWPmY3x+YoVg/95zbjB4CxPPOQ8l5tYYM4mMaCthF8/1DIfBQQyWQ==",
"cpu": [
"arm64"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -1358,12 +428,13 @@
}
},
"node_modules/@esbuild/win32-ia32": {
- "version": "0.28.1",
- "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.1.tgz",
- "integrity": "sha512-zvb/mB2bSCoJOpoCBgYKKpX6YM6mJBlBUVUtVj41DlZJVEB6/0CKlRYxP5wWl1C1ILiCoAU5wZZ4q1P3qeS6Eg==",
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.2.tgz",
+ "integrity": "sha512-YmJbfTlvU7Sdn9BB+4PRES4oB6pxgS37MAONj+hBr/cpXS1aBPKXxNnDbu+QCWPj0o9dgyxeq79g6c5P8KeuYA==",
"cpu": [
"ia32"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -1374,12 +445,13 @@
}
},
"node_modules/@esbuild/win32-x64": {
- "version": "0.28.1",
- "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.1.tgz",
- "integrity": "sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A==",
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.2.tgz",
+ "integrity": "sha512-5ebpxr3nWMzrL/rnUI755Jkuee0bHL/Gq0WTF9lvcpv73wAp5eu8MfBUgWK9bhWvZjj7yX8etf/8tI8Ney695g==",
"cpu": [
"x64"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -1389,2436 +461,447 @@
"node": ">=18"
}
},
- "node_modules/@fontsource-variable/cinzel": {
- "version": "5.3.0",
- "resolved": "https://registry.npmjs.org/@fontsource-variable/cinzel/-/cinzel-5.3.0.tgz",
- "integrity": "sha512-eFcU0TwaAP5QESF2pV8VoOPJJXJ6sSGm/6mXMwqTdMBPAJMaLhfa6oISHuOtFeddsoHP9RYm4Io0j1j3mNEz2Q==",
- "license": "OFL-1.1",
- "funding": {
- "url": "https://github.com/sponsors/ayuhito"
- }
+ "node_modules/@gk/migrate": {
+ "resolved": "tools/migrate",
+ "link": true
},
- "node_modules/@fontsource-variable/jetbrains-mono": {
- "version": "5.3.0",
- "resolved": "https://registry.npmjs.org/@fontsource-variable/jetbrains-mono/-/jetbrains-mono-5.3.0.tgz",
- "integrity": "sha512-F32xpS2NsGYoQi2ADSkKTgpJj7ozajsGgDJ8woTnqjmIB+dxDIqImjl4pXZVEExu8UFZ2ndhmX18EBS/hdz3Lw==",
- "license": "OFL-1.1",
- "funding": {
- "url": "https://github.com/sponsors/ayuhito"
- }
- },
- "node_modules/@fontsource-variable/montserrat": {
- "version": "5.3.0",
- "resolved": "https://registry.npmjs.org/@fontsource-variable/montserrat/-/montserrat-5.3.0.tgz",
- "integrity": "sha512-7PaZoxaxrWLAyrhO46v65An9LhUhfkTExWLhfbywYZCnZEgg/W1rEHnlNmZKjNZ3nJTVYyicqxlJt10z/26yTA==",
- "license": "OFL-1.1",
- "funding": {
- "url": "https://github.com/sponsors/ayuhito"
- }
- },
- "node_modules/@iconify-json/mdi": {
- "version": "1.2.3",
- "resolved": "https://registry.npmjs.org/@iconify-json/mdi/-/mdi-1.2.3.tgz",
- "integrity": "sha512-O3cLwbDOK7NNDf2ihaQOH5F9JglnulNDFV7WprU2dSoZu3h3cWH//h74uQAB87brHmvFVxIOkuBX2sZSzYhScg==",
- "license": "Apache-2.0",
- "dependencies": {
- "@iconify/types": "*"
- }
- },
- "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"
- },
- "node_modules/@img/colour": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.1.0.tgz",
- "integrity": "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==",
- "license": "MIT",
- "optional": true,
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@img/sharp-darwin-arm64": {
- "version": "0.35.3",
- "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.35.3.tgz",
- "integrity": "sha512-RMnFX7YQsMoh7lWfcM4NEHHymBX/rLuKNPVM84XE9ONPcaSCDgE7CHIHpSgPcO2xcRthgBy1HfNO319mwhIAkg==",
- "cpu": [
- "arm64"
- ],
- "license": "Apache-2.0",
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": ">=20.9.0"
- },
- "funding": {
- "url": "https://opencollective.com/libvips"
- },
- "optionalDependencies": {
- "@img/sharp-libvips-darwin-arm64": "1.3.2"
- }
- },
- "node_modules/@img/sharp-darwin-x64": {
- "version": "0.35.3",
- "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.35.3.tgz",
- "integrity": "sha512-Xo+5uFBtLN0BKqieTxiFzFPQAUlBbbH5iBKyRX/z1JrbnYsHTfKJnUfL8+p2TPXr1pXqao4eeL4Rl144uDpK9w==",
- "cpu": [
- "x64"
- ],
- "license": "Apache-2.0",
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": ">=20.9.0"
- },
- "funding": {
- "url": "https://opencollective.com/libvips"
- },
- "optionalDependencies": {
- "@img/sharp-libvips-darwin-x64": "1.3.2"
- }
- },
- "node_modules/@img/sharp-freebsd-wasm32": {
- "version": "0.35.3",
- "resolved": "https://registry.npmjs.org/@img/sharp-freebsd-wasm32/-/sharp-freebsd-wasm32-0.35.3.tgz",
- "integrity": "sha512-lUxcqWIj2wMQ9BrwNjngcr1gWUr5xgaGThBRqPPalIC2n67Cqj1uPh8NnA/ZhAg8hUbKl+kVHKwgUIwe6ZYPrg==",
- "license": "Apache-2.0",
- "optional": true,
- "os": [
- "freebsd"
- ],
- "dependencies": {
- "@img/sharp-wasm32": "0.35.3"
- },
- "engines": {
- "node": ">=20.9.0"
- },
- "funding": {
- "url": "https://opencollective.com/libvips"
- }
- },
- "node_modules/@img/sharp-libvips-darwin-arm64": {
- "version": "1.3.2",
- "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.3.2.tgz",
- "integrity": "sha512-9J6ypZFpQBj4YnePGoq/S38w6nz+vqg5WZLrLGY4YuSemdMq47GMLBPO42MzwdGwpg/agZ7xzZcFHa48xlywfg==",
- "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.3.2",
- "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.3.2.tgz",
- "integrity": "sha512-m2pW1n6cns9VaubNwsZ+c3CRYjxNQWgJ5gPlnL1nbBcpkBvFm6SCFN5o0psFHI8w9n11NKhFkeEDns98tiqbEw==",
- "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.3.2",
- "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.3.2.tgz",
- "integrity": "sha512-1eMLzy92I4J6rmi4mAT8yC3HxOtniyGELlzGbNMLLeqe052ahFQ0h6LFq+lh5DsDIdYViIDst08abvSbcEdLXQ==",
- "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.3.2",
- "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.3.2.tgz",
- "integrity": "sha512-dqVSFynCox4C/J8kT16V7SIFAns0IjgLwkvYT7p8LQVmJ5OS5b6tI9IGflxTeuBS//zXeFIUbwt5dwxyZ17cnA==",
- "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.3.2",
- "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.3.2.tgz",
- "integrity": "sha512-3z0NHDxD6n5I9gc05U1eW1AyRm+Gznzq3naMrthPNqE6oYykcogW0l/jfpJdjYnuNl8R7yI9pNbE1XiUeyq0Aw==",
- "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.3.2",
- "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.3.2.tgz",
- "integrity": "sha512-bsb4rI+NldGOsXuej2r8OdSS8+zXDVaCWxyWrcv6kneTOlgAHtZABRzBBCwdsPiD90J4myNJuHpg6kA20ImW/w==",
- "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.3.2",
- "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.3.2.tgz",
- "integrity": "sha512-/ABshyj8gCpyIrNXnHn4LorDJ0HHm1VhXPBlxZ8zAtfVPAaSafXPGn+sUSIRiwaSBy0mmFjSjiXI5mkcwdChKQ==",
- "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.3.2",
- "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.3.2.tgz",
- "integrity": "sha512-ITPEtgffGJ0S6G9dRyw/366tJQqFRcHWPHhC+Stpg3Z8AEMrDrTr2lhdz4f/Y/HMbRh//7Z5mBzEpVdi62Oc3w==",
- "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.3.2",
- "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.3.2.tgz",
- "integrity": "sha512-zE9EdiUzUmg5mDT5a1rk5fYJ6GWPloTwWBYDS14naqHsL+EaMpDj1AWnpLgh3u0YCORv2Tt50wrcrpYqkP97Kw==",
- "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.3.2",
- "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.3.2.tgz",
- "integrity": "sha512-m0lrLiUt+lBYnCFr8qV/65yMR4E/c7/wf78I5eKTdkEakFAlZ9QlzEM3QIhhAwVeUhLAHLcCq7a7Vszq/oFNZQ==",
- "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.35.3",
- "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.35.3.tgz",
- "integrity": "sha512-affVWCTLooy8TSxbDx2qkzuDeaWLNVBA+P//FNBirHsXpP2fuBhk5AuboYUnrDnzoXes8GFjpTx0SBFOCRg+FA==",
- "cpu": [
- "arm"
- ],
- "license": "Apache-2.0",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=20.9.0"
- },
- "funding": {
- "url": "https://opencollective.com/libvips"
- },
- "optionalDependencies": {
- "@img/sharp-libvips-linux-arm": "1.3.2"
- }
- },
- "node_modules/@img/sharp-linux-arm64": {
- "version": "0.35.3",
- "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.35.3.tgz",
- "integrity": "sha512-QgKDspHPnrU+GQ55XPhGwyhC8acLVOOSyAvo1oVfFmrIXLkDNmGWzAfDZ4xK8oSA1qBQrALcHX0G5UZni/SuFQ==",
- "cpu": [
- "arm64"
- ],
- "license": "Apache-2.0",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=20.9.0"
- },
- "funding": {
- "url": "https://opencollective.com/libvips"
- },
- "optionalDependencies": {
- "@img/sharp-libvips-linux-arm64": "1.3.2"
- }
- },
- "node_modules/@img/sharp-linux-ppc64": {
- "version": "0.35.3",
- "resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.35.3.tgz",
- "integrity": "sha512-sMd8rDxmpLOwv/7N44klFjOD5DUO7FLdjiXDI0hoxYaf7Ar262dQIEkosE98bps+5HPLtp/EvNqeqQtOycP/IA==",
- "cpu": [
- "ppc64"
- ],
- "license": "Apache-2.0",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=20.9.0"
- },
- "funding": {
- "url": "https://opencollective.com/libvips"
- },
- "optionalDependencies": {
- "@img/sharp-libvips-linux-ppc64": "1.3.2"
- }
- },
- "node_modules/@img/sharp-linux-riscv64": {
- "version": "0.35.3",
- "resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.35.3.tgz",
- "integrity": "sha512-0Eob78yjlYPfL5vMNWAW55l3R9Y6BQS/gOfe0ZcP9mEz9ohhKSt4im1hayiknXgf8AWrFqMvJcKIdmLmEe7yeQ==",
- "cpu": [
- "riscv64"
- ],
- "license": "Apache-2.0",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=20.9.0"
- },
- "funding": {
- "url": "https://opencollective.com/libvips"
- },
- "optionalDependencies": {
- "@img/sharp-libvips-linux-riscv64": "1.3.2"
- }
- },
- "node_modules/@img/sharp-linux-s390x": {
- "version": "0.35.3",
- "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.35.3.tgz",
- "integrity": "sha512-KgAxQ0DxpNOq1rG2t5cgTgShJFGSuU7XO45cqC+1NVOuZnP6tlgZRuSYOfNupGkHID0o3cJOsw4DVeJpMovcGw==",
- "cpu": [
- "s390x"
- ],
- "license": "Apache-2.0",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=20.9.0"
- },
- "funding": {
- "url": "https://opencollective.com/libvips"
- },
- "optionalDependencies": {
- "@img/sharp-libvips-linux-s390x": "1.3.2"
- }
- },
- "node_modules/@img/sharp-linux-x64": {
- "version": "0.35.3",
- "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.35.3.tgz",
- "integrity": "sha512-8pqvxubL2PGdhlPy6GLqzDYMUjyRmKAwKHYKixpdJYBUK7PJ0C029XdsnpFIdgRZG68fZiGdHVWcKPvtiPB4cA==",
- "cpu": [
- "x64"
- ],
- "license": "Apache-2.0",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=20.9.0"
- },
- "funding": {
- "url": "https://opencollective.com/libvips"
- },
- "optionalDependencies": {
- "@img/sharp-libvips-linux-x64": "1.3.2"
- }
- },
- "node_modules/@img/sharp-linuxmusl-arm64": {
- "version": "0.35.3",
- "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.35.3.tgz",
- "integrity": "sha512-Vz0iQjzzcSX3HCbfwFfCSG/9SCIqyO0mH2sXyiHaAYfBk0cRsCWXRyQYX0ovCK/PAQBbTzQ0dsPQHh5MAFL59w==",
- "cpu": [
- "arm64"
- ],
- "license": "Apache-2.0",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=20.9.0"
- },
- "funding": {
- "url": "https://opencollective.com/libvips"
- },
- "optionalDependencies": {
- "@img/sharp-libvips-linuxmusl-arm64": "1.3.2"
- }
- },
- "node_modules/@img/sharp-linuxmusl-x64": {
- "version": "0.35.3",
- "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.35.3.tgz",
- "integrity": "sha512-6O1NPKcDVj9QEdg7Hx549EX8U0rp6yXQERqru6yRN7fGBn32UvIRJUlWnk+8xDCiG76hXVBbX82NZ/ZKr0euIg==",
- "cpu": [
- "x64"
- ],
- "license": "Apache-2.0",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=20.9.0"
- },
- "funding": {
- "url": "https://opencollective.com/libvips"
- },
- "optionalDependencies": {
- "@img/sharp-libvips-linuxmusl-x64": "1.3.2"
- }
- },
- "node_modules/@img/sharp-wasm32": {
- "version": "0.35.3",
- "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.35.3.tgz",
- "integrity": "sha512-cZ0XkcYGpHZkqW6iCkqTcmUC0CD9DhD5d/qeZlZkfRBn6GnHniZXLUo5+9xw8Iv76YE6LQFN9YNBlKREcCG76w==",
- "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT",
- "optional": true,
- "dependencies": {
- "@emnapi/runtime": "^1.11.1"
- },
- "engines": {
- "node": ">=20.9.0"
- },
- "funding": {
- "url": "https://opencollective.com/libvips"
- }
- },
- "node_modules/@img/sharp-webcontainers-wasm32": {
- "version": "0.35.3",
- "resolved": "https://registry.npmjs.org/@img/sharp-webcontainers-wasm32/-/sharp-webcontainers-wasm32-0.35.3.tgz",
- "integrity": "sha512-2rnq7bX3NzeR2T4YWgz8qiG4h3TSdMe+vN1iQXpJleSJ3SM5zQ8Fy2SyyXAWlbxpEZ2Y+Z4u1BePgJEYbSy80Q==",
- "cpu": [
- "wasm32"
- ],
- "license": "Apache-2.0",
- "optional": true,
- "dependencies": {
- "@img/sharp-wasm32": "0.35.3"
- },
- "engines": {
- "node": ">=20.9.0"
- },
- "funding": {
- "url": "https://opencollective.com/libvips"
- }
- },
- "node_modules/@img/sharp-win32-arm64": {
- "version": "0.35.3",
- "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.35.3.tgz",
- "integrity": "sha512-4bPwFdMbeC4JQ8L8LOyWp6nsHcboP5fxkp6iPOXz2Vg49R42TuMs2whkJ5OAP4/Ul035qOzy0AecOF9VOscn4w==",
- "cpu": [
- "arm64"
- ],
- "license": "Apache-2.0 AND LGPL-3.0-or-later",
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": ">=20.9.0"
- },
- "funding": {
- "url": "https://opencollective.com/libvips"
- }
- },
- "node_modules/@img/sharp-win32-ia32": {
- "version": "0.35.3",
- "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.35.3.tgz",
- "integrity": "sha512-r53mXsBN6lFUDiST764SvgwUdHAqM4rPAiDzAmf4fLoB6X/rkfyTrLCg6+g17wJJiCmB3JYgHuUldCWUIRFSXw==",
- "cpu": [
- "ia32"
- ],
- "license": "Apache-2.0 AND LGPL-3.0-or-later",
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": "^20.9.0"
- },
- "funding": {
- "url": "https://opencollective.com/libvips"
- }
- },
- "node_modules/@img/sharp-win32-x64": {
- "version": "0.35.3",
- "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.35.3.tgz",
- "integrity": "sha512-D4y1vNeZrIIJCN+uHaWVtH86B+aCrdMYYjicy9pXHvbGZeGYLLSd3wdVuC37FxVXlU1ARsk84eKWfWMXGYEqvA==",
- "cpu": [
- "x64"
- ],
- "license": "Apache-2.0 AND LGPL-3.0-or-later",
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": ">=20.9.0"
- },
- "funding": {
- "url": "https://opencollective.com/libvips"
- }
- },
- "node_modules/@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==",
- "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==",
- "license": "MIT",
- "dependencies": {
- "@jridgewell/gen-mapping": "^0.3.5",
- "@jridgewell/trace-mapping": "^0.3.24"
- }
+ "node_modules/@gk/schema": {
+ "resolved": "packages/schema",
+ "link": true
},
"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==",
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.6.0.tgz",
+ "integrity": "sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw==",
+ "dev": true,
"license": "MIT"
},
"node_modules/@jridgewell/trace-mapping": {
"version": "0.3.31",
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz",
"integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==",
+ "dev": true,
"license": "MIT",
"dependencies": {
"@jridgewell/resolve-uri": "^3.1.0",
"@jridgewell/sourcemap-codec": "^1.4.14"
}
},
- "node_modules/@mdx-js/mdx": {
- "version": "3.1.1",
- "resolved": "https://registry.npmjs.org/@mdx-js/mdx/-/mdx-3.1.1.tgz",
- "integrity": "sha512-f6ZO2ifpwAQIpzGWaBQT2TXxPv6z3RBzQKpVftEWN78Vl/YweF1uwussDx8ECAXVtr3Rs89fKyG9YlzUs9DyGQ==",
- "license": "MIT",
- "dependencies": {
- "@types/estree": "^1.0.0",
- "@types/estree-jsx": "^1.0.0",
- "@types/hast": "^3.0.0",
- "@types/mdx": "^2.0.0",
- "acorn": "^8.0.0",
- "collapse-white-space": "^2.0.0",
- "devlop": "^1.0.0",
- "estree-util-is-identifier-name": "^3.0.0",
- "estree-util-scope": "^1.0.0",
- "estree-walker": "^3.0.0",
- "hast-util-to-jsx-runtime": "^2.0.0",
- "markdown-extensions": "^2.0.0",
- "recma-build-jsx": "^1.0.0",
- "recma-jsx": "^1.0.0",
- "recma-stringify": "^1.0.0",
- "rehype-recma": "^1.0.0",
- "remark-mdx": "^3.0.0",
- "remark-parse": "^11.0.0",
- "remark-rehype": "^11.0.0",
- "source-map": "^0.7.0",
- "unified": "^11.0.0",
- "unist-util-position-from-estree": "^2.0.0",
- "unist-util-stringify-position": "^4.0.0",
- "unist-util-visit": "^5.0.0",
- "vfile": "^6.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/unified"
- }
- },
- "node_modules/@napi-rs/wasm-runtime": {
- "version": "1.2.2",
- "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.2.2.tgz",
- "integrity": "sha512-JfB4kuJQjaoHuCTseIINHtHWeJnvgEcxjwA5t/Y00ZgaOO1Crz3fjT/p8kT28zA/Caz7oiUMn3d6H2yOVCVwuw==",
- "license": "MIT",
- "optional": true,
- "dependencies": {
- "@tybys/wasm-util": "^0.10.3"
- },
- "engines": {
- "node": "^20.19.0 || ^22.13.0 || >=23.5.0"
- },
- "funding": {
- "type": "github",
- "url": "https://github.com/sponsors/Brooooooklyn"
- },
- "peerDependencies": {
- "@emnapi/core": "^1.7.1 || ^2.0.0-alpha.3",
- "@emnapi/runtime": "^1.7.1 || ^2.0.0-alpha.3"
- }
- },
- "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/@oxc-project/types": {
- "version": "0.143.0",
- "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.143.0.tgz",
- "integrity": "sha512-u6JZdLBTLotrNC9Vd6vPssINdzcCzleKAH6EJKImQb7GtYvX5keN2dxkoK44stCc4tffE6QQRtZTXVSzsLUlWA==",
+ "version": "0.153.0",
+ "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.153.0.tgz",
+ "integrity": "sha512-jSNrRIyvPVLL5E1z/tS5zLAprosGz/FuI1PWwEOz59j3E4TTTzSpqKb/RDGqJhhW0QHYISGXxq582wGx6T11Tw==",
+ "dev": true,
"license": "MIT",
+ "peer": true,
"funding": {
- "url": "https://github.com/sponsors/Boshen"
+ "url": "https://github.com/sponsors/oxc-project"
}
},
- "node_modules/@rolldown/binding-android-arm64": {
- "version": "1.2.3",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.2.3.tgz",
- "integrity": "sha512-zrJtHDcaZJ1Fp7xf4hNl+7seH9Cn/N5TwLYkhgXREtBwAd/jaqW3uqeHxpDugJLVICWg4eW44kOQEGJ1r6jCGw==",
+ "node_modules/@rolldown/binding-android-arm-eabi": {
+ "version": "1.2.13",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm-eabi/-/binding-android-arm-eabi-1.2.13.tgz",
+ "integrity": "sha512-Vu/NICrgdRvUOeaJZIyB2FP63s6fO/4ud5l8dXmxDx9yDyn+mN0y+gsvsVviSli+T361Aqo0h46EuMoobrwawA==",
"cpu": [
- "arm64"
+ "arm"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
"android"
],
+ "peer": true,
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-android-arm64": {
+ "version": "1.2.13",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.2.13.tgz",
+ "integrity": "sha512-6LEoIrGy5vvCd2boNnv2soPSUZj/PRvhp8R/t9KlX+mIZxThIE/ZqxrGXxmuNX0FuvQFvLdZr7S4HrYpYBVhEQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "peer": true,
"engines": {
"node": "^20.19.0 || >=22.12.0"
}
},
"node_modules/@rolldown/binding-darwin-arm64": {
- "version": "1.2.3",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.2.3.tgz",
- "integrity": "sha512-ieIiibVCp0tX7TLu2cafoNPv8wJyYi01ekXpbf8q2j7F4rGAhhXb/eQh7ge9DRBY78GwmRQtvjZDux7EDbA8kA==",
+ "version": "1.2.13",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.2.13.tgz",
+ "integrity": "sha512-CaJSRzlQ6Q+zkDMVA9OkVxBjVoPRCDEE3v0FSJ9kIQBgRjFApA3Si4ijhbNVUji7ZJt+PfO0e46qy4nPInHUPA==",
"cpu": [
"arm64"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
+ "peer": true,
"engines": {
"node": "^20.19.0 || >=22.12.0"
}
},
"node_modules/@rolldown/binding-darwin-x64": {
- "version": "1.2.3",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.2.3.tgz",
- "integrity": "sha512-Zh9tCon19eDXJoihx0rqKhMUlMYqzwj3aPsSuHmI4RWZh62dWUL+DJN4C5YQya5TcQBJU/Fe8+rY0jhXTQITqA==",
+ "version": "1.2.13",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.2.13.tgz",
+ "integrity": "sha512-e4r340I5hxSzJkD3YJBxgzq/rJ3wwxwdLLb2FPt1Zpyem20nrQPT11DDEEkazwxaHumH8wpglsp/Ozgj/wT6mw==",
"cpu": [
"x64"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
+ "peer": true,
"engines": {
"node": "^20.19.0 || >=22.12.0"
}
},
"node_modules/@rolldown/binding-freebsd-x64": {
- "version": "1.2.3",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.2.3.tgz",
- "integrity": "sha512-nGbJWewA1wrXXZiQhjAT5rhibGfns5ZNkDVqxsO6zJ3f3YvpoDNNmGMSbbhLuXKjNScaBJVOAboztAWVespQMg==",
+ "version": "1.2.13",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.2.13.tgz",
+ "integrity": "sha512-hoBWDZ4/K/mJKVMbsgUd1jfpA2DiOrJ54mpegObU9TBHBBrtX4FLIXJJQRDPZ/2Dn91GvPob+Z2hwKJzf4E5SQ==",
"cpu": [
"x64"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
"freebsd"
],
+ "peer": true,
"engines": {
"node": "^20.19.0 || >=22.12.0"
}
},
"node_modules/@rolldown/binding-linux-arm-gnueabihf": {
- "version": "1.2.3",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.2.3.tgz",
- "integrity": "sha512-QNniJr5Kml0kDEB98jiDOJjXNroxIIi0IXIbdYzY26Xt1pVbeP62+KnoIZLwirOymX/0jDk/2gI/bNUv7A7OIw==",
+ "version": "1.2.13",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.2.13.tgz",
+ "integrity": "sha512-oF9fZu3vTWnTiVAkDFxU9PJLt1nh20K9RRpuHsR7ZnE2L7XookBbDBVLojCVSLGbvtSlxeUNnjAj6DbNsKyzbg==",
"cpu": [
"arm"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
+ "peer": true,
"engines": {
"node": "^20.19.0 || >=22.12.0"
}
},
"node_modules/@rolldown/binding-linux-arm64-gnu": {
- "version": "1.2.3",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.2.3.tgz",
- "integrity": "sha512-TkqEAcmmvH3I/q4114NB4RVt6241Dao48pF45uLcFGrwAaIn0iITgTAKP/dLjbN0R4buJjGb91+UHSoFmpgIWw==",
+ "version": "1.2.13",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.2.13.tgz",
+ "integrity": "sha512-DTpb/+jMiqWMPgphn4ksW1zW+XLJ6dJJ9FMvnIk9I291LhCctMPhMYJrP5t9p6C9qa3KXadDdUEZHBaPh3kNxQ==",
"cpu": [
"arm64"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
+ "peer": true,
"engines": {
"node": "^20.19.0 || >=22.12.0"
}
},
"node_modules/@rolldown/binding-linux-arm64-musl": {
- "version": "1.2.3",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.2.3.tgz",
- "integrity": "sha512-NHqjnxpsndf4MPymxteFAWHHfkTL8HjWh1KB7z23ofZ6QO2euONuxDXjat69dKZRALnGypg8k8SsK8vZJoXv1Q==",
+ "version": "1.2.13",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.2.13.tgz",
+ "integrity": "sha512-xU+Kvv9N2A3SY35zFh1FWWNjxbadE/BKeqnkXe7vScU4GZsw757BlVmDLITZEq8DPpk6m7uoDYkK7P+x/uU5Ow==",
"cpu": [
"arm64"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
+ "peer": true,
"engines": {
"node": "^20.19.0 || >=22.12.0"
}
},
"node_modules/@rolldown/binding-linux-ppc64-gnu": {
- "version": "1.2.3",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.2.3.tgz",
- "integrity": "sha512-6tbrbwfz5GB9DQ4Jwo6hy9v+vR31xZlvzZ6n5Xut6Hhx5PvrA9q/HsK8KMaYQp063iqZGXwNvZtYNLD7EM/x0w==",
+ "version": "1.2.13",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.2.13.tgz",
+ "integrity": "sha512-XMk/hnhh25oQ756Lclb1Q11h4ll0xC0Esa5w4Vzp+mAE2S3AGJgeM0SonrIgY8fHhjarb6hcoTuwoFG0VVP2ig==",
"cpu": [
"ppc64"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
+ "peer": true,
"engines": {
"node": "^20.19.0 || >=22.12.0"
}
},
"node_modules/@rolldown/binding-linux-s390x-gnu": {
- "version": "1.2.3",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.2.3.tgz",
- "integrity": "sha512-oyuXxXmoZHjXC917IAPFAAv4wWAa0cM9afk8nx1+9/jNNOX1uPf8yDA6p7G0RypOfw/X0PQt5IfoquY1um+zSg==",
+ "version": "1.2.13",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.2.13.tgz",
+ "integrity": "sha512-U1BoTHYmlywE2n3HUWjzZ+8EQsKr+OwpxYuDWaubx42tUL8QSL7nOqRz/NVHanibbFP3M1Rxuprz/wNcbMKjqw==",
"cpu": [
"s390x"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
+ "peer": true,
"engines": {
"node": "^20.19.0 || >=22.12.0"
}
},
"node_modules/@rolldown/binding-linux-x64-gnu": {
- "version": "1.2.3",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.2.3.tgz",
- "integrity": "sha512-TytMwF2KVGqP2tgd0I1OY0PAv78dZRAYcF5ssDzjM34SUXCED3uXvSd5+lHoC0bTD6eEdFz7LdQNCO1y0oVk9w==",
+ "version": "1.2.13",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.2.13.tgz",
+ "integrity": "sha512-J4cm80Pc6wEwV5AZ42o62sIQgDk+Y7R2s646jIwuvO+4V46QB58N4vVuMPWCBaNgursXLpmUWJV//WQAkPvHBQ==",
"cpu": [
"x64"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
+ "peer": true,
"engines": {
"node": "^20.19.0 || >=22.12.0"
}
},
"node_modules/@rolldown/binding-linux-x64-musl": {
- "version": "1.2.3",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.2.3.tgz",
- "integrity": "sha512-/E9m3qstrJFVPoULV25mVQblSNExY2+kBsYe4sy0Tn0yOOgJ8wZbZt3KnRbF/XeU2Gl1STKUQnDNTqhIE5MD4A==",
+ "version": "1.2.13",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.2.13.tgz",
+ "integrity": "sha512-W0euZS40U4V6JkmEnVCIX9mcsL6cbXQ4X8c2w+MMOIjFg68TXUW+u3RT+2e1t1pv9mg/EP3pDtK3POXdQ7Rxyw==",
"cpu": [
"x64"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
+ "peer": true,
"engines": {
"node": "^20.19.0 || >=22.12.0"
}
},
"node_modules/@rolldown/binding-openharmony-arm64": {
- "version": "1.2.3",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.2.3.tgz",
- "integrity": "sha512-Kr0OcsoQI816i6HOl3vFHpd1K0eZyh76zgfj4c1nTyaTsd5r2Mj1lwM4R90y/qaCfmTn9eHy0SKwi98eitRxug==",
+ "version": "1.2.13",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.2.13.tgz",
+ "integrity": "sha512-do/AF/4n9MRshia3a5aXoy8xM2c6mgfT6D7H/dY6my5z+hfCIR4ab7mk4IRj++nsEQpKy39kd0DQnDq+r692tw==",
"cpu": [
"arm64"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
"openharmony"
],
+ "peer": true,
"engines": {
"node": "^20.19.0 || >=22.12.0"
}
},
"node_modules/@rolldown/binding-win32-arm64-msvc": {
- "version": "1.2.3",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.2.3.tgz",
- "integrity": "sha512-hOtMwTqnME+/gJcH/PCZ0wn0zPUjiWOgkHpxbSJpfGKMezHltx1S7/k1SitzVa7Ww2cqrDDaFbZEhcJZO8o+Jw==",
+ "version": "1.2.13",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.2.13.tgz",
+ "integrity": "sha512-6FtUf2cJZobutJdxnGYX8z0vtbgqU0b2v8vfBhkXb9wp2MppMFtkunE+IaOntyi01rkIQoPW2GHABtfzsC0BRQ==",
"cpu": [
"arm64"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
],
+ "peer": true,
"engines": {
"node": "^20.19.0 || >=22.12.0"
}
},
"node_modules/@rolldown/binding-win32-x64-msvc": {
- "version": "1.2.3",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.2.3.tgz",
- "integrity": "sha512-ekcqMMkI2PlhYnfzQnB/cEdYUVVJViWvoUyLrbzgDoi3Snfc1mVBwdnc306ufA5ejy8JSPjT2RlW1nQSjW7efg==",
+ "version": "1.2.13",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.2.13.tgz",
+ "integrity": "sha512-UKhFaZTeTcbKrb3iU82m17jVcPEAoqDkCN5MBqgPyZSwp/cQmib21crTS0DdH7T1B7wZRWEI89YQAOJMet5yhg==",
"cpu": [
"x64"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
],
+ "peer": true,
"engines": {
"node": "^20.19.0 || >=22.12.0"
}
},
"node_modules/@rolldown/pluginutils": {
- "version": "1.0.0-rc.3",
- "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.3.tgz",
- "integrity": "sha512-eybk3TjzzzV97Dlj5c+XrBFW57eTNhzod66y9HrBlzJ6NsCrWCp/2kaPS3K9wJmurBC0Tdw4yPjXKZqlznim3Q==",
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz",
+ "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true
+ },
+ "node_modules/@types/chai": {
+ "version": "5.2.3",
+ "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz",
+ "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/deep-eql": "*",
+ "assertion-error": "^2.0.1"
+ }
+ },
+ "node_modules/@types/deep-eql": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz",
+ "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==",
+ "dev": true,
"license": "MIT"
},
- "node_modules/@shikijs/core": {
- "version": "4.4.2",
- "resolved": "https://registry.npmjs.org/@shikijs/core/-/core-4.4.2.tgz",
- "integrity": "sha512-StyzbAyxg2/tBGf78gwbBkGyeQ73lf8UiJArFaQhTQIDqQOCKPCQFanvrs4/Yv3Yfyc+ONInJM6K+FMIf+P+kA==",
- "license": "MIT",
- "dependencies": {
- "@shikijs/primitive": "4.4.2",
- "@shikijs/types": "4.4.2",
- "@shikijs/vscode-textmate": "^10.0.2",
- "@types/hast": "^3.0.5",
- "hast-util-to-html": "^9.0.5"
- },
- "engines": {
- "node": ">=20"
- }
- },
- "node_modules/@shikijs/engine-javascript": {
- "version": "4.4.2",
- "resolved": "https://registry.npmjs.org/@shikijs/engine-javascript/-/engine-javascript-4.4.2.tgz",
- "integrity": "sha512-MnIkeqWdVPUWsxlx8gKLVCJFTsqrQJgpTPBPpQwaFeJ56lOnJxj5aN2LUFnfxUEcvOQuNocmbaMnVrCEln6rkw==",
- "license": "MIT",
- "dependencies": {
- "@shikijs/types": "4.4.2",
- "@shikijs/vscode-textmate": "^10.0.2",
- "oniguruma-to-es": "^4.3.6"
- },
- "engines": {
- "node": ">=20"
- }
- },
- "node_modules/@shikijs/engine-oniguruma": {
- "version": "4.4.2",
- "resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-4.4.2.tgz",
- "integrity": "sha512-GLhowz1+jixjz+wiZ3wMnOn1jTxiFCGl2PkXufivbnwPHKuyw1AYqu5/hbWhZZ2oAb0NP05WUJhYeigY14drnw==",
- "license": "MIT",
- "dependencies": {
- "@shikijs/types": "4.4.2",
- "@shikijs/vscode-textmate": "^10.0.2"
- },
- "engines": {
- "node": ">=20"
- }
- },
- "node_modules/@shikijs/langs": {
- "version": "4.4.2",
- "resolved": "https://registry.npmjs.org/@shikijs/langs/-/langs-4.4.2.tgz",
- "integrity": "sha512-8DfeusD+Zdv/eYIDdXyJTUnSMHt+aAWjAOCXV20HNGAHRlInXpG8wh421v6B91WOm9TFwRLN+b/LG5F2NAIojg==",
- "license": "MIT",
- "dependencies": {
- "@shikijs/types": "4.4.2"
- },
- "engines": {
- "node": ">=20"
- }
- },
- "node_modules/@shikijs/primitive": {
- "version": "4.4.2",
- "resolved": "https://registry.npmjs.org/@shikijs/primitive/-/primitive-4.4.2.tgz",
- "integrity": "sha512-l6fQQKsOMlz72n38fztmSgZ76MO6KSWuw8o+GJ+FhmqrpC9pIOJNQNXGgbb5yX2AwpzlEHwsaLPnk/8o4Fm+rA==",
- "license": "MIT",
- "dependencies": {
- "@shikijs/types": "4.4.2",
- "@shikijs/vscode-textmate": "^10.0.2",
- "@types/hast": "^3.0.5"
- },
- "engines": {
- "node": ">=20"
- }
- },
- "node_modules/@shikijs/themes": {
- "version": "4.4.2",
- "resolved": "https://registry.npmjs.org/@shikijs/themes/-/themes-4.4.2.tgz",
- "integrity": "sha512-H0CFoL07ddDC2Dd6EdrPYNkRhUR6YCkJlnuYFceYYUJJA5TIm2b5B33qqiDYryBExgbKMndFJPb2u1gTuqO37g==",
- "license": "MIT",
- "dependencies": {
- "@shikijs/types": "4.4.2"
- },
- "engines": {
- "node": ">=20"
- }
- },
- "node_modules/@shikijs/types": {
- "version": "4.4.2",
- "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-4.4.2.tgz",
- "integrity": "sha512-PFYitV4vpDr/iPCIhnHp+Q4ftic5N5VeNJ3KQ1O8gn3h2ar8qgwMAXF7tq4m1CWaMS60fV4VqF6vfnWH4F7vqQ==",
- "license": "MIT",
- "dependencies": {
- "@shikijs/vscode-textmate": "^10.0.2",
- "@types/hast": "^3.0.5"
- },
- "engines": {
- "node": ">=20"
- }
- },
- "node_modules/@shikijs/vscode-textmate": {
- "version": "10.0.2",
- "resolved": "https://registry.npmjs.org/@shikijs/vscode-textmate/-/vscode-textmate-10.0.2.tgz",
- "integrity": "sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==",
- "license": "MIT"
- },
- "node_modules/@tailwindcss/node": {
- "version": "4.3.3",
- "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.3.3.tgz",
- "integrity": "sha512-/T8IKEsf9VTU6tLjgC7+sv2mOPtQxzE2jMw7u4Tt40Tx+QSZxpzh95/H6cMKoja9XuW7iMdLJYBB0o9G1CaAgg==",
- "license": "MIT",
- "dependencies": {
- "@jridgewell/remapping": "^2.3.5",
- "enhanced-resolve": "^5.24.1",
- "jiti": "^2.7.0",
- "lightningcss": "1.32.0",
- "magic-string": "^0.30.21",
- "source-map-js": "^1.2.1",
- "tailwindcss": "4.3.3"
- }
- },
- "node_modules/@tailwindcss/oxide": {
- "version": "4.3.3",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.3.3.tgz",
- "integrity": "sha512-krXjAikiaFSPaK/FkAQT5UTx3VormQaiZ5hBFlJZ9UFQGB/rwg1MZIhHAG9smMQRTdyJxP6Qt5MwMtdyU5FWrA==",
- "license": "MIT",
- "engines": {
- "node": ">= 20"
- },
- "optionalDependencies": {
- "@tailwindcss/oxide-android-arm64": "4.3.3",
- "@tailwindcss/oxide-darwin-arm64": "4.3.3",
- "@tailwindcss/oxide-darwin-x64": "4.3.3",
- "@tailwindcss/oxide-freebsd-x64": "4.3.3",
- "@tailwindcss/oxide-linux-arm-gnueabihf": "4.3.3",
- "@tailwindcss/oxide-linux-arm64-gnu": "4.3.3",
- "@tailwindcss/oxide-linux-arm64-musl": "4.3.3",
- "@tailwindcss/oxide-linux-x64-gnu": "4.3.3",
- "@tailwindcss/oxide-linux-x64-musl": "4.3.3",
- "@tailwindcss/oxide-wasm32-wasi": "4.3.3",
- "@tailwindcss/oxide-win32-arm64-msvc": "4.3.3",
- "@tailwindcss/oxide-win32-x64-msvc": "4.3.3"
- }
- },
- "node_modules/@tailwindcss/oxide-android-arm64": {
- "version": "4.3.3",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.3.3.tgz",
- "integrity": "sha512-Y85A2gmPSkl5Ve5qR86GL4HT509cFqQh1aes9p3sSkyTPwt0Pppf3GkwGe4JPACcRYjgJIEhQgM6dBClnr0NYw==",
- "cpu": [
- "arm64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "android"
- ],
- "engines": {
- "node": ">= 20"
- }
- },
- "node_modules/@tailwindcss/oxide-darwin-arm64": {
- "version": "4.3.3",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.3.3.tgz",
- "integrity": "sha512-BiaWatpBcERQFDlOjRDpIVXuFK5PJez5SA4JMg6VYZdBYU+qKfV/vqjcIs+IYmtitf1xYQZTwXvU/8y4lfZUGw==",
- "cpu": [
- "arm64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": ">= 20"
- }
- },
- "node_modules/@tailwindcss/oxide-darwin-x64": {
- "version": "4.3.3",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.3.3.tgz",
- "integrity": "sha512-fAeUqfV5ndhxRwai8cXGzdLvul9utWOmeTkv69unv4ZXixjn61Z+p9lCWdwOwA3TYboG3BwdVuN/RDjhBRl0mw==",
- "cpu": [
- "x64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": ">= 20"
- }
- },
- "node_modules/@tailwindcss/oxide-freebsd-x64": {
- "version": "4.3.3",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.3.3.tgz",
- "integrity": "sha512-iyf5bV6+wnAlflVeEy7R25dupxTNECZN5QMI0qNT6eT+EgaGdZcKhGkr5SdoaWiLJ3spLqIY9VCeSGrwmtg4kw==",
- "cpu": [
- "x64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "freebsd"
- ],
- "engines": {
- "node": ">= 20"
- }
- },
- "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": {
- "version": "4.3.3",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.3.3.tgz",
- "integrity": "sha512-aAYUprJAJQWWbRrPvtjdroZ56Md+JM8pMiopS6xGEwDfLhqj+2ver2p4nU4Mb3CRqcMmNBjo8KkUgcxhkzVQGQ==",
- "cpu": [
- "arm"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">= 20"
- }
- },
- "node_modules/@tailwindcss/oxide-linux-arm64-gnu": {
- "version": "4.3.3",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.3.3.tgz",
- "integrity": "sha512-nDxldcEENOxZRzC2uu9jrutZdAAQtb+8WWDCSnWL1zvBk1+FN+x6MtDViPB5AJMfttVCUhehGWus3XBPgatM/w==",
- "cpu": [
- "arm64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">= 20"
- }
- },
- "node_modules/@tailwindcss/oxide-linux-arm64-musl": {
- "version": "4.3.3",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.3.3.tgz",
- "integrity": "sha512-Md44bD6veX/PC5iyF8cDVnw4HBIANZepRZZ7a8DQOvkfo5WUBwcp6iAuCUz23u+4SUkhJlD3eL7hNdW8ezd/kA==",
- "cpu": [
- "arm64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">= 20"
- }
- },
- "node_modules/@tailwindcss/oxide-linux-x64-gnu": {
- "version": "4.3.3",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.3.3.tgz",
- "integrity": "sha512-tx7us1muwOKAKWao2v/GaafFeQboE6aj88vC6ziN2NCGcRm8gWUhwjzg+YdVB1e4boAtdtma4L43onunI6NS4w==",
- "cpu": [
- "x64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">= 20"
- }
- },
- "node_modules/@tailwindcss/oxide-linux-x64-musl": {
- "version": "4.3.3",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.3.3.tgz",
- "integrity": "sha512-SJxX60smvHgasZoBy11dX6YRjXJFovwWBoedhbQPOBzgFWBHGB+TVPWB9BxzR7TTxU8FQZAI2AyiNCMzFm8Img==",
- "cpu": [
- "x64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">= 20"
- }
- },
- "node_modules/@tailwindcss/oxide-wasm32-wasi": {
- "version": "4.3.3",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.3.3.tgz",
- "integrity": "sha512-jx1+rPhY/5Ympkktd656HBWEBLxP7dH06losBLjjf5vgCODXvi9KhtftWcMIwTFIDqBr7cRnQkdLnAG+IOlGvQ==",
- "bundleDependencies": [
- "@napi-rs/wasm-runtime",
- "@emnapi/core",
- "@emnapi/runtime",
- "@tybys/wasm-util",
- "@emnapi/wasi-threads",
- "tslib"
- ],
- "cpu": [
- "wasm32"
- ],
- "license": "MIT",
- "optional": true,
- "dependencies": {
- "@emnapi/core": "^1.11.1",
- "@emnapi/runtime": "^1.11.1",
- "@emnapi/wasi-threads": "^1.2.2",
- "@napi-rs/wasm-runtime": "^1.1.4",
- "@tybys/wasm-util": "^0.10.2",
- "tslib": "^2.8.1"
- },
- "engines": {
- "node": ">=14.0.0"
- }
- },
- "node_modules/@tailwindcss/oxide-win32-arm64-msvc": {
- "version": "4.3.3",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.3.3.tgz",
- "integrity": "sha512-3rc292Ca2ceK6Ulcc/bAVnTs/3nDtoPhyEKlgPv+yQJQi/JS/AMJlqzxvlDacL1nekbrcf6bTqp/jV4qgnPxNQ==",
- "cpu": [
- "arm64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": ">= 20"
- }
- },
- "node_modules/@tailwindcss/oxide-win32-x64-msvc": {
- "version": "4.3.3",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.3.3.tgz",
- "integrity": "sha512-yJ0pwIVc/nYeGoV02WtsN8KYyLQv7kyI2wDnkezyJlGGjkd4QLwDGAwl47YpPJeuI0M0ObaXGSPjvWDPeTPggw==",
- "cpu": [
- "x64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": ">= 20"
- }
- },
- "node_modules/@tailwindcss/vite": {
- "version": "4.3.3",
- "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.3.3.tgz",
- "integrity": "sha512-yYU8cogLeSh/ms2jh8Fj7jaba/EWa7Ja6GoUqYZaraEuCI5YS6ms6ObZgjjedm+jm6XZjdNRWBpPP6Z86oOxcw==",
- "license": "MIT",
- "dependencies": {
- "@tailwindcss/node": "4.3.3",
- "@tailwindcss/oxide": "4.3.3",
- "tailwindcss": "4.3.3"
- },
- "peerDependencies": {
- "vite": "^5.2.0 || ^6 || ^7 || ^8"
- }
- },
- "node_modules/@tybys/wasm-util": {
- "version": "0.10.3",
- "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.3.tgz",
- "integrity": "sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg==",
- "license": "MIT",
- "optional": true,
- "dependencies": {
- "tslib": "^2.4.0"
- }
- },
- "node_modules/@types/babel__core": {
- "version": "7.20.5",
- "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
- "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==",
- "license": "MIT",
- "dependencies": {
- "@babel/parser": "^7.20.7",
- "@babel/types": "^7.20.7",
- "@types/babel__generator": "*",
- "@types/babel__template": "*",
- "@types/babel__traverse": "*"
- }
- },
- "node_modules/@types/babel__generator": {
- "version": "7.27.0",
- "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz",
- "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==",
- "license": "MIT",
- "dependencies": {
- "@babel/types": "^7.0.0"
- }
- },
- "node_modules/@types/babel__template": {
- "version": "7.4.4",
- "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz",
- "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==",
- "license": "MIT",
- "dependencies": {
- "@babel/parser": "^7.1.0",
- "@babel/types": "^7.0.0"
- }
- },
- "node_modules/@types/babel__traverse": {
- "version": "7.28.0",
- "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz",
- "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==",
- "license": "MIT",
- "dependencies": {
- "@babel/types": "^7.28.2"
- }
- },
- "node_modules/@types/debug": {
- "version": "4.1.13",
- "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.13.tgz",
- "integrity": "sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw==",
- "license": "MIT",
- "dependencies": {
- "@types/ms": "*"
- }
- },
"node_modules/@types/estree": {
"version": "1.0.9",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz",
"integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==",
+ "dev": true,
"license": "MIT"
},
- "node_modules/@types/estree-jsx": {
- "version": "1.0.5",
- "resolved": "https://registry.npmjs.org/@types/estree-jsx/-/estree-jsx-1.0.5.tgz",
- "integrity": "sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==",
- "license": "MIT",
- "dependencies": {
- "@types/estree": "*"
- }
- },
- "node_modules/@types/hast": {
- "version": "3.0.5",
- "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.5.tgz",
- "integrity": "sha512-rp/ezSWaD1m44dPKICGhiskI13nVr7qTloFwDa/IYkhhf5nzwP+zIQcIJh3WIFSBOy/H1PzB40jPjMDksN4F+g==",
- "license": "MIT",
- "dependencies": {
- "@types/unist": "*"
- }
- },
- "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.14",
- "resolved": "https://registry.npmjs.org/@types/mdx/-/mdx-2.0.14.tgz",
- "integrity": "sha512-T48PeuJtvLosNTPVhfnIp3i/n3a4g4Bad7YCq5k64D4u7NwDrAotikQ+5+sjtUvBmxCMlbo3dVL+C2dP0rWHzg==",
- "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": "26.1.2",
- "resolved": "https://registry.npmjs.org/@types/node/-/node-26.1.2.tgz",
- "integrity": "sha512-Vu4a5UFA9rIIFJ7rB/Vaafh9lrCQszopTCx6KjFboXTGQbPNasehVR5TEiithSDGyd1DEiUByggTZsg8jukeIg==",
+ "version": "22.20.5",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-22.20.5.tgz",
+ "integrity": "sha512-U2+DNr+wSjpsTS/wZGYHq7GcwfuSmKiKvoPvK22zwTlRhU91yOniN4qRR5KhIjvif7ysw/dz/hKmfDH0Ris4aA==",
+ "dev": true,
"license": "MIT",
"dependencies": {
- "undici-types": "~8.3.0"
+ "undici-types": "~6.21.0"
}
},
- "node_modules/@types/nodemailer": {
- "version": "8.0.1",
- "resolved": "https://registry.npmjs.org/@types/nodemailer/-/nodemailer-8.0.1.tgz",
- "integrity": "sha512-PxpaInm8V1JQDd4j0ds5HfvWQk8JupS1C0Picb96QJsrrRDjBH+DlK7L4ZdNSqNULhiZRQHc40nLVShaGxXAMw==",
+ "node_modules/@vitest/mocker": {
+ "version": "5.0.3",
+ "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-5.0.3.tgz",
+ "integrity": "sha512-T8sWAIbkSyAjkwTcaEc3Iu0o9A27X1/kdXrizhZkGuSKScRQtRzclfAMpOTcGdXCsqxeWlpGy3XjqaW8CpLORg==",
+ "dev": true,
"license": "MIT",
"dependencies": {
- "@types/node": "*"
- }
- },
- "node_modules/@types/react": {
- "version": "19.2.18",
- "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.18.tgz",
- "integrity": "sha512-AnzbBERsrLKtk2XSfTbYRLjQPdy116Sty4q+T+Bp3IC4l6jNBvreVPAHmpq9qhXQM7CXZPjLVmGMw9sy+hxQ3w==",
- "license": "MIT",
- "dependencies": {
- "csstype": "^3.2.2"
- }
- },
- "node_modules/@types/react-dom": {
- "version": "19.2.4",
- "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.4.tgz",
- "integrity": "sha512-Bsc+QHgp+P/F02XDzNCY9jnZNCUuLki36KT7VKrTXXLdHf+vHMNZnW1rVu5DNW/rCK+fya3DATySbLM4yhtKUw==",
- "license": "MIT",
- "peerDependencies": {
- "@types/react": "^19.2.0"
- }
- },
- "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.3",
- "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.3.tgz",
- "integrity": "sha512-60YRaenCQcVjYEKOcG824+DRGGIQ3VKErcBoAEDJZz5bKIs2ZG+X/H9Nk+Q6EVkwJk5QNApxbrc5QtBSwtrXAg==",
- "license": "ISC"
- },
- "node_modules/@vitejs/plugin-react": {
- "version": "5.2.0",
- "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-5.2.0.tgz",
- "integrity": "sha512-YmKkfhOAi3wsB1PhJq5Scj3GXMn3WvtQ/JC0xoopuHoXSdmtdStOpFrYaT1kie2YgFBcIe64ROzMYRjCrYOdYw==",
- "license": "MIT",
- "dependencies": {
- "@babel/core": "^7.29.0",
- "@babel/plugin-transform-react-jsx-self": "^7.27.1",
- "@babel/plugin-transform-react-jsx-source": "^7.27.1",
- "@rolldown/pluginutils": "1.0.0-rc.3",
- "@types/babel__core": "^7.20.5",
- "react-refresh": "^0.18.0"
- },
- "engines": {
- "node": "^20.19.0 || >=22.12.0"
- },
- "peerDependencies": {
- "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0"
- }
- },
- "node_modules/@volar/kit": {
- "version": "2.4.28",
- "resolved": "https://registry.npmjs.org/@volar/kit/-/kit-2.4.28.tgz",
- "integrity": "sha512-cKX4vK9dtZvDRaAzeoUdaAJEew6IdxHNCRrdp5Kvcl6zZOqb6jTOfk3kXkIkG3T7oTFXguEMt5+9ptyqYR84Pg==",
- "license": "MIT",
- "dependencies": {
- "@volar/language-service": "2.4.28",
- "@volar/typescript": "2.4.28",
- "typesafe-path": "^0.2.2",
- "vscode-languageserver-textdocument": "^1.0.11",
- "vscode-uri": "^3.0.8"
- },
- "peerDependencies": {
- "typescript": "*"
- }
- },
- "node_modules/@volar/language-core": {
- "version": "2.4.28",
- "resolved": "https://registry.npmjs.org/@volar/language-core/-/language-core-2.4.28.tgz",
- "integrity": "sha512-w4qhIJ8ZSitgLAkVay6AbcnC7gP3glYM3fYwKV3srj8m494E3xtrCv6E+bWviiK/8hs6e6t1ij1s2Endql7vzQ==",
- "license": "MIT",
- "dependencies": {
- "@volar/source-map": "2.4.28"
- }
- },
- "node_modules/@volar/language-server": {
- "version": "2.4.28",
- "resolved": "https://registry.npmjs.org/@volar/language-server/-/language-server-2.4.28.tgz",
- "integrity": "sha512-NqcLnE5gERKuS4PUFwlhMxf6vqYo7hXtbMFbViXcbVkbZ905AIVWhnSo0ZNBC2V127H1/2zP7RvVOVnyITFfBw==",
- "license": "MIT",
- "dependencies": {
- "@volar/language-core": "2.4.28",
- "@volar/language-service": "2.4.28",
- "@volar/typescript": "2.4.28",
- "path-browserify": "^1.0.1",
- "request-light": "^0.7.0",
- "vscode-languageserver": "^9.0.1",
- "vscode-languageserver-protocol": "^3.17.5",
- "vscode-languageserver-textdocument": "^1.0.11",
- "vscode-uri": "^3.0.8"
- }
- },
- "node_modules/@volar/language-service": {
- "version": "2.4.28",
- "resolved": "https://registry.npmjs.org/@volar/language-service/-/language-service-2.4.28.tgz",
- "integrity": "sha512-Rh/wYCZJrI5vCwMk9xyw/Z+MsWxlJY1rmMZPsxUoJKfzIRjS/NF1NmnuEcrMbEVGja00aVpCsInJfixQTMdvLw==",
- "license": "MIT",
- "dependencies": {
- "@volar/language-core": "2.4.28",
- "vscode-languageserver-protocol": "^3.17.5",
- "vscode-languageserver-textdocument": "^1.0.11",
- "vscode-uri": "^3.0.8"
- }
- },
- "node_modules/@volar/source-map": {
- "version": "2.4.28",
- "resolved": "https://registry.npmjs.org/@volar/source-map/-/source-map-2.4.28.tgz",
- "integrity": "sha512-yX2BDBqJkRXfKw8my8VarTyjv48QwxdJtvRgUpNE5erCsgEUdI2DsLbpa+rOQVAJYshY99szEcRDmyHbF10ggQ==",
- "license": "MIT"
- },
- "node_modules/@volar/typescript": {
- "version": "2.4.28",
- "resolved": "https://registry.npmjs.org/@volar/typescript/-/typescript-2.4.28.tgz",
- "integrity": "sha512-Ja6yvWrbis2QtN4ClAKreeUZPVYMARDYZl9LMEv1iQ1QdepB6wn0jTRxA9MftYmYa4DQ4k/DaSZpFPUfxl8giw==",
- "license": "MIT",
- "dependencies": {
- "@volar/language-core": "2.4.28",
- "path-browserify": "^1.0.1",
- "vscode-uri": "^3.0.8"
- }
- },
- "node_modules/@vscode/emmet-helper": {
- "version": "2.11.0",
- "resolved": "https://registry.npmjs.org/@vscode/emmet-helper/-/emmet-helper-2.11.0.tgz",
- "integrity": "sha512-QLxjQR3imPZPQltfbWRnHU6JecWTF1QSWhx3GAKQpslx7y3Dp6sIIXhKjiUJ/BR9FX8PVthjr9PD6pNwOJfAzw==",
- "license": "MIT",
- "dependencies": {
- "emmet": "^2.4.3",
- "jsonc-parser": "^2.3.0",
- "vscode-languageserver-textdocument": "^1.0.1",
- "vscode-languageserver-types": "^3.15.1",
- "vscode-uri": "^3.0.8"
- }
- },
- "node_modules/@vscode/emmet-helper/node_modules/jsonc-parser": {
- "version": "2.3.1",
- "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-2.3.1.tgz",
- "integrity": "sha512-H8jvkz1O50L3dMZCsLqiuB2tA7muqbSg1AtGEkN0leAqGjsUzDJir3Zwr02BhqdcITPg3ei3mZ+HjMocAknhhg==",
- "license": "MIT"
- },
- "node_modules/@vscode/l10n": {
- "version": "0.0.18",
- "resolved": "https://registry.npmjs.org/@vscode/l10n/-/l10n-0.0.18.tgz",
- "integrity": "sha512-KYSIHVmslkaCDyw013pphY+d7x1qV8IZupYfeIfzNA+nsaWHbn5uPuQRvdRFsa9zFzGeudPuoGoZ1Op4jrJXIQ==",
- "license": "MIT"
- },
- "node_modules/acorn": {
- "version": "8.18.0",
- "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.18.0.tgz",
- "integrity": "sha512-lGq+9yr1/GuAWaVYIHRjvvySG5/4VfKIvC8EWxStPdcDh/Ka7FG3twP6v4d5BkravUilhIAsG4Qj83t02LWUPQ==",
- "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/ajv": {
- "version": "8.20.0",
- "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz",
- "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==",
- "license": "MIT",
- "dependencies": {
- "fast-deep-equal": "^3.1.3",
- "fast-uri": "^3.0.1",
- "json-schema-traverse": "^1.0.0",
- "require-from-string": "^2.0.2"
+ "@jridgewell/trace-mapping": "0.3.31",
+ "@vitest/spy": "5.0.3",
+ "estree-walker": "^3.0.3",
+ "magic-string": "^1.2.3"
},
"funding": {
- "type": "github",
- "url": "https://github.com/sponsors/epoberezkin"
- }
- },
- "node_modules/ajv-draft-04": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/ajv-draft-04/-/ajv-draft-04-1.0.0.tgz",
- "integrity": "sha512-mv00Te6nmYbRp5DCwclxtt7yV/joXJPGS7nM+97GdxvuttCOfgI3K4U25zboyeX0O+myI8ERluxQe5wljMmVIw==",
- "license": "MIT",
+ "url": "https://opencollective.com/vitest"
+ },
"peerDependencies": {
- "ajv": "^8.5.0"
+ "msw": "^2.4.9",
+ "vite": "^6.0.0 || ^7.0.0 || ^8.0.0"
},
"peerDependenciesMeta": {
- "ajv": {
+ "msw": {
+ "optional": true
+ },
+ "vite": {
"optional": true
}
}
},
- "node_modules/ajv-i18n": {
- "version": "4.2.0",
- "resolved": "https://registry.npmjs.org/ajv-i18n/-/ajv-i18n-4.2.0.tgz",
- "integrity": "sha512-v/ei2UkCEeuKNXh8RToiFsUclmU+G57LO1Oo22OagNMENIw+Yb8eMwvHu7Vn9fmkjJyv6XclhJ8TbuigSglPkg==",
+ "node_modules/@vitest/spy": {
+ "version": "5.0.3",
+ "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-5.0.3.tgz",
+ "integrity": "sha512-XhFysQTB8AZ+P4gMi+Lpo99vg2AZi0qKpaB9yXQl37+CaMEAPO3iH/wGVnSyL5MPERiLezpqTVtrR6UZH5GCXg==",
+ "dev": true,
"license": "MIT",
- "peerDependencies": {
- "ajv": "^8.0.0-beta.0"
+ "funding": {
+ "url": "https://opencollective.com/vitest"
}
},
- "node_modules/am-i-vibing": {
- "version": "0.4.0",
- "resolved": "https://registry.npmjs.org/am-i-vibing/-/am-i-vibing-0.4.0.tgz",
- "integrity": "sha512-MxT4XZL7pzLHpuvhDKdMaQHMGGkJDLluKBLsbstn+8wv9sWcFT6h+0ve9qkml95amVTZtZV83gQe2hY+ojgHLg==",
- "license": "MIT",
- "dependencies": {
- "process-ancestry": "^0.1.0"
- },
- "bin": {
- "am-i-vibing": "dist/cli.mjs"
- }
- },
- "node_modules/ansi-regex": {
- "version": "6.2.2",
- "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz",
- "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==",
+ "node_modules/assertion-error": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz",
+ "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==",
+ "dev": true,
"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==",
+ "node_modules/chai": {
+ "version": "6.3.0",
+ "resolved": "https://registry.npmjs.org/chai/-/chai-6.3.0.tgz",
+ "integrity": "sha512-XWAtwJ6OHO+tj0EKCs0Y2UamnyOxseZWltU4x2U2wh8g4AigdjwvtUjvLP2tqkA/avxHEtzxNaqGq/YGNwckKg==",
+ "dev": true,
"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.2",
- "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz",
- "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==",
- "license": "MIT",
- "engines": {
- "node": ">=8.6"
- },
- "funding": {
- "url": "https://github.com/sponsors/jonschlinkert"
- }
- },
- "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": "7.2.0",
- "resolved": "https://registry.npmjs.org/astro/-/astro-7.2.0.tgz",
- "integrity": "sha512-lLTYzx3fOvCmtwD3JVBLQcbORbIOW1/j0R+3IvJx/XKwMGrk7mFnF0BYSOeRiNw1qHUR5mdA6+hRnyvyDfqrWQ==",
- "license": "MIT",
- "dependencies": {
- "@astrojs/compiler-rs": "^0.3.2",
- "@astrojs/internal-helpers": "0.10.2",
- "@astrojs/markdown-satteri": "0.3.5",
- "@astrojs/telemetry": "3.3.3",
- "@capsizecss/unpack": "^4.0.0",
- "@clack/prompts": "^1.1.0",
- "@oslojs/encoding": "^1.1.0",
- "am-i-vibing": "^0.4.0",
- "aria-query": "^5.3.2",
- "axobject-query": "^4.1.0",
- "ci-info": "^4.4.0",
- "clsx": "^2.1.1",
- "common-ancestor-path": "^2.0.0",
- "cookie": "^2.0.1",
- "devalue": "^5.8.1",
- "diff": "^8.0.3",
- "dset": "^3.1.4",
- "es-module-lexer": "^2.0.0",
- "esbuild": "^0.28.0",
- "flattie": "^1.1.1",
- "fontace": "~0.4.1",
- "get-tsconfig": "5.0.0-beta.4",
- "github-slugger": "^2.0.0",
- "html-escaper": "3.0.3",
- "http-cache-semantics": "^4.2.0",
- "js-yaml": "^4.3.0",
- "jsonc-parser": "^3.3.1",
- "magic-string": "^1.0.0",
- "magicast": "^0.5.2",
- "mrmime": "^2.0.1",
- "neotraverse": "^1.0.1",
- "obug": "^2.1.1",
- "p-limit": "^7.3.0",
- "p-queue": "^9.1.0",
- "package-manager-detector": "^1.6.0",
- "piccolore": "^0.1.3",
- "picomatch": "^4.0.4",
- "semver": "^7.7.4",
- "shiki": "^4.0.2",
- "smol-toml": "^1.6.0",
- "svgo": "^4.0.1",
- "tinyclip": "^0.1.12",
- "tinyexec": "^1.0.4",
- "tinyglobby": "^0.2.15",
- "ultrahtml": "^1.6.0",
- "unifont": "~0.7.4",
- "unstorage": "^1.17.5",
- "vite": "^8.0.13",
- "vitefu": "^1.1.2",
- "xxhash-wasm": "^1.1.0",
- "yargs-parser": "^22.0.0",
- "zod": "^4.3.6"
- },
- "bin": {
- "astro": "bin/astro.mjs"
- },
- "engines": {
- "node": ">=22.12.0",
- "npm": ">=9.6.5",
- "pnpm": ">=7.1.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/astrodotbuild"
- },
- "optionalDependencies": {
- "sharp": "^0.34.0 || ^0.35.0"
- },
- "peerDependencies": {
- "@astrojs/markdown-remark": "7.2.2"
- },
- "peerDependenciesMeta": {
- "@astrojs/markdown-remark": {
- "optional": true
- }
- }
- },
- "node_modules/astro/node_modules/magic-string": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-1.1.0.tgz",
- "integrity": "sha512-kS3VHe0nEPST2saQV4Rbkchcd3UBRkVTQHo1D3h/ZTwFDhai/mfKkmtPAtD129EOI7K3HlHIsFOt0WrI2/oU9g==",
- "license": "MIT",
- "dependencies": {
- "@jridgewell/sourcemap-codec": "^1.5.5"
- }
- },
- "node_modules/astro/node_modules/semver": {
- "version": "7.8.5",
- "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz",
- "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==",
- "license": "ISC",
- "bin": {
- "semver": "bin/semver.js"
- },
- "engines": {
- "node": ">=10"
- }
- },
- "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/baseline-browser-mapping": {
- "version": "2.11.12",
- "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.12.tgz",
- "integrity": "sha512-r7WnVImvVCeFpf2DOXfy41aPWzeNg3H/A2X4dKmy1QL0MSyyk/e7z8ihJ3N6Nn2PsdhkVlqnEfnUE4a05P2aTA==",
- "license": "Apache-2.0",
- "bin": {
- "baseline-browser-mapping": "dist/cli.cjs"
- },
- "engines": {
- "node": ">=6.0.0"
- }
- },
- "node_modules/boolbase": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz",
- "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==",
- "license": "ISC"
- },
- "node_modules/browserslist": {
- "version": "4.28.7",
- "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.7.tgz",
- "integrity": "sha512-JxV13hNrFxqjOc8alRbq9dK1MM79NEXYpma2B2J4wAtpWS5zIEIKqWPGCl7N4o7Uc7B7itylh7SuDujATRyyTw==",
- "funding": [
- {
- "type": "opencollective",
- "url": "https://opencollective.com/browserslist"
- },
- {
- "type": "tidelift",
- "url": "https://tidelift.com/funding/github/npm/browserslist"
- },
- {
- "type": "github",
- "url": "https://github.com/sponsors/ai"
- }
- ],
- "license": "MIT",
- "dependencies": {
- "baseline-browser-mapping": "^2.10.44",
- "caniuse-lite": "^1.0.30001806",
- "electron-to-chromium": "^1.5.393",
- "node-releases": "^2.0.51",
- "update-browserslist-db": "^1.2.3"
- },
- "bin": {
- "browserslist": "cli.js"
- },
- "engines": {
- "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
- }
- },
- "node_modules/caniuse-lite": {
- "version": "1.0.30001807",
- "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001807.tgz",
- "integrity": "sha512-daRXJ9EB/rdRgu7kV+TTl1YUKtlsMWblPl2sLnpg9DZae16QCegol6A1SmCE31Lm9mXC1sRWGt/krouH+/dl7Q==",
- "funding": [
- {
- "type": "opencollective",
- "url": "https://opencollective.com/browserslist"
- },
- {
- "type": "tidelift",
- "url": "https://tidelift.com/funding/github/npm/caniuse-lite"
- },
- {
- "type": "github",
- "url": "https://github.com/sponsors/ai"
- }
- ],
- "license": "CC-BY-4.0"
- },
- "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/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/chokidar": {
- "version": "4.0.3",
- "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz",
- "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==",
- "license": "MIT",
- "dependencies": {
- "readdirp": "^4.0.1"
- },
- "engines": {
- "node": ">= 14.16.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/cliui": {
- "version": "9.0.1",
- "resolved": "https://registry.npmjs.org/cliui/-/cliui-9.0.1.tgz",
- "integrity": "sha512-k7ndgKhwoQveBL+/1tqGJYNz097I7WOvwbmmU2AR5+magtbjPWQTS1C5vzGkBC8Ym8UWRzfKUzUUqFLypY4Q+w==",
- "license": "ISC",
- "dependencies": {
- "string-width": "^7.2.0",
- "strip-ansi": "^7.1.0",
- "wrap-ansi": "^9.0.0"
- },
- "engines": {
- "node": ">=20"
- }
- },
- "node_modules/cliui/node_modules/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/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/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": "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/common-ancestor-path": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/common-ancestor-path/-/common-ancestor-path-2.0.0.tgz",
- "integrity": "sha512-dnN3ibLeoRf2HNC+OlCiNc5d2zxbLJXOtiZUudNFSXZrNSydxcCsSpRzXwfu7BBWCIfHPw+xTayeBvJCP/D8Ng==",
- "license": "BlueOak-1.0.0",
- "engines": {
- "node": ">= 18"
- }
- },
- "node_modules/convert-source-map": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
- "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
- "license": "MIT"
- },
- "node_modules/cookie": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/cookie/-/cookie-2.0.1.tgz",
- "integrity": "sha512-yuToqVvRrj6pfDXREyQAAv8SkAEk/8GS3jQRTiUMm66TVtBYmqQeoEjL2Lmq8Rpo6271vH76InTChTitEAm65w==",
- "license": "MIT",
- "engines": {
- "node": ">=22"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/express"
- }
- },
- "node_modules/cookie-es": {
- "version": "1.2.3",
- "resolved": "https://registry.npmjs.org/cookie-es/-/cookie-es-1.2.3.tgz",
- "integrity": "sha512-lXVyvUvrNXblMqzIRrxHb57UUVmqsSWlxqt3XIjCkUP0wDAf6uicO6KMbEgYrMNtEvWgWHwe42CKxPu9MYAnWw==",
- "license": "MIT"
- },
- "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-tree": {
- "version": "3.2.1",
- "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.2.1.tgz",
- "integrity": "sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA==",
- "license": "MIT",
- "dependencies": {
- "mdn-data": "2.27.1",
- "source-map-js": "^1.2.1"
- },
- "engines": {
- "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0"
- }
- },
- "node_modules/css-what": {
- "version": "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/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/csstype": {
- "version": "3.2.3",
- "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
- "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
- "license": "MIT"
- },
- "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.7",
- "resolved": "https://registry.npmjs.org/defu/-/defu-6.1.7.tgz",
- "integrity": "sha512-7z22QmUWiQ/2d0KkdYmANbRUVABpZ9SNYyH5vx6PZ+nE5bcC0l7uFvEfHlyld/HcGBFTL536ClDt3DEcSlEJAQ==",
- "license": "MIT"
- },
- "node_modules/depd": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
- "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
- "license": "MIT",
- "engines": {
- "node": ">= 0.8"
- }
- },
- "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==",
+ "dev": true,
"license": "Apache-2.0",
+ "peer": true,
"engines": {
"node": ">=8"
}
},
- "node_modules/devalue": {
- "version": "5.9.0",
- "resolved": "https://registry.npmjs.org/devalue/-/devalue-5.9.0.tgz",
- "integrity": "sha512-RWrqdArjvPbsATEhOPUo6Wndc/iWnkWKlhIrdlF3zMMYo/c3CVtoaVAyLtWxz5h8nSlkHzxnzV2uLydPXmtF+A==",
- "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.4",
- "resolved": "https://registry.npmjs.org/diff/-/diff-8.0.4.tgz",
- "integrity": "sha512-DPi0FmjiSU5EvQV0++GFDOJ9ASQUVFh5kD+OzOnYdi7n3Wpm9hWWGfB/O2blfHcMVTL5WkQXSnRiK9makhrcnw==",
- "license": "BSD-3-Clause",
- "engines": {
- "node": ">=0.3.1"
- }
- },
- "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/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/ee-first": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
- "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==",
- "license": "MIT"
- },
- "node_modules/electron-to-chromium": {
- "version": "1.5.402",
- "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.402.tgz",
- "integrity": "sha512-/oOpMaPT6Yg+6/1XQhyIPlzgj7Ye9zf+nNM2Uh6OcE2G2oNptWazFa+qB2Pdqqbsc9KnIDzgAntoYN0dbwOXwA==",
- "license": "ISC"
- },
- "node_modules/emmet": {
- "version": "2.4.11",
- "resolved": "https://registry.npmjs.org/emmet/-/emmet-2.4.11.tgz",
- "integrity": "sha512-23QPJB3moh/U9sT4rQzGgeyyGIrcM+GH5uVYg2C6wZIxAIJq7Ng3QLT79tl8FUwDXhyq9SusfknOrofAKqvgyQ==",
- "license": "MIT",
- "workspaces": [
- "./packages/scanner",
- "./packages/abbreviation",
- "./packages/css-abbreviation",
- "./"
- ],
- "dependencies": {
- "@emmetio/abbreviation": "^2.3.3",
- "@emmetio/css-abbreviation": "^2.1.8"
- }
- },
- "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/encodeurl": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
- "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==",
- "license": "MIT",
- "engines": {
- "node": ">= 0.8"
- }
- },
- "node_modules/enhanced-resolve": {
- "version": "5.24.5",
- "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.24.5.tgz",
- "integrity": "sha512-L1l8TNvomm6UVW5B253AGxQagSQr+vGwhMlrrfRS2qmhx46AMpMVJKQYLvWYbysTMY8VoicOvzHzoHMbyzB+4A==",
- "license": "MIT",
- "dependencies": {
- "graceful-fs": "^4.2.4",
- "tapable": "^2.3.3"
- },
- "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": "2.3.1",
- "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.3.1.tgz",
- "integrity": "sha512-shc1dbU90Yl/xq1QrC7QRtfcwURZuVRfPhZbDoldJ1cn1gzDvBaBWlv0eFolj5+0znnPJz5TXLxsN77X/12KTA==",
+ "version": "2.3.2",
+ "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.3.2.tgz",
+ "integrity": "sha512-poHGpORABojJJucnV9KbOavETW8lBVnphkW77ER5/BQ5Fz7oXSoCNek7IH3vR5nRjdsEz926ibFYX8KtLQmdyw==",
+ "dev": true,
"license": "MIT"
},
- "node_modules/esast-util-from-estree": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/esast-util-from-estree/-/esast-util-from-estree-2.0.0.tgz",
- "integrity": "sha512-4CyanoAudUSBAn5K13H4JhsMH6L9ZP7XbLVe/dKybkxMO7eDyLsT8UHl9TRNrU2Gr9nz+FovfSIjuXWJ81uVwQ==",
- "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.28.1",
- "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.1.tgz",
- "integrity": "sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw==",
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.2.tgz",
+ "integrity": "sha512-HKVLS8dvII+xoKW9kmqxbRKrnWEXfJJr/FZhhJmiqIB0e053QNYFqOBouTMO/k5sID4MvCiUCvv8b9M4h32wIA==",
+ "dev": true,
"hasInstallScript": true,
"license": "MIT",
"bin": {
@@ -3828,223 +911,59 @@
"node": ">=18"
},
"optionalDependencies": {
- "@esbuild/aix-ppc64": "0.28.1",
- "@esbuild/android-arm": "0.28.1",
- "@esbuild/android-arm64": "0.28.1",
- "@esbuild/android-x64": "0.28.1",
- "@esbuild/darwin-arm64": "0.28.1",
- "@esbuild/darwin-x64": "0.28.1",
- "@esbuild/freebsd-arm64": "0.28.1",
- "@esbuild/freebsd-x64": "0.28.1",
- "@esbuild/linux-arm": "0.28.1",
- "@esbuild/linux-arm64": "0.28.1",
- "@esbuild/linux-ia32": "0.28.1",
- "@esbuild/linux-loong64": "0.28.1",
- "@esbuild/linux-mips64el": "0.28.1",
- "@esbuild/linux-ppc64": "0.28.1",
- "@esbuild/linux-riscv64": "0.28.1",
- "@esbuild/linux-s390x": "0.28.1",
- "@esbuild/linux-x64": "0.28.1",
- "@esbuild/netbsd-arm64": "0.28.1",
- "@esbuild/netbsd-x64": "0.28.1",
- "@esbuild/openbsd-arm64": "0.28.1",
- "@esbuild/openbsd-x64": "0.28.1",
- "@esbuild/openharmony-arm64": "0.28.1",
- "@esbuild/sunos-x64": "0.28.1",
- "@esbuild/win32-arm64": "0.28.1",
- "@esbuild/win32-ia32": "0.28.1",
- "@esbuild/win32-x64": "0.28.1"
- }
- },
- "node_modules/escalade": {
- "version": "3.2.0",
- "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
- "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
- "license": "MIT",
- "engines": {
- "node": ">=6"
- }
- },
- "node_modules/escape-html": {
- "version": "1.0.3",
- "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
- "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==",
- "license": "MIT"
- },
- "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"
+ "@esbuild/aix-ppc64": "0.28.2",
+ "@esbuild/android-arm": "0.28.2",
+ "@esbuild/android-arm64": "0.28.2",
+ "@esbuild/android-x64": "0.28.2",
+ "@esbuild/darwin-arm64": "0.28.2",
+ "@esbuild/darwin-x64": "0.28.2",
+ "@esbuild/freebsd-arm64": "0.28.2",
+ "@esbuild/freebsd-x64": "0.28.2",
+ "@esbuild/linux-arm": "0.28.2",
+ "@esbuild/linux-arm64": "0.28.2",
+ "@esbuild/linux-ia32": "0.28.2",
+ "@esbuild/linux-loong64": "0.28.2",
+ "@esbuild/linux-mips64el": "0.28.2",
+ "@esbuild/linux-ppc64": "0.28.2",
+ "@esbuild/linux-riscv64": "0.28.2",
+ "@esbuild/linux-s390x": "0.28.2",
+ "@esbuild/linux-x64": "0.28.2",
+ "@esbuild/netbsd-arm64": "0.28.2",
+ "@esbuild/netbsd-x64": "0.28.2",
+ "@esbuild/openbsd-arm64": "0.28.2",
+ "@esbuild/openbsd-x64": "0.28.2",
+ "@esbuild/openharmony-arm64": "0.28.2",
+ "@esbuild/sunos-x64": "0.28.2",
+ "@esbuild/win32-arm64": "0.28.2",
+ "@esbuild/win32-ia32": "0.28.2",
+ "@esbuild/win32-x64": "0.28.2"
}
},
"node_modules/estree-walker": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz",
"integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==",
+ "dev": true,
"license": "MIT",
"dependencies": {
"@types/estree": "^1.0.0"
}
},
- "node_modules/etag": {
- "version": "1.8.1",
- "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
- "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==",
- "license": "MIT",
+ "node_modules/expect-type": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.4.0.tgz",
+ "integrity": "sha512-KfYbmpRm0VbLjEvVa9yGwCi9GI34xvi7A/HXYWQO65CSD2u3MczUJSuwXKFIxlGsgBQizV9q5J9NHj4VG0n+pA==",
+ "dev": true,
+ "license": "Apache-2.0",
"engines": {
- "node": ">= 0.6"
- }
- },
- "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/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/fast-deep-equal": {
- "version": "3.1.3",
- "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
- "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
- "license": "MIT"
- },
- "node_modules/fast-string-truncated-width": {
- "version": "3.0.3",
- "resolved": "https://registry.npmjs.org/fast-string-truncated-width/-/fast-string-truncated-width-3.0.3.tgz",
- "integrity": "sha512-0jjjIEL6+0jag3l2XWWizO64/aZVtpiGE3t0Zgqxv0DPuxiMjvB3M24fCyhZUO4KomJQPj3LTSUnDP3GpdwC0g==",
- "license": "MIT"
- },
- "node_modules/fast-string-width": {
- "version": "3.0.2",
- "resolved": "https://registry.npmjs.org/fast-string-width/-/fast-string-width-3.0.2.tgz",
- "integrity": "sha512-gX8LrtNEI5hq8DVUfRQMbr5lpaS4nMIWV+7XEbXk2b8kiQIizgnlr12B4dA3ZEx3308ze0O4Q1R+cHts8kyUJg==",
- "license": "MIT",
- "dependencies": {
- "fast-string-truncated-width": "^3.0.2"
- }
- },
- "node_modules/fast-uri": {
- "version": "3.1.5",
- "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.5.tgz",
- "integrity": "sha512-gHwA1O9LDIcKunMKhObS/HimwtehO1nPUECKAu5TpKgaO19fcWEl4bliWe1jWxVFvIXztJjjQ4L8XQ1EU9f7Jw==",
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/fastify"
- },
- {
- "type": "opencollective",
- "url": "https://opencollective.com/fastify"
- }
- ],
- "license": "BSD-3-Clause"
- },
- "node_modules/fast-wrap-ansi": {
- "version": "0.2.2",
- "resolved": "https://registry.npmjs.org/fast-wrap-ansi/-/fast-wrap-ansi-0.2.2.tgz",
- "integrity": "sha512-7F2Fl+TjRSenLqlU3UjSH0iyqopqoZIu7eZVpEirP2g1GtWa2G/ecEmBdgz31+Mxr+ELclgg6sokpSFIQiZ02Q==",
- "license": "MIT",
- "dependencies": {
- "fast-string-width": "^3.0.2"
+ "node": ">=12.0.0"
}
},
"node_modules/fdir": {
"version": "6.5.0",
"resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
"integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
+ "dev": true,
"license": "MIT",
"engines": {
"node": ">=12.0.0"
@@ -4058,76 +977,11 @@
}
}
},
- "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.3",
- "resolved": "https://registry.npmjs.org/fontkitten/-/fontkitten-1.0.3.tgz",
- "integrity": "sha512-Wp1zXWPVUPBmfoa3Cqc9ctaKuzKAV6uLstRqlR56kSjplf5uAce+qeyYym7F+PHbGTk+tCEdkCW6RD7DX/gBZw==",
- "license": "MIT",
- "dependencies": {
- "tiny-inflate": "^1.0.3"
- },
- "engines": {
- "node": ">=20"
- }
- },
- "node_modules/framer-motion": {
- "version": "12.43.0",
- "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.43.0.tgz",
- "integrity": "sha512-1eaL3RvR/kAlbG7UYcpMptEyzPoENO0c6w7ZnB3/hh2vSAz/6uGAFn6fdoqTBguNstf3MsFhJHsD/0DHiclG+g==",
- "license": "MIT",
- "dependencies": {
- "motion-dom": "^12.43.0",
- "motion-utils": "^12.39.0",
- "tslib": "^2.4.0"
- },
- "peerDependencies": {
- "@emotion/is-prop-valid": "*",
- "react": "^18.0.0 || ^19.0.0",
- "react-dom": "^18.0.0 || ^19.0.0"
- },
- "peerDependenciesMeta": {
- "@emotion/is-prop-valid": {
- "optional": true
- },
- "react": {
- "optional": true
- },
- "react-dom": {
- "optional": true
- }
- }
- },
- "node_modules/fresh": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz",
- "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==",
- "license": "MIT",
- "engines": {
- "node": ">= 0.8"
- }
- },
"node_modules/fsevents": {
"version": "2.3.3",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
+ "dev": true,
"hasInstallScript": true,
"license": "MIT",
"optional": true,
@@ -4138,533 +992,13 @@
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
}
},
- "node_modules/gensync": {
- "version": "1.0.0-beta.2",
- "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
- "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==",
- "license": "MIT",
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/get-caller-file": {
- "version": "2.0.5",
- "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
- "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
- "license": "ISC",
- "engines": {
- "node": "6.* || 8.* || >= 10.*"
- }
- },
- "node_modules/get-east-asian-width": {
- "version": "1.6.0",
- "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.6.0.tgz",
- "integrity": "sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA==",
- "license": "MIT",
- "engines": {
- "node": ">=18"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/get-tsconfig": {
- "version": "5.0.0-beta.4",
- "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-5.0.0-beta.4.tgz",
- "integrity": "sha512-7nF7C9fIPFEMHgEMEfgIlO9wDdZ8CyHw27rWciFZfHvHDReIiPhsYuzPRXsfvBCqFy1l8RRyyWV7QLM+ZhUJsQ==",
- "license": "MIT",
- "dependencies": {
- "resolve-pkg-maps": "^1.0.0"
- },
- "engines": {
- "node": ">=20.20.0"
- },
- "funding": {
- "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1"
- }
- },
- "node_modules/github-slugger": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/github-slugger/-/github-slugger-2.0.0.tgz",
- "integrity": "sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw==",
- "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==",
- "license": "ISC"
- },
- "node_modules/h3": {
- "version": "1.15.11",
- "resolved": "https://registry.npmjs.org/h3/-/h3-1.15.11.tgz",
- "integrity": "sha512-L3THSe2MPeBwgIZVSH5zLdBBU90TOxarvhK9d04IDY2AmVS8j2Jz2LIWtwsGOU3lu2I5jCN7FNvVfY2+XyF+mg==",
- "license": "MIT",
- "dependencies": {
- "cookie-es": "^1.2.3",
- "crossws": "^0.3.5",
- "defu": "^6.1.6",
- "destr": "^2.0.5",
- "iron-webcrypto": "^1.2.1",
- "node-mock-http": "^1.0.4",
- "radix3": "^1.1.2",
- "ufo": "^1.6.3",
- "uncrypto": "^0.1.3"
- }
- },
- "node_modules/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-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-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-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-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-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-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/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/http-errors": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz",
- "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==",
- "license": "MIT",
- "dependencies": {
- "depd": "~2.0.0",
- "inherits": "~2.0.4",
- "setprototypeof": "~1.2.0",
- "statuses": "~2.0.2",
- "toidentifier": "~1.0.1"
- },
- "engines": {
- "node": ">= 0.8"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/express"
- }
- },
- "node_modules/inherits": {
- "version": "2.0.4",
- "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
- "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
- "license": "ISC"
- },
- "node_modules/inline-style-parser": {
- "version": "0.2.7",
- "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.2.7.tgz",
- "integrity": "sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA==",
- "license": "MIT"
- },
- "node_modules/iron-webcrypto": {
- "version": "1.2.1",
- "resolved": "https://registry.npmjs.org/iron-webcrypto/-/iron-webcrypto-1.2.1.tgz",
- "integrity": "sha512-feOM6FaSr6rEABp/eDfVseKyTMDt+KGpeB35SkVn9Tyn0CqvVsY3EwI0v5i8nMHyJnzCIQf7nsy3p41TPkJZhg==",
- "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-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": "4.0.0",
- "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-4.0.0.tgz",
- "integrity": "sha512-LHE+wROyG/Y/0ZnbktRCoTix2c1RhgWaZraMZ8o1Q7zCh0VSrICJQO5oqIIISrcSBtrXv0o233w1IYwsWCjTzA==",
- "license": "MIT",
- "bin": {
- "is-docker": "cli.js"
- },
- "engines": {
- "node": ">=20"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "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-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/jiti": {
- "version": "2.7.0",
- "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.7.0.tgz",
- "integrity": "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==",
- "license": "MIT",
- "bin": {
- "jiti": "lib/jiti-cli.mjs"
- }
- },
- "node_modules/js-tokens": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
- "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
- "license": "MIT"
- },
- "node_modules/js-yaml": {
- "version": "4.3.1",
- "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.1.tgz",
- "integrity": "sha512-CY6crGq313MX8GkwvB7tzgp99vjQxY1++5y10/BKN/GUfHqWaOGQMNZkBvqSzsZKWk/ijwHlWzzkLulsGHhjWQ==",
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/puzrin"
- },
- {
- "type": "github",
- "url": "https://github.com/sponsors/nodeca"
- }
- ],
- "license": "MIT",
- "dependencies": {
- "argparse": "^2.0.1"
- },
- "bin": {
- "js-yaml": "bin/js-yaml.js"
- }
- },
- "node_modules/jsesc": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz",
- "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==",
- "license": "MIT",
- "bin": {
- "jsesc": "bin/jsesc"
- },
- "engines": {
- "node": ">=6"
- }
- },
- "node_modules/json-schema-traverse": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
- "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
- "license": "MIT"
- },
- "node_modules/json5": {
- "version": "2.2.3",
- "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
- "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
- "license": "MIT",
- "bin": {
- "json5": "lib/cli.js"
- },
- "engines": {
- "node": ">=6"
- }
- },
- "node_modules/jsonc-parser": {
- "version": "3.3.1",
- "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.3.1.tgz",
- "integrity": "sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==",
- "license": "MIT"
- },
- "node_modules/kleur": {
- "version": "4.1.5",
- "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz",
- "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==",
- "license": "MIT",
- "engines": {
- "node": ">=6"
- }
- },
"node_modules/lightningcss": {
- "version": "1.32.0",
- "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz",
- "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==",
+ "version": "1.33.0",
+ "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.33.0.tgz",
+ "integrity": "sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA==",
+ "dev": true,
"license": "MPL-2.0",
+ "peer": true,
"dependencies": {
"detect-libc": "^2.0.3"
},
@@ -4676,31 +1010,33 @@
"url": "https://opencollective.com/parcel"
},
"optionalDependencies": {
- "lightningcss-android-arm64": "1.32.0",
- "lightningcss-darwin-arm64": "1.32.0",
- "lightningcss-darwin-x64": "1.32.0",
- "lightningcss-freebsd-x64": "1.32.0",
- "lightningcss-linux-arm-gnueabihf": "1.32.0",
- "lightningcss-linux-arm64-gnu": "1.32.0",
- "lightningcss-linux-arm64-musl": "1.32.0",
- "lightningcss-linux-x64-gnu": "1.32.0",
- "lightningcss-linux-x64-musl": "1.32.0",
- "lightningcss-win32-arm64-msvc": "1.32.0",
- "lightningcss-win32-x64-msvc": "1.32.0"
+ "lightningcss-android-arm64": "1.33.0",
+ "lightningcss-darwin-arm64": "1.33.0",
+ "lightningcss-darwin-x64": "1.33.0",
+ "lightningcss-freebsd-x64": "1.33.0",
+ "lightningcss-linux-arm-gnueabihf": "1.33.0",
+ "lightningcss-linux-arm64-gnu": "1.33.0",
+ "lightningcss-linux-arm64-musl": "1.33.0",
+ "lightningcss-linux-x64-gnu": "1.33.0",
+ "lightningcss-linux-x64-musl": "1.33.0",
+ "lightningcss-win32-arm64-msvc": "1.33.0",
+ "lightningcss-win32-x64-msvc": "1.33.0"
}
},
"node_modules/lightningcss-android-arm64": {
- "version": "1.32.0",
- "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz",
- "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==",
+ "version": "1.33.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.33.0.tgz",
+ "integrity": "sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg==",
"cpu": [
"arm64"
],
+ "dev": true,
"license": "MPL-2.0",
"optional": true,
"os": [
"android"
],
+ "peer": true,
"engines": {
"node": ">= 12.0.0"
},
@@ -4710,17 +1046,19 @@
}
},
"node_modules/lightningcss-darwin-arm64": {
- "version": "1.32.0",
- "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz",
- "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==",
+ "version": "1.33.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.33.0.tgz",
+ "integrity": "sha512-Sciaz8eenNTKn9b3t7+xr0ipTp9YxKQY4npwQ3mrRuL0BAVHBLyZxofhaKBAVtzmtRZ/zTyo0/to4B1uWG/Djg==",
"cpu": [
"arm64"
],
+ "dev": true,
"license": "MPL-2.0",
"optional": true,
"os": [
"darwin"
],
+ "peer": true,
"engines": {
"node": ">= 12.0.0"
},
@@ -4730,17 +1068,19 @@
}
},
"node_modules/lightningcss-darwin-x64": {
- "version": "1.32.0",
- "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz",
- "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==",
+ "version": "1.33.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.33.0.tgz",
+ "integrity": "sha512-Z5UPAxzrjlWNNyGy6i65cJzzvgJ5D3T6wMvs+gWpY9d7qRhANrxqAp6LhxIgZhWEw18RfJTGcRxjuLIBr+m8XQ==",
"cpu": [
"x64"
],
+ "dev": true,
"license": "MPL-2.0",
"optional": true,
"os": [
"darwin"
],
+ "peer": true,
"engines": {
"node": ">= 12.0.0"
},
@@ -4750,17 +1090,19 @@
}
},
"node_modules/lightningcss-freebsd-x64": {
- "version": "1.32.0",
- "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz",
- "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==",
+ "version": "1.33.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.33.0.tgz",
+ "integrity": "sha512-QQM/Ti/hQajJwCY+RiWuCZ9sdtI/XQk7nDK5vC8kkdwixezOlDgvDx7+RT+QjK6FcFT4MpsuoBnHIo/O3StRRg==",
"cpu": [
"x64"
],
+ "dev": true,
"license": "MPL-2.0",
"optional": true,
"os": [
"freebsd"
],
+ "peer": true,
"engines": {
"node": ">= 12.0.0"
},
@@ -4770,17 +1112,19 @@
}
},
"node_modules/lightningcss-linux-arm-gnueabihf": {
- "version": "1.32.0",
- "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz",
- "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==",
+ "version": "1.33.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.33.0.tgz",
+ "integrity": "sha512-N7FVBe6iS24MlM6R/4RBTxGhQheZGs7tiQ9U32UtF75NzP5Q7xWPRqLBCKxlRQRk3rY1jCIPLzx7WzOhuUIRLQ==",
"cpu": [
"arm"
],
+ "dev": true,
"license": "MPL-2.0",
"optional": true,
"os": [
"linux"
],
+ "peer": true,
"engines": {
"node": ">= 12.0.0"
},
@@ -4790,17 +1134,19 @@
}
},
"node_modules/lightningcss-linux-arm64-gnu": {
- "version": "1.32.0",
- "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz",
- "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==",
+ "version": "1.33.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.33.0.tgz",
+ "integrity": "sha512-j2v/itmy4HlNxlc6voKXYgBqNi0Ng2LShg4z7GufpEgs05P+2suBVyi9I6YHq5uoVFx9ETin3eCEhLVyXGQnKg==",
"cpu": [
"arm64"
],
+ "dev": true,
"license": "MPL-2.0",
"optional": true,
"os": [
"linux"
],
+ "peer": true,
"engines": {
"node": ">= 12.0.0"
},
@@ -4810,17 +1156,19 @@
}
},
"node_modules/lightningcss-linux-arm64-musl": {
- "version": "1.32.0",
- "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz",
- "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==",
+ "version": "1.33.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.33.0.tgz",
+ "integrity": "sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ==",
"cpu": [
"arm64"
],
+ "dev": true,
"license": "MPL-2.0",
"optional": true,
"os": [
"linux"
],
+ "peer": true,
"engines": {
"node": ">= 12.0.0"
},
@@ -4830,17 +1178,19 @@
}
},
"node_modules/lightningcss-linux-x64-gnu": {
- "version": "1.32.0",
- "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz",
- "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==",
+ "version": "1.33.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.33.0.tgz",
+ "integrity": "sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg==",
"cpu": [
"x64"
],
+ "dev": true,
"license": "MPL-2.0",
"optional": true,
"os": [
"linux"
],
+ "peer": true,
"engines": {
"node": ">= 12.0.0"
},
@@ -4850,17 +1200,19 @@
}
},
"node_modules/lightningcss-linux-x64-musl": {
- "version": "1.32.0",
- "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz",
- "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==",
+ "version": "1.33.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.33.0.tgz",
+ "integrity": "sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw==",
"cpu": [
"x64"
],
+ "dev": true,
"license": "MPL-2.0",
"optional": true,
"os": [
"linux"
],
+ "peer": true,
"engines": {
"node": ">= 12.0.0"
},
@@ -4870,17 +1222,19 @@
}
},
"node_modules/lightningcss-win32-arm64-msvc": {
- "version": "1.32.0",
- "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz",
- "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==",
+ "version": "1.33.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.33.0.tgz",
+ "integrity": "sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA==",
"cpu": [
"arm64"
],
+ "dev": true,
"license": "MPL-2.0",
"optional": true,
"os": [
"win32"
],
+ "peer": true,
"engines": {
"node": ">= 12.0.0"
},
@@ -4890,17 +1244,19 @@
}
},
"node_modules/lightningcss-win32-x64-msvc": {
- "version": "1.32.0",
- "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz",
- "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==",
+ "version": "1.33.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.33.0.tgz",
+ "integrity": "sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA==",
"cpu": [
"x64"
],
+ "dev": true,
"license": "MPL-2.0",
"optional": true,
"os": [
"win32"
],
+ "peer": true,
"engines": {
"node": ">= 12.0.0"
},
@@ -4909,1157 +1265,21 @@
"url": "https://opencollective.com/parcel"
}
},
- "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": "5.1.1",
- "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
- "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
- "license": "ISC",
- "dependencies": {
- "yallist": "^3.0.2"
- }
- },
"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==",
+ "version": "1.4.3",
+ "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-1.4.3.tgz",
+ "integrity": "sha512-z12OxmaPGE0F4xlpGdjuAUckipLpQlTAuAmyGhNoD7ODpYUzvDfvtxUbjM/jwznyP178oju/KDdyi220wNJ0RQ==",
+ "dev": true,
"license": "MIT",
"dependencies": {
- "@jridgewell/sourcemap-codec": "^1.5.5"
+ "@jridgewell/sourcemap-codec": "^1.6.0"
}
},
- "node_modules/magicast": {
- "version": "0.5.4",
- "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.5.4.tgz",
- "integrity": "sha512-llBEhWm1SacoRwgHUoQJYtwp4PBLF4faQi5TCpIGyGs9n4y5+juI0tDgyKIfpqxckRHaHzouUEph3THklWh03w==",
- "license": "MIT",
- "dependencies": {
- "@babel/parser": "^7.29.7",
- "@babel/types": "^7.29.7",
- "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/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-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.3",
- "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.3.tgz",
- "integrity": "sha512-W4mAWTvSlKvf8L6J+VN9yLSqQ9AOAAvHuoDAmPkz4dHf553m5gVj2ejadHJhoJmcmxEnOv6Pa8XJhpxE93kb8Q==",
- "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-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.27.1",
- "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.27.1.tgz",
- "integrity": "sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==",
- "license": "CC0-1.0"
- },
- "node_modules/micromark": {
- "version": "4.0.2",
- "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.2.tgz",
- "integrity": "sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==",
- "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-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-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/mime-db": {
- "version": "1.54.0",
- "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz",
- "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==",
- "license": "MIT",
- "engines": {
- "node": ">= 0.6"
- }
- },
- "node_modules/mime-types": {
- "version": "3.0.2",
- "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz",
- "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==",
- "license": "MIT",
- "dependencies": {
- "mime-db": "^1.54.0"
- },
- "engines": {
- "node": ">=18"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/express"
- }
- },
- "node_modules/motion-dom": {
- "version": "12.43.0",
- "resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.43.0.tgz",
- "integrity": "sha512-azKON4d9S65PEoFUiQTMTgPheEmzf2QngdRc50AKfJp9Q9mmcBVw22c8eMq9k8kxOFHdL7+WZY7N/5F/lwiDag==",
- "license": "MIT",
- "dependencies": {
- "motion-utils": "^12.39.0"
- }
- },
- "node_modules/motion-utils": {
- "version": "12.39.0",
- "resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-12.39.0.tgz",
- "integrity": "sha512-8nadJAJjTtqRkmRF36FoJTrywK9nnFmnPwnSMyxaOCU7GDjN9RTMJIxx9De8ErM+vpPhMccr/6fo5WciyQLnMQ==",
- "license": "MIT"
- },
- "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/muggle-string": {
- "version": "0.4.1",
- "resolved": "https://registry.npmjs.org/muggle-string/-/muggle-string-0.4.1.tgz",
- "integrity": "sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==",
- "license": "MIT"
- },
"node_modules/nanoid": {
- "version": "3.3.17",
- "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.17.tgz",
- "integrity": "sha512-xQLf0A3HOMlgHq0n247/LRuAOYmB7dXJ/DvAxGvsSBij45XtBSmQycu+F8ODbHwns/XyFZagyL1+J0Offw1E0g==",
+ "version": "3.3.20",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.20.tgz",
+ "integrity": "sha512-uKdg2G3GNCKQn9byYOpxbGqrT2fGO5KRt5J/8b3pok8rT6qxGWF6hxMyJiEYtAf+FVyYuD9hRaDqX5uPFYJ4ZQ==",
+ "dev": true,
"funding": [
{
"type": "github",
@@ -6067,6 +1287,7 @@
}
],
"license": "MIT",
+ "peer": true,
"bin": {
"nanoid": "bin/nanoid.cjs"
},
@@ -6074,83 +1295,11 @@
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
}
},
- "node_modules/neotraverse": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/neotraverse/-/neotraverse-1.0.1.tgz",
- "integrity": "sha512-WmmLty1YWwJl9yZi77v2dVIV6X2kuYV8YYBI/G3LWGKdGHmHUvL1z7FW0iDvEvGAwNEoc5x1tOOOyDnf5jJw/w==",
- "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.5",
- "resolved": "https://registry.npmjs.org/node-mock-http/-/node-mock-http-1.0.5.tgz",
- "integrity": "sha512-KQyt/wLjG3TAc7DOUhpqWzgd4ERxR80JOlTK5VE5R1S12IaPVN5qkj4klBce9HPG1Njuup4Sb5bljaT34lIyjw==",
- "license": "MIT"
- },
- "node_modules/node-releases": {
- "version": "2.0.53",
- "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.53.tgz",
- "integrity": "sha512-D9UOmYG3UH1V+ENW56t5QXBwJw1YEY18ruVeus89Rw+SyIgjPkCO84bRzO3uNIYosJbNwiabWVn48o3uJLjxFQ==",
- "license": "MIT",
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/nodemailer": {
- "version": "9.0.4",
- "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-9.0.4.tgz",
- "integrity": "sha512-LmJNRVRtfSCULxcZpy0Cpg4WWenlUZ9+zbmTO+S7v9wD6XreYLjXRFtDjtV/4F0HT5p1GyZfA0Ux/myxHb18CQ==",
- "license": "MIT-0",
- "engines": {
- "node": ">=6.0.0"
- }
- },
- "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/obug": {
- "version": "2.1.4",
- "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.4.tgz",
- "integrity": "sha512-4a+OsYv9UktOJKE+l1A4OufDgdRF9PifWj+tJnHURo/P+WOxpG4GzUFL9qCalmWauao6ogiG+QvnCovwPoyAWA==",
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/obug/-/obug-2.2.1.tgz",
+ "integrity": "sha512-XrsrhT5sybtKI6wakr2SPOlGZWWYbUXZ7a0jT8/QOeAPau+1X/bSegNe5YR75oJmEZQbKningirmGOEJCIk61Q==",
+ "dev": true,
"funding": [
"https://github.com/sponsors/sxzz",
"https://opencollective.com/debug"
@@ -6160,178 +1309,19 @@
"node": ">=12.20.0"
}
},
- "node_modules/ofetch": {
- "version": "1.5.1",
- "resolved": "https://registry.npmjs.org/ofetch/-/ofetch-1.5.1.tgz",
- "integrity": "sha512-2W4oUZlVaqAPAil6FUg/difl6YhqhUR7x2eZY4bQCko22UXg3hptq9KLQdqFClV+Wu85UX7hNtdGTngi/1BxcA==",
- "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/on-finished": {
- "version": "2.4.1",
- "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
- "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==",
- "license": "MIT",
- "dependencies": {
- "ee-first": "1.1.1"
- },
- "engines": {
- "node": ">= 0.8"
- }
- },
- "node_modules/oniguruma-parser": {
- "version": "0.12.2",
- "resolved": "https://registry.npmjs.org/oniguruma-parser/-/oniguruma-parser-0.12.2.tgz",
- "integrity": "sha512-6HVa5oIrgMC6aA6WF6XyyqbhRPJrKR02L20+2+zpDtO5QAzGHAUGw5TKQvwi5vctNnRHkJYmjAhRVQF2EKdTQw==",
- "license": "MIT"
- },
- "node_modules/oniguruma-to-es": {
- "version": "4.3.6",
- "resolved": "https://registry.npmjs.org/oniguruma-to-es/-/oniguruma-to-es-4.3.6.tgz",
- "integrity": "sha512-csuQ9x3Yr0cEIs/Zgx/OEt9iBw9vqIunAPQkx19R/fiMq2oGVTgcMqO/V3Ybqefr1TBvosI6jU539ksaBULJyA==",
- "license": "MIT",
- "dependencies": {
- "oniguruma-parser": "^0.12.2",
- "regex": "^6.1.0",
- "regex-recursion": "^6.0.2"
- }
- },
- "node_modules/p-limit": {
- "version": "7.3.1",
- "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-7.3.1.tgz",
- "integrity": "sha512-0trZaiG7Y7kN/Egy9a8j47t9osC0Tch4PaIWd9yGF6bvmlk7muExRvGNYb8sXBwEKMoNKsbNN9P8EefuQekE4Q==",
- "license": "MIT",
- "dependencies": {
- "yocto-queue": "^1.2.1"
- },
- "engines": {
- "node": ">=20"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/p-queue": {
- "version": "9.3.3",
- "resolved": "https://registry.npmjs.org/p-queue/-/p-queue-9.3.3.tgz",
- "integrity": "sha512-NXAOdnEe5FsZJfT4oK84lE1Y5cFFdWlRuOo5tww8DyNMxyRXwn39fIkUtNLKppcPC+UYU/bXujNCUGDv01y7CA==",
- "license": "MIT",
- "dependencies": {
- "eventemitter3": "^5.0.4",
- "p-timeout": "^7.0.0"
- },
- "engines": {
- "node": ">=20"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/p-timeout": {
- "version": "7.0.1",
- "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-7.0.1.tgz",
- "integrity": "sha512-AxTM2wDGORHGEkPCt8yqxOTMgpfbEHqF51f/5fJCmwFC3C/zNcGT63SymH2ttOAaiIws2zVg4+izQCjrakcwHg==",
- "license": "MIT",
- "engines": {
- "node": ">=20"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/package-manager-detector": {
- "version": "1.8.0",
- "resolved": "https://registry.npmjs.org/package-manager-detector/-/package-manager-detector-1.8.0.tgz",
- "integrity": "sha512-yQA4H19AmPEoMUeavPMDIe1higySl/gH/yaQrkT/s07Qp+7pp2hYz30N3z2l5BkjVkF9Ow6o0wjJamm2y7Sn0A==",
- "license": "MIT"
- },
- "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-browserify": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz",
- "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==",
- "license": "MIT"
- },
- "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"
+ "dev": true,
+ "license": "ISC",
+ "peer": true
},
"node_modules/picomatch": {
- "version": "4.0.5",
- "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz",
- "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==",
+ "version": "4.0.7",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz",
+ "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==",
+ "dev": true,
"license": "MIT",
"engines": {
"node": ">=12"
@@ -6341,9 +1331,10 @@
}
},
"node_modules/postcss": {
- "version": "8.5.26",
- "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.26.tgz",
- "integrity": "sha512-u82N74LFzG8ca+dD8puPnplTXoGH4fTPpVGuIbt36G3qvNlkvfD0lEAZSxaly3KX8TS/L1A1gsCEmvKmBcVbkQ==",
+ "version": "8.5.29",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.29.tgz",
+ "integrity": "sha512-49cGhUbXj8Qenv0iTMxA1cFBzxXoctpC9Ujd77t1WcbJIr6nF/eI7g/8MgxrYldFRuAXvja7xQRwavoW7kgrxQ==",
+ "dev": true,
"funding": [
{
"type": "opencollective",
@@ -6359,443 +1350,25 @@
}
],
"license": "MIT",
+ "peer": true,
"dependencies": {
- "nanoid": "^3.3.17",
+ "nanoid": "^3.3.19",
"picocolors": "^1.1.1",
- "source-map-js": "^1.2.1"
+ "source-map-js": "^1.2.2"
},
"engines": {
"node": "^10 || ^12 || >=14"
}
},
- "node_modules/prettier": {
- "version": "3.9.6",
- "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.9.6.tgz",
- "integrity": "sha512-OpN0zzVdiaiAhxpuuj5efpIS4sY9j7bY6uR5mnj5yPzGkdkjNKSJeUThPb60Jw29QuAZgA4o+/iB49kFiaBX6g==",
- "license": "MIT",
- "bin": {
- "prettier": "bin/prettier.cjs"
- },
- "engines": {
- "node": ">=14"
- },
- "funding": {
- "url": "https://github.com/prettier/prettier?sponsor=1"
- }
- },
- "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/process-ancestry": {
- "version": "0.1.0",
- "resolved": "https://registry.npmjs.org/process-ancestry/-/process-ancestry-0.1.0.tgz",
- "integrity": "sha512-tGqJW/UnclpYASFcM6Xh8D8l/BMtaQ9+CSG0vlJSJTcdMM4lDRv4c6H0Pdcsfted+bVczdYSfk2fdukg2gQkZg==",
- "license": "MIT",
- "engines": {
- "node": ">=18.0.0"
- }
- },
- "node_modules/property-information": {
- "version": "7.2.0",
- "resolved": "https://registry.npmjs.org/property-information/-/property-information-7.2.0.tgz",
- "integrity": "sha512-IAtzIB6sUiWaJYrX9smp3V46pBGbBeLFRGdh25kg1334VcBlD8HzhPeNIWQH9zhGmo2itIe25EHt9dQP7G5hmg==",
- "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/range-parser": {
- "version": "1.3.0",
- "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.3.0.tgz",
- "integrity": "sha512-hek2mFQpPuI4E1BBKrSto+BU3e3x4xuarsbiwr3+lf7p44juvFMV0XFWQAP3xUyqXA4RrXLIoaSUGbSt056ZMw==",
- "license": "MIT",
- "engines": {
- "node": ">= 0.6"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/express"
- }
- },
- "node_modules/react": {
- "version": "19.2.8",
- "resolved": "https://registry.npmjs.org/react/-/react-19.2.8.tgz",
- "integrity": "sha512-PWaYA1L/q9u2u7xYQi+Y3L3Yfnie7XyLeaJICV1MGD6LprsBxcAqGjYyr0eY3p+QdsA+x/Irkt4Qif8D63+Sbw==",
- "license": "MIT",
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/react-dom": {
- "version": "19.2.8",
- "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.8.tgz",
- "integrity": "sha512-rVprimfGBG3DR+Tq0IQG2DT5PxKth1WIGDmj5yPmlzr4YBe7uyE+Du4oVqTDXZSHGGGXRtTJEGSSePyQCMBglQ==",
- "license": "MIT",
- "dependencies": {
- "scheduler": "^0.27.0"
- },
- "peerDependencies": {
- "react": "^19.2.8"
- }
- },
- "node_modules/react-refresh": {
- "version": "0.18.0",
- "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.18.0.tgz",
- "integrity": "sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw==",
- "license": "MIT",
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/readdirp": {
- "version": "4.1.2",
- "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz",
- "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==",
- "license": "MIT",
- "engines": {
- "node": ">= 14.18.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-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-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-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.3",
- "resolved": "https://registry.npmjs.org/remark-smartypants/-/remark-smartypants-3.0.3.tgz",
- "integrity": "sha512-gCaK+ndZ0hYezlqFegHFCVh2CQemsi0Npdh1qVM9bxlUFknjkbP6VmojWhddOCrbK0PbbacmYLWfTULRiT1eWA==",
- "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/request-light": {
- "version": "0.7.0",
- "resolved": "https://registry.npmjs.org/request-light/-/request-light-0.7.0.tgz",
- "integrity": "sha512-lMbBMrDoxgsyO+yB3sDcrDuX85yYt7sS8BfQd11jtbW/z5ZWgLZRcEGLsLoYw7I0WSUGQBs8CC8ScIxkTX1+6Q==",
- "license": "MIT"
- },
- "node_modules/require-from-string": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz",
- "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==",
- "license": "MIT",
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/resolve-pkg-maps": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz",
- "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==",
- "license": "MIT",
- "funding": {
- "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1"
- }
- },
- "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/rolldown": {
- "version": "1.2.3",
- "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.2.3.tgz",
- "integrity": "sha512-rn9wpmxplLf7NLNyCk9FyWh3FM43DbY8jOzCdEPzH7uflhTftRbCEpqi6Ly2osgoU8OwObtmavMbWLaWy4LX7A==",
+ "version": "1.2.13",
+ "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.2.13.tgz",
+ "integrity": "sha512-/5apOkwWxtACcQtZ5WlrAsKS4JwXVThQPJZ3RqIruRY9UG9M8e0VlXN068xum/JRKA4aIUoJ13ghe/kGCvidiQ==",
+ "dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
- "@oxc-project/types": "=0.143.0",
+ "@oxc-project/types": "=0.153.0",
"@rolldown/pluginutils": "^1.0.0"
},
"bin": {
@@ -6805,376 +1378,56 @@
"node": "^20.19.0 || >=22.12.0"
},
"optionalDependencies": {
- "@rolldown/binding-android-arm64": "1.2.3",
- "@rolldown/binding-darwin-arm64": "1.2.3",
- "@rolldown/binding-darwin-x64": "1.2.3",
- "@rolldown/binding-freebsd-x64": "1.2.3",
- "@rolldown/binding-linux-arm-gnueabihf": "1.2.3",
- "@rolldown/binding-linux-arm64-gnu": "1.2.3",
- "@rolldown/binding-linux-arm64-musl": "1.2.3",
- "@rolldown/binding-linux-ppc64-gnu": "1.2.3",
- "@rolldown/binding-linux-s390x-gnu": "1.2.3",
- "@rolldown/binding-linux-x64-gnu": "1.2.3",
- "@rolldown/binding-linux-x64-musl": "1.2.3",
- "@rolldown/binding-openharmony-arm64": "1.2.3",
- "@rolldown/binding-win32-arm64-msvc": "1.2.3",
- "@rolldown/binding-win32-x64-msvc": "1.2.3"
- }
- },
- "node_modules/rolldown/node_modules/@rolldown/pluginutils": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz",
- "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==",
- "license": "MIT"
- },
- "node_modules/satteri": {
- "version": "0.9.5",
- "resolved": "https://registry.npmjs.org/satteri/-/satteri-0.9.5.tgz",
- "integrity": "sha512-ZuWVl+vnM64y+/TtX8Kosv2c00W+hLQiiwnEL6H0UKVVrxFqMw4D2CJHHQaouVd89OAhtBBfjWLqhKi3TVUV4w==",
- "license": "MIT",
- "dependencies": {
- "@types/estree-jsx": "^1.0.5",
- "@types/hast": "^3.0.4",
- "@types/mdast": "^4.0.4",
- "@types/unist": "^3.0.3"
- },
- "optionalDependencies": {
- "@bruits/satteri-darwin-arm64": "0.9.5",
- "@bruits/satteri-darwin-x64": "0.9.5",
- "@bruits/satteri-linux-arm64-gnu": "0.9.5",
- "@bruits/satteri-linux-arm64-musl": "0.9.5",
- "@bruits/satteri-linux-x64-gnu": "0.9.5",
- "@bruits/satteri-linux-x64-musl": "0.9.5",
- "@bruits/satteri-wasm32-wasi": "0.9.5",
- "@bruits/satteri-win32-arm64-msvc": "0.9.5",
- "@bruits/satteri-win32-x64-msvc": "0.9.5"
- }
- },
- "node_modules/sax": {
- "version": "1.6.1",
- "resolved": "https://registry.npmjs.org/sax/-/sax-1.6.1.tgz",
- "integrity": "sha512-42tBVwLWnaQvW5zc4HbZrTuWccECCZfBi92FDuwtqxasH+JbPB3/FOKb1m222K42R4WxuxzzMsTswfzgtSu64Q==",
- "license": "BlueOak-1.0.0",
- "engines": {
- "node": ">=11.0.0"
- }
- },
- "node_modules/scheduler": {
- "version": "0.27.0",
- "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz",
- "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==",
- "license": "MIT"
- },
- "node_modules/semver": {
- "version": "6.3.1",
- "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
- "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
- "license": "ISC",
- "bin": {
- "semver": "bin/semver.js"
- }
- },
- "node_modules/send": {
- "version": "1.2.1",
- "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz",
- "integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==",
- "license": "MIT",
- "dependencies": {
- "debug": "^4.4.3",
- "encodeurl": "^2.0.0",
- "escape-html": "^1.0.3",
- "etag": "^1.8.1",
- "fresh": "^2.0.0",
- "http-errors": "^2.0.1",
- "mime-types": "^3.0.2",
- "ms": "^2.1.3",
- "on-finished": "^2.4.1",
- "range-parser": "^1.2.1",
- "statuses": "^2.0.2"
- },
- "engines": {
- "node": ">= 18"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/express"
- }
- },
- "node_modules/server-destroy": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/server-destroy/-/server-destroy-1.0.1.tgz",
- "integrity": "sha512-rb+9B5YBIEzYcD6x2VKidaa+cqYBJQKnU4oe4E3ANwRRN56yk/ua1YCJT1n21NTS8w6CcOclAKNP3PhdCXKYtQ==",
- "license": "ISC"
- },
- "node_modules/setprototypeof": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
- "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==",
- "license": "ISC"
- },
- "node_modules/sharp": {
- "version": "0.35.3",
- "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.35.3.tgz",
- "integrity": "sha512-ej0zVHuZGHCiABXcNxeYhpRnPNPAcvbG8RMdBAhDAxLKkCRVSpK3Iyu7qbqw3JMzoj0REeM6f3tJLtVwl0023Q==",
- "license": "Apache-2.0",
- "optional": true,
- "dependencies": {
- "@img/colour": "^1.1.0",
- "detect-libc": "^2.1.2",
- "semver": "^7.8.5"
- },
- "engines": {
- "node": ">=20.9.0"
- },
- "funding": {
- "url": "https://opencollective.com/libvips"
- },
- "optionalDependencies": {
- "@img/sharp-darwin-arm64": "0.35.3",
- "@img/sharp-darwin-x64": "0.35.3",
- "@img/sharp-freebsd-wasm32": "0.35.3",
- "@img/sharp-libvips-darwin-arm64": "1.3.2",
- "@img/sharp-libvips-darwin-x64": "1.3.2",
- "@img/sharp-libvips-linux-arm": "1.3.2",
- "@img/sharp-libvips-linux-arm64": "1.3.2",
- "@img/sharp-libvips-linux-ppc64": "1.3.2",
- "@img/sharp-libvips-linux-riscv64": "1.3.2",
- "@img/sharp-libvips-linux-s390x": "1.3.2",
- "@img/sharp-libvips-linux-x64": "1.3.2",
- "@img/sharp-libvips-linuxmusl-arm64": "1.3.2",
- "@img/sharp-libvips-linuxmusl-x64": "1.3.2",
- "@img/sharp-linux-arm": "0.35.3",
- "@img/sharp-linux-arm64": "0.35.3",
- "@img/sharp-linux-ppc64": "0.35.3",
- "@img/sharp-linux-riscv64": "0.35.3",
- "@img/sharp-linux-s390x": "0.35.3",
- "@img/sharp-linux-x64": "0.35.3",
- "@img/sharp-linuxmusl-arm64": "0.35.3",
- "@img/sharp-linuxmusl-x64": "0.35.3",
- "@img/sharp-webcontainers-wasm32": "0.35.3",
- "@img/sharp-win32-arm64": "0.35.3",
- "@img/sharp-win32-ia32": "0.35.3",
- "@img/sharp-win32-x64": "0.35.3"
- },
- "peerDependenciesMeta": {
- "@types/node": {
- "optional": true
- }
- }
- },
- "node_modules/sharp/node_modules/semver": {
- "version": "7.8.5",
- "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz",
- "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==",
- "license": "ISC",
- "optional": true,
- "bin": {
- "semver": "bin/semver.js"
- },
- "engines": {
- "node": ">=10"
- }
- },
- "node_modules/shiki": {
- "version": "4.4.2",
- "resolved": "https://registry.npmjs.org/shiki/-/shiki-4.4.2.tgz",
- "integrity": "sha512-P8F/dFhRevaw2uSdeIYlq/5SXZNY85DPtmXQ947gD1Zj2JqO5AkNvVVBar0Me9JkFx3uzVud/qOtP5ek9NEQGA==",
- "license": "MIT",
- "dependencies": {
- "@shikijs/core": "4.4.2",
- "@shikijs/engine-javascript": "4.4.2",
- "@shikijs/engine-oniguruma": "4.4.2",
- "@shikijs/langs": "4.4.2",
- "@shikijs/themes": "4.4.2",
- "@shikijs/types": "4.4.2",
- "@shikijs/vscode-textmate": "^10.0.2",
- "@types/hast": "^3.0.5"
- },
- "engines": {
- "node": ">=20"
- }
- },
- "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/smol-toml": {
- "version": "1.7.1",
- "resolved": "https://registry.npmjs.org/smol-toml/-/smol-toml-1.7.1.tgz",
- "integrity": "sha512-PPlsspAZ4jbMBu5DMFhfUGDQLu/vrL4SyBROVS37x8ynnVmFIs1VPBz1Co8Xks3TvpIaZXmU85y4DrQ+UyVFoQ==",
- "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"
+ "@rolldown/binding-android-arm-eabi": "1.2.13",
+ "@rolldown/binding-android-arm64": "1.2.13",
+ "@rolldown/binding-darwin-arm64": "1.2.13",
+ "@rolldown/binding-darwin-x64": "1.2.13",
+ "@rolldown/binding-freebsd-x64": "1.2.13",
+ "@rolldown/binding-linux-arm-gnueabihf": "1.2.13",
+ "@rolldown/binding-linux-arm64-gnu": "1.2.13",
+ "@rolldown/binding-linux-arm64-musl": "1.2.13",
+ "@rolldown/binding-linux-ppc64-gnu": "1.2.13",
+ "@rolldown/binding-linux-s390x-gnu": "1.2.13",
+ "@rolldown/binding-linux-x64-gnu": "1.2.13",
+ "@rolldown/binding-linux-x64-musl": "1.2.13",
+ "@rolldown/binding-openharmony-arm64": "1.2.13",
+ "@rolldown/binding-win32-arm64-msvc": "1.2.13",
+ "@rolldown/binding-win32-x64-msvc": "1.2.13"
}
},
"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==",
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.2.tgz",
+ "integrity": "sha512-KGj/8Y43x35aZVDtt+J4mK1hoLGHULMYfSkODJNQjNDC3oW1PqPoxMwo0pLUsWM/UEGzON/NxeHywEfNXNP3Vw==",
+ "dev": true,
"license": "BSD-3-Clause",
+ "peer": true,
"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/statuses": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz",
- "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==",
- "license": "MIT",
- "engines": {
- "node": ">= 0.8"
- }
- },
- "node_modules/string-width": {
- "version": "8.2.2",
- "resolved": "https://registry.npmjs.org/string-width/-/string-width-8.2.2.tgz",
- "integrity": "sha512-GaPUh5gfdrYzqeVNZvUfT23vYYxXzKYidUcnMtJg/3rxRV63EFZy3k6xfKlmfeJD0176lnUV/Usr3XcwSvFzpg==",
- "license": "MIT",
- "dependencies": {
- "get-east-asian-width": "^1.5.0",
- "strip-ansi": "^7.1.2"
- },
- "engines": {
- "node": ">=20"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/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.2.0",
- "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz",
- "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==",
- "license": "MIT",
- "dependencies": {
- "ansi-regex": "^6.2.2"
- },
- "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/svgo": {
- "version": "4.0.2",
- "resolved": "https://registry.npmjs.org/svgo/-/svgo-4.0.2.tgz",
- "integrity": "sha512-ekx94z1rRc5LDi6oSUaeRnYhd0UOJxdtQCL2rF8xpWxD3TPAsISWOrxezqGovqS38GRZOdpDfvQe3ts6F7nsng==",
- "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.5.0"
- },
- "bin": {
- "svgo": "bin/svgo.js"
- },
- "engines": {
- "node": ">=16"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/svgo"
- }
- },
- "node_modules/tailwindcss": {
- "version": "4.3.3",
- "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.3.3.tgz",
- "integrity": "sha512-gOhV3P7ufE62QDGg1zVaTgCR+EtPv92k2nIhVcVKcLmxT1sUBsQGhnZj175j+MqRt4zLF7ic+sCYjfhxMxj7YQ==",
+ "node_modules/std-env": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/std-env/-/std-env-4.3.0.tgz",
+ "integrity": "sha512-OtU/EgQ1kIm5KwqQpBC6ZEMXrZRui11w8zgfTWp8cdO9B8OaPsbA8bTHO2P+HNo1VlUTGMVBwPhydu6poeXiag==",
+ "dev": true,
"license": "MIT"
},
- "node_modules/tapable": {
- "version": "2.3.3",
- "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.3.tgz",
- "integrity": "sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==",
+ "node_modules/tinybench": {
+ "version": "6.2.2",
+ "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-6.2.2.tgz",
+ "integrity": "sha512-nIZ2N2dFpjBsg2hgitKu7mA+FvsIJuGJOwysRngmVRRmWgR/UyPjwJj8c/fT/dhm8EkHnF5EOogRf+0Z2qrimA==",
+ "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/tinyclip": {
- "version": "0.1.15",
- "resolved": "https://registry.npmjs.org/tinyclip/-/tinyclip-0.1.15.tgz",
- "integrity": "sha512-uo33abH+Ays0xYaDysoBt494Hb3hsEczMpcC0MwFl773pazORx4fmvKhclhR1wonUbB6vvpRsvVMwnhfqeMc+A==",
- "license": "MIT",
- "engines": {
- "node": "^16.14.0 || >= 17.3.0"
+ "node": ">=20.0.0"
}
},
"node_modules/tinyexec": {
- "version": "1.3.0",
- "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.3.0.tgz",
- "integrity": "sha512-QKAl9m8gWWGHV8jZcPeym6j+XULi6tOf1mT83WYJ4Lk2ytW/uwAWkrP0uFsdoYMdueVJ0qs26wZ+23xeB4ibNQ==",
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.3.1.tgz",
+ "integrity": "sha512-GCvB3aoys96IuDFBMcTB46JOR6mdMtAToqwiW8JlWhsoh1mhHi/xn9ss/Dg7N555GiJyEt2qzoG/NHCwM6h1EA==",
+ "dev": true,
"license": "MIT",
"engines": {
"node": ">=18"
@@ -7184,6 +1437,7 @@
"version": "0.2.17",
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz",
"integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==",
+ "dev": true,
"license": "MIT",
"dependencies": {
"fdir": "^6.5.0",
@@ -7196,51 +1450,29 @@
"url": "https://github.com/sponsors/SuperchupuDev"
}
},
- "node_modules/toidentifier": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
- "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==",
+ "node_modules/tsx": {
+ "version": "4.23.15",
+ "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.23.15.tgz",
+ "integrity": "sha512-Yiex1Ovn8z2xPpOWckIiysV1SSyRMY9BkLF++q0yKiDxCqRhosKfMg3janKkiLBwZ5c/YryloKwGZcrEmtwxKw==",
+ "dev": true,
"license": "MIT",
+ "dependencies": {
+ "esbuild": "~0.28.0"
+ },
+ "bin": {
+ "tsx": "dist/cli.mjs"
+ },
"engines": {
- "node": ">=0.6"
+ "node": ">=18.0.0"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.3"
}
},
- "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/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"
- },
- "node_modules/typesafe-path": {
- "version": "0.2.2",
- "resolved": "https://registry.npmjs.org/typesafe-path/-/typesafe-path-0.2.2.tgz",
- "integrity": "sha512-OJabfkAg1WLZSqJAJ0Z6Sdt3utnbzr/jh+NAHoyWHJe8CMSy79Gm085094M9nvTPy22KzTVn5Zq5mbapCI/hPA==",
- "license": "MIT"
- },
"node_modules/typescript": {
- "version": "6.0.3",
- "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz",
- "integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==",
+ "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",
@@ -7250,432 +1482,25 @@
"node": ">=14.17"
}
},
- "node_modules/typescript-auto-import-cache": {
- "version": "0.3.6",
- "resolved": "https://registry.npmjs.org/typescript-auto-import-cache/-/typescript-auto-import-cache-0.3.6.tgz",
- "integrity": "sha512-RpuHXrknHdVdK7wv/8ug3Fr0WNsNi5l5aB8MYYuXhq2UH5lnEB1htJ1smhtD5VeCsGr2p8mUDtd83LCQDFVgjQ==",
- "license": "MIT",
- "dependencies": {
- "semver": "^7.3.8"
- }
- },
- "node_modules/typescript-auto-import-cache/node_modules/semver": {
- "version": "7.8.5",
- "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz",
- "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==",
- "license": "ISC",
- "bin": {
- "semver": "bin/semver.js"
- },
- "engines": {
- "node": ">=10"
- }
- },
- "node_modules/ufo": {
- "version": "1.6.4",
- "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.4.tgz",
- "integrity": "sha512-JFNbkD1Svwe0KvGi8GOeLcP4kAWQ609twvCdcHxq1oSL8svv39ZuSvajcD8B+5D0eL4+s1Is2D/O6KN3qcTeRA==",
- "license": "MIT"
- },
- "node_modules/ultrahtml": {
- "version": "1.7.0",
- "resolved": "https://registry.npmjs.org/ultrahtml/-/ultrahtml-1.7.0.tgz",
- "integrity": "sha512-2xRd0VHoAQE4M+vF/DvFFB7pUV0ZxTW1TLi7lHQWnF/Sb5TPeEUV/l+hxcNnGO00ZXGnR0voCMmYRKQf+rvJ2g==",
- "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": "8.3.0",
- "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-8.3.0.tgz",
- "integrity": "sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ==",
+ "version": "6.21.0",
+ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
+ "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
+ "dev": true,
"license": "MIT"
},
- "node_modules/unified": {
- "version": "11.0.5",
- "resolved": "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz",
- "integrity": "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==",
- "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.5",
- "resolved": "https://registry.npmjs.org/unstorage/-/unstorage-1.17.5.tgz",
- "integrity": "sha512-0i3iqvRfx29hkNntHyQvJTpf5W9dQ9ZadSoRU8+xVlhVtT7jAX57fazYO9EHvcRCfBCyi5YRya7XCDOsbTgkPg==",
- "license": "MIT",
- "dependencies": {
- "anymatch": "^3.1.3",
- "chokidar": "^5.0.0",
- "destr": "^2.0.5",
- "h3": "^1.15.10",
- "lru-cache": "^11.2.7",
- "node-fetch-native": "^1.6.7",
- "ofetch": "^1.5.1",
- "ufo": "^1.6.3"
- },
- "peerDependencies": {
- "@azure/app-configuration": "^1.8.0",
- "@azure/cosmos": "^4.2.0",
- "@azure/data-tables": "^13.3.0",
- "@azure/identity": "^4.6.0",
- "@azure/keyvault-secrets": "^4.9.0",
- "@azure/storage-blob": "^12.26.0",
- "@capacitor/preferences": "^6 || ^7 || ^8",
- "@deno/kv": ">=0.9.0",
- "@netlify/blobs": "^6.5.0 || ^7.0.0 || ^8.1.0 || ^9.0.0 || ^10.0.0",
- "@planetscale/database": "^1.19.0",
- "@upstash/redis": "^1.34.3",
- "@vercel/blob": ">=0.27.1",
- "@vercel/functions": "^2.2.12 || ^3.0.0",
- "@vercel/kv": "^1 || ^2 || ^3",
- "aws4fetch": "^1.0.20",
- "db0": ">=0.2.1",
- "idb-keyval": "^6.2.1",
- "ioredis": "^5.4.2",
- "uploadthing": "^7.4.4"
- },
- "peerDependenciesMeta": {
- "@azure/app-configuration": {
- "optional": true
- },
- "@azure/cosmos": {
- "optional": true
- },
- "@azure/data-tables": {
- "optional": true
- },
- "@azure/identity": {
- "optional": true
- },
- "@azure/keyvault-secrets": {
- "optional": true
- },
- "@azure/storage-blob": {
- "optional": true
- },
- "@capacitor/preferences": {
- "optional": true
- },
- "@deno/kv": {
- "optional": true
- },
- "@netlify/blobs": {
- "optional": true
- },
- "@planetscale/database": {
- "optional": true
- },
- "@upstash/redis": {
- "optional": true
- },
- "@vercel/blob": {
- "optional": true
- },
- "@vercel/functions": {
- "optional": true
- },
- "@vercel/kv": {
- "optional": true
- },
- "aws4fetch": {
- "optional": true
- },
- "db0": {
- "optional": true
- },
- "idb-keyval": {
- "optional": true
- },
- "ioredis": {
- "optional": true
- },
- "uploadthing": {
- "optional": true
- }
- }
- },
- "node_modules/unstorage/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/unstorage/node_modules/lru-cache": {
- "version": "11.5.2",
- "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.2.tgz",
- "integrity": "sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g==",
- "license": "BlueOak-1.0.0",
- "engines": {
- "node": "20 || >=22"
- }
- },
- "node_modules/unstorage/node_modules/readdirp": {
- "version": "5.1.1",
- "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-5.1.1.tgz",
- "integrity": "sha512-Kko+Y5XQ6fM+Ce3dq3m9YGxnacYZYl9cA1wZjaF3Vbry2L3i1qVg8+CAgNPsXRArPMUMCaOR7oa9Nqntc43JKA==",
- "license": "MIT",
- "engines": {
- "node": ">= 20.19.0"
- },
- "funding": {
- "type": "individual",
- "url": "https://paulmillr.com/funding/"
- }
- },
- "node_modules/update-browserslist-db": {
- "version": "1.2.3",
- "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz",
- "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==",
- "funding": [
- {
- "type": "opencollective",
- "url": "https://opencollective.com/browserslist"
- },
- {
- "type": "tidelift",
- "url": "https://tidelift.com/funding/github/npm/browserslist"
- },
- {
- "type": "github",
- "url": "https://github.com/sponsors/ai"
- }
- ],
- "license": "MIT",
- "dependencies": {
- "escalade": "^3.2.0",
- "picocolors": "^1.1.1"
- },
- "bin": {
- "update-browserslist-db": "cli.js"
- },
- "peerDependencies": {
- "browserslist": ">= 4.21.0"
- }
- },
- "node_modules/vfile": {
- "version": "6.0.3",
- "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz",
- "integrity": "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==",
- "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": "8.2.1",
- "resolved": "https://registry.npmjs.org/vite/-/vite-8.2.1.tgz",
- "integrity": "sha512-EU/eS7BH3XROHh2YnBefjM6DBKA6ZeMZEYQbj7NLWg5wHYlhB8B/Mayd5XsgWq+NFYccDOTemRpdETWR6Ka/lw==",
+ "version": "8.3.4",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-8.3.4.tgz",
+ "integrity": "sha512-IftLE8phJhdNRIGNEV4JCCvfNScoZQVfcXaN8TQropWGJtOHcu+iAB9pHcuGvmEb/kddw0LBe/jS8ztdC6xdiQ==",
+ "dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"lightningcss": "^1.33.0",
- "picomatch": "^4.0.5",
- "postcss": "^8.5.25",
- "rolldown": "~1.2.1",
+ "picomatch": "^4.0.7",
+ "postcss": "^8.5.29",
+ "rolldown": "~1.2.12",
"tinyglobby": "^0.2.17"
},
"bin": {
@@ -7692,7 +1517,7 @@
},
"peerDependencies": {
"@types/node": "^20.19.0 || >=22.12.0",
- "@vitejs/devtools": "^0.4.0",
+ "@vitejs/devtools": "^0.7.1",
"esbuild": "^0.27.0 || ^0.28.0",
"jiti": ">=1.21.0",
"less": "^4.0.0",
@@ -7743,617 +1568,106 @@
}
}
},
- "node_modules/vite/node_modules/lightningcss": {
- "version": "1.33.0",
- "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.33.0.tgz",
- "integrity": "sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA==",
- "license": "MPL-2.0",
- "dependencies": {
- "detect-libc": "^2.0.3"
- },
- "engines": {
- "node": ">= 12.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/parcel"
- },
- "optionalDependencies": {
- "lightningcss-android-arm64": "1.33.0",
- "lightningcss-darwin-arm64": "1.33.0",
- "lightningcss-darwin-x64": "1.33.0",
- "lightningcss-freebsd-x64": "1.33.0",
- "lightningcss-linux-arm-gnueabihf": "1.33.0",
- "lightningcss-linux-arm64-gnu": "1.33.0",
- "lightningcss-linux-arm64-musl": "1.33.0",
- "lightningcss-linux-x64-gnu": "1.33.0",
- "lightningcss-linux-x64-musl": "1.33.0",
- "lightningcss-win32-arm64-msvc": "1.33.0",
- "lightningcss-win32-x64-msvc": "1.33.0"
- }
- },
- "node_modules/vite/node_modules/lightningcss-android-arm64": {
- "version": "1.33.0",
- "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.33.0.tgz",
- "integrity": "sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg==",
- "cpu": [
- "arm64"
- ],
- "license": "MPL-2.0",
- "optional": true,
- "os": [
- "android"
- ],
- "engines": {
- "node": ">= 12.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/parcel"
- }
- },
- "node_modules/vite/node_modules/lightningcss-darwin-arm64": {
- "version": "1.33.0",
- "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.33.0.tgz",
- "integrity": "sha512-Sciaz8eenNTKn9b3t7+xr0ipTp9YxKQY4npwQ3mrRuL0BAVHBLyZxofhaKBAVtzmtRZ/zTyo0/to4B1uWG/Djg==",
- "cpu": [
- "arm64"
- ],
- "license": "MPL-2.0",
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": ">= 12.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/parcel"
- }
- },
- "node_modules/vite/node_modules/lightningcss-darwin-x64": {
- "version": "1.33.0",
- "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.33.0.tgz",
- "integrity": "sha512-Z5UPAxzrjlWNNyGy6i65cJzzvgJ5D3T6wMvs+gWpY9d7qRhANrxqAp6LhxIgZhWEw18RfJTGcRxjuLIBr+m8XQ==",
- "cpu": [
- "x64"
- ],
- "license": "MPL-2.0",
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": ">= 12.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/parcel"
- }
- },
- "node_modules/vite/node_modules/lightningcss-freebsd-x64": {
- "version": "1.33.0",
- "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.33.0.tgz",
- "integrity": "sha512-QQM/Ti/hQajJwCY+RiWuCZ9sdtI/XQk7nDK5vC8kkdwixezOlDgvDx7+RT+QjK6FcFT4MpsuoBnHIo/O3StRRg==",
- "cpu": [
- "x64"
- ],
- "license": "MPL-2.0",
- "optional": true,
- "os": [
- "freebsd"
- ],
- "engines": {
- "node": ">= 12.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/parcel"
- }
- },
- "node_modules/vite/node_modules/lightningcss-linux-arm-gnueabihf": {
- "version": "1.33.0",
- "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.33.0.tgz",
- "integrity": "sha512-N7FVBe6iS24MlM6R/4RBTxGhQheZGs7tiQ9U32UtF75NzP5Q7xWPRqLBCKxlRQRk3rY1jCIPLzx7WzOhuUIRLQ==",
- "cpu": [
- "arm"
- ],
- "license": "MPL-2.0",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">= 12.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/parcel"
- }
- },
- "node_modules/vite/node_modules/lightningcss-linux-arm64-gnu": {
- "version": "1.33.0",
- "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.33.0.tgz",
- "integrity": "sha512-j2v/itmy4HlNxlc6voKXYgBqNi0Ng2LShg4z7GufpEgs05P+2suBVyi9I6YHq5uoVFx9ETin3eCEhLVyXGQnKg==",
- "cpu": [
- "arm64"
- ],
- "license": "MPL-2.0",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">= 12.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/parcel"
- }
- },
- "node_modules/vite/node_modules/lightningcss-linux-arm64-musl": {
- "version": "1.33.0",
- "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.33.0.tgz",
- "integrity": "sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ==",
- "cpu": [
- "arm64"
- ],
- "license": "MPL-2.0",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">= 12.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/parcel"
- }
- },
- "node_modules/vite/node_modules/lightningcss-linux-x64-gnu": {
- "version": "1.33.0",
- "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.33.0.tgz",
- "integrity": "sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg==",
- "cpu": [
- "x64"
- ],
- "license": "MPL-2.0",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">= 12.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/parcel"
- }
- },
- "node_modules/vite/node_modules/lightningcss-linux-x64-musl": {
- "version": "1.33.0",
- "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.33.0.tgz",
- "integrity": "sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw==",
- "cpu": [
- "x64"
- ],
- "license": "MPL-2.0",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">= 12.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/parcel"
- }
- },
- "node_modules/vite/node_modules/lightningcss-win32-arm64-msvc": {
- "version": "1.33.0",
- "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.33.0.tgz",
- "integrity": "sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA==",
- "cpu": [
- "arm64"
- ],
- "license": "MPL-2.0",
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": ">= 12.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/parcel"
- }
- },
- "node_modules/vite/node_modules/lightningcss-win32-x64-msvc": {
- "version": "1.33.0",
- "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.33.0.tgz",
- "integrity": "sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA==",
- "cpu": [
- "x64"
- ],
- "license": "MPL-2.0",
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": ">= 12.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/parcel"
- }
- },
- "node_modules/vitefu": {
- "version": "1.1.3",
- "resolved": "https://registry.npmjs.org/vitefu/-/vitefu-1.1.3.tgz",
- "integrity": "sha512-ub4okH7Z5KLjb6hDyjqrGXqWtWvoYdU3IGm/NorpgHncKoLTCfRIbvlhBm7r0YstIaQRYlp4yEbFqDcKSzXSSg==",
- "license": "MIT",
- "workspaces": [
- "tests/deps/*",
- "tests/projects/*",
- "tests/projects/workspace/packages/*"
- ],
- "peerDependencies": {
- "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0"
- },
- "peerDependenciesMeta": {
- "vite": {
- "optional": true
- }
- }
- },
- "node_modules/volar-service-css": {
- "version": "0.0.71",
- "resolved": "https://registry.npmjs.org/volar-service-css/-/volar-service-css-0.0.71.tgz",
- "integrity": "sha512-wRRFt9BpjMKCazcgOh67MSjUjiWUCAh99DyYSDIOTuxaRjEtDC7PpB0k1Y1wbJIW/pVtMUSVbpPo3UGSm0Byxw==",
+ "node_modules/vitest": {
+ "version": "5.0.3",
+ "resolved": "https://registry.npmjs.org/vitest/-/vitest-5.0.3.tgz",
+ "integrity": "sha512-xMw97S3rjdtj5dkVat7jCsqWBpvchs3RlpQctUqwJD0KkERk40vz2fJ77lDwW/Vzh/pk18eItYAzkodhSes3jQ==",
+ "dev": true,
"license": "MIT",
"dependencies": {
- "vscode-css-languageservice": "^6.3.0",
- "vscode-languageserver-textdocument": "^1.0.11",
- "vscode-uri": "^3.0.8"
+ "@types/chai": "^5.2.2",
+ "@vitest/mocker": "5.0.3",
+ "chai": "^6.2.2",
+ "es-module-lexer": "^2.3.2",
+ "expect-type": "^1.4.0",
+ "magic-string": "^1.2.3",
+ "obug": "^2.1.4",
+ "picomatch": "^4.0.7",
+ "std-env": "^4.2.0",
+ "tinybench": "^6.1.4",
+ "tinyexec": "^1.3.0",
+ "tinyglobby": "^0.2.17",
+ "why-is-node-running": "3.2.1"
+ },
+ "bin": {
+ "vitest": "vitest.mjs"
+ },
+ "engines": {
+ "node": "^22.12.0 || ^24.0.0 || >=26.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
},
"peerDependencies": {
- "@volar/language-service": "~2.4.0"
+ "@edge-runtime/vm": "*",
+ "@opentelemetry/api": "^1.9.0",
+ "@types/node": "^22.0.0 || >=24.0.0",
+ "@vitest/browser-playwright": "5.0.3",
+ "@vitest/browser-preview": "5.0.3",
+ "@vitest/browser-webdriverio": "^5.0.0-beta.5 || >=5.0.0",
+ "@vitest/coverage-istanbul": "5.0.3",
+ "@vitest/coverage-v8": "5.0.3",
+ "@vitest/ui": "5.0.3",
+ "happy-dom": "*",
+ "jsdom": "*",
+ "vite": "^6.4.0 || ^7.0.0 || ^8.0.0"
},
"peerDependenciesMeta": {
- "@volar/language-service": {
- "optional": true
- }
- }
- },
- "node_modules/volar-service-emmet": {
- "version": "0.0.71",
- "resolved": "https://registry.npmjs.org/volar-service-emmet/-/volar-service-emmet-0.0.71.tgz",
- "integrity": "sha512-zqjzt6bN95e3CUstBm0PBFAJnrfz0ZAARka87fart46/gNCLLuP3Vujy8V/J8HEziTFLnfkgIASLFYPUhonJcA==",
- "license": "MIT",
- "dependencies": {
- "@emmetio/css-parser": "^0.4.1",
- "@emmetio/html-matcher": "^1.3.0",
- "@vscode/emmet-helper": "^2.9.3",
- "vscode-uri": "^3.0.8"
- },
- "peerDependencies": {
- "@volar/language-service": "~2.4.0"
- },
- "peerDependenciesMeta": {
- "@volar/language-service": {
- "optional": true
- }
- }
- },
- "node_modules/volar-service-html": {
- "version": "0.0.71",
- "resolved": "https://registry.npmjs.org/volar-service-html/-/volar-service-html-0.0.71.tgz",
- "integrity": "sha512-e8tHPhgQ7ooLfudAEIku+kgd9pWkq3SSz8RbnQDI1+Eb8wbenkLGHqoirLqz5ORLV6wIMr2Iv08RWBG5eOcgpw==",
- "license": "MIT",
- "dependencies": {
- "vscode-html-languageservice": "^5.3.0",
- "vscode-languageserver-textdocument": "^1.0.11",
- "vscode-uri": "^3.0.8"
- },
- "peerDependencies": {
- "@volar/language-service": "~2.4.0"
- },
- "peerDependenciesMeta": {
- "@volar/language-service": {
- "optional": true
- }
- }
- },
- "node_modules/volar-service-prettier": {
- "version": "0.0.71",
- "resolved": "https://registry.npmjs.org/volar-service-prettier/-/volar-service-prettier-0.0.71.tgz",
- "integrity": "sha512-Rz7JVH3qD108UCdmIEiZvOBNljMt2nLFdbN8AXcDfn7xD9F5I2aCIsDVqBbXw21PsnxG0b7MfwtNF+zPS/NKUg==",
- "license": "MIT",
- "dependencies": {
- "vscode-uri": "^3.0.8"
- },
- "peerDependencies": {
- "@volar/language-service": "~2.4.0",
- "prettier": "^2.2 || ^3.0"
- },
- "peerDependenciesMeta": {
- "@volar/language-service": {
+ "@edge-runtime/vm": {
"optional": true
},
- "prettier": {
+ "@opentelemetry/api": {
"optional": true
+ },
+ "@types/node": {
+ "optional": true
+ },
+ "@vitest/browser-playwright": {
+ "optional": true
+ },
+ "@vitest/browser-preview": {
+ "optional": true
+ },
+ "@vitest/browser-webdriverio": {
+ "optional": true
+ },
+ "@vitest/coverage-istanbul": {
+ "optional": true
+ },
+ "@vitest/coverage-v8": {
+ "optional": true
+ },
+ "@vitest/ui": {
+ "optional": true
+ },
+ "happy-dom": {
+ "optional": true
+ },
+ "jsdom": {
+ "optional": true
+ },
+ "vite": {
+ "optional": false
}
}
},
- "node_modules/volar-service-typescript": {
- "version": "0.0.71",
- "resolved": "https://registry.npmjs.org/volar-service-typescript/-/volar-service-typescript-0.0.71.tgz",
- "integrity": "sha512-yTtM/BVT6hoyEYnDtaCyAtNhdNeS/mhTTABlBOdw3NNiRBUin3IznFJpgfjer4c6RYopiPjjQjc9VFhxVl1mLw==",
+ "node_modules/why-is-node-running": {
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-3.2.1.tgz",
+ "integrity": "sha512-Tb2FUhB4vUsGQlfSquQLYkApkuPAFQXGFzxWKHHumVz2dK+X1RUm/HnID4+TfIGYJ1kTcwOaCk/buYCEJr6YjQ==",
+ "dev": true,
"license": "MIT",
- "dependencies": {
- "path-browserify": "^1.0.1",
- "semver": "^7.6.2",
- "typescript-auto-import-cache": "^0.3.5",
- "vscode-languageserver-textdocument": "^1.0.11",
- "vscode-nls": "^5.2.0",
- "vscode-uri": "^3.0.8"
- },
- "peerDependencies": {
- "@volar/language-service": "~2.4.0"
- },
- "peerDependenciesMeta": {
- "@volar/language-service": {
- "optional": true
- }
- }
- },
- "node_modules/volar-service-typescript-twoslash-queries": {
- "version": "0.0.71",
- "resolved": "https://registry.npmjs.org/volar-service-typescript-twoslash-queries/-/volar-service-typescript-twoslash-queries-0.0.71.tgz",
- "integrity": "sha512-9K2k72s4n7rV9s4bX0MyjbX9iBribvKZbBJKuEmTCZfeWJXs6Yh7bGpY4eoc7UufAjvpheBqwyZCOIPBvxCv0A==",
- "license": "MIT",
- "dependencies": {
- "vscode-uri": "^3.0.8"
- },
- "peerDependencies": {
- "@volar/language-service": "~2.4.0"
- },
- "peerDependenciesMeta": {
- "@volar/language-service": {
- "optional": true
- }
- }
- },
- "node_modules/volar-service-typescript/node_modules/semver": {
- "version": "7.8.5",
- "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz",
- "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==",
- "license": "ISC",
"bin": {
- "semver": "bin/semver.js"
+ "why-is-node-running": "cli.js"
},
"engines": {
- "node": ">=10"
+ "node": ">=20.11"
}
},
- "node_modules/volar-service-yaml": {
- "version": "0.0.71",
- "resolved": "https://registry.npmjs.org/volar-service-yaml/-/volar-service-yaml-0.0.71.tgz",
- "integrity": "sha512-qYGWGuVpUTnZGu5P/CR4KLK4aIR8RrcVnmfZ2eRcj9q/I8VZCoC5yy9FtEvfNvnDp4MU17yhdJcvpQPIqhJS2Q==",
- "license": "MIT",
- "dependencies": {
- "vscode-uri": "^3.0.8",
- "yaml-language-server": "~1.23.0"
- },
- "peerDependencies": {
- "@volar/language-service": "~2.4.0"
- },
- "peerDependenciesMeta": {
- "@volar/language-service": {
- "optional": true
- }
- }
- },
- "node_modules/vscode-css-languageservice": {
- "version": "6.3.10",
- "resolved": "https://registry.npmjs.org/vscode-css-languageservice/-/vscode-css-languageservice-6.3.10.tgz",
- "integrity": "sha512-eq5N9Er3fC4vA9zd9EFhyBG90wtCCuXgRSpAndaOgXMh1Wgep5lBgRIeDgjZBW9pa+332yC9+49cZMW8jcL3MA==",
- "license": "MIT",
- "dependencies": {
- "@vscode/l10n": "^0.0.18",
- "vscode-languageserver-textdocument": "^1.0.12",
- "vscode-languageserver-types": "3.17.5",
- "vscode-uri": "^3.1.0"
- }
- },
- "node_modules/vscode-css-languageservice/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"
- },
- "node_modules/vscode-html-languageservice": {
- "version": "5.6.2",
- "resolved": "https://registry.npmjs.org/vscode-html-languageservice/-/vscode-html-languageservice-5.6.2.tgz",
- "integrity": "sha512-ulCrSnFnfQ16YzvwnYUgEbUEl/ZG7u2eV27YhvLObSHKkb8fw1Z9cgsnUwjTEeDIdJDoTDTDpxuhQwoenoLNMg==",
- "license": "MIT",
- "dependencies": {
- "@vscode/l10n": "^0.0.18",
- "vscode-languageserver-textdocument": "^1.0.12",
- "vscode-languageserver-types": "^3.17.5",
- "vscode-uri": "^3.1.0"
- }
- },
- "node_modules/vscode-json-languageservice": {
- "version": "4.1.8",
- "resolved": "https://registry.npmjs.org/vscode-json-languageservice/-/vscode-json-languageservice-4.1.8.tgz",
- "integrity": "sha512-0vSpg6Xd9hfV+eZAaYN63xVVMOTmJ4GgHxXnkLCh+9RsQBkWKIghzLhW2B9ebfG+LQQg8uLtsQ2aUKjTgE+QOg==",
- "license": "MIT",
- "dependencies": {
- "jsonc-parser": "^3.0.0",
- "vscode-languageserver-textdocument": "^1.0.1",
- "vscode-languageserver-types": "^3.16.0",
- "vscode-nls": "^5.0.0",
- "vscode-uri": "^3.0.2"
- },
- "engines": {
- "npm": ">=7.0.0"
- }
- },
- "node_modules/vscode-jsonrpc": {
- "version": "9.0.1",
- "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-9.0.1.tgz",
- "integrity": "sha512-rfuA6T75H6m5EkbhtEPzre9pT0HPcDI2MMy4+nPFIBks5J8JBAUHD4tRYSgaBOijIEC7SRkC1kKyXTLqbmh9jw==",
- "license": "MIT",
- "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",
- "dependencies": {
- "vscode-languageserver-protocol": "3.17.5"
- },
- "bin": {
- "installServerIntoExtension": "bin/installServerIntoExtension"
- }
- },
- "node_modules/vscode-languageserver-protocol": {
- "version": "3.18.2",
- "resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.18.2.tgz",
- "integrity": "sha512-XRyDbT0Pp3sSNti3JmxVEUMySWCSi1hhM+/KUlCy1hV1zmrqpM1OwO12EAki8blhmLuIMpaJrYbo0OzGVfK2Qg==",
- "license": "MIT",
- "dependencies": {
- "vscode-jsonrpc": "9.0.1",
- "vscode-languageserver-types": "3.18.0"
- }
- },
- "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"
- },
- "node_modules/vscode-languageserver-types": {
- "version": "3.18.0",
- "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.18.0.tgz",
- "integrity": "sha512-8TsGPNMIMiiBdkORgRSvLjuiEIiAFtO+KssmYWxQ+uSVvlf7RjK8YKCOjPzZ+YA04jXEV7+7LvkSmHkhpNS99g==",
- "license": "MIT"
- },
- "node_modules/vscode-languageserver/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",
- "engines": {
- "node": ">=14.0.0"
- }
- },
- "node_modules/vscode-languageserver/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",
- "dependencies": {
- "vscode-jsonrpc": "8.2.0",
- "vscode-languageserver-types": "3.17.5"
- }
- },
- "node_modules/vscode-languageserver/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"
- },
- "node_modules/vscode-nls": {
- "version": "5.2.0",
- "resolved": "https://registry.npmjs.org/vscode-nls/-/vscode-nls-5.2.0.tgz",
- "integrity": "sha512-RAaHx7B14ZU04EU31pT+rKz2/zSl7xMsfIZuo8pd+KZO6PXtQmpevpq3vxvWNcrGbdmhM/rr5Uw5Mz+NBfhVng==",
- "license": "MIT"
- },
- "node_modules/vscode-uri": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.1.0.tgz",
- "integrity": "sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==",
- "license": "MIT"
- },
- "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/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/wrap-ansi/node_modules/string-width": {
- "version": "7.2.0",
- "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz",
- "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==",
- "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/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/y18n": {
- "version": "5.0.8",
- "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
- "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==",
- "license": "ISC",
- "engines": {
- "node": ">=10"
- }
- },
- "node_modules/yallist": {
- "version": "3.1.1",
- "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
- "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
- "license": "ISC"
- },
"node_modules/yaml": {
- "version": "2.9.0",
- "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.0.tgz",
- "integrity": "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==",
+ "version": "2.9.1",
+ "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.1.tgz",
+ "integrity": "sha512-3NxN8+78OdzbT7C/WjGsyfPAtJaN3FNDsWxv7Y7mcDsT/oOmgW8BpyQQFFBnvZE3j9Y2Sdz1ULFLezL7Eb2yFw==",
"license": "ISC",
"bin": {
"yaml": "bin.mjs"
@@ -8365,105 +1679,30 @@
"url": "https://github.com/sponsors/eemeli"
}
},
- "node_modules/yaml-language-server": {
- "version": "1.23.0",
- "resolved": "https://registry.npmjs.org/yaml-language-server/-/yaml-language-server-1.23.0.tgz",
- "integrity": "sha512-3qVyCOexLCWw06PQa5kRPwvMWMZ/eZeCRWUvgD6a0OkqL/4iCnxy2WumbWifa937Uo5xhyWJ0uxlU39ljhNh7A==",
- "license": "MIT",
- "dependencies": {
- "@vscode/l10n": "^0.0.18",
- "ajv": "^8.17.1",
- "ajv-draft-04": "^1.0.0",
- "ajv-i18n": "^4.2.0",
- "prettier": "^3.8.1",
- "request-light": "^0.5.7",
- "vscode-json-languageservice": "4.1.8",
- "vscode-languageserver": "^9.0.0",
- "vscode-languageserver-textdocument": "^1.0.1",
- "vscode-languageserver-types": "^3.16.0",
- "vscode-uri": "^3.0.2",
- "yaml": "2.8.3"
- },
- "bin": {
- "yaml-language-server": "bin/yaml-language-server"
- }
- },
- "node_modules/yaml-language-server/node_modules/request-light": {
- "version": "0.5.8",
- "resolved": "https://registry.npmjs.org/request-light/-/request-light-0.5.8.tgz",
- "integrity": "sha512-3Zjgh+8b5fhRJBQZoy+zbVKpAQGLyka0MPgW3zruTF4dFFJ8Fqcfu9YsAvi/rvdcaTeWG3MkbZv4WKxAn/84Lg==",
- "license": "MIT"
- },
- "node_modules/yaml-language-server/node_modules/yaml": {
- "version": "2.8.3",
- "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.3.tgz",
- "integrity": "sha512-AvbaCLOO2Otw/lW5bmh9d/WEdcDFdQp2Z2ZUH3pX9U2ihyUY0nvLv7J6TrWowklRGPYbB/IuIMfYgxaCPg5Bpg==",
- "license": "ISC",
- "bin": {
- "yaml": "bin.mjs"
- },
- "engines": {
- "node": ">= 14.6"
- },
- "funding": {
- "url": "https://github.com/sponsors/eemeli"
- }
- },
- "node_modules/yargs": {
- "version": "18.1.0",
- "resolved": "https://registry.npmjs.org/yargs/-/yargs-18.1.0.tgz",
- "integrity": "sha512-2rAgRKu54VsHkqI0/tYkmluGXHD4KW7yZoycuqDQ15QOTnc2VVfy0nN/1eMhnQLO00A+dwtK20xuCnc1YGeUyg==",
- "license": "MIT",
- "dependencies": {
- "cliui": "^9.0.1",
- "escalade": "^3.1.1",
- "get-caller-file": "^2.0.5",
- "string-width": "^8.2.1",
- "y18n": "^5.0.5",
- "yargs-parser": "^22.0.0"
- },
- "engines": {
- "node": "^20.19.0 || ^22.12.0 || >=23"
- }
- },
- "node_modules/yargs-parser": {
- "version": "22.0.0",
- "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-22.0.0.tgz",
- "integrity": "sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw==",
- "license": "ISC",
- "engines": {
- "node": "^20.19.0 || ^22.12.0 || >=23"
- }
- },
- "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/zod": {
- "version": "4.4.3",
- "resolved": "https://registry.npmjs.org/zod/-/zod-4.4.3.tgz",
- "integrity": "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==",
+ "version": "4.6.6",
+ "resolved": "https://registry.npmjs.org/zod/-/zod-4.6.6.tgz",
+ "integrity": "sha512-4UmzQUF5fxvfcYHlbjBmejU+w+hbDnWFx4aS8kBx7nPBHcO2SqPbpAG8snbdg+Xse4pRefR98hgjFgIMCI/UhA==",
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/colinhacks"
}
},
- "node_modules/zwitch": {
- "version": "2.0.4",
- "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz",
- "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==",
- "license": "MIT",
- "funding": {
- "type": "github",
- "url": "https://github.com/sponsors/wooorm"
+ "packages/schema": {
+ "name": "@gk/schema",
+ "version": "0.1.0",
+ "dependencies": {
+ "yaml": "^2.6.0",
+ "zod": "^4.1.0"
+ }
+ },
+ "tools/migrate": {
+ "name": "@gk/migrate",
+ "version": "0.1.0",
+ "dependencies": {
+ "@gk/schema": "*",
+ "typescript": "^5.7.0",
+ "yaml": "^2.6.0"
}
}
}
diff --git a/package.json b/package.json
index f432565..b2aa82e 100644
--- a/package.json
+++ b/package.json
@@ -1,39 +1,27 @@
{
- "name": "greg-kilmer-profile",
- "version": "1.0.0",
- "type": "module",
+ "name": "gregkilmer-com",
"private": true,
- "description": "Personal profile / portfolio website for Greg Kilmer. Astro + React Islands + Node server.",
- "scripts": {
- "dev": "astro dev",
- "start": "astro dev",
- "build": "astro check && astro build",
- "preview": "astro preview",
- "serve": "node ./dist/server/entry.mjs",
- "astro": "astro",
- "check": "astro check",
- "typecheck": "astro check && tsc --noEmit"
+ "type": "module",
+ "description": "GregKilmer.com: the note schema, content service and site that serve Greg's vault notes. The old site lives in legacy/ for reference only.",
+ "workspaces": [
+ "packages/*",
+ "tools/*"
+ ],
+ "engines": {
+ "node": ">=22"
},
- "dependencies": {
- "@astrojs/check": "^0.9.9",
- "@astrojs/mdx": "^7.0.2",
- "@astrojs/node": "^11.0.2",
- "@astrojs/react": "^6.0.2",
- "@fontsource-variable/cinzel": "^5.3.0",
- "@fontsource-variable/jetbrains-mono": "^5.2.8",
- "@fontsource-variable/montserrat": "^5.3.0",
- "@iconify-json/mdi": "^1.2.3",
- "@tailwindcss/vite": "^4.3.2",
- "@types/nodemailer": "^8.0.1",
- "@types/react": "^19.2.17",
- "@types/react-dom": "^19.2.3",
- "astro": "^7.2.0",
- "framer-motion": "^12.42.2",
- "nodemailer": "^9.0.3",
- "react": "^19.2.7",
- "react-dom": "^19.2.7",
- "tailwindcss": "^4.3.2",
- "typescript": "^6.0.3",
- "zod": "^4.4.3"
+ "scripts": {
+ "typecheck": "tsc -p packages/schema && tsc -p tools/migrate",
+ "test": "vitest run",
+ "check": "npm run typecheck && npm test",
+ "schema:json": "npm run json -w @gk/schema",
+ "templates": "npm run templates -w @gk/schema",
+ "migrate": "npm run migrate -w @gk/migrate --"
+ },
+ "devDependencies": {
+ "@types/node": "^22.10.0",
+ "tsx": "^4.19.0",
+ "typescript": "^5.7.0",
+ "vitest": "^5.0.3"
}
}
diff --git a/packages/schema/package.json b/packages/schema/package.json
new file mode 100644
index 0000000..b292a9e
--- /dev/null
+++ b/packages/schema/package.json
@@ -0,0 +1,18 @@
+{
+ "name": "@gk/schema",
+ "version": "0.1.0",
+ "private": true,
+ "type": "module",
+ "description": "The one definition of every GregKilmer.com note type, shared by the migration, the content service, the MCP server, the site and any future editor.",
+ "exports": {
+ ".": "./src/index.ts"
+ },
+ "scripts": {
+ "json": "tsx scripts/emit-json-schema.ts",
+ "templates": "tsx scripts/emit-templates.ts"
+ },
+ "dependencies": {
+ "yaml": "^2.6.0",
+ "zod": "^4.1.0"
+ }
+}
diff --git a/packages/schema/scripts/emit-json-schema.ts b/packages/schema/scripts/emit-json-schema.ts
new file mode 100644
index 0000000..7048a8d
--- /dev/null
+++ b/packages/schema/scripts/emit-json-schema.ts
@@ -0,0 +1,24 @@
+/**
+ * Writes one JSON Schema per note type to json/, with every field's title and
+ * description. The MCP server and future editors can read these without
+ * importing TypeScript.
+ */
+import { mkdirSync, writeFileSync } from "node:fs";
+import { z } from "zod";
+import { noteTypes } from "../src/types.ts";
+
+const out = new URL("../json/", import.meta.url);
+mkdirSync(out, { recursive: true });
+for (const def of Object.values(noteTypes)) {
+ const schema = z.toJSONSchema(def.schema, { io: "input", unrepresentable: "any" });
+ const doc = {
+ ...schema,
+ title: def.label,
+ description: def.description,
+ "x-folder": typeof def.folder === "string" ? def.folder : "depends on the note",
+ "x-body": def.body,
+ "x-singleton": def.singleton?.fileName ?? null,
+ };
+ writeFileSync(new URL(`${def.type}.schema.json`, out), `${JSON.stringify(doc, null, 2)}\n`);
+}
+console.log(`Wrote ${Object.keys(noteTypes).length} schemas to json/`);
diff --git a/packages/schema/scripts/emit-templates.ts b/packages/schema/scripts/emit-templates.ts
new file mode 100644
index 0000000..9320a5d
--- /dev/null
+++ b/packages/schema/scripts/emit-templates.ts
@@ -0,0 +1,9 @@
+/** Writes the Obsidian templates into vault-kit/. */
+import { mkdirSync, readdirSync, rmSync, writeFileSync } from "node:fs";
+import { allTemplates } from "../src/templates.ts";
+
+const dir = new URL("../../../vault-kit/24.01_PersonalWebsite/Site/_templates/", import.meta.url);
+mkdirSync(dir, { recursive: true });
+for (const f of readdirSync(dir)) rmSync(new URL(f, dir));
+for (const t of allTemplates()) writeFileSync(new URL(`${t.name}.md`, dir), t.text);
+console.log(`Wrote ${allTemplates().length} templates`);
diff --git a/packages/schema/src/fields.ts b/packages/schema/src/fields.ts
new file mode 100644
index 0000000..e58f3ee
--- /dev/null
+++ b/packages/schema/src/fields.ts
@@ -0,0 +1,97 @@
+/**
+ * Field builders shared by every note type.
+ *
+ * Each field carries `title` and `description` metadata. The JSON Schema
+ * export keeps them, so the MCP server, the admin page and any later editor
+ * can render a form or explain a field without a second definition.
+ */
+import { z } from "zod";
+import { normalizeWikilinkInput, wikilinkTarget } from "./names.ts";
+
+type Meta = { title: string; description?: string };
+
+/** A calendar date written `YYYY-MM-DD`, as Obsidian writes dates. */
+export const isoDate = (meta: Meta) =>
+ z
+ .preprocess(
+ (v) => (v instanceof Date ? v.toISOString().slice(0, 10) : v),
+ z.string().regex(/^\d{4}-\d{2}-\d{2}$/, "Use a date like 2026-10-11"),
+ )
+ .meta(meta);
+
+/** A month written `YYYY-MM`, for resume dates. */
+export const isoMonth = (meta: Meta) =>
+ z.string().regex(/^\d{4}-\d{2}$/, "Use a month like 2019-03").meta(meta);
+
+/** A link to another note, written `[[Note name]]`. */
+export const wikilink = (meta: Meta) =>
+ z
+ .preprocess(
+ normalizeWikilinkInput,
+ z.string().refine((v) => wikilinkTarget(v) !== null, "Write a link as [[Note name]]"),
+ )
+ .meta(meta);
+
+/** A list of links to other notes. */
+export const wikilinks = (meta: Meta) =>
+ z
+ .preprocess(
+ (v) => (Array.isArray(v) ? v.map(normalizeWikilinkInput) : v),
+ z.array(z.string().refine((v) => wikilinkTarget(v) !== null, "Write each link as [[Note name]]")),
+ )
+ .meta(meta);
+
+/** An image: a vault attachment as `[[file.jpg]]`, or a full https URL. */
+export const image = (meta: Meta) =>
+ z
+ .preprocess(
+ normalizeWikilinkInput,
+ z
+ .string()
+ .refine(
+ (v) => wikilinkTarget(v) !== null || /^https:\/\//.test(v),
+ "Use a vault image as [[file.jpg]] or a full https URL",
+ ),
+ )
+ .meta(meta);
+
+/** One short line of text. */
+export const line = (meta: Meta) => z.string().trim().min(1).meta(meta);
+
+/** A list of short tags or names. */
+export const strings = (meta: Meta) => z.array(z.string().trim().min(1)).meta(meta);
+
+/**
+ * Fields every note shares. `type` is added per note type.
+ *
+ * `publish` defaults to false: a note goes live only when it says so.
+ */
+export const baseFields = {
+ title: line({ title: "Title" }),
+ slug: z
+ .string()
+ .regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/, "Lowercase words joined by hyphens")
+ .meta({ title: "Slug", description: "The last part of the page address. Made from the title when left out." })
+ .optional(),
+ publish: z
+ .boolean()
+ .default(false)
+ .meta({ title: "Publish", description: "Only notes set to true appear on the site." }),
+ favorite: z.boolean().meta({ title: "Favorite", description: "Starred on the site." }).optional(),
+ tags: strings({ title: "Tags" }).optional(),
+ links: wikilinks({
+ title: "Links",
+ description: "Other notes this one is about. The site shows the link on both notes.",
+ }).optional(),
+ cover: image({ title: "Cover image" }).optional(),
+ created: isoDate({ title: "Created" }).optional(),
+ updated: isoDate({ title: "Updated" }).optional(),
+ updatedBy: z
+ .enum(["greg", "agent", "admin"])
+ .meta({ title: "Last edited by", description: "Set by the content service; you never need to write it." })
+ .optional(),
+ agentEdits: z
+ .boolean()
+ .meta({ title: "Agents may edit", description: "Let agents change this note without your review." })
+ .optional(),
+};
diff --git a/packages/schema/src/index.ts b/packages/schema/src/index.ts
new file mode 100644
index 0000000..be075b4
--- /dev/null
+++ b/packages/schema/src/index.ts
@@ -0,0 +1,6 @@
+export * from "./names.ts";
+export * from "./fields.ts";
+export * from "./types.ts";
+export * from "./notes.ts";
+export * from "./vault.ts";
+export * from "./templates.ts";
diff --git a/packages/schema/src/names.ts b/packages/schema/src/names.ts
new file mode 100644
index 0000000..0b89eb4
--- /dev/null
+++ b/packages/schema/src/names.ts
@@ -0,0 +1,64 @@
+/**
+ * Names, slugs and wikilinks.
+ *
+ * Every note is addressed three ways: its file name in the vault (what
+ * Obsidian wikilinks resolve), its slug (what the URL uses), and its type.
+ * These helpers are the only place those rules live.
+ */
+
+/** Characters Obsidian or common file systems refuse in a file name. */
+const UNSAFE_FILE_CHARS = /[\\/:*?"<>|#^[\]]/g;
+
+/** A file name (without `.md`) safe for Obsidian on every platform. */
+export function safeFileName(title: string): string {
+ return title
+ .replace(UNSAFE_FILE_CHARS, " ")
+ .replace(/\s+/g, " ")
+ .trim()
+ .replace(/[. ]+$/, "");
+}
+
+/** URL slug from a title: lowercase ASCII words joined by hyphens. */
+export function slugify(text: string): string {
+ return text
+ .normalize("NFKD")
+ .replace(/[̀-ͯ]/g, "")
+ .toLowerCase()
+ .replace(/&/g, " and ")
+ .replace(/['’]/g, "")
+ .replace(/[^a-z0-9]+/g, "-")
+ .replace(/^-+|-+$/g, "");
+}
+
+const WIKILINK = /^\[\[([^[\]|#]+)(?:#[^[\]|]*)?(?:\|[^[\]]*)?\]\]$/;
+const WIKILINK_GLOBAL = /\[\[([^[\]|#]+)(?:#[^[\]|]*)?(?:\|[^[\]]*)?\]\]/g;
+
+/** `Name` → `[[Name]]`. */
+export function toWikilink(target: string): string {
+ return `[[${target}]]`;
+}
+
+/** `[[Name|alias]]` → `Name`, or null when the value is not a wikilink. */
+export function wikilinkTarget(value: string): string | null {
+ const m = WIKILINK.exec(value.trim());
+ return m ? m[1]!.trim() : null;
+}
+
+/** Every wikilink target in a block of Markdown, in order, without repeats. */
+export function wikilinksIn(markdown: string): string[] {
+ const seen = new Set();
+ for (const m of markdown.matchAll(WIKILINK_GLOBAL)) seen.add(m[1]!.trim());
+ return [...seen];
+}
+
+/**
+ * An unquoted `[[Name]]` in YAML parses as a nested list (`[["Name"]]`).
+ * Obsidian quotes them, but hand edits may not, so accept both shapes.
+ */
+export function normalizeWikilinkInput(value: unknown): unknown {
+ if (Array.isArray(value) && value.length === 1 && Array.isArray(value[0]) && value[0].length === 1) {
+ const inner = value[0][0];
+ if (typeof inner === "string") return toWikilink(inner);
+ }
+ return value;
+}
diff --git a/packages/schema/src/notes.ts b/packages/schema/src/notes.ts
new file mode 100644
index 0000000..f35bcb7
--- /dev/null
+++ b/packages/schema/src/notes.ts
@@ -0,0 +1,112 @@
+/**
+ * Reading, checking and writing one note.
+ *
+ * A note is Markdown with YAML frontmatter. These functions never touch the
+ * file system, so the migration, the content service and tests share them.
+ */
+import { parse as parseYaml, stringify as stringifyYaml } from "yaml";
+import type { z } from "zod";
+import { baseFields } from "./fields.ts";
+import { safeFileName } from "./names.ts";
+import { noteTypes, type NoteType, type NoteTypeDef } from "./types.ts";
+
+export interface RawNote {
+ /** Parsed frontmatter, unchecked. */
+ frontmatter: Record;
+ /** The Markdown under the frontmatter. */
+ body: string;
+}
+
+export interface NoteProblem {
+ /** Dotted path to the field, or "" for the whole note. */
+ field: string;
+ message: string;
+}
+
+export type CheckedNote =
+ | { ok: true; type: NoteType; data: Record; body: string }
+ | { ok: false; type: NoteType | null; problems: NoteProblem[] };
+
+const FRONTMATTER = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?/;
+
+/** Split a note's text into frontmatter and body. Throws on broken YAML. */
+export function splitNote(text: string): RawNote {
+ const m = FRONTMATTER.exec(text);
+ if (!m) return { frontmatter: {}, body: text };
+ const parsed = parseYaml(m[1]!) ?? {};
+ if (typeof parsed !== "object" || Array.isArray(parsed)) {
+ throw new Error("Frontmatter must be a set of fields");
+ }
+ return { frontmatter: parsed as Record, body: text.slice(m[0].length) };
+}
+
+export function typeDef(type: string): NoteTypeDef | undefined {
+ return (noteTypes as Record)[type];
+}
+
+/** Check a note's text against its type. Never throws. */
+export function checkNote(text: string): CheckedNote {
+ let raw: RawNote;
+ try {
+ raw = splitNote(text);
+ } catch (e) {
+ return { ok: false, type: null, problems: [{ field: "", message: `Frontmatter is not valid YAML: ${(e as Error).message}` }] };
+ }
+ const type = raw.frontmatter.type;
+ if (typeof type !== "string") {
+ return { ok: false, type: null, problems: [{ field: "type", message: "Missing type" }] };
+ }
+ const def = typeDef(type);
+ if (!def) {
+ return { ok: false, type: null, problems: [{ field: "type", message: `Unknown type "${type}"` }] };
+ }
+ const result = (def.schema as z.ZodType).safeParse(raw.frontmatter);
+ if (!result.success) {
+ return {
+ ok: false,
+ type: type as NoteType,
+ problems: result.error.issues.map((i) => ({ field: i.path.join("."), message: i.message })),
+ };
+ }
+ return { ok: true, type: type as NoteType, data: result.data as Record, body: raw.body };
+}
+
+/**
+ * Frontmatter key order: type, title, slug and publish, then the type's own
+ * fields in schema order, then the other shared fields, then anything else.
+ */
+function orderKeys(def: NoteTypeDef, data: Record): Record {
+ const shape = (def.schema as unknown as { shape: Record }).shape;
+ const first = ["type", "title", "slug", "publish"];
+ const shared = Object.keys(baseFields).filter((k) => !first.includes(k));
+ const own = Object.keys(shape).filter((k) => !first.includes(k) && !shared.includes(k));
+ const keys = [...first, ...own, ...shared];
+ const out: Record = {};
+ for (const k of keys) if (data[k] !== undefined) out[k] = data[k];
+ for (const k of Object.keys(data)) if (!(k in out) && data[k] !== undefined) out[k] = data[k];
+ return out;
+}
+
+/** Write a note's text. The data is checked first; this throws when it fails. */
+export function writeNote(data: Record, body = ""): string {
+ const def = typeDef(String(data.type));
+ if (!def) throw new Error(`Unknown type "${String(data.type)}"`);
+ const result = (def.schema as z.ZodType).safeParse(data);
+ if (!result.success) {
+ const problems = result.error.issues.map((i) => `${i.path.join(".") || "note"}: ${i.message}`).join("; ");
+ throw new Error(`"${String(data.title)}" is not a valid ${def.label}: ${problems}`);
+ }
+ // Write what was given (not schema defaults), so notes stay short.
+ const yaml = stringifyYaml(orderKeys(def, data), { lineWidth: 0 }).trimEnd();
+ const text = body.trim() ? `\n${body.trim()}\n` : "";
+ return `---\n${yaml}\n---\n${text}`;
+}
+
+/** Where a note of this type lives, relative to the Site folder, with `.md`. */
+export function notePath(data: Record): string {
+ const def = typeDef(String(data.type));
+ if (!def) throw new Error(`Unknown type "${String(data.type)}"`);
+ const folder = typeof def.folder === "function" ? def.folder(data) : def.folder;
+ const name = def.singleton?.fileName ?? (def.fileName ? def.fileName(data) : safeFileName(String(data.title)));
+ return `${folder ? `${folder}/` : ""}${name}.md`;
+}
diff --git a/packages/schema/src/templates.ts b/packages/schema/src/templates.ts
new file mode 100644
index 0000000..0b541dd
--- /dev/null
+++ b/packages/schema/src/templates.ts
@@ -0,0 +1,60 @@
+/**
+ * Obsidian templates, one per note type, made from the schema so they can
+ * never drift from what the content service accepts.
+ */
+import { z } from "zod";
+import { noteTypes } from "./types.ts";
+import type { NoteTypeDef } from "./types.ts";
+
+type Prop = { type?: string; enum?: unknown[]; const?: unknown; description?: string; title?: string; default?: unknown; items?: Prop };
+
+/** Shared fields that stay out of templates: the service or the title fills them. */
+const SKIP = new Set(["slug", "updatedBy", "agentEdits", "updated", "cover", "links", "tags", "favorite"]);
+
+function placeholder(key: string, p: Prop): string {
+ if (key === "created" || key === "date") return '"{{date:YYYY-MM-DD}}"';
+ if (p.const !== undefined) return JSON.stringify(p.const);
+ if (p.default !== undefined) return JSON.stringify(p.default);
+ if (p.type === "array") return "[]";
+ if (p.type === "boolean") return "false";
+ return "";
+}
+
+function hint(p: Prop): string {
+ const parts: string[] = [];
+ if (p.enum) parts.push(`one of: ${p.enum.join(", ")}`);
+ else if (p.items?.enum) parts.push(`any of: ${p.items.enum.join(", ")}`);
+ if (p.description) parts.push(p.description);
+ return parts.join(" ");
+}
+
+export function templateFor(def: NoteTypeDef): string {
+ const schema = z.toJSONSchema(def.schema, { io: "input", unrepresentable: "any" }) as {
+ properties: Record;
+ required?: string[];
+ };
+ const required = new Set(schema.required ?? []);
+ const lines: string[] = [];
+ for (const [key, p] of Object.entries(schema.properties)) {
+ if (SKIP.has(key)) continue;
+ if (key === "title") {
+ lines.push(`title: "{{title}}"`);
+ continue;
+ }
+ // Optional object lists (specs, prices...) are easier to add when needed.
+ if (!required.has(key) && p.type === "array" && p.items?.type === "object") continue;
+ const value = placeholder(key, p);
+ const optional = !required.has(key) && key !== "publish" && key !== "type";
+ const h = [optional ? "optional" : "", hint(p)].filter(Boolean).join(". ");
+ lines.push(`${key}: ${value}`.trimEnd() + (h ? ` # ${h}` : ""));
+ }
+ const body = def.body ? `\n\n` : "";
+ return `---\n${lines.join("\n")}\n---\n${body}`;
+}
+
+/** File name (without `.md`) and text for every template. */
+export function allTemplates(): { name: string; text: string }[] {
+ return Object.values(noteTypes)
+ .filter((d) => !d.singleton)
+ .map((d) => ({ name: d.label, text: templateFor(d as NoteTypeDef) }));
+}
diff --git a/packages/schema/src/types.ts b/packages/schema/src/types.ts
new file mode 100644
index 0000000..bcf7b6b
--- /dev/null
+++ b/packages/schema/src/types.ts
@@ -0,0 +1,508 @@
+/**
+ * Every note type the site understands.
+ *
+ * A note type is one Markdown note shape: its frontmatter fields, the folder
+ * it lives in under the Site folder, and how its file is named. This file is
+ * the single definition; the migration, the content service, the MCP tools,
+ * the admin page and the templates all read it.
+ */
+import { z } from "zod";
+import { baseFields, image, isoDate, isoMonth, line, strings, wikilink, wikilinks } from "./fields.ts";
+import { safeFileName } from "./names.ts";
+
+export interface NoteTypeDef {
+ /** The value of the note's `type` field. */
+ type: string;
+ /** What people call it. */
+ label: string;
+ /** One sentence for editors and agents. */
+ description: string;
+ /** Folder under the Site folder. A function when the folder depends on the note. */
+ folder: string | ((data: any) => string);
+ /** A single note at a fixed path, such as site settings. */
+ singleton?: { fileName: string };
+ /** File name (without `.md`) for a new note of this type. Defaults to the title. */
+ fileName?: (data: any) => string;
+ /** What the Markdown body under the frontmatter is for, or null when unused. */
+ body: string | null;
+ schema: S;
+}
+
+const note = (type: T, fields: F) =>
+ z.looseObject({ type: z.literal(type), ...baseFields, ...fields });
+
+const def = (d: NoteTypeDef) => d;
+
+// ---------------------------------------------------------------- Blog
+
+export const topics = ["Making", "Tabletop", "Library", "Work and tech", "Life"] as const;
+
+export const post = def({
+ type: "post",
+ label: "Post",
+ description: "A blog post. Case studies are posts with postType: case study and show on the Work page too.",
+ folder: "Posts",
+ body: "The post itself.",
+ schema: note("post", {
+ date: isoDate({ title: "Publish date" }),
+ topic: z.enum(topics).meta({ title: "Topic" }),
+ postType: z
+ .enum(["article", "build log", "case study"])
+ .default("article")
+ .meta({ title: "Post type" }),
+ summary: line({ title: "Summary", description: "One or two sentences for lists and link previews." }),
+ pinned: z.boolean().meta({ title: "Featured", description: "Takes the top slot on the Blog." }).optional(),
+ // Case study fields
+ employer: line({ title: "Employer", description: "Case studies only. Leave out for personal projects." }).optional(),
+ role: line({ title: "Role", description: "Case studies only." }).optional(),
+ period: line({ title: "Period", description: "Case studies only, like 2021 to now." }).optional(),
+ tech: strings({ title: "Tech" }).optional(),
+ facts: z
+ .array(z.object({ label: line({ title: "Label" }), value: line({ title: "Value" }) }))
+ .meta({ title: "Key facts", description: "Case studies only, like Users: 1,000+." })
+ .optional(),
+ }).superRefine((v, ctx) => {
+ if (v.postType === "case study" && !v.role) {
+ ctx.addIssue({ code: "custom", path: ["role"], message: "A case study needs a role" });
+ }
+ }),
+});
+
+// ---------------------------------------------------------------- Library
+
+export const media = [
+ "book",
+ "light novel",
+ "webnovel",
+ "manga",
+ "manhwa",
+ "video game",
+ "anime",
+ "TV",
+ "movie",
+] as const;
+
+const mediaFolder: Record<(typeof media)[number], string> = {
+ book: "Books",
+ "light novel": "Light novels",
+ webnovel: "Webnovels",
+ manga: "Manga",
+ manhwa: "Manhwa",
+ "video game": "Video games",
+ anime: "Anime",
+ TV: "TV",
+ movie: "Movies",
+};
+
+export const libraryStates = ["now", "up next", "played", "finished", "caught up", "dropped"] as const;
+
+export const libraryItem = def({
+ type: "library-item",
+ label: "Library item",
+ description: "A book, series, show, film or video game, with its state and a short verdict.",
+ folder: (d: { medium: (typeof media)[number] }) => `Library/${mediaFolder[d.medium]}`,
+ body: "Optional longer thoughts.",
+ schema: note("library-item", {
+ medium: z.enum(media).meta({ title: "Medium" }),
+ state: z.enum(libraryStates).meta({ title: "State" }),
+ creator: line({ title: "Author or creator" }).optional(),
+ group: line({
+ title: "Group",
+ description: "Where it sits in the list, like Cosmere, Marvel Cinematic Universe or Prestige Drama.",
+ }).optional(),
+ year: z.number().int().meta({ title: "Year" }).optional(),
+ platform: line({ title: "Platform", description: "Video games only, like Steam or PlayStation 5." }).optional(),
+ verdict: line({ title: "Verdict", description: "A short review. No score." }).optional(),
+ note: line({ title: "Note", description: "Context, like where you are or how you came to it." }).optional(),
+ progress: line({ title: "Progress", description: "Like 16/18 volumes. Synced from PersonalReader when linked." }).optional(),
+ anilistId: z.number().int().meta({ title: "AniList id", description: "Links the item to PersonalReader's progress." }).optional(),
+ }),
+});
+
+// ---------------------------------------------------------------- The Shelf
+
+export const boardGame = def({
+ type: "board-game",
+ label: "Board game",
+ description: "A board game on The Shelf. Players, time and art sync from BoardGameGeek.",
+ folder: "Shelf",
+ body: "Optional longer thoughts.",
+ schema: note("board-game", {
+ bggId: z.number().int().meta({ title: "BoardGameGeek id" }),
+ blurb: line({ title: "Blurb" }),
+ howItPlays: strings({ title: "How it plays", description: "Like engine building, social deduction, co-op." }).optional(),
+ owned: z.boolean().default(true).meta({ title: "Owned" }),
+ wantToPlay: z.boolean().meta({ title: "Want to play" }).optional(),
+ lastPlayed: isoDate({ title: "Last played" }).optional(),
+ }),
+});
+
+// ---------------------------------------------------------------- Workshop
+
+export const craftStatuses = ["Practicing", "Learning", "Getting into", "Paused"] as const;
+
+export const craft = def({
+ type: "craft",
+ label: "Craft",
+ description: "A craft in the Workshop, with its status, station and tools.",
+ folder: "Workshop/Crafts",
+ body: "What you make with it, for the craft page.",
+ schema: note("craft", {
+ status: z.enum(craftStatuses).meta({ title: "Status" }),
+ blurb: line({ title: "Blurb" }).optional(),
+ station: wikilink({ title: "Station" }).optional(),
+ tools: wikilinks({ title: "Tools and materials" }).optional(),
+ order: z.number().int().meta({ title: "Order" }).optional(),
+ }),
+});
+
+export const station = def({
+ type: "station",
+ label: "Station",
+ description: "A place in the workspace where tools live.",
+ folder: "Workshop/Stations",
+ body: null,
+ schema: note("station", {
+ where: line({ title: "Where", description: "Like the work counter and filament wall." }),
+ goodFor: line({ title: "Good for" }).optional(),
+ order: z.number().int().meta({ title: "Order" }),
+ }),
+});
+
+export const tool = def({
+ type: "tool",
+ label: "Tool or material",
+ description: "A tool, machine or material, placed at a station.",
+ folder: "Workshop/Tools",
+ body: null,
+ schema: note("tool", {
+ kind: z.enum(["printer", "cutter", "casting", "finishing", "material", "other"]).meta({ title: "Kind" }),
+ station: wikilink({ title: "Station" }).optional(),
+ blurb: line({ title: "Blurb" }).optional(),
+ }),
+});
+
+export const make = def({
+ type: "make",
+ label: "Make",
+ description: "Something finished in the Workshop.",
+ folder: "Workshop/Makes",
+ body: "Optional story of the make.",
+ schema: note("make", {
+ craft: wikilink({ title: "Craft" }),
+ date: isoDate({ title: "Finished" }).optional(),
+ photos: z.array(image({ title: "Photo" })).meta({ title: "Photos" }).optional(),
+ blurb: line({ title: "Blurb" }).optional(),
+ }),
+});
+
+// ---------------------------------------------------------------- Tabletop
+
+export const tabletopSystem = def({
+ type: "tabletop-system",
+ label: "Tabletop system",
+ description: "A tabletop RPG system you run, have played or want to try.",
+ folder: "Tabletop/Systems",
+ body: null,
+ schema: note("tabletop-system", {
+ state: z.enum(["running", "played", "want to run", "want to play", "want to try"]).meta({ title: "State" }),
+ blurb: line({ title: "Blurb" }),
+ order: z.number().int().meta({ title: "Order" }).optional(),
+ }),
+});
+
+export const campaign = def({
+ type: "campaign",
+ label: "Campaign",
+ description: "A campaign or playtest at the table now or in the past.",
+ folder: "Tabletop/Campaigns",
+ body: "Optional campaign notes for the page.",
+ schema: note("campaign", {
+ system: wikilink({ title: "System", description: "Leave out for a playtest of an unreleased game." }).optional(),
+ status: z.enum(["active", "playtest", "paused", "finished"]).meta({ title: "Status" }),
+ schedule: line({ title: "Schedule", description: "Like Thursdays, every other week." }).optional(),
+ platform: line({ title: "Platform", description: "Like Foundry VTT." }).optional(),
+ blurb: line({ title: "Blurb" }).optional(),
+ }),
+});
+
+export const cardGame = def({
+ type: "card-game",
+ label: "Card game",
+ description: "A trading card game you play or follow.",
+ folder: "Tabletop/Card games",
+ body: null,
+ schema: note("card-game", {
+ stance: line({ title: "Where you are with it", description: "Like Main event, Collecting, Getting into." }),
+ blurb: line({ title: "Blurb" }).optional(),
+ order: z.number().int().meta({ title: "Order" }).optional(),
+ }),
+});
+
+export const deck = def({
+ type: "deck",
+ label: "Deck",
+ description: "A deck for any card game. Magic decks pull their details from Archidekt.",
+ folder: "Tabletop/Decks",
+ body: null,
+ schema: note("deck", {
+ game: wikilink({ title: "Card game" }),
+ format: line({ title: "Format", description: "Like Commander." }).optional(),
+ archidektId: z.number().int().meta({ title: "Archidekt deck id" }).optional(),
+ listUrl: z.url().meta({ title: "Deck list link", description: "For games not on Archidekt." }).optional(),
+ take: line({ title: "Your take", description: "One line on the deck." }).optional(),
+ }),
+});
+
+// ---------------------------------------------------------------- Resume
+
+export const experience = def({
+ type: "experience",
+ label: "Experience",
+ description: "One role on the resume. The page and the PDF both come from these.",
+ folder: "Resume/Experience",
+ fileName: (d: { company: string; start: string }) => safeFileName(`${d.company} ${d.start.slice(0, 4)}`),
+ body: "Optional longer notes; the resume uses the bullets.",
+ schema: note("experience", {
+ company: line({ title: "Company" }),
+ role: line({ title: "Role" }),
+ start: isoMonth({ title: "Start" }),
+ end: isoMonth({ title: "End", description: "Leave out while it is your current role." }).optional(),
+ location: line({ title: "Location" }).optional(),
+ focus: line({ title: "Focus", description: "One line under the role." }).optional(),
+ bullets: strings({ title: "Bullets" }),
+ tech: strings({ title: "Tech" }).optional(),
+ }),
+});
+
+export const skills = def({
+ type: "skills",
+ label: "Skills",
+ description: "The resume's skills sidebar.",
+ folder: "Resume",
+ singleton: { fileName: "Skills" },
+ body: null,
+ schema: note("skills", {
+ core: strings({ title: "Core strengths" }),
+ learning: strings({ title: "Learning now" }).optional(),
+ stack: z
+ .array(z.object({ label: line({ title: "Group" }), items: strings({ title: "Items" }) }))
+ .meta({ title: "Technical stack" }),
+ }),
+});
+
+export const education = def({
+ type: "education",
+ label: "Education",
+ description: "Degrees and certifications on the resume.",
+ folder: "Resume",
+ singleton: { fileName: "Education" },
+ body: null,
+ schema: note("education", {
+ degrees: z
+ .array(
+ z.object({
+ credential: line({ title: "Credential" }),
+ institution: line({ title: "Institution" }),
+ period: line({ title: "Period" }),
+ detail: line({ title: "Detail" }).optional(),
+ }),
+ )
+ .meta({ title: "Degrees" }),
+ certifications: z
+ .array(
+ z.object({
+ name: line({ title: "Name" }),
+ issuer: line({ title: "Issuer" }),
+ year: line({ title: "Year" }),
+ status: z.enum(["Active", "Expired", "In progress"]).meta({ title: "Status" }).optional(),
+ }),
+ )
+ .meta({ title: "Certifications" })
+ .optional(),
+ }),
+});
+
+// ---------------------------------------------------------------- Now
+
+export const status = def({
+ type: "status",
+ label: "Status",
+ description: "One line in the status log: what you are reading, playing, making or learning.",
+ folder: "Now",
+ fileName: (d: { date: string; kind: string; title: string }) => safeFileName(`${d.date} ${d.kind} ${d.title}`),
+ body: null,
+ schema: note("status", {
+ kind: line({ title: "Kind", description: "One of the kinds listed in Now/_kinds." }),
+ date: isoDate({ title: "Date" }),
+ subject: wikilink({ title: "About", description: "The note this status is about, if there is one." }).optional(),
+ note: line({ title: "Note" }).optional(),
+ pinned: z.boolean().meta({ title: "Pinned" }).optional(),
+ }),
+});
+
+export const statusKinds = def({
+ type: "status-kinds",
+ label: "Status kinds",
+ description: "The kinds a status can have. Adding a kind is one line here.",
+ folder: "Now",
+ singleton: { fileName: "_kinds" },
+ body: null,
+ schema: note("status-kinds", {
+ kinds: z
+ .array(
+ z.object({
+ label: line({ title: "Label" }),
+ icon: line({ title: "Icon" }),
+ page: line({ title: "Page", description: "The page this kind belongs to, like /library." }).optional(),
+ }),
+ )
+ .meta({ title: "Kinds" }),
+ }),
+});
+
+// ---------------------------------------------------------------- Store
+
+export const productLine = def({
+ type: "product-line",
+ label: "Product line",
+ description: "One line in the Store, made to order or in stock.",
+ folder: "Store/Lines",
+ body: "Optional longer description for the product page.",
+ schema: note("product-line", {
+ category: z.enum(["Tabletop", "Paper", "Leather"]).meta({ title: "Category" }),
+ mode: z.enum(["made to order", "in stock"]).meta({ title: "Mode" }),
+ channel: z.enum(["here", "Etsy", "Patiun.com", "coming soon"]).meta({ title: "Where to buy" }),
+ channelUrl: z.url().meta({ title: "Shop link", description: "For Etsy or Patiun.com lines." }).optional(),
+ summary: line({ title: "Summary" }),
+ icon: line({ title: "Icon" }).optional(),
+ order: z.number().int().meta({ title: "Order" }).optional(),
+ slots: z
+ .object({ open: z.number().int().min(0), total: z.number().int().min(1) })
+ .meta({ title: "Order slots", description: "Made to order lines only." })
+ .optional(),
+ quantity: z.number().int().min(0).meta({ title: "In stock", description: "In stock lines only." }).optional(),
+ images: z
+ .array(z.object({ file: image({ title: "Image" }), alt: line({ title: "Alt text" }), label: line({ title: "Caption" }) }))
+ .meta({ title: "Images" })
+ .optional(),
+ specs: z.array(z.object({ label: line({ title: "Label" }), value: line({ title: "Value" }) })).meta({ title: "Specs" }).optional(),
+ priceTiers: z
+ .array(z.object({ band: line({ title: "Band" }), price: line({ title: "Price" }), note: line({ title: "Note" }).optional() }))
+ .meta({ title: "Prices" }),
+ options: z
+ .array(
+ z.object({
+ group: line({ title: "Option group", description: "Like Filament, Leather or Thread." }),
+ name: line({ title: "Name" }),
+ hex: z.string().regex(/^#[0-9a-fA-F]{6}$/).meta({ title: "Color" }).optional(),
+ inStock: z.boolean().meta({ title: "In stock" }),
+ }),
+ )
+ .meta({ title: "Options" })
+ .optional(),
+ terms: z
+ .array(
+ z.object({
+ kind: z.enum(["info", "warning", "rule"]).meta({ title: "Kind" }),
+ title: line({ title: "Title" }),
+ body: line({ title: "Text" }),
+ }),
+ )
+ .meta({ title: "Order terms" })
+ .optional(),
+ }).superRefine((v, ctx) => {
+ if (v.mode === "made to order" && v.channel === "here" && !v.slots) {
+ ctx.addIssue({ code: "custom", path: ["slots"], message: "A made to order line sold here needs slots" });
+ }
+ }),
+});
+
+export const storeSettings = def({
+ type: "store-settings",
+ label: "Store settings",
+ description: "Whether each category takes orders, and the terms for every order.",
+ folder: "Store",
+ singleton: { fileName: "_settings" },
+ body: null,
+ schema: note("store-settings", {
+ categories: z
+ .array(
+ z.object({
+ name: z.enum(["Tabletop", "Paper", "Leather"]).meta({ title: "Category" }),
+ ordersOpen: z.boolean().meta({ title: "Orders open" }),
+ reopens: isoDate({ title: "Reopens" }).optional(),
+ turnaround: line({ title: "Turnaround" }).optional(),
+ }),
+ )
+ .meta({ title: "Categories" }),
+ capacityNote: line({ title: "Capacity note" }).optional(),
+ terms: productLine.schema.shape.terms,
+ }),
+});
+
+// ---------------------------------------------------------------- Site
+
+export const siteSettings = def({
+ type: "site-settings",
+ label: "Site settings",
+ description: "Name, tagline, availability and links used across the site.",
+ folder: "",
+ singleton: { fileName: "_site" },
+ body: null,
+ schema: note("site-settings", {
+ name: line({ title: "Name" }),
+ tagline: line({ title: "Tagline" }),
+ lockup: line({ title: "Lockup line" }),
+ location: line({ title: "Location" }).optional(),
+ availability: line({ title: "Availability", description: "Shown on the resume and Inquiries, like Open to freelance web work." }).optional(),
+ socials: z
+ .array(z.object({ label: line({ title: "Label" }), handle: line({ title: "Handle" }), url: z.url().meta({ title: "Link" }) }))
+ .meta({ title: "Social links" }),
+ footer: line({ title: "Footer line" }).optional(),
+ }),
+});
+
+export const page = def({
+ type: "page",
+ label: "Page text",
+ description: "The written introduction for one page of the site.",
+ folder: "Pages",
+ body: "The page's introduction.",
+ schema: note("page", {
+ page: line({ title: "Page", description: "The page address this text belongs to, like /about." }),
+ lede: line({ title: "Lede", description: "The one line under the page title." }).optional(),
+ quickFacts: z
+ .array(z.object({ icon: line({ title: "Icon" }), text: line({ title: "Text" }) }))
+ .meta({ title: "Quick facts" })
+ .optional(),
+ }),
+});
+
+/** Every note type, keyed by the `type` value. */
+export const noteTypes = {
+ post,
+ "library-item": libraryItem,
+ "board-game": boardGame,
+ craft,
+ station,
+ tool,
+ make,
+ "tabletop-system": tabletopSystem,
+ campaign,
+ "card-game": cardGame,
+ deck,
+ experience,
+ skills,
+ education,
+ status,
+ "status-kinds": statusKinds,
+ "product-line": productLine,
+ "store-settings": storeSettings,
+ "site-settings": siteSettings,
+ page,
+} as const;
+
+export type NoteType = keyof typeof noteTypes;
+export type NoteData = z.output<(typeof noteTypes)[T]["schema"]>;
+export const noteTypeNames = Object.keys(noteTypes) as NoteType[];
diff --git a/packages/schema/src/vault.ts b/packages/schema/src/vault.ts
new file mode 100644
index 0000000..3fea52d
--- /dev/null
+++ b/packages/schema/src/vault.ts
@@ -0,0 +1,75 @@
+/**
+ * Checks that need every note at once: unique names, unique slugs and links
+ * that resolve. The content service runs these on each change; the migration
+ * runs them on its output.
+ */
+import { slugify, wikilinkTarget, wikilinksIn } from "./names.ts";
+import { typeDef } from "./notes.ts";
+
+export interface VaultNote {
+ /** Path under the Site folder, like `Library/Books/Cosmere.md`. */
+ path: string;
+ type: string;
+ data: Record;
+ body: string;
+}
+
+export interface VaultProblem {
+ path: string;
+ message: string;
+}
+
+/** The name Obsidian resolves a wikilink against: the file name without `.md`. */
+export const linkName = (path: string) => path.replace(/^.*\//, "").replace(/\.md$/, "");
+
+/** Every wikilink a note makes, from frontmatter and body. */
+export function outgoingLinks(note: VaultNote): string[] {
+ const found = new Set();
+ const visit = (v: unknown) => {
+ if (typeof v === "string") {
+ const t = wikilinkTarget(v);
+ if (t) found.add(t);
+ } else if (Array.isArray(v)) v.forEach(visit);
+ else if (v && typeof v === "object") Object.values(v).forEach(visit);
+ };
+ visit(note.data);
+ for (const t of wikilinksIn(note.body)) found.add(t);
+ return [...found];
+}
+
+const IMAGE = /\.(jpe?g|png|gif|webp|svg|avif)$/i;
+
+/**
+ * Vault-wide problems. `attachments` lists attachment file names so image
+ * links can be checked too.
+ */
+export function checkVault(notes: VaultNote[], attachments: string[] = []): VaultProblem[] {
+ const problems: VaultProblem[] = [];
+ const byName = new Map();
+ for (const n of notes) {
+ const name = linkName(n.path).toLowerCase();
+ const other = byName.get(name);
+ if (other) problems.push({ path: n.path, message: `Same file name as ${other.path}, so links to it are ambiguous` });
+ else byName.set(name, n);
+ }
+
+ const slugs = new Map();
+ for (const n of notes) {
+ const def = typeDef(n.type);
+ if (!def || def.singleton) continue;
+ const slug = String(n.data.slug ?? slugify(String(n.data.title)));
+ const key = `${n.type}:${slug}`;
+ const other = slugs.get(key);
+ if (other) problems.push({ path: n.path, message: `Slug "${slug}" is already used by ${other}` });
+ else slugs.set(key, n.path);
+ }
+
+ const files = new Set(attachments.map((a) => a.toLowerCase()));
+ for (const n of notes) {
+ for (const target of outgoingLinks(n)) {
+ const known = IMAGE.test(target) ? files.has(target.toLowerCase()) : byName.has(target.toLowerCase());
+ if (!known) problems.push({ path: n.path, message: `Links to [[${target}]], which does not exist` });
+ }
+ }
+ return problems;
+}
diff --git a/packages/schema/test/schema.test.ts b/packages/schema/test/schema.test.ts
new file mode 100644
index 0000000..243d92e
--- /dev/null
+++ b/packages/schema/test/schema.test.ts
@@ -0,0 +1,89 @@
+import { readFileSync, readdirSync } from "node:fs";
+import { describe, expect, it } from "vitest";
+import { allTemplates, checkNote, checkVault, notePath, slugify, wikilinkTarget, writeNote } from "../src/index.ts";
+
+describe("names", () => {
+ it("slugs titles", () => {
+ expect(slugify("Pokémon TCG")).toBe("pokemon-tcg");
+ expect(slugify("The Queen's Gambit")).toBe("the-queens-gambit");
+ expect(slugify("Dungeons & Dragons")).toBe("dungeons-and-dragons");
+ });
+ it("reads wikilinks with aliases and headings", () => {
+ expect(wikilinkTarget("[[Galaxy dice|the galaxy set]]")).toBe("Galaxy dice");
+ expect(wikilinkTarget("[[Resin#Tools]]")).toBe("Resin");
+ expect(wikilinkTarget("Resin")).toBeNull();
+ });
+});
+
+describe("checkNote", () => {
+ const status = `---\ntype: status\ntitle: Throne of Glass\npublish: true\nkind: Reading\ndate: 2026-10-11\nsubject: [[Throne of Glass]]\n---\n`;
+
+ it("accepts an unquoted wikilink, as a hand edit might write it", () => {
+ const r = checkNote(status);
+ expect(r.ok).toBe(true);
+ if (r.ok) expect(r.data.subject).toBe("[[Throne of Glass]]");
+ });
+
+ it("names the field that is wrong", () => {
+ const r = checkNote(status.replace("date: 2026-10-11", "date: last tuesday"));
+ expect(r.ok).toBe(false);
+ if (!r.ok) expect(r.problems.map((p) => p.field)).toContain("date");
+ });
+
+ it("rejects unknown types and broken YAML without throwing", () => {
+ expect(checkNote("---\ntype: recipe\ntitle: x\n---\n").ok).toBe(false);
+ expect(checkNote("---\ntitle: [unclosed\n---\n").ok).toBe(false);
+ });
+
+ it("defaults publish to false", () => {
+ const r = checkNote("---\ntype: card-game\ntitle: Riftbound\nstance: Exploring\n---\n");
+ expect(r.ok && r.data.publish).toBe(false);
+ });
+
+ it("requires a role on case studies", () => {
+ const post = "---\ntype: post\ntitle: X\ndate: 2026-10-11\ntopic: Work and tech\npostType: case study\nsummary: Y\n---\n";
+ expect(checkNote(post).ok).toBe(false);
+ expect(checkNote(post.replace("summary: Y", "summary: Y\nrole: Lead")).ok).toBe(true);
+ });
+});
+
+describe("writeNote", () => {
+ it("round-trips through checkNote", () => {
+ const data = { type: "deck", title: "Kynaios and Tiro", publish: true, game: "[[Magic: The Gathering]]", format: "Commander" };
+ const r = checkNote(writeNote(data));
+ expect(r.ok && r.data).toMatchObject(data);
+ });
+ it("refuses invalid data", () => {
+ expect(() => writeNote({ type: "deck", title: "No game" })).toThrow(/game/);
+ });
+ it("places notes by type", () => {
+ expect(notePath({ type: "library-item", title: "Re:Zero", medium: "light novel" })).toBe("Library/Light novels/Re Zero.md");
+ expect(notePath({ type: "site-settings", title: "Site settings" })).toBe("_site.md");
+ });
+});
+
+describe("checkVault", () => {
+ const n = (path: string, type: string, data: Record, body = "") => ({ path, type, data: { title: path, ...data }, body });
+ it("finds duplicate names, duplicate slugs and dangling links", () => {
+ const problems = checkVault([
+ n("Shelf/Coup.md", "board-game", { slug: "coup" }),
+ n("Library/Movies/Coup.md", "library-item", {}),
+ n("Shelf/Other.md", "board-game", { slug: "coup" }),
+ n("Posts/A.md", "post", { links: ["[[Missing]]"] }, "See [[Coup]] and ![[photo.jpg]]"),
+ ], ["photo.jpg"]);
+ const messages = problems.map((p) => p.message).join("\n");
+ expect(messages).toMatch(/Same file name/);
+ expect(messages).toMatch(/Slug "coup"/);
+ expect(messages).toMatch(/\[\[Missing\]\]/);
+ expect(messages).not.toMatch(/photo\.jpg/);
+ });
+});
+
+describe("templates", () => {
+ it("match the committed vault kit", () => {
+ const dir = new URL("../../../vault-kit/24.01_PersonalWebsite/Site/_templates/", import.meta.url);
+ const committed = Object.fromEntries(readdirSync(dir).map((f) => [f.replace(/\.md$/, ""), readFileSync(new URL(f, dir), "utf8")]));
+ for (const t of allTemplates()) expect(committed[t.name], `Run npm run templates -w @gk/schema`).toBe(t.text);
+ expect(Object.keys(committed).length).toBe(allTemplates().length);
+ });
+});
diff --git a/packages/schema/tsconfig.json b/packages/schema/tsconfig.json
new file mode 100644
index 0000000..8a8f3fb
--- /dev/null
+++ b/packages/schema/tsconfig.json
@@ -0,0 +1,5 @@
+{
+ "extends": "../../tsconfig.base.json",
+ "compilerOptions": { "rootDir": "." },
+ "include": ["src", "scripts", "test"]
+}
diff --git a/tools/migrate/package.json b/tools/migrate/package.json
new file mode 100644
index 0000000..9fee301
--- /dev/null
+++ b/tools/migrate/package.json
@@ -0,0 +1,15 @@
+{
+ "name": "@gk/migrate",
+ "version": "0.1.0",
+ "private": true,
+ "type": "module",
+ "description": "One-time migration of the legacy site's data into Site folder notes.",
+ "scripts": {
+ "migrate": "tsx src/cli.ts"
+ },
+ "dependencies": {
+ "@gk/schema": "*",
+ "yaml": "^2.6.0",
+ "typescript": "^5.7.0"
+ }
+}
diff --git a/tools/migrate/src/build.ts b/tools/migrate/src/build.ts
new file mode 100644
index 0000000..cf7d449
--- /dev/null
+++ b/tools/migrate/src/build.ts
@@ -0,0 +1,402 @@
+/**
+ * Turns the legacy site's data into Site folder notes.
+ *
+ * Pure apart from reading legacy/: it returns the notes and the attachments
+ * to copy, and the CLI writes them. Every note goes through the schema's
+ * writeNote, so nothing invalid can be produced.
+ */
+import { readFileSync, readdirSync } from "node:fs";
+import { basename, extname, join } from "node:path";
+import ts from "typescript";
+import { notePath, safeFileName, slugify, splitNote, toWikilink, writeNote } from "@gk/schema";
+import * as seed from "./seed.ts";
+
+export interface BuiltNote {
+ path: string;
+ text: string;
+ data: Record;
+ body: string;
+}
+
+export interface Attachment {
+ /** Absolute path of the legacy file. */
+ from: string;
+ /** Path under the Site folder. */
+ to: string;
+}
+
+export interface BuildResult {
+ notes: BuiltNote[];
+ attachments: Attachment[];
+ /** Plain-language notes about choices the migration made, for the report. */
+ log: string[];
+}
+
+const today = seed.MIGRATED_ON;
+
+export function build(legacyDir: string): BuildResult {
+ const notes: BuiltNote[] = [];
+ const attachments = new Map();
+ const usedNames = new Set();
+ const log: string[] = [];
+ const json = (p: string) => JSON.parse(readFileSync(join(legacyDir, p), "utf8"));
+
+ /** Copy a legacy public image into _attachments/ / and return its wikilink. */
+ const attach = (publicPath: string, area: string): string => {
+ const from = join(legacyDir, "public", publicPath);
+ const existing = attachments.get(from);
+ if (existing) return toWikilink(basename(existing.to));
+ const ext = extname(publicPath).toLowerCase();
+ let name = `${slugify(basename(publicPath, extname(publicPath)))}${ext}`;
+ for (let i = 2; usedNames.has(name); i++) name = `${slugify(basename(publicPath, extname(publicPath)))}-${i}${ext}`;
+ usedNames.add(name);
+ attachments.set(from, { from, to: `_attachments/${area}/${name}` });
+ return toWikilink(name);
+ };
+
+ const noteNames = new Set();
+ const add = (data: Record, body = "") => {
+ let path = notePath(data);
+ let name = basename(path, ".md").toLowerCase();
+ if (noteNames.has(name)) {
+ // Same title as an earlier note: keep both linkable by naming the type.
+ const qualifier = String(data.medium ?? data.type);
+ const renamed = notePath({ ...data, title: `${String(data.title)} (${qualifier})` });
+ const fixedPath = renamed.replace(/[^/]+\.md$/, `${safeFileName(`${String(data.title)} (${qualifier})`)}.md`);
+ log.push(`Two notes are called "${String(data.title)}", so one file is named "${basename(fixedPath, ".md")}".`);
+ path = fixedPath;
+ name = basename(path, ".md").toLowerCase();
+ data = { ...data, slug: slugify(`${String(data.title)} ${qualifier}`) };
+ }
+ noteNames.add(name);
+ notes.push({ path, text: writeNote(data, body), data, body });
+ return basename(path, ".md");
+ };
+ const link = (title: string) => toWikilink(safeFileName(title));
+
+ // ------------------------------------------------------------ Site, pages, kinds
+ add({ type: "site-settings", publish: true, ...seed.site });
+ add({ type: "status-kinds", title: "Status kinds", publish: true, kinds: seed.statusKinds });
+ for (const p of seed.pages) {
+ const { body, source: _source, ...rest } = p;
+ add({ type: "page", created: today, ...rest }, body);
+ }
+
+ // ------------------------------------------------------------ Library: reading
+ const readingMedium: Record = {
+ Books: "book",
+ "Light Novels": "light novel",
+ Webnovels: "webnovel",
+ Webtoons: "manhwa",
+ };
+ // The legacy list grouped manga and manhwa together; these two are Japanese manga.
+ const mangaTitles = new Set(["Tokyo Revengers", "Wistoria: Wand and Sword"]);
+ const readingState: Record = {
+ Complete: { state: "finished" },
+ Reading: { state: "now", kind: "Reading" },
+ Listening: { state: "now", note: "Listening to the audiobooks.", kind: "Listening" },
+ Rereading: { state: "now", note: "Rereading.", kind: "Reading" },
+ "Reread-pending": { state: "up next", note: "Planning a reread." },
+ Next: { state: "up next" },
+ New: { state: "up next" },
+ };
+ const nowItems: { title: string; kind: string; note?: string }[] = [];
+ for (const r of json("src/data/reading.json") as any[]) {
+ let title: string = r.title;
+ let creator: string | undefined;
+ const dash = /^(.+?) — (.+)$/.exec(title);
+ const paren = /^(.+?) \(([^)]+)\)$/.exec(title);
+ if (dash) [creator, title] = [dash[1]!, dash[2]!];
+ else if (paren) [title, creator] = [paren[1]!, paren[2]!];
+ const medium = mangaTitles.has(title) ? "manga" : r.category === "Manga & Manhwa" ? "manhwa" : readingMedium[r.category];
+ if (!medium) throw new Error(`Unknown reading category ${r.category}`);
+ const s = readingState[r.status];
+ if (!s) throw new Error(`Unknown reading status ${r.status}`);
+ const note = [s.note, r.note].filter(Boolean).join(" ") || undefined;
+ const name = add({
+ type: "library-item",
+ title,
+ publish: true,
+ favorite: r.favorite || undefined,
+ medium,
+ state: s.state,
+ creator,
+ verdict: r.blurb,
+ note,
+ cover: r.image ? attach(r.image, "Library") : undefined,
+ created: today,
+ });
+ if (s.kind) nowItems.push({ title: name, kind: s.kind });
+ }
+ log.push("Reading: \"Author — Series\" titles were split into a title and a creator.");
+
+ // ------------------------------------------------------------ Library: screen
+ const merged = (file: string) => {
+ // The legacy files list a favorite twice: once with its blurb and once in its group.
+ const byTitle = new Map();
+ for (const item of json(file) as any[]) {
+ const prev = byTitle.get(item.title);
+ if (prev) log.push(`${item.title}: two legacy entries were merged into one note.`);
+ byTitle.set(item.title, { ...prev, ...Object.fromEntries(Object.entries(item).filter(([, v]) => v != null)) });
+ }
+ return [...byTitle.values()];
+ };
+ const screenState: Record = { Completed: "finished", "Caught up": "caught up", Watching: "now" };
+ for (const [file, medium] of [
+ ["src/data/media/anime.json", "anime"],
+ ["src/data/media/tv.json", "TV"],
+ ["src/data/media/movies.json", "movie"],
+ ] as const) {
+ for (const m of merged(file)) {
+ const state = m.status ? screenState[m.status] : "finished";
+ if (!state) throw new Error(`Unknown status ${m.status}`);
+ const name = add({
+ type: "library-item",
+ title: m.title,
+ publish: true,
+ favorite: m.favorite || undefined,
+ medium,
+ state,
+ group: m.category,
+ year: m.year,
+ verdict: m.blurb,
+ note: m.note,
+ cover: m.image ? attach(m.image, "Library") : undefined,
+ created: today,
+ });
+ if (state === "now") nowItems.push({ title: name, kind: "Watching", note: m.note });
+ }
+ }
+ log.push("Movies and shows with no status in the legacy lists were marked finished, since the lists were what Greg had watched.");
+
+ // ------------------------------------------------------------ Library: video games
+ const hallDir = join(legacyDir, "src/content/games/hall-of-fame");
+ for (const file of readdirSync(hallDir).sort()) {
+ const { frontmatter: g } = splitNote(readFileSync(join(hallDir, file), "utf8")) as { frontmatter: any };
+ add({
+ type: "library-item",
+ title: g.title,
+ publish: true,
+ favorite: true,
+ medium: "video game",
+ state: "played",
+ platform: g.platform,
+ year: g.year,
+ verdict: String(g.blurb).trim(),
+ cover: g.art ? attach(g.art, "Library") : undefined,
+ created: today,
+ });
+ }
+ const nowPlaying = splitNote(`---\n${readFileSync(join(legacyDir, "src/content/games/now-playing.yaml"), "utf8")}\n---\n`)
+ .frontmatter as { games: { title: string; platform: string }[] };
+ for (const g of nowPlaying.games) {
+ const name = add({ type: "library-item", title: g.title, publish: true, medium: "video game", state: "now", platform: g.platform, created: today });
+ nowItems.push({ title: name, kind: "Playing" });
+ }
+ log.push("The Gaming Hall of Fame became favorite video games with the state played.");
+
+ // ------------------------------------------------------------ The Shelf
+ for (const b of json("src/data/boardgames.json") as any[]) {
+ if (/^hhttps:/.test(b.image)) {
+ b.image = b.image.slice(1);
+ log.push(`${b.name}: fixed a typo in its cover link (hhttps).`);
+ }
+ add({
+ type: "board-game",
+ title: b.name,
+ publish: true,
+ favorite: seed.favoriteBoardGames.includes(b.name) || undefined,
+ bggId: b.bggId,
+ blurb: b.note,
+ owned: true,
+ cover: b.image,
+ created: today,
+ });
+ }
+
+ // ------------------------------------------------------------ Workshop
+ for (const s of seed.stations) add({ type: "station", publish: true, created: today, ...s });
+ for (const t of seed.tools) add({ type: "tool", publish: true, created: today, ...t, station: link(t.station) });
+ seed.crafts.forEach((c, i) =>
+ add({
+ type: "craft",
+ publish: true,
+ created: today,
+ title: c.title,
+ status: c.status,
+ blurb: "blurb" in c ? c.blurb : undefined,
+ station: "station" in c ? link(c.station) : undefined,
+ tools: "tools" in c ? c.tools.map(link) : undefined,
+ order: i + 1,
+ }),
+ );
+ for (const m of seed.makes) {
+ add({
+ type: "make",
+ title: m.title,
+ publish: m.publish ?? true,
+ created: today,
+ craft: link(m.craft),
+ photos: m.photo ? [attach(m.photo, "Workshop")] : undefined,
+ cover: m.photo ? attach(m.photo, "Workshop") : undefined,
+ blurb: m.blurb,
+ tags: m.tags,
+ links: m.links?.map(link),
+ });
+ }
+
+ // ------------------------------------------------------------ Tabletop
+ seed.systems.forEach((s, i) => add({ type: "tabletop-system", publish: true, created: today, ...s, order: i + 1 }));
+ for (const c of seed.campaigns) {
+ const { body, system, ...rest } = c as typeof c & { body?: string; system?: string };
+ add({ type: "campaign", created: today, ...rest, system: system ? link(system) : undefined }, body);
+ }
+ seed.cardGames.forEach((g, i) => add({ type: "card-game", publish: true, created: today, ...g, order: i + 1 }));
+ for (const d of seed.decks) add({ type: "deck", publish: true, created: today, ...d, game: link(d.game) });
+
+ // ------------------------------------------------------------ Resume
+ const expDir = join(legacyDir, "src/content/experience");
+ const months: Record = { Jan: "01", Feb: "02", Mar: "03", Apr: "04", May: "05", Jun: "06", Jul: "07", Aug: "08", Sep: "09", Oct: "10", Nov: "11", Dec: "12" };
+ const month = (s: string) => {
+ const m = /^([A-Z][a-z]{2}) (\d{4})$/.exec(s.trim());
+ return m ? `${m[2]}-${months[m[1]!]}` : undefined;
+ };
+ for (const file of readdirSync(expDir).sort()) {
+ const { frontmatter: e, body } = splitNote(readFileSync(join(expDir, file), "utf8")) as { frontmatter: any; body: string };
+ const [start, end] = String(e.period).split(" - ");
+ const bullets = body.split("\n").filter((l) => l.startsWith("- ")).map((l) => l.slice(2).trim());
+ const extra = body.split("\n").filter((l) => l.trim() && !l.startsWith("- ") && !/^Key contributions:?$/.test(l.trim())).join("\n");
+ add(
+ {
+ type: "experience",
+ title: `${e.title} at ${e.company}`,
+ publish: true,
+ company: e.company,
+ role: e.title,
+ start: month(start!),
+ end: end && end.trim() !== "Present" ? month(end) : undefined,
+ focus: e.focus,
+ bullets,
+ created: today,
+ },
+ extra,
+ );
+ }
+ add({ type: "skills", title: "Skills", publish: true, ...seed.skills });
+ add({ type: "education", title: "Education", publish: true, ...seed.education });
+
+ // Projects become case study posts. Their bodies were never written, so they
+ // stay unpublished until the professional pass writes them.
+ const projDir = join(legacyDir, "src/content/projects");
+ for (const file of readdirSync(projDir).sort()) {
+ const { frontmatter: p } = splitNote(readFileSync(join(projDir, file), "utf8")) as { frontmatter: any };
+ const key = basename(file, ".md");
+ const extra = seed.caseStudies[key] ?? {};
+ const summary = String(p.summary).trim();
+ const firstSentence = /^.+?[.!?](\s|$)/.exec(summary)?.[0].trim() ?? summary;
+ add(
+ {
+ type: "post",
+ title: p.title,
+ publish: false,
+ date: today,
+ topic: "Work and tech",
+ postType: "case study",
+ summary: firstSentence,
+ role: p.role,
+ tech: p.tech,
+ employer: extra.employer,
+ facts: extra.facts,
+ created: today,
+ },
+ `${summary}\n\n`,
+ );
+ }
+ log.push("The four legacy projects became case study posts, unpublished until each one is written up.");
+
+ // ------------------------------------------------------------ Store
+ const store = json("src/data/store-availability.json");
+ const products = readProducts(legacyDir);
+ const tabletopTerms = products.requirements.map((r: any) => ({
+ kind: r.icon === "information" ? "info" : r.icon === "alert" ? "warning" : "rule",
+ title: r.title,
+ body: r.body,
+ }));
+ const general = new Set(["All sales are final", "Limited capacity"]);
+ products.items.forEach((p: any, i: number) => {
+ add(
+ {
+ type: "product-line",
+ title: p.title,
+ publish: true,
+ category: "Tabletop",
+ mode: "made to order",
+ channel: "here",
+ summary: p.summary,
+ icon: p.icon,
+ order: i + 1,
+ slots: store.slots[p.slotKey],
+ images: p.images.map((im: any) => ({ file: attach(im.src, "Store"), alt: im.alt, label: im.label })),
+ specs: p.specs,
+ priceTiers: p.pricing,
+ options: p.colors?.map((c: any) => ({ group: "Filament", name: c.name, hex: c.hex, inStock: c.inStock })),
+ // The legacy terms were written for the proxy cards.
+ terms: p.slotKey === "cards" ? tabletopTerms.filter((t: any) => !general.has(t.title)) : undefined,
+ created: today,
+ },
+ );
+ });
+ seed.futureLines.forEach((l, i) =>
+ add({
+ type: "product-line",
+ title: l.title,
+ publish: false,
+ category: l.category,
+ mode: "made to order",
+ channel: "coming soon",
+ summary: "Coming soon.",
+ order: products.items.length + i + 1,
+ priceTiers: [],
+ created: today,
+ }),
+ );
+ add({
+ type: "store-settings",
+ title: "Store settings",
+ publish: true,
+ categories: [
+ { name: "Tabletop", ordersOpen: store.ordersOpen, turnaround: store.turnaround },
+ { name: "Paper", ordersOpen: false },
+ { name: "Leather", ordersOpen: false },
+ ],
+ capacityNote: store.maxNote,
+ terms: tabletopTerms.filter((t: any) => general.has(t.title)),
+ });
+
+ // ------------------------------------------------------------ Status log
+ for (const item of nowItems) {
+ add({ type: "status", title: item.title, publish: true, kind: item.kind, date: today, subject: toWikilink(item.title), note: item.note });
+ }
+ for (const system of ["Dungeons & Dragons", "Daggerheart"]) {
+ add({ type: "status", title: system, publish: true, kind: "Running", date: today, subject: link(system) });
+ }
+ log.push(`Every status is dated ${today}, the day of the migration, because the legacy site did not record when each one started.`);
+
+ return { notes, attachments: [...attachments.values()], log };
+}
+
+/** The legacy store data is a TypeScript module; loadProducts reads it. */
+function readProducts(legacyDir: string): { items: any[]; requirements: any[] } {
+ return productsCache.get(legacyDir) ?? (() => {
+ throw new Error("Call loadProducts(legacyDir) before build()");
+ })();
+}
+const productsCache = new Map();
+export async function loadProducts(legacyDir: string) {
+ // Transpile on its own, so nothing from the legacy project's config or
+ // dependencies is loaded. The module only declares data.
+ const source = readFileSync(join(legacyDir, "src/data/store-products.ts"), "utf8");
+ const js = ts.transpileModule(source, { compilerOptions: { module: ts.ModuleKind.ESNext, target: ts.ScriptTarget.ES2022 } }).outputText;
+ const mod = await import(`data:text/javascript;base64,${Buffer.from(js).toString("base64")}`);
+ productsCache.set(legacyDir, { items: mod.products, requirements: mod.requirements });
+}
diff --git a/tools/migrate/src/cli.ts b/tools/migrate/src/cli.ts
new file mode 100644
index 0000000..f98b6d8
--- /dev/null
+++ b/tools/migrate/src/cli.ts
@@ -0,0 +1,73 @@
+/**
+ * npm run migrate -- [outDir]
+ *
+ * Writes the migrated Site folder to out/24.01_PersonalWebsite/Site (or the
+ * given folder), with the vault kit's templates, then checks every note and
+ * the vault as a whole and writes MIGRATION-REPORT.md beside it.
+ */
+import { cpSync, existsSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
+import { basename, dirname, join, resolve } from "node:path";
+import { checkNote, checkVault } from "@gk/schema";
+import { build, loadProducts } from "./build.ts";
+
+const root = resolve(import.meta.dirname, "../../..");
+const legacy = join(root, "legacy");
+const outRoot = resolve(process.argv[2] ?? join(root, "out"));
+const area = join(outRoot, "24.01_PersonalWebsite");
+const site = join(area, "Site");
+
+await loadProducts(legacy);
+const result = build(legacy);
+
+rmSync(area, { recursive: true, force: true });
+cpSync(join(root, "vault-kit/24.01_PersonalWebsite"), area, { recursive: true });
+for (const n of result.notes) {
+ mkdirSync(dirname(join(site, n.path)), { recursive: true });
+ writeFileSync(join(site, n.path), n.text);
+}
+for (const a of result.attachments) {
+ if (!existsSync(a.from)) throw new Error(`Missing legacy image ${a.from}`);
+ mkdirSync(dirname(join(site, a.to)), { recursive: true });
+ cpSync(a.from, join(site, a.to));
+}
+
+// Check what was written, the way the content service will read it.
+const problems: string[] = [];
+const vaultNotes = result.notes.map((n) => {
+ const checked = checkNote(n.text);
+ if (!checked.ok) problems.push(`${n.path}: ${checked.problems.map((p) => `${p.field} ${p.message}`).join("; ")}`);
+ return { path: n.path, type: String(n.data.type), data: checked.ok ? checked.data : n.data, body: n.body };
+});
+for (const p of checkVault(vaultNotes, result.attachments.map((a) => basename(a.to)))) problems.push(`${p.path}: ${p.message}`);
+
+const counts = new Map();
+for (const n of result.notes) {
+ const c = counts.get(String(n.data.type)) ?? { all: 0, published: 0 };
+ c.all++;
+ if (n.data.publish) c.published++;
+ counts.set(String(n.data.type), c);
+}
+const unpublished = result.notes.filter((n) => !n.data.publish).map((n) => `- ${n.path}`);
+const report = `# Migration report
+
+${result.notes.length} notes and ${result.attachments.length} images, checked against the schema: ${problems.length ? `**${problems.length} problems**` : "no problems"}.
+
+| Type | Notes | Published |
+| --- | --- | --- |
+${[...counts].map(([t, c]) => `| ${t} | ${c.all} | ${c.published} |`).join("\n")}
+
+## Problems
+
+${problems.length ? problems.map((p) => `- ${p}`).join("\n") : "None."}
+
+## Choices the migration made
+
+${result.log.map((l) => `- ${l}`).join("\n")}
+
+## Unpublished, waiting on you
+
+${unpublished.join("\n")}
+`;
+writeFileSync(join(outRoot, "MIGRATION-REPORT.md"), report);
+console.log(report.split("\n").slice(0, 3).join("\n"));
+if (problems.length) process.exit(1);
diff --git a/tools/migrate/src/seed.ts b/tools/migrate/src/seed.ts
new file mode 100644
index 0000000..334da5a
--- /dev/null
+++ b/tools/migrate/src/seed.ts
@@ -0,0 +1,278 @@
+/**
+ * Content that lived inside the legacy site's page code rather than in data
+ * files, extracted by hand once. Every entry names where it came from:
+ * a legacy file, or Greg's own answers during the 2026-10-11 redesign. Nothing
+ * here is invented; anything not known yet is left out or kept unpublished.
+ */
+
+export const MIGRATED_ON = "2026-10-11";
+
+// ---------------------------------------------------------------- Site
+
+/** legacy/src/lib/site.ts, legacy/src/pages/about.astro, legacy/src/lib/archidekt.ts */
+export const site = {
+ title: "Site settings",
+ name: "Greg Kilmer",
+ tagline: "Developer. Maker. Storyteller.",
+ lockup: "Web Development · a Patiun LLC company",
+ location: "Philadelphia, PA",
+ socials: [
+ { label: "GitHub", handle: "Patiun", url: "https://github.com/patiun" },
+ { label: "LinkedIn", handle: "Gregory Kilmer", url: "https://www.linkedin.com/in/gregory-kilmer-36646bb5/" },
+ { label: "Archidekt", handle: "Patiun", url: "https://archidekt.com/search/decks?ownerUsername=Patiun" },
+ ],
+ footer: "Built with intention. Powered by caffeine.",
+};
+
+/** The status log's kinds. Icons are mdi names until the brand icon set lands. */
+export const statusKinds = [
+ { label: "Reading", icon: "book-open-variant", page: "/library" },
+ { label: "Listening", icon: "headphones", page: "/library" },
+ { label: "Watching", icon: "television-classic", page: "/library" },
+ { label: "Playing", icon: "gamepad-variant", page: "/library" },
+ { label: "Running", icon: "dice-multiple", page: "/tabletop" },
+ { label: "Making", icon: "hammer-wrench", page: "/workshop" },
+ { label: "Learning", icon: "school", page: "/workshop" },
+];
+
+// ---------------------------------------------------------------- Pages
+
+/** Written introductions, verbatim from the legacy pages. */
+export const pages = [
+ {
+ title: "Landing",
+ page: "/",
+ publish: true,
+ body: "I build things with code during the day and with resin, plastic, and imagination at night. I lead cloud and mobile development at Implico Inc., run D&D campaigns that span years, and fill my shelves with Brandon Sanderson novels, custom dice, and board games. This is the place where all of those worlds meet.",
+ source: "legacy/src/pages/index.astro",
+ },
+ {
+ title: "About me",
+ page: "/about",
+ publish: true,
+ quickFacts: [
+ { icon: "map-marker", text: "Philadelphia, PA" },
+ { icon: "briefcase", text: "BTP, Cloud, and Mobile App Lead at Implico Inc." },
+ { icon: "dog", text: "Dog dad to Kashi the mini dachshund" },
+ { icon: "dice-multiple", text: "Running D&D & Daggerheart campaigns" },
+ { icon: "book-open-variant", text: "Currently tandem reading: Heir of Fire and Tower of Dawn by Sarah J. Maas" },
+ { icon: "tea", text: "Fueled by loose-leaf Chinese tea and Mountain Dew" },
+ ],
+ body: `I am Greg: a software developer and technical team lead based in Philadelphia who spends his free time running tabletop RPGs, making resin dice and custom cards, 3D printing anything I can, and reading his way through fantasy series at an alarming rate. I live with my partner Krystina, our mini dachshund Kashi, and more board games than any reasonable apartment should hold.
+
+## The longer arc
+
+I grew up loving books and games, and not much has changed. Technology pulled me into a career building cloud and mobile applications, but I never stopped collecting dice, designing campaigns, or losing weekends to Sanderson reads. I am currently expanding into SAP's Business Technology Platform, exploring AI and machine learning, and trying to convince my group that yes, we can fit one more board game into game night.
+
+I believe the best work happens when curiosity isn't compartmentalized. The same system-thinking that makes a good developer makes a good gamemaster. The patience that produces a clean resin cast is the same patience that debugs a stubborn deployment. This site is where I don't have to pretend those things are separate.`,
+ source: "legacy/src/pages/about.astro",
+ },
+ {
+ title: "Workshop",
+ page: "/workshop",
+ publish: true,
+ lede: "A magnet for super glue and nice cardboard.",
+ body: `Making is part of me. Ever since I can remember, I had been dreaming of making inventions and things from cardboard and clay. I took things apart to see how they worked, and rarely got them back together. But now I have the tools and the skills to take ideas from my imagination and physically produce them. I cast resin dice using pressure pots and custom molds, print miniatures and props on my Elegoo Mars 4 (resin) and Bambu Labs P1S (FDM), and generally fill my workspace with projects-in-progress. I make beautiful cards as gifts and even make proxy mockups of trading cards to enjoy games casually.
+
+There's something deeply satisfying about taking raw material through a process and holding the finished piece, whether that's a perfect polyhedral set or a hook that fits just right in that weird spot in the bathroom. The ability to blend my technical skills and my creative impulses into tangible objects is a fantasy come true my child self only dreamed of.`,
+ source: "legacy/src/pages/interests/making.astro",
+ },
+ {
+ title: "Tabletop",
+ page: "/tabletop",
+ publish: true,
+ lede: "Long-running D&D and Daggerheart campaigns on Foundry VTT, the MCDM Crows playtest, and a growing board game collection.",
+ body: `I love playing tabletop games from trading card games to role-playing games. I run Dungeons & Dragons and Daggerheart campaigns that span years of real-time play, maintaining worlds, NPCs, and narrative threads that my players remember years later. I host everything on Foundry VTT, which lets me build the kind of dynamic, visually rich experiences that keep players invested session after session and lets me flex my technical skills.
+
+I also love board games, from strategy to social deduction. My collection is growing, and I enjoy hosting game nights with friends. I have a particular fondness for games that challenge players to think critically and creatively, and I appreciate the social aspect of gaming as much as the gameplay itself. Although, I do love the ability to play some of the engine building and deck-building games solo. Spirit Island and Wyrmspan have been my favorites lately, and I am always on the lookout for new games to add to my collection.
+
+Trading card games are where my collecting, deckbuilding, and making instincts all collide. I primarily play Magic: The Gathering, mostly Commander (Elder Dragon Highlander), where a hundred cards becomes a personal expression and the brewing never really ends. But I keep a hand in the Pokémon TCG, and I'm watching Riftbound and the new Gundam card game closely. If it has nice cardboard, pretty art, and a decision tree, I'm interested.
+
+The hobby overlaps the rest of the fun wing: the same workshop that produces resin dice and 3D prints also turns out proxies, tokens, and deckboxes and more. And the deckbuilding itch is a close relation of the RPG character-builder in me. Optimizing a list tells a story about how I want to play and the theme I build the deck around.`,
+ source: "legacy/src/pages/interests/ttrpgs.astro, legacy/src/pages/interests/tcgs.astro",
+ },
+ {
+ title: "Video games",
+ page: "/library",
+ publish: false,
+ tags: ["legacy text"],
+ body: `Gaming has always been a passion in my life. I span console generations from the Sega Genesis to the modern era. I play mainly with a PlayStation 5, Nintendo Switch 2, and a Steam library that has gotten politely out of hand. I'm particularly drawn to RPGs, strategy games, and anything with a creature to tame or a deeply layered world to explore. But a good game loop will get me sucked in harder than the sunk cost fallacy of a League of Legends streak.
+
+I love to play with friends, but have found myself leaning more towards single-player experiences. Someone should have told younger me that scheduling would be the final boss!
+
+## Platforms
+
+- **PlayStation 5.** Mainly for single player and exclusives.
+- **Nintendo Switch / Switch 2.** Handheld and docked. Pokemon, Mario, and many other Nintendo and indie gems.
+- **Steam.** The primary game library. Strategy, RPGs, Adventure, oh my! And an ever-growing backlog.
+
+## Favorite genres
+
+- **RPGs (Western & JRPG).** Character progression and stories I can live in for 100+ hours and then turn around and do it again.
+- **Creature Collecting.** If I can catch, train, or befriend a roster, I'm sold.
+- **Strategy & Tactics.** Turn-based puzzles and tactical challenges where the right plan is oh so rewarding.
+- **Building & Crafting.** Give me a grid, resources, and a goal, and watch the evening vanish.
+- **Automation.** Optimizing a production line until its perfect is my zen.
+- **Open-world Exploration.** The map IS the point. I will try to climb every mountain and I will check every waterfall for treasure.
+- **Indie Darlings.** The small, weird, passionate projects that punch far above their size.`,
+ source: "legacy/src/pages/interests/gaming.astro",
+ },
+ {
+ title: "Pokémon",
+ page: "/library",
+ publish: false,
+ tags: ["legacy text"],
+ body: `Pokémon has been a lifelong obsession. From Red and Blue on the original Game Boy to modern competitive formats, I've followed the franchise through every generation. My favorite? Metagross, the iron leg Pokémon. Steel/Psychic, beautiful design, absolutely devastating in battle. No further questions.`,
+ source: "legacy/src/pages/interests/pokemon.astro",
+ },
+];
+
+// ---------------------------------------------------------------- Workshop
+
+/** Greg's workspace description, 2026-10-11 redesign interview. */
+export const stations = [
+ { title: "Print station", where: "Work counter and filament wall", goodFor: "Deck boxes, terrain, props", order: 1 },
+ { title: "Print and cut desk", where: "The smaller desk", goodFor: "Proxy cards, stickers, decals", order: 2 },
+ { title: "Main desk", where: "Monitors plus a large cutting mat", goodFor: "Card finishing, leatherwork soon", order: 3 },
+ { title: "Fume hood", where: "Custom built for resin", goodFor: "Minis, dice, rings", order: 4 },
+];
+
+/** Blurbs from legacy/src/pages/interests/making.astro; placement from the interview. */
+export const tools = [
+ { title: "Bambu Labs P1S", kind: "printer", station: "Print station", blurb: "FDM 3D printer. Functional prints, terrain pieces, and larger prototypes." },
+ { title: "Filament wall", kind: "material", station: "Print station" },
+ { title: "Epson 8500", kind: "printer", station: "Print and cut desk" },
+ { title: "Silhouette Cameo 5a", kind: "cutter", station: "Print and cut desk", blurb: "Multi material cutting machine. Silly stickers, custom vinyl decals, and custom/proxy TCG cards." },
+ { title: "Cutting mat", kind: "finishing", station: "Main desk" },
+ { title: "Elegoo Mars 4", kind: "printer", station: "Fume hood", blurb: "Resin 3D printer. Miniatures, terrain, and high-detail props." },
+ { title: "Pressure pot", kind: "casting", station: "Fume hood" },
+ { title: "Dice molds", kind: "casting", station: "Fume hood" },
+] as const;
+
+/** Crafts and statuses from the interview; the resin blurb from making.astro. */
+export const crafts = [
+ { title: "Resin", status: "Practicing", station: "Fume hood", tools: ["Pressure pot", "Dice molds"], blurb: "Custom molds, pressure pot curing, pigment experimentation. Producing dice sets for personal use and gifts." },
+ { title: "3D printing", status: "Practicing", station: "Print station", tools: ["Bambu Labs P1S", "Elegoo Mars 4", "Filament wall"] },
+ { title: "Cards and decals", status: "Practicing", station: "Print and cut desk", tools: ["Silhouette Cameo 5a", "Epson 8500"] },
+ { title: "Paper and bookbinding", status: "Learning" },
+ { title: "Leather", status: "Getting into", station: "Main desk", tools: ["Cutting mat"] },
+ { title: "Props and gifts", status: "Practicing" },
+] as const;
+
+/** The legacy gallery (making.astro) with its captions, plus the sword rack from the interview. */
+export const makes = [
+ { title: "Teal and gold leaf dice", craft: "Resin", photo: "/images/making-resin.jpg", blurb: "Resin dice set, teal with gold leaf inclusions." },
+ { title: "Golden moss dice", craft: "Resin", photo: "/images/dice_golden_moss.jpg", blurb: "Resin dice set, a moss-like pattern with gold lettering." },
+ { title: "Scarlet dice", craft: "Resin", photo: "/images/dice_scarlet.jpg", blurb: "Resin dice set, scarlet with gold lettering." },
+ { title: "Galaxy dice", craft: "Resin", photo: "/images/dice_galaxy.jpg", blurb: "Resin dice set, a dark galaxy pattern with gold powder swirls." },
+ { title: "Seastone dice", craft: "Resin", photo: "/images/dice_seastone.jpg", publish: false },
+ { title: "Resin and painted rings", craft: "Resin", photo: "/images/resin_rings.jpg" },
+ { title: "Miniature", craft: "3D printing", photo: "/images/minis.jpg", blurb: "3D printed miniature." },
+ { title: "Deck boxes", craft: "3D printing", photo: "/images/deckboxes.jpg", blurb: "Design by @Iceenchanted on MakerWorld." },
+ { title: "Kynaios and Tiro proxies", craft: "Cards and decals", photo: "/images/proxies.jpg", blurb: "Proxy cards for my Kynaios and Tiro deck.", links: ["Kynaios and Tiro"] },
+ { title: "Foil proxies", craft: "Cards and decals", photo: "/images/proxies2.jpg", blurb: "Successful foil proxies for Magic." },
+ { title: "Christmas gifts for family", craft: "Props and gifts", photo: "/images/xmasgifts.jpg", blurb: "CS:GO cases, Nightblood, a Runescape helmet and ACOTAR drawers." },
+ // The sword rack, 3D printed (interview, round 1)
+ { title: "Nightblood", craft: "3D printing", blurb: "From the Cosmere.", tags: ["sword rack"] },
+ { title: "Ronin lightsaber", craft: "3D printing", blurb: "From Star Wars Visions.", tags: ["sword rack"] },
+ { title: "Wado Ichimonji", craft: "3D printing", blurb: "Zoro's blade, One Piece.", tags: ["sword rack"] },
+ { title: "Enma", craft: "3D printing", blurb: "Zoro's blade, One Piece.", tags: ["sword rack"] },
+ { title: "Sandai Kitetsu", craft: "3D printing", blurb: "Zoro's blade, One Piece.", tags: ["sword rack"] },
+ { title: "Shusui", craft: "3D printing", blurb: "Zoro's blade, One Piece.", tags: ["sword rack"] },
+ { title: "Tensa Zangetsu", craft: "3D printing", blurb: "Ichigo's blade, Bleach.", tags: ["sword rack"] },
+ { title: "Tanjiro's Nichirin blade", craft: "3D printing", blurb: "Tanjiro's final blade, Demon Slayer.", tags: ["sword rack"] },
+];
+
+// ---------------------------------------------------------------- Tabletop
+
+/** Blurbs from legacy/src/pages/interests/ttrpgs.astro; states from the interview. */
+export const systems = [
+ { title: "Dungeons & Dragons", state: "running", blurb: "Lately 5th Edition. Long-running campaigns on and off Foundry VTT. Focus on rich worldbuilding, meaningful NPC relationships, and player-driven narrative." },
+ { title: "Daggerheart", state: "running", blurb: "Darrington Press's dice-building RPG. Currently running a campaign and exploring how its mechanics create different story rhythms than D&D." },
+ { title: "Cypher System", state: "played", blurb: "Monte Cook's narrative-focused system. Played a few one-shots and short form campaigns." },
+ { title: "Draw Steel", state: "want to run", blurb: "MCDM's fantasy tactical cinematic combat game. Interested in running it when I get a chance." },
+ { title: "Blades in the Dark", state: "want to play", blurb: "A gritty, cinematic system for heists and criminal enterprises. Interested in playing short campaigns in it one day." },
+ { title: "Avatar Legends", state: "want to try", blurb: "A narrative-focused system set in the world of Avatar. Interested in exploring bending mechanics and character-driven stories." },
+] as const;
+
+/**
+ * The campaigns are real, but their names and schedules are not known yet,
+ * so the D&D and Daggerheart ones stay unpublished until Greg fills them in.
+ */
+export const campaigns = [
+ { title: "D&D campaign", system: "Dungeons & Dragons", status: "active", platform: "Foundry VTT", publish: false, body: "Name, schedule and a one-line status to fill in before publishing." },
+ { title: "Daggerheart campaign", system: "Daggerheart", status: "active", platform: "Foundry VTT", publish: false, body: "Name, schedule and a one-line status to fill in before publishing." },
+ { title: "MCDM Crows playtest", status: "playtest", publish: true },
+] as const;
+
+/** Blurbs from legacy/src/pages/interests/tcgs.astro; stances and Flesh and Blood from the interview. */
+export const cardGames = [
+ { title: "Magic: The Gathering", stance: "Main event", blurb: "Mostly Commander (EDH), with a deckbuilding obsession that Archidekt enables far too well." },
+ { title: "Pokémon TCG", stance: "Collecting", blurb: "Collector energy and casual play. Beautiful art pulls, deck drafts, and a soft spot for Steel/Psychic. Mostly on Pocket TCG." },
+ { title: "Flesh and Blood", stance: "Getting into" },
+ { title: "Riftbound", stance: "Exploring", blurb: "Riot's League of Legends TCG. A new game with a lot of potential. Interested in exploring deckbuilding and the evolving meta." },
+ { title: "Gundam Card Game", stance: "Watching", blurb: "Bandai's Gundam TCG. A new game with a lot of potential. Interested in how some of the figures will come into play with the other table top pieces for this entry." },
+] as const;
+
+/** The deck behind the legacy proxy photo. Its Archidekt id gets filled in by the sync. */
+export const decks = [{ title: "Kynaios and Tiro", game: "Magic: The Gathering", format: "Commander", favorite: true }];
+
+/** "Spirit Island and Wyrmspan have been my favorites lately" (ttrpgs.astro). */
+export const favoriteBoardGames = ["Spirit Island", "Wyrmspan"];
+
+// ---------------------------------------------------------------- Resume
+
+/** legacy/src/components/professional/SkillGrid.astro */
+export const skills = {
+ core: [
+ "Cloud Architecture & Development",
+ "Mobile Application Development",
+ "SAP Business Technology Platform (BTP)",
+ "Team Leadership & Technical Mentorship",
+ "Cross-functional Communication",
+ ],
+ learning: [
+ "SAP BTP deep-dive (active expansion)",
+ "Cutting edge AI-powered development tooling",
+ "Advanced automation and workflow engineering",
+ ],
+ stack: [
+ { label: "Languages", items: ["JavaScript", "TypeScript", "Java", "Python", "C#", "C", "SQL", "HTML", "CSS"] },
+ { label: "Frameworks", items: ["React", "Vue", "Astro", "Flask"] },
+ { label: "Cloud Platforms", items: ["SAP BTP", "Google Cloud Platform", "AWS"] },
+ { label: "Tools", items: ["PostgreSQL", "VSCode", "Unity", "XCode", "Android Studio", "Git", "Gitea", "CI/CD pipelines"] },
+ { label: "AI/ML", items: ["Machine learning frameworks", "LLM integration", "Claude Code", "Hermes", "OpenClaw"] },
+ ],
+};
+
+/** legacy/src/components/professional/EducationHistory.astro and Certifications.astro */
+export const education = {
+ degrees: [
+ {
+ credential: "B.S., Computer Science",
+ institution: "Drexel University",
+ period: "2014 – 2018",
+ detail: "Foundational coursework in software engineering, algorithms, and systems. Focuses in AI and human-computer interaction.",
+ },
+ ],
+ certifications: [{ name: "Planet 9 Development", issuer: "Neptune", year: "2019", status: "Active" as const }],
+};
+
+/** Employer and facts for the legacy projects; facts from the Implico and Cosmos entries. */
+export const caseStudies: Record = {
+ "carrier-connect": { employer: "Implico Inc.", facts: [{ label: "Active users", value: "1,000+" }, { label: "Companies", value: "100" }] },
+ "driver-app": { employer: "Implico Inc." },
+ "cosmos-homelab": { facts: [{ label: "AI agents", value: "17" }] },
+ "placeholder-project": {},
+};
+
+// ---------------------------------------------------------------- Store
+
+/** Lines Greg named on 2026-10-11 that are not made yet; kept unpublished. */
+export const futureLines = [
+ { title: "Book-bound journals", category: "Paper" },
+ { title: "Field notes journals", category: "Paper" },
+ { title: "Leather bracelets", category: "Leather" },
+ { title: "Journal folios", category: "Leather" },
+ { title: "Card holders", category: "Leather" },
+] as const;
diff --git a/tools/migrate/test/migrate.test.ts b/tools/migrate/test/migrate.test.ts
new file mode 100644
index 0000000..25d7588
--- /dev/null
+++ b/tools/migrate/test/migrate.test.ts
@@ -0,0 +1,62 @@
+import { basename, join, resolve } from "node:path";
+import { beforeAll, describe, expect, it } from "vitest";
+import { checkNote, checkVault } from "@gk/schema";
+import { build, loadProducts, type BuildResult } from "../src/build.ts";
+
+const legacy = resolve(import.meta.dirname, "../../../legacy");
+let result: BuildResult;
+
+beforeAll(async () => {
+ await loadProducts(legacy);
+ result = build(legacy);
+});
+
+const find = (type: string, title: string) => result.notes.find((n) => n.data.type === type && n.data.title === title);
+
+describe("migration", () => {
+ it("produces only valid notes and a consistent vault", () => {
+ const vault = result.notes.map((n) => {
+ const r = checkNote(n.text);
+ expect(r.ok, n.path).toBe(true);
+ return { path: n.path, type: String(n.data.type), data: n.data, body: n.body };
+ });
+ expect(checkVault(vault, result.attachments.map((a) => basename(a.to)))).toEqual([]);
+ });
+
+ it("carries every legacy list item over", () => {
+ const count = (t: string) => result.notes.filter((n) => n.data.type === t).length;
+ expect(count("board-game")).toBe(50);
+ // 24 reading + 108 anime + 103 TV + 152 movies + 11 hall of fame + 5 now playing
+ expect(count("library-item")).toBe(403);
+ expect(count("experience")).toBe(3);
+ });
+
+ it("merges a favorite listed twice into one note", () => {
+ const demonSlayer = result.notes.filter((n) => n.data.title === "Demon Slayer");
+ expect(demonSlayer).toHaveLength(1);
+ expect(demonSlayer[0]!.data).toMatchObject({ favorite: true, group: "Major Modern Hits", state: "finished" });
+ });
+
+ it("splits author from series", () => {
+ expect(find("library-item", "Cosmere")?.data).toMatchObject({ creator: "Brandon Sanderson", medium: "book" });
+ });
+
+ it("keeps the store's real prices and slots", () => {
+ const proxies = find("product-line", "Proxy MTG Cards")!.data as any;
+ expect(proxies.slots).toEqual({ open: 1, total: 3 });
+ expect(proxies.priceTiers).toContainEqual({ band: "100 card deck", price: "$40.00", note: "per set of cards" });
+ });
+
+ it("logs status for everything in progress", () => {
+ const kinds = result.notes.filter((n) => n.data.type === "status").map((n) => `${n.data.kind}: ${n.data.title}`);
+ expect(kinds).toContain("Playing: Slay the Spire 2");
+ expect(kinds).toContain("Watching: House of the Dragon");
+ expect(kinds).toContain("Listening: The Lord of the Rings");
+ });
+
+ it("never publishes anything still waiting on Greg", () => {
+ for (const title of ["D&D campaign", "Daggerheart campaign", "Carrier Connect", "Book-bound journals"]) {
+ expect(result.notes.find((n) => n.data.title === title)?.data.publish, title).toBe(false);
+ }
+ });
+});
diff --git a/tools/migrate/tsconfig.json b/tools/migrate/tsconfig.json
new file mode 100644
index 0000000..7680f99
--- /dev/null
+++ b/tools/migrate/tsconfig.json
@@ -0,0 +1,4 @@
+{
+ "extends": "../../tsconfig.base.json",
+ "include": ["src", "test"]
+}
diff --git a/tsconfig.base.json b/tsconfig.base.json
new file mode 100644
index 0000000..e041118
--- /dev/null
+++ b/tsconfig.base.json
@@ -0,0 +1,15 @@
+{
+ "compilerOptions": {
+ "target": "ES2023",
+ "module": "NodeNext",
+ "moduleResolution": "NodeNext",
+ "strict": true,
+ "noUncheckedIndexedAccess": true,
+ "allowImportingTsExtensions": true,
+ "noEmit": true,
+ "skipLibCheck": true,
+ "types": ["node"],
+ "verbatimModuleSyntax": true,
+ "isolatedModules": true
+ }
+}
diff --git a/vault-kit/24.01_PersonalWebsite/Site guide.md b/vault-kit/24.01_PersonalWebsite/Site guide.md
new file mode 100644
index 0000000..d0ed427
--- /dev/null
+++ b/vault-kit/24.01_PersonalWebsite/Site guide.md
@@ -0,0 +1,19 @@
+# Site guide
+
+Everything in the `Site` folder can appear on GregKilmer.com. Nothing outside
+it ever can.
+
+- **Going live.** A note is on the site only when its `publish` property is
+ true. Turn it off and the page comes down; nothing is deleted.
+- **New notes.** Use a template from `Site/_templates`. Each note type has its
+ own folder; the template's comments list the allowed values.
+- **Links.** Link notes the usual way, `[[Note name]]`. A post that links to
+ a make shows up on that make, its craft and the Workshop page.
+- **Status updates.** Add a note in `Now/` with a kind from `Now/_kinds`, a
+ date and the `[[note]]` it is about. The newest one per note wins.
+- **Images.** Put them in `Site/_attachments/` and link them by file name.
+- **Settings.** `_site.md`, `Now/_kinds.md` and `Store/_settings.md`.
+ Opening and closing store orders is one property in `Store/_settings.md`.
+
+If a change has a problem, the site keeps showing the last good version and
+the admin page explains what to fix.
diff --git a/vault-kit/24.01_PersonalWebsite/Site/_templates/Board game.md b/vault-kit/24.01_PersonalWebsite/Site/_templates/Board game.md
new file mode 100644
index 0000000..6dfd4d4
--- /dev/null
+++ b/vault-kit/24.01_PersonalWebsite/Site/_templates/Board game.md
@@ -0,0 +1,14 @@
+---
+type: "board-game"
+title: "{{title}}"
+publish: false # Only notes set to true appear on the site.
+created: "{{date:YYYY-MM-DD}}" # optional
+bggId:
+blurb:
+howItPlays: [] # optional. Like engine building, social deduction, co-op.
+owned: true # optional
+wantToPlay: false # optional
+lastPlayed: # optional
+---
+
+
diff --git a/vault-kit/24.01_PersonalWebsite/Site/_templates/Campaign.md b/vault-kit/24.01_PersonalWebsite/Site/_templates/Campaign.md
new file mode 100644
index 0000000..bc134e1
--- /dev/null
+++ b/vault-kit/24.01_PersonalWebsite/Site/_templates/Campaign.md
@@ -0,0 +1,13 @@
+---
+type: "campaign"
+title: "{{title}}"
+publish: false # Only notes set to true appear on the site.
+created: "{{date:YYYY-MM-DD}}" # optional
+system: # optional. Leave out for a playtest of an unreleased game.
+status: # one of: active, playtest, paused, finished
+schedule: # optional. Like Thursdays, every other week.
+platform: # optional. Like Foundry VTT.
+blurb: # optional
+---
+
+
diff --git a/vault-kit/24.01_PersonalWebsite/Site/_templates/Card game.md b/vault-kit/24.01_PersonalWebsite/Site/_templates/Card game.md
new file mode 100644
index 0000000..5a70159
--- /dev/null
+++ b/vault-kit/24.01_PersonalWebsite/Site/_templates/Card game.md
@@ -0,0 +1,9 @@
+---
+type: "card-game"
+title: "{{title}}"
+publish: false # Only notes set to true appear on the site.
+created: "{{date:YYYY-MM-DD}}" # optional
+stance: # Like Main event, Collecting, Getting into.
+blurb: # optional
+order: # optional
+---
diff --git a/vault-kit/24.01_PersonalWebsite/Site/_templates/Craft.md b/vault-kit/24.01_PersonalWebsite/Site/_templates/Craft.md
new file mode 100644
index 0000000..13cbcd5
--- /dev/null
+++ b/vault-kit/24.01_PersonalWebsite/Site/_templates/Craft.md
@@ -0,0 +1,13 @@
+---
+type: "craft"
+title: "{{title}}"
+publish: false # Only notes set to true appear on the site.
+created: "{{date:YYYY-MM-DD}}" # optional
+status: # one of: Practicing, Learning, Getting into, Paused
+blurb: # optional
+station: # optional
+tools: [] # optional
+order: # optional
+---
+
+
diff --git a/vault-kit/24.01_PersonalWebsite/Site/_templates/Deck.md b/vault-kit/24.01_PersonalWebsite/Site/_templates/Deck.md
new file mode 100644
index 0000000..a227dc2
--- /dev/null
+++ b/vault-kit/24.01_PersonalWebsite/Site/_templates/Deck.md
@@ -0,0 +1,11 @@
+---
+type: "deck"
+title: "{{title}}"
+publish: false # Only notes set to true appear on the site.
+created: "{{date:YYYY-MM-DD}}" # optional
+game:
+format: # optional. Like Commander.
+archidektId: # optional
+listUrl: # optional. For games not on Archidekt.
+take: # optional. One line on the deck.
+---
diff --git a/vault-kit/24.01_PersonalWebsite/Site/_templates/Experience.md b/vault-kit/24.01_PersonalWebsite/Site/_templates/Experience.md
new file mode 100644
index 0000000..dc961a5
--- /dev/null
+++ b/vault-kit/24.01_PersonalWebsite/Site/_templates/Experience.md
@@ -0,0 +1,16 @@
+---
+type: "experience"
+title: "{{title}}"
+publish: false # Only notes set to true appear on the site.
+created: "{{date:YYYY-MM-DD}}" # optional
+company:
+role:
+start:
+end: # optional. Leave out while it is your current role.
+location: # optional
+focus: # optional. One line under the role.
+bullets: []
+tech: [] # optional
+---
+
+
diff --git a/vault-kit/24.01_PersonalWebsite/Site/_templates/Library item.md b/vault-kit/24.01_PersonalWebsite/Site/_templates/Library item.md
new file mode 100644
index 0000000..a87a90e
--- /dev/null
+++ b/vault-kit/24.01_PersonalWebsite/Site/_templates/Library item.md
@@ -0,0 +1,18 @@
+---
+type: "library-item"
+title: "{{title}}"
+publish: false # Only notes set to true appear on the site.
+created: "{{date:YYYY-MM-DD}}" # optional
+medium: # one of: book, light novel, webnovel, manga, manhwa, video game, anime, TV, movie
+state: # one of: now, up next, played, finished, caught up, dropped
+creator: # optional
+group: # optional. Where it sits in the list, like Cosmere, Marvel Cinematic Universe or Prestige Drama.
+year: # optional
+platform: # optional. Video games only, like Steam or PlayStation 5.
+verdict: # optional. A short review. No score.
+note: # optional. Context, like where you are or how you came to it.
+progress: # optional. Like 16/18 volumes. Synced from PersonalReader when linked.
+anilistId: # optional. Links the item to PersonalReader's progress.
+---
+
+
diff --git a/vault-kit/24.01_PersonalWebsite/Site/_templates/Make.md b/vault-kit/24.01_PersonalWebsite/Site/_templates/Make.md
new file mode 100644
index 0000000..720a5e3
--- /dev/null
+++ b/vault-kit/24.01_PersonalWebsite/Site/_templates/Make.md
@@ -0,0 +1,12 @@
+---
+type: "make"
+title: "{{title}}"
+publish: false # Only notes set to true appear on the site.
+created: "{{date:YYYY-MM-DD}}" # optional
+craft:
+date: "{{date:YYYY-MM-DD}}" # optional
+photos: [] # optional
+blurb: # optional
+---
+
+
diff --git a/vault-kit/24.01_PersonalWebsite/Site/_templates/Page text.md b/vault-kit/24.01_PersonalWebsite/Site/_templates/Page text.md
new file mode 100644
index 0000000..a75c47a
--- /dev/null
+++ b/vault-kit/24.01_PersonalWebsite/Site/_templates/Page text.md
@@ -0,0 +1,10 @@
+---
+type: "page"
+title: "{{title}}"
+publish: false # Only notes set to true appear on the site.
+created: "{{date:YYYY-MM-DD}}" # optional
+page: # The page address this text belongs to, like /about.
+lede: # optional. The one line under the page title.
+---
+
+
diff --git a/vault-kit/24.01_PersonalWebsite/Site/_templates/Post.md b/vault-kit/24.01_PersonalWebsite/Site/_templates/Post.md
new file mode 100644
index 0000000..052879d
--- /dev/null
+++ b/vault-kit/24.01_PersonalWebsite/Site/_templates/Post.md
@@ -0,0 +1,17 @@
+---
+type: "post"
+title: "{{title}}"
+publish: false # Only notes set to true appear on the site.
+created: "{{date:YYYY-MM-DD}}" # optional
+date: "{{date:YYYY-MM-DD}}"
+topic: # one of: Making, Tabletop, Library, Work and tech, Life
+postType: "article" # optional. one of: article, build log, case study
+summary: # One or two sentences for lists and link previews.
+pinned: false # optional. Takes the top slot on the Blog.
+employer: # optional. Case studies only. Leave out for personal projects.
+role: # optional. Case studies only.
+period: # optional. Case studies only, like 2021 to now.
+tech: [] # optional
+---
+
+
diff --git a/vault-kit/24.01_PersonalWebsite/Site/_templates/Product line.md b/vault-kit/24.01_PersonalWebsite/Site/_templates/Product line.md
new file mode 100644
index 0000000..2315c0b
--- /dev/null
+++ b/vault-kit/24.01_PersonalWebsite/Site/_templates/Product line.md
@@ -0,0 +1,18 @@
+---
+type: "product-line"
+title: "{{title}}"
+publish: false # Only notes set to true appear on the site.
+created: "{{date:YYYY-MM-DD}}" # optional
+category: # one of: Tabletop, Paper, Leather
+mode: # one of: made to order, in stock
+channel: # one of: here, Etsy, Patiun.com, coming soon
+channelUrl: # optional. For Etsy or Patiun.com lines.
+summary:
+icon: # optional
+order: # optional
+slots: # optional. Made to order lines only.
+quantity: # optional. In stock lines only.
+priceTiers: []
+---
+
+
diff --git a/vault-kit/24.01_PersonalWebsite/Site/_templates/Station.md b/vault-kit/24.01_PersonalWebsite/Site/_templates/Station.md
new file mode 100644
index 0000000..d97acc3
--- /dev/null
+++ b/vault-kit/24.01_PersonalWebsite/Site/_templates/Station.md
@@ -0,0 +1,9 @@
+---
+type: "station"
+title: "{{title}}"
+publish: false # Only notes set to true appear on the site.
+created: "{{date:YYYY-MM-DD}}" # optional
+where: # Like the work counter and filament wall.
+goodFor: # optional
+order:
+---
diff --git a/vault-kit/24.01_PersonalWebsite/Site/_templates/Status.md b/vault-kit/24.01_PersonalWebsite/Site/_templates/Status.md
new file mode 100644
index 0000000..87c3f7d
--- /dev/null
+++ b/vault-kit/24.01_PersonalWebsite/Site/_templates/Status.md
@@ -0,0 +1,11 @@
+---
+type: "status"
+title: "{{title}}"
+publish: false # Only notes set to true appear on the site.
+created: "{{date:YYYY-MM-DD}}" # optional
+kind: # One of the kinds listed in Now/_kinds.
+date: "{{date:YYYY-MM-DD}}"
+subject: # optional. The note this status is about, if there is one.
+note: # optional
+pinned: false # optional
+---
diff --git a/vault-kit/24.01_PersonalWebsite/Site/_templates/Tabletop system.md b/vault-kit/24.01_PersonalWebsite/Site/_templates/Tabletop system.md
new file mode 100644
index 0000000..1a4f0fc
--- /dev/null
+++ b/vault-kit/24.01_PersonalWebsite/Site/_templates/Tabletop system.md
@@ -0,0 +1,9 @@
+---
+type: "tabletop-system"
+title: "{{title}}"
+publish: false # Only notes set to true appear on the site.
+created: "{{date:YYYY-MM-DD}}" # optional
+state: # one of: running, played, want to run, want to play, want to try
+blurb:
+order: # optional
+---
diff --git a/vault-kit/24.01_PersonalWebsite/Site/_templates/Tool or material.md b/vault-kit/24.01_PersonalWebsite/Site/_templates/Tool or material.md
new file mode 100644
index 0000000..7261085
--- /dev/null
+++ b/vault-kit/24.01_PersonalWebsite/Site/_templates/Tool or material.md
@@ -0,0 +1,9 @@
+---
+type: "tool"
+title: "{{title}}"
+publish: false # Only notes set to true appear on the site.
+created: "{{date:YYYY-MM-DD}}" # optional
+kind: # one of: printer, cutter, casting, finishing, material, other
+station: # optional
+blurb: # optional
+---
diff --git a/vitest.config.ts b/vitest.config.ts
new file mode 100644
index 0000000..9ca545d
--- /dev/null
+++ b/vitest.config.ts
@@ -0,0 +1,5 @@
+import { defineConfig } from "vitest/config";
+
+export default defineConfig({
+ test: { include: ["packages/*/test/**/*.test.ts", "tools/*/test/**/*.test.ts"] },
+});
From 3f646048a290cb4be474ffdbcbea729859f94372 Mon Sep 17 00:00:00 2001
From: Claude
Date: Sun, 11 Oct 2026 19:10:38 +0000
Subject: [PATCH 02/16] Add the content service and MCP server (phase 2)
services/content watches the vault's Site folder, runs every change through
fixed rules, keeps the last accepted version of each note with its history,
and serves one write API. The HTTP API, the MCP server for agents, and the
admin page and editing tools to come all write through it.
- SQLite store with history, review queue, redirects, links and full-text search
- Rules: invalid, misplaced, duplicate name or slug, text loss, secrets,
agent contact details, agent edits, agent publishing, agent batches
- Watcher with debounce and a periodic full scan
- HTTP API with reader, agent, editor and admin tokens
- MCP tools: search, get_note, get_schema, list_held, set_status,
add_library_item, update_fields, draft_post
- Tests, including taking in all 545 migrated notes with nothing held
Co-Authored-By: Claude Opus 5.5 (1M context)
Claude-Session: https://claude.ai/code/session_012aSbo2D1qP8cMFQCykf7kx
---
.gitignore | 1 +
README.md | 8 +-
docs/content-service.md | 120 +++
package-lock.json | 1235 +++++++++++++++++++++++-
package.json | 6 +-
packages/schema/src/notes.ts | 18 +-
services/content/package.json | 17 +
services/content/src/config.ts | 65 ++
services/content/src/http.ts | 262 +++++
services/content/src/main.ts | 54 ++
services/content/src/mcp.ts | 229 +++++
services/content/src/rules.ts | 153 +++
services/content/src/service.ts | 335 +++++++
services/content/src/store.ts | 382 ++++++++
services/content/src/vault.ts | 95 ++
services/content/src/views.ts | 61 ++
services/content/src/watcher.ts | 76 ++
services/content/test/helpers.ts | 78 ++
services/content/test/http.test.ts | 96 ++
services/content/test/migrated.test.ts | 35 +
services/content/test/service.test.ts | 188 ++++
services/content/tsconfig.json | 4 +
vitest.config.ts | 2 +-
23 files changed, 3482 insertions(+), 38 deletions(-)
create mode 100644 docs/content-service.md
create mode 100644 services/content/package.json
create mode 100644 services/content/src/config.ts
create mode 100644 services/content/src/http.ts
create mode 100644 services/content/src/main.ts
create mode 100644 services/content/src/mcp.ts
create mode 100644 services/content/src/rules.ts
create mode 100644 services/content/src/service.ts
create mode 100644 services/content/src/store.ts
create mode 100644 services/content/src/vault.ts
create mode 100644 services/content/src/views.ts
create mode 100644 services/content/src/watcher.ts
create mode 100644 services/content/test/helpers.ts
create mode 100644 services/content/test/http.test.ts
create mode 100644 services/content/test/migrated.test.ts
create mode 100644 services/content/test/service.test.ts
create mode 100644 services/content/tsconfig.json
diff --git a/.gitignore b/.gitignore
index 226df75..e97e18d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -19,3 +19,4 @@ npm-debug.log*
!.vscode/extensions.json
!.vscode/settings.json
.idea/
+data/
diff --git a/README.md b/README.md
index bccf14d..ced09bb 100644
--- a/README.md
+++ b/README.md
@@ -12,12 +12,12 @@ with the decisions behind it, is the
| --- | --- |
| `packages/schema` | The one definition of every note type: fields, folders, file names, and the checks for one note and for the whole folder. The migration, the content service, the MCP server, the site and any later editing tool all use it. |
| `vault-kit` | What goes into the vault: the Obsidian templates (generated from the schema) and the Site guide. |
+| `services/content` | The content service: watches the Site folder, runs the rules, keeps the last good version and its history, and serves the one write API. The MCP server for agents is part of it. See [docs/content-service.md](docs/content-service.md). |
| `tools/migrate` | The one-time migration from the old site's data into Site folder notes. |
| `docs` | How the pieces fit. Start with [docs/content-model.md](docs/content-model.md). |
| `legacy` | The old site, kept for reference only. Nothing outside it imports from it except the migration. |
-Coming in later phases: `services/content` (the content service and its MCP
-server), the admin page, and the new site.
+Coming in later phases: the admin page, the component library, and the new site.
## Commands
@@ -29,6 +29,10 @@ npm run check # typecheck and tests
npm run migrate # writes out/24.01_PersonalWebsite and out/MIGRATION-REPORT.md
npm run templates # regenerates vault-kit templates after a schema change
npm run schema:json # writes a JSON Schema per note type to packages/schema/json/
+
+# the content service, against a copy of the Site folder
+SITE_DIR=out/24.01_PersonalWebsite/Site DATA_DIR=data \
+CONTENT_TOKENS="reader:<24+ chars>,admin:<24+ chars>" npm run content
```
## The old site
diff --git a/docs/content-service.md b/docs/content-service.md
new file mode 100644
index 0000000..be91a21
--- /dev/null
+++ b/docs/content-service.md
@@ -0,0 +1,120 @@
+# Content service
+
+The content service sits between the vault and everything that shows or
+changes content. It is the only thing that reads or writes the Site folder.
+
+```
+Obsidian ──edits──▶ Site folder ──watch──▶ rules ──▶ accepted store ──▶ site, admin page
+ ▲ ▲
+ └──── accepted API changes ────────┤
+ write API (HTTP)
+ ▲ ▲
+ MCP server admin page, later editors
+```
+
+- **The vault is the source of truth.** Greg edits in Obsidian; the service
+ watches the folder and takes each change in.
+- **Every change runs the same rules**, whether it came from Obsidian, an
+ agent or the admin page. A change that passes is accepted. A change that
+ fails is held for review, and the site keeps showing the last accepted
+ version, so a bad edit never reaches a page.
+- **Changes through the API are written back into the vault** once accepted,
+ so Obsidian always shows what the site shows. The service marks its own
+ writes as seen before writing, so the watcher does not take them in twice.
+- **Deleting or moving a file unpublishes the note.** Its history stays, and
+ any version can be rolled back.
+
+## The rules
+
+Pure functions in [`services/content/src/rules.ts`](../services/content/src/rules.ts),
+run in this order. The rules live in code, not in the vault, so no agent can
+loosen them.
+
+| Code | Holds a change when |
+| --- | --- |
+| `invalid` | The note fails its type's schema. |
+| `misplaced` | A settings note (`_site`, `Now/_kinds`, `Store/_settings`) is not at its fixed path. |
+| `duplicate-name`, `duplicate-slug` | Another live note already has the file name, or the same type already uses the address. |
+| `text-loss` | A body of 400 or more characters loses more than half in one edit. |
+| `secret` | New text looks like a key, token or password. |
+| `contact` | An agent adds an email address or phone number. |
+| `agent-edit` | An agent changes a note it did not write, unless the note has `agentEdits: true`. |
+| `agent-publish` | An agent publishes anything but a status or a Library item. |
+| `batch` | An agent makes more than 10 changes in 10 minutes. |
+
+Invalid input through the API is turned down at once with the problems, rather
+than held, so callers fix it themselves. A change from Obsidian that fails is
+held, because the file is already on disk.
+
+## Addresses
+
+A slug change or a rename keeps the old address working as a redirect. When a
+note disappears and a note of the same type with the same text or title
+appears within ten minutes, that is treated as a move: redirects to the old
+path are pointed at the new one, so redirects never chain. A slug that comes
+back into use belongs to its note, not to the redirect.
+
+## The write API
+
+`ContentService.apply` takes one change, `create` (type, fields, body) or
+`update` (path, the fields to change, `null` to remove one, and optionally
+the body), along with who made it. Every client uses it:
+
+- the MCP server, as `agent`;
+- the admin page and later editing tools, as `greg` (editor token) or `admin`;
+- the review queue: `acceptHeld`, `rejectHeld` (optionally restoring the live
+ version into the vault), and `rollback` to any earlier revision.
+
+An update first takes in any unsaved Obsidian edit to that file, so an API
+change never overwrites one. A note with a change waiting for review takes no
+other API change until that one is decided. A note's type never changes.
+
+### HTTP
+
+Every route but `/health` needs `Authorization: Bearer `. Tokens map to
+roles in `CONTENT_TOKENS`.
+
+| Route | Who | What |
+| --- | --- | --- |
+| `GET /health` | anyone | Note, published and held counts. |
+| `GET /api/schema` | all | Every note type with its JSON Schema. |
+| `GET /api/notes?type=&q=&limit=&offset=&full=` | all | List or search. Readers see published notes only. |
+| `GET /api/note?path=` or `?type=&slug=` | all | One note with its backlinks. An old slug answers with `{ redirect }`. |
+| `GET /api/backlinks?name=` | all | Notes linking to a note. |
+| `GET /api/redirects` | all | Every redirect. |
+| `GET /attachments/` | all | An image from `_attachments`. |
+| `GET /api/note/history?path=`, `/api/note/revision?path=&rev=` | agent, editor, admin | History and old versions. |
+| `GET /api/problems` | agent, editor, admin | Broken links and other whole-folder problems. |
+| `GET /api/held`, `/api/held/` | agent, editor, admin | The review queue; one entry comes with the current text for comparison. |
+| `POST /api/changes` | agent, editor, admin | One change. 422 with reasons when turned down. |
+| `POST /api/held//accept`, `/reject` | editor, admin | Decide a held change. `{ "restore": true }` on reject puts the live version back into the vault. |
+| `POST /api/note/rollback` | admin | `{ path, rev }` |
+| `POST /api/scan` | editor, admin | Compare the whole folder now. |
+| `/mcp` | agent, editor, admin | The MCP server. |
+
+### MCP
+
+Streamable HTTP at `/mcp`, stateless. Tools: `search`, `get_note`,
+`get_schema`, `list_held`, `set_status`, `add_library_item`, `update_fields`
+and `draft_post` (always unpublished). There is no tool to delete, rename,
+move or write a raw file.
+
+## Running it
+
+| Variable | Default | |
+| --- | --- | --- |
+| `SITE_DIR` | required | The vault's Site folder. |
+| `DATA_DIR` | `data` | Holds `content.db`. Back it up: history and the review queue cannot be rebuilt from the vault. |
+| `PORT`, `HOST` | `4310`, `0.0.0.0` | |
+| `CONTENT_TOKENS` | none | `role:token` pairs, comma separated. Roles: reader, agent, editor, admin. Tokens are 24 characters or more. |
+| `ALLOWED_EMAILS` | none | Addresses an agent may add without a hold. |
+| `HEALTHCHECK_URL` | none | Pinged after every full scan. |
+| `NOTIFY_WEBHOOK_URL` | none | Receives `{ event: "held", held }` when a change is held. |
+| `SCAN_MINUTES` | `5` | Full scan interval, on top of the file watch. |
+
+```sh
+SITE_DIR=/path/to/Site CONTENT_TOKENS="reader:…,agent:…,editor:…,admin:…" npm run content
+```
+
+The store is SQLite (`node:sqlite`) with full-text search, so the service has
+no database server to run.
diff --git a/package-lock.json b/package-lock.json
index f3fcd09..7506337 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -7,6 +7,7 @@
"name": "gregkilmer-com",
"workspaces": [
"packages/*",
+ "services/*",
"tools/*"
],
"devDependencies": {
@@ -26,7 +27,6 @@
"cpu": [
"ppc64"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -43,7 +43,6 @@
"cpu": [
"arm"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -60,7 +59,6 @@
"cpu": [
"arm64"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -77,7 +75,6 @@
"cpu": [
"x64"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -94,7 +91,6 @@
"cpu": [
"arm64"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -111,7 +107,6 @@
"cpu": [
"x64"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -128,7 +123,6 @@
"cpu": [
"arm64"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -145,7 +139,6 @@
"cpu": [
"x64"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -162,7 +155,6 @@
"cpu": [
"arm"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -179,7 +171,6 @@
"cpu": [
"arm64"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -196,7 +187,6 @@
"cpu": [
"ia32"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -213,7 +203,6 @@
"cpu": [
"loong64"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -230,7 +219,6 @@
"cpu": [
"mips64el"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -247,7 +235,6 @@
"cpu": [
"ppc64"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -264,7 +251,6 @@
"cpu": [
"riscv64"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -281,7 +267,6 @@
"cpu": [
"s390x"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -298,7 +283,6 @@
"cpu": [
"x64"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -315,7 +299,6 @@
"cpu": [
"arm64"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -332,7 +315,6 @@
"cpu": [
"x64"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -349,7 +331,6 @@
"cpu": [
"arm64"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -366,7 +347,6 @@
"cpu": [
"x64"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -383,7 +363,6 @@
"cpu": [
"arm64"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -400,7 +379,6 @@
"cpu": [
"x64"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -417,7 +395,6 @@
"cpu": [
"arm64"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -434,7 +411,6 @@
"cpu": [
"ia32"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -451,7 +427,6 @@
"cpu": [
"x64"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -461,6 +436,10 @@
"node": ">=18"
}
},
+ "node_modules/@gk/content": {
+ "resolved": "services/content",
+ "link": true
+ },
"node_modules/@gk/migrate": {
"resolved": "tools/migrate",
"link": true
@@ -469,6 +448,18 @@
"resolved": "packages/schema",
"link": true
},
+ "node_modules/@hono/node-server": {
+ "version": "2.1.4",
+ "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-2.1.4.tgz",
+ "integrity": "sha512-2g1qeHl4BduwDn6L8nkAvDrPUWPMlkQ//a5NhP/zIyaXKMjZNRV+cG9Csu+BiJFf6+68pqzjf4Hi5sJGKHuS1g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=20"
+ },
+ "peerDependencies": {
+ "hono": "^4 || ^5.0.0-0"
+ }
+ },
"node_modules/@jridgewell/resolve-uri": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
@@ -497,6 +488,46 @@
"@jridgewell/sourcemap-codec": "^1.4.14"
}
},
+ "node_modules/@modelcontextprotocol/sdk": {
+ "version": "1.32.1",
+ "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.32.1.tgz",
+ "integrity": "sha512-2DdE+SJDtzLEEWzY1ZjY7Q+VcPhcV1KisD3zI4u0XZyktsjHum1mwbMI+JaulUBi2OZk+KJAi2uPXzxichPkdw==",
+ "license": "MIT",
+ "dependencies": {
+ "@hono/node-server": "^1.19.9 || ^2.0.5",
+ "ajv": "^8.17.1",
+ "ajv-formats": "^3.0.1",
+ "content-type": "^1.0.5",
+ "cors": "^2.8.5",
+ "cross-spawn": "^7.0.5",
+ "eventsource": "^3.0.2",
+ "eventsource-parser": "^3.0.0",
+ "express": "^5.2.1",
+ "express-rate-limit": "^8.2.1",
+ "hono": "^4.11.4",
+ "jose": "^6.1.3",
+ "json-schema-typed": "^8.0.2",
+ "pkce-challenge": "^5.0.0",
+ "raw-body": "^3.0.0",
+ "zod": "^3.25 || ^4.0",
+ "zod-to-json-schema": "^3.25.1"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "peerDependencies": {
+ "@cfworker/json-schema": "^4.1.1",
+ "zod": "^3.25 || ^4.0"
+ },
+ "peerDependenciesMeta": {
+ "@cfworker/json-schema": {
+ "optional": true
+ },
+ "zod": {
+ "optional": false
+ }
+ }
+ },
"node_modules/@oxc-project/types": {
"version": "0.153.0",
"resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.153.0.tgz",
@@ -859,6 +890,52 @@
"url": "https://opencollective.com/vitest"
}
},
+ "node_modules/accepts": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz",
+ "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==",
+ "license": "MIT",
+ "dependencies": {
+ "mime-types": "^3.0.0",
+ "negotiator": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/ajv": {
+ "version": "8.20.0",
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz",
+ "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==",
+ "license": "MIT",
+ "dependencies": {
+ "fast-deep-equal": "^3.1.3",
+ "fast-uri": "^3.0.1",
+ "json-schema-traverse": "^1.0.0",
+ "require-from-string": "^2.0.2"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/epoberezkin"
+ }
+ },
+ "node_modules/ajv-formats": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz",
+ "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==",
+ "license": "MIT",
+ "dependencies": {
+ "ajv": "^8.0.0"
+ },
+ "peerDependencies": {
+ "ajv": "^8.0.0"
+ },
+ "peerDependenciesMeta": {
+ "ajv": {
+ "optional": true
+ }
+ }
+ },
"node_modules/assertion-error": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz",
@@ -869,6 +946,81 @@
"node": ">=12"
}
},
+ "node_modules/body-parser": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.3.0.tgz",
+ "integrity": "sha512-2cGmJupaNgg+QUwVLAucDuWuoMZ6EX9iHDRswZ5lsNYEmwPaRknMPCLZz07yTzVq/83p4o/wzbDZbBrTvGGTIw==",
+ "license": "MIT",
+ "dependencies": {
+ "bytes": "^3.1.2",
+ "content-type": "^2.0.0",
+ "debug": "^4.4.3",
+ "http-errors": "^2.0.1",
+ "iconv-lite": "^0.7.2",
+ "on-finished": "^2.4.1",
+ "qs": "^6.15.2",
+ "raw-body": "^3.0.2",
+ "type-is": "^2.1.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/body-parser/node_modules/content-type": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.1.0.tgz",
+ "integrity": "sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/bytes": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
+ "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/call-bind-apply-helpers": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
+ "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "function-bind": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/call-bound": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz",
+ "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bind-apply-helpers": "^1.0.2",
+ "get-intrinsic": "^1.3.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
"node_modules/chai": {
"version": "6.3.0",
"resolved": "https://registry.npmjs.org/chai/-/chai-6.3.0.tgz",
@@ -879,6 +1031,103 @@
"node": ">=18"
}
},
+ "node_modules/content-disposition": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-2.0.1.tgz",
+ "integrity": "sha512-e+H0ZXHSWYrENhQzw1LPuP4oF5MzVKmDU6d3hxlvaPEYLLg62MxtQNPRx4SYSuYJSBUgnQIG4HIN2tEtNv7Dog==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/content-type": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz",
+ "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/cookie": {
+ "version": "0.7.2",
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz",
+ "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/cookie-signature": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz",
+ "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.6.0"
+ }
+ },
+ "node_modules/cors": {
+ "version": "2.8.6",
+ "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.6.tgz",
+ "integrity": "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==",
+ "license": "MIT",
+ "dependencies": {
+ "object-assign": "^4",
+ "vary": "^1"
+ },
+ "engines": {
+ "node": ">= 0.10"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/cross-spawn": {
+ "version": "7.0.6",
+ "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
+ "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
+ "license": "MIT",
+ "dependencies": {
+ "path-key": "^3.1.0",
+ "shebang-command": "^2.0.0",
+ "which": "^2.0.1"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "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/depd": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
+ "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
"node_modules/detect-libc": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
@@ -890,6 +1139,53 @@
"node": ">=8"
}
},
+ "node_modules/dunder-proto": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
+ "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bind-apply-helpers": "^1.0.1",
+ "es-errors": "^1.3.0",
+ "gopd": "^1.2.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/ee-first": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
+ "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==",
+ "license": "MIT"
+ },
+ "node_modules/encodeurl": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
+ "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/es-define-property": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
+ "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-errors": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
+ "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
"node_modules/es-module-lexer": {
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.3.2.tgz",
@@ -897,11 +1193,22 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/es-object-atoms": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz",
+ "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==",
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
"node_modules/esbuild": {
"version": "0.28.2",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.2.tgz",
"integrity": "sha512-HKVLS8dvII+xoKW9kmqxbRKrnWEXfJJr/FZhhJmiqIB0e053QNYFqOBouTMO/k5sID4MvCiUCvv8b9M4h32wIA==",
- "dev": true,
"hasInstallScript": true,
"license": "MIT",
"bin": {
@@ -939,6 +1246,12 @@
"@esbuild/win32-x64": "0.28.2"
}
},
+ "node_modules/escape-html": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
+ "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==",
+ "license": "MIT"
+ },
"node_modules/estree-walker": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz",
@@ -949,6 +1262,36 @@
"@types/estree": "^1.0.0"
}
},
+ "node_modules/etag": {
+ "version": "1.8.1",
+ "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
+ "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/eventsource": {
+ "version": "3.0.7",
+ "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-3.0.7.tgz",
+ "integrity": "sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==",
+ "license": "MIT",
+ "dependencies": {
+ "eventsource-parser": "^3.0.1"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ }
+ },
+ "node_modules/eventsource-parser": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.1.1.tgz",
+ "integrity": "sha512-EKN1vKAMcZ8MlYMpaNuxN6R9yakzH6uajHcHVTqWJzvu5pWw9DyhbP35HH8MVBQ+dZjAfDxk+A8NiR9KWaXiyQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18.0.0"
+ }
+ },
"node_modules/expect-type": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.4.0.tgz",
@@ -959,6 +1302,103 @@
"node": ">=12.0.0"
}
},
+ "node_modules/express": {
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/express/-/express-5.3.0.tgz",
+ "integrity": "sha512-qCqy1xSoaugxOD4IVHb1ZWdkvCF2W3aE2okKouWj8VwJ+ztskSiXoBnCSJqfKT+cVPJhuJqe9YLbY425+Zh00g==",
+ "license": "MIT",
+ "dependencies": {
+ "accepts": "^2.0.0",
+ "body-parser": "^2.3.0",
+ "content-disposition": "^2.0.1",
+ "content-type": "^2.0.0",
+ "cookie": "^0.7.1",
+ "cookie-signature": "^1.2.1",
+ "debug": "^4.4.0",
+ "depd": "^2.0.0",
+ "encodeurl": "^2.0.0",
+ "escape-html": "^1.0.3",
+ "etag": "^1.8.1",
+ "finalhandler": "^2.1.0",
+ "fresh": "^2.0.0",
+ "http-errors": "^2.0.0",
+ "merge-descriptors": "^2.0.0",
+ "mime-types": "^3.0.0",
+ "on-finished": "^2.4.1",
+ "once": "^1.4.0",
+ "parseurl": "^1.3.3",
+ "proxy-addr": "^2.0.8",
+ "qs": "^6.16.0",
+ "range-parser": "^1.2.1",
+ "router": "^2.2.0",
+ "send": "^1.1.0",
+ "serve-static": "^2.2.0",
+ "statuses": "^2.0.1",
+ "type-is": "^2.1.0",
+ "vary": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/express-rate-limit": {
+ "version": "8.7.1",
+ "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.7.1.tgz",
+ "integrity": "sha512-2FXbqkygil3mZiXzEtM0IjE/UBmBNot0J1GEVrJT4JbrvwOSoEtqtO+wHbiMQELWe1eNvHoYRg2dkjOid+6JkA==",
+ "license": "MIT",
+ "dependencies": {
+ "debug": "^4.4.3",
+ "ip-address": "^10.2.0"
+ },
+ "engines": {
+ "node": ">= 16"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/express-rate-limit"
+ },
+ "peerDependencies": {
+ "express": ">= 4.11"
+ }
+ },
+ "node_modules/express/node_modules/content-type": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.1.0.tgz",
+ "integrity": "sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/fast-deep-equal": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
+ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
+ "license": "MIT"
+ },
+ "node_modules/fast-uri": {
+ "version": "3.1.8",
+ "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.8.tgz",
+ "integrity": "sha512-GZMtZUTNRpOVIECoXwLNZS5xUGE+mVNbTB8h/7Rwh2TFWcBQiPzTgyZi05BF9UMZKkLJv8XBRJTlU7zg8+ZfMg==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/fastify"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/fastify"
+ }
+ ],
+ "license": "BSD-3-Clause"
+ },
"node_modules/fdir": {
"version": "6.5.0",
"resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
@@ -977,11 +1417,49 @@
}
}
},
+ "node_modules/finalhandler": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz",
+ "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==",
+ "license": "MIT",
+ "dependencies": {
+ "debug": "^4.4.0",
+ "encodeurl": "^2.0.0",
+ "escape-html": "^1.0.3",
+ "on-finished": "^2.4.1",
+ "parseurl": "^1.3.3",
+ "statuses": "^2.0.1"
+ },
+ "engines": {
+ "node": ">= 18.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/forwarded": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
+ "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/fresh": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz",
+ "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
"node_modules/fsevents": {
"version": "2.3.3",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
- "dev": true,
"hasInstallScript": true,
"license": "MIT",
"optional": true,
@@ -992,6 +1470,190 @@
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
}
},
+ "node_modules/function-bind": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
+ "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/get-intrinsic": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
+ "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bind-apply-helpers": "^1.0.2",
+ "es-define-property": "^1.0.1",
+ "es-errors": "^1.3.0",
+ "es-object-atoms": "^1.1.1",
+ "function-bind": "^1.1.2",
+ "get-proto": "^1.0.1",
+ "gopd": "^1.2.0",
+ "has-symbols": "^1.1.0",
+ "hasown": "^2.0.2",
+ "math-intrinsics": "^1.1.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/get-proto": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
+ "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
+ "license": "MIT",
+ "dependencies": {
+ "dunder-proto": "^1.0.1",
+ "es-object-atoms": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/gopd": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
+ "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/has-symbols": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
+ "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/hasown": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz",
+ "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==",
+ "license": "MIT",
+ "dependencies": {
+ "function-bind": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/hono": {
+ "version": "4.13.13",
+ "resolved": "https://registry.npmjs.org/hono/-/hono-4.13.13.tgz",
+ "integrity": "sha512-CQ46U0ZkAGmbT/4UxdzzGJpacP2IeKgY4a5/tOI9AABbpOMfK739wfDXmv1usCk+3RkKj1hQy4/fjhiwa2xlrA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=16.9.0"
+ }
+ },
+ "node_modules/http-errors": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz",
+ "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==",
+ "license": "MIT",
+ "dependencies": {
+ "depd": "~2.0.0",
+ "inherits": "~2.0.4",
+ "setprototypeof": "~1.2.0",
+ "statuses": "~2.0.2",
+ "toidentifier": "~1.0.1"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/iconv-lite": {
+ "version": "0.7.3",
+ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.3.tgz",
+ "integrity": "sha512-IKXpvIzjnC9XTAUbVBcMfGS0EPaIXtW6v+zr+RRp+hqULEpo0owZax6wyRwPOJbWbzjYspQwusTsfVr0ifh4uQ==",
+ "license": "MIT",
+ "dependencies": {
+ "safer-buffer": ">= 2.1.2 < 3.0.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/inherits": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
+ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
+ "license": "ISC"
+ },
+ "node_modules/ip-address": {
+ "version": "10.7.3",
+ "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.7.3.tgz",
+ "integrity": "sha512-A1kdq/tSb5QjvKvAMgIoEvDBIgL7qaqVP/jkvSwYYRZ9iEzvPpopxp2wQfu3SuZRHtpHNxMn8Fs0bS+gf5Xmwg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 12"
+ }
+ },
+ "node_modules/ipaddr.js": {
+ "version": "1.9.1",
+ "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
+ "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.10"
+ }
+ },
+ "node_modules/is-promise": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz",
+ "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==",
+ "license": "MIT"
+ },
+ "node_modules/isexe": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
+ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
+ "license": "ISC"
+ },
+ "node_modules/jose": {
+ "version": "6.2.12",
+ "resolved": "https://registry.npmjs.org/jose/-/jose-6.2.12.tgz",
+ "integrity": "sha512-9NiFmJEex0sy2Dk58j2UGBSHgUs2ypF9eZSu4L6vjOX3Dp96Sw1F3uL+H+D1sx02jZZdzUT0HgvCy59CuvXcWw==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/panva"
+ }
+ },
+ "node_modules/json-schema-traverse": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
+ "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
+ "license": "MIT"
+ },
+ "node_modules/json-schema-typed": {
+ "version": "8.0.2",
+ "resolved": "https://registry.npmjs.org/json-schema-typed/-/json-schema-typed-8.0.2.tgz",
+ "integrity": "sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==",
+ "license": "BSD-2-Clause"
+ },
"node_modules/lightningcss": {
"version": "1.33.0",
"resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.33.0.tgz",
@@ -1275,6 +1937,71 @@
"@jridgewell/sourcemap-codec": "^1.6.0"
}
},
+ "node_modules/math-intrinsics": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
+ "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/media-typer": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.1.tgz",
+ "integrity": "sha512-yz3xRaG20c6/BOzvYoDaGtPmGscs7YivItZEEqe6GbwNfHuxu9YNmvnEkMzKldAGY4/80pRcQRZSEnhquk9XuQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/merge-descriptors": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz",
+ "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/mime-db": {
+ "version": "1.54.0",
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz",
+ "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/mime-types": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz",
+ "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==",
+ "license": "MIT",
+ "dependencies": {
+ "mime-db": "^1.54.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "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.20",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.20.tgz",
@@ -1295,6 +2022,56 @@
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
}
},
+ "node_modules/negotiator": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.1.0.tgz",
+ "integrity": "sha512-NMPBRMJgiQHjbd8phG3Vebdx4kZ1H121rbl5IkMqeOsahptB9BKo/d7oJ3zTXqTgagn2bWlNSXkh0QUGM31RYg==",
+ "license": "MIT",
+ "dependencies": {
+ "content-type": "^2.1.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/negotiator/node_modules/content-type": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.1.0.tgz",
+ "integrity": "sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/object-assign": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
+ "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/object-inspect": {
+ "version": "1.13.4",
+ "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz",
+ "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
"node_modules/obug": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/obug/-/obug-2.2.1.tgz",
@@ -1309,6 +2086,55 @@
"node": ">=12.20.0"
}
},
+ "node_modules/on-finished": {
+ "version": "2.4.1",
+ "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
+ "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==",
+ "license": "MIT",
+ "dependencies": {
+ "ee-first": "1.1.1"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/once": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
+ "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
+ "license": "ISC",
+ "dependencies": {
+ "wrappy": "1"
+ }
+ },
+ "node_modules/parseurl": {
+ "version": "1.3.3",
+ "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
+ "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/path-key": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
+ "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/path-to-regexp": {
+ "version": "8.4.2",
+ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.4.2.tgz",
+ "integrity": "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==",
+ "license": "MIT",
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
"node_modules/picocolors": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
@@ -1330,6 +2156,15 @@
"url": "https://github.com/sponsors/jonschlinkert"
}
},
+ "node_modules/pkce-challenge": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-5.0.1.tgz",
+ "integrity": "sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=16.20.0"
+ }
+ },
"node_modules/postcss": {
"version": "8.5.29",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.29.tgz",
@@ -1360,6 +2195,76 @@
"node": "^10 || ^12 || >=14"
}
},
+ "node_modules/proxy-addr": {
+ "version": "2.0.8",
+ "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.8.tgz",
+ "integrity": "sha512-5nnx0yGyVUcY6t9RnWcARWtwT9F1D8O9rt08htPvnd49W1IgZtmLkhu9WfMzQj1cFxjHIO6connUNVW5k7AVyQ==",
+ "license": "MIT",
+ "dependencies": {
+ "forwarded": "0.2.0",
+ "ipaddr.js": "1.9.1"
+ },
+ "engines": {
+ "node": ">= 0.10"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/qs": {
+ "version": "6.16.0",
+ "resolved": "https://registry.npmjs.org/qs/-/qs-6.16.0.tgz",
+ "integrity": "sha512-h6fhOIaRrID2CbEY2fqs+7t+UXZo+MLAnU5gRIq85uFtdiUPCdsApMlHhXogKVM4HM2DVbIjGNTTYH2OcmP1vA==",
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "es-define-property": "^1.0.1",
+ "side-channel": "^1.1.1"
+ },
+ "engines": {
+ "node": ">=0.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/range-parser": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.3.0.tgz",
+ "integrity": "sha512-hek2mFQpPuI4E1BBKrSto+BU3e3x4xuarsbiwr3+lf7p44juvFMV0XFWQAP3xUyqXA4RrXLIoaSUGbSt056ZMw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/raw-body": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz",
+ "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==",
+ "license": "MIT",
+ "dependencies": {
+ "bytes": "~3.1.2",
+ "http-errors": "~2.0.1",
+ "iconv-lite": "~0.7.0",
+ "unpipe": "~1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.10"
+ }
+ },
+ "node_modules/require-from-string": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz",
+ "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
"node_modules/rolldown": {
"version": "1.2.13",
"resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.2.13.tgz",
@@ -1395,6 +2300,172 @@
"@rolldown/binding-win32-x64-msvc": "1.2.13"
}
},
+ "node_modules/router": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz",
+ "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==",
+ "license": "MIT",
+ "dependencies": {
+ "debug": "^4.4.0",
+ "depd": "^2.0.0",
+ "is-promise": "^4.0.0",
+ "parseurl": "^1.3.3",
+ "path-to-regexp": "^8.0.0"
+ },
+ "engines": {
+ "node": ">= 18"
+ }
+ },
+ "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"
+ },
+ "node_modules/send": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz",
+ "integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==",
+ "license": "MIT",
+ "dependencies": {
+ "debug": "^4.4.3",
+ "encodeurl": "^2.0.0",
+ "escape-html": "^1.0.3",
+ "etag": "^1.8.1",
+ "fresh": "^2.0.0",
+ "http-errors": "^2.0.1",
+ "mime-types": "^3.0.2",
+ "ms": "^2.1.3",
+ "on-finished": "^2.4.1",
+ "range-parser": "^1.2.1",
+ "statuses": "^2.0.2"
+ },
+ "engines": {
+ "node": ">= 18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/serve-static": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz",
+ "integrity": "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==",
+ "license": "MIT",
+ "dependencies": {
+ "encodeurl": "^2.0.0",
+ "escape-html": "^1.0.3",
+ "parseurl": "^1.3.3",
+ "send": "^1.2.0"
+ },
+ "engines": {
+ "node": ">= 18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/setprototypeof": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
+ "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==",
+ "license": "ISC"
+ },
+ "node_modules/shebang-command": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
+ "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
+ "license": "MIT",
+ "dependencies": {
+ "shebang-regex": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/shebang-regex": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
+ "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/side-channel": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.1.tgz",
+ "integrity": "sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==",
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "object-inspect": "^1.13.4",
+ "side-channel-list": "^1.0.1",
+ "side-channel-map": "^1.0.1",
+ "side-channel-weakmap": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/side-channel-list": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz",
+ "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==",
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "object-inspect": "^1.13.4"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/side-channel-map": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz",
+ "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.2",
+ "es-errors": "^1.3.0",
+ "get-intrinsic": "^1.2.5",
+ "object-inspect": "^1.13.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/side-channel-weakmap": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz",
+ "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.2",
+ "es-errors": "^1.3.0",
+ "get-intrinsic": "^1.2.5",
+ "object-inspect": "^1.13.3",
+ "side-channel-map": "^1.0.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
"node_modules/source-map-js": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.2.tgz",
@@ -1406,6 +2477,15 @@
"node": ">=0.10.0"
}
},
+ "node_modules/statuses": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz",
+ "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
"node_modules/std-env": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/std-env/-/std-env-4.3.0.tgz",
@@ -1450,11 +2530,19 @@
"url": "https://github.com/sponsors/SuperchupuDev"
}
},
+ "node_modules/toidentifier": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
+ "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.6"
+ }
+ },
"node_modules/tsx": {
"version": "4.23.15",
"resolved": "https://registry.npmjs.org/tsx/-/tsx-4.23.15.tgz",
"integrity": "sha512-Yiex1Ovn8z2xPpOWckIiysV1SSyRMY9BkLF++q0yKiDxCqRhosKfMg3janKkiLBwZ5c/YryloKwGZcrEmtwxKw==",
- "dev": true,
"license": "MIT",
"dependencies": {
"esbuild": "~0.28.0"
@@ -1469,6 +2557,37 @@
"fsevents": "~2.3.3"
}
},
+ "node_modules/type-is": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.1.0.tgz",
+ "integrity": "sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA==",
+ "license": "MIT",
+ "dependencies": {
+ "content-type": "^2.0.0",
+ "media-typer": "^1.1.0",
+ "mime-types": "^3.0.0"
+ },
+ "engines": {
+ "node": ">= 18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/type-is/node_modules/content-type": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.1.0.tgz",
+ "integrity": "sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
"node_modules/typescript": {
"version": "5.9.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
@@ -1489,6 +2608,24 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/unpipe": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
+ "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/vary": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
+ "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
"node_modules/vite": {
"version": "8.3.4",
"resolved": "https://registry.npmjs.org/vite/-/vite-8.3.4.tgz",
@@ -1651,6 +2788,21 @@
}
}
},
+ "node_modules/which": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
+ "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
+ "license": "ISC",
+ "dependencies": {
+ "isexe": "^2.0.0"
+ },
+ "bin": {
+ "node-which": "bin/node-which"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
"node_modules/why-is-node-running": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-3.2.1.tgz",
@@ -1664,6 +2816,12 @@
"node": ">=20.11"
}
},
+ "node_modules/wrappy": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
+ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
+ "license": "ISC"
+ },
"node_modules/yaml": {
"version": "2.9.1",
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.1.tgz",
@@ -1688,6 +2846,15 @@
"url": "https://github.com/sponsors/colinhacks"
}
},
+ "node_modules/zod-to-json-schema": {
+ "version": "3.25.2",
+ "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.2.tgz",
+ "integrity": "sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA==",
+ "license": "ISC",
+ "peerDependencies": {
+ "zod": "^3.25.28 || ^4"
+ }
+ },
"packages/schema": {
"name": "@gk/schema",
"version": "0.1.0",
@@ -1696,6 +2863,16 @@
"zod": "^4.1.0"
}
},
+ "services/content": {
+ "name": "@gk/content",
+ "version": "0.1.0",
+ "dependencies": {
+ "@gk/schema": "*",
+ "@modelcontextprotocol/sdk": "^1.32.1",
+ "tsx": "^4.19.0",
+ "zod": "^4.1.0"
+ }
+ },
"tools/migrate": {
"name": "@gk/migrate",
"version": "0.1.0",
diff --git a/package.json b/package.json
index b2aa82e..707ec47 100644
--- a/package.json
+++ b/package.json
@@ -5,18 +5,20 @@
"description": "GregKilmer.com: the note schema, content service and site that serve Greg's vault notes. The old site lives in legacy/ for reference only.",
"workspaces": [
"packages/*",
+ "services/*",
"tools/*"
],
"engines": {
"node": ">=22"
},
"scripts": {
- "typecheck": "tsc -p packages/schema && tsc -p tools/migrate",
+ "typecheck": "tsc -p packages/schema && tsc -p tools/migrate && tsc -p services/content",
"test": "vitest run",
"check": "npm run typecheck && npm test",
"schema:json": "npm run json -w @gk/schema",
"templates": "npm run templates -w @gk/schema",
- "migrate": "npm run migrate -w @gk/migrate --"
+ "migrate": "npm run migrate -w @gk/migrate --",
+ "content": "npm run start -w @gk/content"
},
"devDependencies": {
"@types/node": "^22.10.0",
diff --git a/packages/schema/src/notes.ts b/packages/schema/src/notes.ts
index f35bcb7..84e3f48 100644
--- a/packages/schema/src/notes.ts
+++ b/packages/schema/src/notes.ts
@@ -87,6 +87,19 @@ function orderKeys(def: NoteTypeDef, data: Record): Record, body = ""): string {
+ const def = typeDef(String(data.type));
+ const ordered = def ? orderKeys(def, data) : data;
+ // Write what was given (not schema defaults), so notes stay short.
+ const yaml = stringifyYaml(ordered, { lineWidth: 0 }).trimEnd();
+ const text = body.trim() ? `\n${body.trim()}\n` : "";
+ return `---\n${yaml}\n---\n${text}`;
+}
+
/** Write a note's text. The data is checked first; this throws when it fails. */
export function writeNote(data: Record, body = ""): string {
const def = typeDef(String(data.type));
@@ -96,10 +109,7 @@ export function writeNote(data: Record, body = ""): string {
const problems = result.error.issues.map((i) => `${i.path.join(".") || "note"}: ${i.message}`).join("; ");
throw new Error(`"${String(data.title)}" is not a valid ${def.label}: ${problems}`);
}
- // Write what was given (not schema defaults), so notes stay short.
- const yaml = stringifyYaml(orderKeys(def, data), { lineWidth: 0 }).trimEnd();
- const text = body.trim() ? `\n${body.trim()}\n` : "";
- return `---\n${yaml}\n---\n${text}`;
+ return formatNote(data, body);
}
/** Where a note of this type lives, relative to the Site folder, with `.md`. */
diff --git a/services/content/package.json b/services/content/package.json
new file mode 100644
index 0000000..d7d1d52
--- /dev/null
+++ b/services/content/package.json
@@ -0,0 +1,17 @@
+{
+ "name": "@gk/content",
+ "version": "0.1.0",
+ "private": true,
+ "type": "module",
+ "description": "The content service: watches the vault's Site folder, checks every change, keeps the last good version of each note, and serves the one write API that the MCP server, the admin page and later editing tools share.",
+ "scripts": {
+ "start": "node --disable-warning=ExperimentalWarning --import tsx src/main.ts",
+ "dev": "node --disable-warning=ExperimentalWarning --watch --import tsx src/main.ts"
+ },
+ "dependencies": {
+ "@gk/schema": "*",
+ "@modelcontextprotocol/sdk": "^1.32.1",
+ "tsx": "^4.19.0",
+ "zod": "^4.1.0"
+ }
+}
diff --git a/services/content/src/config.ts b/services/content/src/config.ts
new file mode 100644
index 0000000..3dd63e3
--- /dev/null
+++ b/services/content/src/config.ts
@@ -0,0 +1,65 @@
+/**
+ * Settings come from the environment, so the same image runs on Cosmos and in
+ * tests. Nothing here is read from the vault: an agent that can edit notes
+ * cannot change who may write or what the rules allow.
+ */
+import { resolve } from "node:path";
+
+export type Role = "reader" | "agent" | "editor" | "admin";
+
+export interface Config {
+ /** The vault's Site folder. */
+ siteDir: string;
+ /** The SQLite file holding accepted notes, history and the review queue. */
+ dbFile: string;
+ port: number;
+ host: string;
+ /** Bearer token to role. */
+ tokens: Map;
+ /** Pinged after every full scan, so a stopped watcher is noticed. */
+ healthcheckUrl?: string;
+ /** Receives a JSON message whenever a change is held. */
+ notifyWebhookUrl?: string;
+ allowedEmails: string[];
+ scanMinutes: number;
+}
+
+const roles = new Set(["reader", "agent", "editor", "admin"]);
+
+/** CONTENT_TOKENS is a comma separated list of role:token pairs. */
+export function parseTokens(raw: string | undefined): Map {
+ const tokens = new Map();
+ for (const pair of (raw ?? "").split(",").map((s) => s.trim()).filter(Boolean)) {
+ const at = pair.indexOf(":");
+ const role = pair.slice(0, at) as Role;
+ const token = pair.slice(at + 1);
+ if (at < 1 || !roles.has(role)) throw new Error(`CONTENT_TOKENS: "${pair.slice(0, Math.max(at, 0))}" is not a role`);
+ if (token.length < 24) throw new Error(`CONTENT_TOKENS: the ${role} token is shorter than 24 characters`);
+ tokens.set(token, role);
+ }
+ return tokens;
+}
+
+export function loadConfig(env: NodeJS.ProcessEnv = process.env): Config {
+ if (!env.SITE_DIR) throw new Error("SITE_DIR is required: the vault's Site folder");
+ const dataDir = resolve(env.DATA_DIR ?? "data");
+ return {
+ siteDir: resolve(env.SITE_DIR),
+ dbFile: resolve(dataDir, "content.db"),
+ port: Number(env.PORT ?? 4310),
+ host: env.HOST ?? "0.0.0.0",
+ tokens: parseTokens(env.CONTENT_TOKENS),
+ healthcheckUrl: env.HEALTHCHECK_URL || undefined,
+ notifyWebhookUrl: env.NOTIFY_WEBHOOK_URL || undefined,
+ allowedEmails: (env.ALLOWED_EMAILS ?? "").split(",").map((s) => s.trim().toLowerCase()).filter(Boolean),
+ scanMinutes: Number(env.SCAN_MINUTES ?? 5),
+ };
+}
+
+/** What each role may do. Checked by the HTTP API and the MCP server alike. */
+export const can = {
+ readDrafts: (r: Role) => r !== "reader",
+ write: (r: Role) => r === "agent" || r === "editor" || r === "admin",
+ review: (r: Role) => r === "editor" || r === "admin",
+ rollback: (r: Role) => r === "admin",
+};
diff --git a/services/content/src/http.ts b/services/content/src/http.ts
new file mode 100644
index 0000000..b137fd6
--- /dev/null
+++ b/services/content/src/http.ts
@@ -0,0 +1,262 @@
+/**
+ * The HTTP API. The site reads through it with the reader token; the admin
+ * page and later editing tools read and write through it; agents use the MCP
+ * server mounted at /mcp. Every write lands in `ContentService.apply` or a
+ * review method, so the rules are the same whichever door a change comes in by.
+ */
+import { createReadStream } from "node:fs";
+import { createServer, type IncomingMessage, type ServerResponse } from "node:http";
+import { extname } from "node:path";
+import { z } from "zod";
+import { noteTypes, typeDef } from "@gk/schema";
+import { can, type Config, type Role } from "./config.ts";
+import { handleMcp } from "./mcp.ts";
+import type { Change, ContentService } from "./service.ts";
+import type { Author, StoredNote } from "./store.ts";
+import { heldView, noteView, outcomeView, revisionView, summaryView } from "./views.ts";
+
+const MAX_BODY = 1_000_000;
+
+class HttpError extends Error {
+ constructor(readonly status: number, message: string) {
+ super(message);
+ }
+}
+
+export const authorFor = (role: Role): Author => (role === "agent" ? "agent" : role === "admin" ? "admin" : "greg");
+
+const changeSchema = z.discriminatedUnion("op", [
+ z.object({ op: z.literal("create"), data: z.record(z.string(), z.unknown()), body: z.string().optional() }),
+ z.object({
+ op: z.literal("update"),
+ path: z.string(),
+ fields: z.record(z.string(), z.unknown()).optional(),
+ body: z.string().optional(),
+ }),
+]);
+
+const types = {
+ ".png": "image/png",
+ ".jpg": "image/jpeg",
+ ".jpeg": "image/jpeg",
+ ".webp": "image/webp",
+ ".gif": "image/gif",
+ ".svg": "image/svg+xml",
+ ".avif": "image/avif",
+ ".pdf": "application/pdf",
+} as Record;
+
+/** Every note type with its JSON Schema, for the admin page, editors and agents. */
+export function schemaList() {
+ return Object.values(noteTypes).map((def) => ({
+ type: def.type,
+ label: def.label,
+ description: def.description,
+ folder: typeof def.folder === "string" ? def.folder : null,
+ singleton: def.singleton?.fileName ?? null,
+ body: def.body,
+ schema: z.toJSONSchema(def.schema, { io: "input", unrepresentable: "any" }),
+ }));
+}
+
+export function createHttpServer(service: ContentService, config: Pick) {
+ const { store } = service;
+
+ const visible = (n: StoredNote | null, role: Role) => (n && !n.removed && (n.publish || can.readDrafts(role)) ? n : null);
+
+ async function route(req: IncomingMessage, res: ServerResponse) {
+ const url = new URL(req.url ?? "/", "http://localhost");
+ const path = url.pathname;
+ const q = (k: string) => url.searchParams.get(k) ?? undefined;
+ const method = req.method ?? "GET";
+
+ if (path === "/health") {
+ return send(res, 200, { ok: true, ...store.count() });
+ }
+
+ const role = authenticate(req, config.tokens);
+ if (!role) throw new HttpError(401, "A valid bearer token is required");
+ const need = (ok: boolean) => {
+ if (!ok) throw new HttpError(403, `The ${role} token cannot do this`);
+ };
+
+ if (path === "/mcp") {
+ need(can.write(role));
+ const body = method === "POST" ? await readJson(req) : undefined;
+ return handleMcp(req, res, body, service, authorFor(role));
+ }
+
+ if (method === "GET") {
+ if (path === "/api/schema") return send(res, 200, schemaList());
+
+ if (path === "/api/notes") {
+ const limit = Math.min(Number(q("limit") ?? 100), 1000);
+ const publishedOnly = !can.readDrafts(role) || q("published") === "true";
+ const type = q("type");
+ if (type && !typeDef(type)) throw new HttpError(400, `Unknown type "${type}"`);
+ const query = q("q");
+ const notes = query
+ ? store.search(query, { publishedOnly, limit }).filter((n) => !type || n.type === type)
+ : store.list({ type, publishedOnly, limit, offset: Number(q("offset") ?? 0) });
+ // full=true returns whole notes, so a list page needs one request.
+ return send(res, 200, notes.map((n) => (q("full") === "true" ? noteView(n) : summaryView(n))));
+ }
+
+ if (path === "/api/note") {
+ const p = q("path");
+ const type = q("type");
+ const slug = q("slug");
+ let note: StoredNote | null = null;
+ if (p) note = store.get(p);
+ else if (type && slug) {
+ note = store.bySlug(type, slug);
+ if (!visible(note, role)) {
+ const to = store.redirect(type, slug);
+ const target = to ? visible(store.get(to), role) : null;
+ if (target) return send(res, 200, { redirect: { type: target.type, slug: target.slug, path: target.path } });
+ }
+ } else throw new HttpError(400, "Give path, or type and slug");
+ const found = visible(note, role);
+ if (!found) throw new HttpError(404, "No such note");
+ return send(res, 200, {
+ ...noteView(found, { text: can.readDrafts(role) }),
+ backlinks: store.backlinks(found.name).filter((b) => visible(b, role)).map(summaryView),
+ ...(can.readDrafts(role) ? { pending: store.pendingFor(found.path) ? heldView(store.pendingFor(found.path)!) : null } : {}),
+ });
+ }
+
+ if (path === "/api/note/history") {
+ need(can.readDrafts(role));
+ return send(res, 200, store.history(required(q("path"), "path")).map((r) => revisionView(r)));
+ }
+
+ if (path === "/api/note/revision") {
+ need(can.readDrafts(role));
+ const r = store.revision(required(q("path"), "path"), Number(required(q("rev"), "rev")));
+ if (!r) throw new HttpError(404, "No such revision");
+ return send(res, 200, revisionView(r, { text: true }));
+ }
+
+ if (path === "/api/backlinks") {
+ return send(res, 200, store.backlinks(required(q("name"), "name")).filter((b) => visible(b, role)).map(summaryView));
+ }
+
+ if (path === "/api/redirects") return send(res, 200, store.redirects());
+
+ if (path === "/api/problems") {
+ need(can.readDrafts(role));
+ return send(res, 200, service.problems());
+ }
+
+ if (path === "/api/held") {
+ need(can.readDrafts(role));
+ const status = (q("status") ?? "pending") as "pending";
+ return send(res, 200, store.heldList(status).map((h) => heldView(h)));
+ }
+
+ const heldOne = path.match(/^\/api\/held\/(\d+)$/);
+ if (heldOne) {
+ need(can.readDrafts(role));
+ const h = store.heldById(Number(heldOne[1]));
+ if (!h) throw new HttpError(404, "No such change");
+ const current = store.get(h.path);
+ // The current text comes along so a reviewer can see the difference.
+ return send(res, 200, { ...heldView(h, { text: true }), current: current && !current.removed ? current.text : null });
+ }
+
+ const attachment = path.match(/^\/attachments\/([^/]+)$/);
+ if (attachment) {
+ let name = "";
+ try {
+ name = decodeURIComponent(attachment[1] ?? "");
+ } catch {
+ throw new HttpError(400, "Bad attachment name");
+ }
+ const file = name.includes("..") ? null : service.vault.attachmentPath(name);
+ if (!file) throw new HttpError(404, "No such attachment");
+ res.writeHead(200, {
+ "content-type": types[extname(file).toLowerCase()] ?? "application/octet-stream",
+ "cache-control": "public, max-age=300",
+ "x-content-type-options": "nosniff",
+ });
+ createReadStream(file).pipe(res);
+ return;
+ }
+ }
+
+ if (method === "POST") {
+ if (path === "/api/changes") {
+ need(can.write(role));
+ const parsed = changeSchema.safeParse(await readJson(req));
+ if (!parsed.success) throw new HttpError(400, z.prettifyError(parsed.error));
+ const outcome = service.apply(parsed.data as Change, authorFor(role));
+ return send(res, outcome.status === "rejected" ? 422 : 200, outcomeView(outcome));
+ }
+
+ const decide = path.match(/^\/api\/held\/(\d+)\/(accept|reject)$/);
+ if (decide) {
+ need(can.review(role));
+ const id = Number(decide[1]);
+ const body = (await readJson(req).catch(() => ({}))) as { restore?: boolean } | undefined;
+ const outcome = decide[2] === "accept" ? service.acceptHeld(id) : service.rejectHeld(id, { restore: body?.restore === true });
+ return send(res, outcome.status === "rejected" ? 422 : 200, outcomeView(outcome));
+ }
+
+ if (path === "/api/note/rollback") {
+ need(can.rollback(role));
+ const body = z.object({ path: z.string(), rev: z.number().int() }).safeParse(await readJson(req));
+ if (!body.success) throw new HttpError(400, z.prettifyError(body.error));
+ const outcome = service.rollback(body.data.path, body.data.rev);
+ return send(res, outcome.status === "rejected" ? 422 : 200, outcomeView(outcome));
+ }
+
+ if (path === "/api/scan") {
+ need(can.review(role));
+ return send(res, 200, service.scan());
+ }
+ }
+
+ throw new HttpError(404, "Not found");
+ }
+
+ return createServer((req, res) => {
+ route(req, res).catch((err: unknown) => {
+ if (res.headersSent) return res.end();
+ if (err instanceof HttpError) return send(res, err.status, { error: err.message });
+ console.error(err);
+ send(res, 500, { error: "Something went wrong in the content service" });
+ });
+ });
+}
+
+function authenticate(req: IncomingMessage, tokens: Map): Role | null {
+ const m = /^Bearer\s+(.+)$/i.exec(req.headers.authorization ?? "");
+ return m ? (tokens.get((m[1] ?? "").trim()) ?? null) : null;
+}
+
+function required(v: string | undefined, name: string): string {
+ if (!v) throw new HttpError(400, `${name} is required`);
+ return v;
+}
+
+async function readJson(req: IncomingMessage): Promise {
+ let size = 0;
+ const chunks: Buffer[] = [];
+ for await (const chunk of req) {
+ size += (chunk as Buffer).length;
+ if (size > MAX_BODY) throw new HttpError(413, "The request is too large");
+ chunks.push(chunk as Buffer);
+ }
+ const text = Buffer.concat(chunks).toString("utf8");
+ if (!text) return undefined;
+ try {
+ return JSON.parse(text);
+ } catch {
+ throw new HttpError(400, "The body is not valid JSON");
+ }
+}
+
+function send(res: ServerResponse, status: number, body: unknown) {
+ res.writeHead(status, { "content-type": "application/json; charset=utf-8", "cache-control": "no-store" });
+ res.end(JSON.stringify(body));
+}
diff --git a/services/content/src/main.ts b/services/content/src/main.ts
new file mode 100644
index 0000000..b290a89
--- /dev/null
+++ b/services/content/src/main.ts
@@ -0,0 +1,54 @@
+/**
+ * Starts the content service: open the store, scan the Site folder, watch it,
+ * and serve the API and the MCP server.
+ */
+import { loadConfig } from "./config.ts";
+import { createHttpServer } from "./http.ts";
+import { ContentService } from "./service.ts";
+import { Store } from "./store.ts";
+import { Vault } from "./vault.ts";
+import { Watcher } from "./watcher.ts";
+import { heldView } from "./views.ts";
+
+const config = loadConfig();
+const store = new Store(config.dbFile);
+const service = new ContentService({
+ store,
+ vault: new Vault(config.siteDir),
+ rules: { allowedEmails: config.allowedEmails },
+ onHold: (held) => {
+ console.log(`Held ${held.path}: ${held.reasons.map((r) => r.code).join(", ") || "waiting for review"}`);
+ if (config.notifyWebhookUrl) {
+ fetch(config.notifyWebhookUrl, {
+ method: "POST",
+ headers: { "content-type": "application/json" },
+ body: JSON.stringify({ event: "held", held: heldView(held) }),
+ }).catch((err) => console.error("Notify failed:", err));
+ }
+ },
+});
+
+const watcher = new Watcher(service, config.siteDir, {
+ scanMinutes: config.scanMinutes,
+ onScan: (r) => {
+ if (r.changed) console.log(`Scan: ${r.changed} changed, ${r.held} held`);
+ if (config.healthcheckUrl) fetch(config.healthcheckUrl).catch(() => {});
+ },
+ onError: (err) => console.error("Watcher:", err),
+});
+watcher.start();
+
+const server = createHttpServer(service, config);
+server.listen(config.port, config.host, () => {
+ const c = store.count();
+ console.log(`Content service on ${config.host}:${config.port}: ${c.notes} notes, ${c.published} published, ${c.held} held`);
+});
+
+const stop = () => {
+ watcher.stop();
+ server.close();
+ store.close();
+ process.exit(0);
+};
+process.on("SIGTERM", stop);
+process.on("SIGINT", stop);
diff --git a/services/content/src/mcp.ts b/services/content/src/mcp.ts
new file mode 100644
index 0000000..fcfe11e
--- /dev/null
+++ b/services/content/src/mcp.ts
@@ -0,0 +1,229 @@
+/**
+ * The MCP server agents use. It is one more client of the write API: every
+ * tool that changes something calls `ContentService.apply`, so agents meet
+ * the same rules as everyone else, plus the agent rules.
+ *
+ * Agents can find, read and add. They cannot delete, rename, move or write a
+ * raw file, and a draft post always starts unpublished.
+ */
+import type { IncomingMessage, ServerResponse } from "node:http";
+import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
+import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
+import { z } from "zod";
+import { libraryStates, media, noteTypes, topics, typeDef } from "@gk/schema";
+import type { Change, ContentService, Outcome } from "./service.ts";
+import type { Author } from "./store.ts";
+import { heldView, noteView, outcomeView, summaryView } from "./views.ts";
+
+const json = (value: unknown) => ({ content: [{ type: "text" as const, text: JSON.stringify(value, null, 2) }] });
+const fail = (message: string) => ({ content: [{ type: "text" as const, text: message }], isError: true });
+
+/** Turn an outcome into a tool result an agent can act on. */
+function result(o: Outcome) {
+ if (o.status === "rejected") return fail(`Not saved:\n${o.reasons.map((r) => `- ${r.code}: ${r.message}`).join("\n")}`);
+ if (o.status === "held") {
+ return json({
+ ...outcomeView(o),
+ message: "Saved for Greg to review. It is not live yet. Do not retry; the change is waiting.",
+ });
+ }
+ return json(outcomeView(o));
+}
+
+const today = () => new Date().toISOString().slice(0, 10);
+
+export function createMcpServer(service: ContentService, author: Author) {
+ const { store } = service;
+ const server = new McpServer(
+ { name: "gregkilmer-content", version: "0.1.0" },
+ {
+ instructions:
+ "Content for GregKilmer.com. Search and read freely. Writes go through rules: some are saved straight away, " +
+ "others wait for Greg's review, and a held change should not be retried. Call get_schema before writing a type " +
+ "for the first time. Never invent facts, numbers or dates you were not given.",
+ },
+ );
+ const apply = (change: Change) => result(service.apply(change, author));
+
+ server.registerTool(
+ "search",
+ {
+ title: "Search notes",
+ description: "Full-text search over every note's title and body, drafts included. Optionally limit to one type.",
+ inputSchema: {
+ query: z.string().min(1),
+ type: z.enum(Object.keys(noteTypes) as [string, ...string[]]).optional(),
+ limit: z.number().int().min(1).max(50).optional(),
+ },
+ annotations: { readOnlyHint: true },
+ },
+ async ({ query, type, limit }) =>
+ json(store.search(query, { limit: (limit ?? 10) * (type ? 5 : 1) }).filter((n) => !type || n.type === type).slice(0, limit ?? 10).map(summaryView)),
+ );
+
+ server.registerTool(
+ "get_note",
+ {
+ title: "Read a note",
+ description: "One note by its path (like Library/Books/Mistborn.md) or its name (the file name without .md), with what links to it.",
+ inputSchema: { path: z.string().optional(), name: z.string().optional() },
+ annotations: { readOnlyHint: true },
+ },
+ async ({ path, name }) => {
+ const n = path ? store.get(path) : name ? store.byName(name) : null;
+ if (!n || n.removed) return fail("No such note. Use search to find it.");
+ const pending = store.pendingFor(n.path);
+ return json({
+ ...noteView(n),
+ backlinks: store.backlinks(n.name).map(summaryView),
+ pending: pending ? heldView(pending) : null,
+ });
+ },
+ );
+
+ server.registerTool(
+ "get_schema",
+ {
+ title: "Note types",
+ description: "Without a type: every note type and what it is for. With a type: its fields as JSON Schema.",
+ inputSchema: { type: z.enum(Object.keys(noteTypes) as [string, ...string[]]).optional() },
+ annotations: { readOnlyHint: true },
+ },
+ async ({ type }) => {
+ if (!type) return json(Object.values(noteTypes).map((d) => ({ type: d.type, label: d.label, description: d.description })));
+ const def = typeDef(type)!;
+ return json({
+ type: def.type,
+ description: def.description,
+ body: def.body,
+ schema: z.toJSONSchema(def.schema, { io: "input", unrepresentable: "any" }),
+ });
+ },
+ );
+
+ server.registerTool(
+ "list_held",
+ {
+ title: "Changes waiting for review",
+ description: "Changes held for Greg's review, with the reasons. Useful before retrying anything.",
+ inputSchema: {},
+ annotations: { readOnlyHint: true },
+ },
+ async () => json(store.heldList("pending").map((h) => heldView(h))),
+ );
+
+ server.registerTool(
+ "set_status",
+ {
+ title: "Post a status",
+ description:
+ "Add a line to the Now status log, like reading a book or starting a build. kind must be one of the kinds in Now/_kinds. " +
+ "subject links the note it is about, by name.",
+ inputSchema: {
+ kind: z.string().min(1),
+ title: z.string().min(1),
+ note: z.string().optional(),
+ subject: z.string().optional().describe("Name of the note this is about, without brackets"),
+ date: z.string().optional().describe("YYYY-MM-DD, default today"),
+ publish: z.boolean().optional().describe("Default true"),
+ },
+ },
+ async ({ kind, title, note, subject, date, publish }) => {
+ const kinds = store.list({ type: "status-kinds" })[0]?.data.kinds as { label: string }[] | undefined;
+ if (kinds && !kinds.some((k) => k.label.toLowerCase() === kind.toLowerCase())) {
+ return fail(`Unknown kind "${kind}". Kinds: ${kinds.map((k) => k.label).join(", ")}`);
+ }
+ const label = kinds?.find((k) => k.label.toLowerCase() === kind.toLowerCase())?.label ?? kind;
+ return apply({
+ op: "create",
+ data: {
+ type: "status",
+ title,
+ kind: label,
+ date: date ?? today(),
+ ...(note ? { note } : {}),
+ ...(subject ? { subject: `[[${subject.replace(/^\[\[|\]\]$/g, "")}]]` } : {}),
+ publish: publish ?? true,
+ },
+ });
+ },
+ );
+
+ server.registerTool(
+ "add_library_item",
+ {
+ title: "Add to the Library",
+ description: "Add a book, series, show, film or game. To change one that exists, use update_fields.",
+ inputSchema: {
+ title: z.string().min(1),
+ medium: z.enum(media),
+ state: z.enum(libraryStates),
+ creator: z.string().optional(),
+ group: z.string().optional(),
+ year: z.number().int().optional(),
+ platform: z.string().optional(),
+ verdict: z.string().optional(),
+ note: z.string().optional(),
+ progress: z.string().optional(),
+ anilistId: z.number().int().optional(),
+ publish: z.boolean().optional().describe("Default true"),
+ },
+ },
+ async ({ publish, ...fields }) => {
+ const existing = store.search(fields.title, { limit: 5 }).find((n) => n.type === "library-item" && n.title.toLowerCase() === fields.title.toLowerCase());
+ if (existing) return fail(`Already in the Library at ${existing.path}. Use update_fields to change it.`);
+ const data = Object.fromEntries(Object.entries(fields).filter(([, v]) => v !== undefined));
+ return apply({ op: "create", data: { type: "library-item", ...data, publish: publish ?? true } });
+ },
+ );
+
+ server.registerTool(
+ "update_fields",
+ {
+ title: "Change a note's fields",
+ description:
+ "Change some fields of an existing note; fields not named stay as they are, and null removes one. " +
+ "Give body only to replace the whole body. Greg's own notes need agentEdits: true before an agent may change them.",
+ inputSchema: {
+ path: z.string(),
+ fields: z.record(z.string(), z.unknown()),
+ body: z.string().optional(),
+ },
+ },
+ async ({ path, fields, body }) => apply({ op: "update", path, fields, ...(body !== undefined ? { body } : {}) }),
+ );
+
+ server.registerTool(
+ "draft_post",
+ {
+ title: "Draft a blog post",
+ description: "Start a blog post as an unpublished draft for Greg to finish. It never goes live from here.",
+ inputSchema: {
+ title: z.string().min(1),
+ topic: z.enum(topics),
+ summary: z.string().min(1),
+ body: z.string().min(1),
+ postType: z.enum(["article", "build log", "case study"]).optional(),
+ tags: z.array(z.string()).optional(),
+ },
+ },
+ async ({ body, ...fields }) => {
+ const data = Object.fromEntries(Object.entries(fields).filter(([, v]) => v !== undefined));
+ return apply({ op: "create", data: { type: "post", date: today(), ...data, publish: false }, body });
+ },
+ );
+
+ return server;
+}
+
+/** One stateless MCP exchange per request: nothing is kept between calls. */
+export async function handleMcp(req: IncomingMessage, res: ServerResponse, body: unknown, service: ContentService, author: Author) {
+ const server = createMcpServer(service, author);
+ const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined, enableJsonResponse: true });
+ res.on("close", () => {
+ void transport.close();
+ void server.close();
+ });
+ await server.connect(transport);
+ await transport.handleRequest(req, res, body);
+}
diff --git a/services/content/src/rules.ts b/services/content/src/rules.ts
new file mode 100644
index 0000000..782ed8c
--- /dev/null
+++ b/services/content/src/rules.ts
@@ -0,0 +1,153 @@
+/**
+ * The rules every change runs, in a fixed order. Pure functions: the same
+ * change against the same accepted state always gets the same answer, and no
+ * model decides anything.
+ *
+ * The rules live here in code, not in the vault, so no agent can loosen them.
+ */
+import { notePath, typeDef, type CheckedNote } from "@gk/schema";
+import type { Author, HeldReason, StoredNote } from "./store.ts";
+
+export interface RuleSettings {
+ /** A note with at least this much body text… */
+ textLossMinChars: number;
+ /** …is held when an edit keeps less than this share of it. */
+ textLossKeepRatio: number;
+ /** An agent making more changes than this inside the window is held. */
+ agentBatchLimit: number;
+ agentBatchWindowMinutes: number;
+ /** Note types an agent may publish without review. */
+ agentAutoPublish: string[];
+ /** Email addresses that may appear in notes. */
+ allowedEmails: string[];
+}
+
+export const defaultRules: RuleSettings = {
+ textLossMinChars: 400,
+ textLossKeepRatio: 0.5,
+ agentBatchLimit: 10,
+ agentBatchWindowMinutes: 10,
+ agentAutoPublish: ["status", "library-item"],
+ allowedEmails: [],
+};
+
+export interface Proposed {
+ path: string;
+ name: string;
+ slug: string;
+ text: string;
+ author: Author;
+ checked: CheckedNote;
+}
+
+export interface RuleContext {
+ /** The accepted version at this path, if any. */
+ current: StoredNote | null;
+ /** Another live note with the same file name, if any. */
+ nameTakenBy: string | null;
+ /** Another live note of the same type with the same slug, if any. */
+ slugTakenBy: string | null;
+ /** The agent's changes inside the batch window, before this one. */
+ recentAgentChanges: number;
+ settings: RuleSettings;
+}
+
+export type Verdict = { decision: "accept" } | { decision: "hold"; reasons: HeldReason[] };
+
+/** Strings that look like credentials. Matching is on new text only. */
+const SECRET_PATTERNS: [string, RegExp][] = [
+ ["a private key", /-----BEGIN [A-Z ]*PRIVATE KEY-----/],
+ ["an AWS key", /\bAKIA[0-9A-Z]{16}\b/],
+ ["a GitHub token", /\b(?:ghp|gho|ghs|ghu|ghr)_[A-Za-z0-9]{30,}\b|\bgithub_pat_[A-Za-z0-9_]{30,}\b/],
+ ["a Slack token", /\bxox[abpors]-[A-Za-z0-9-]{10,}\b/],
+ ["an API key", /\bsk-[A-Za-z0-9_-]{20,}\b/],
+ ["a bearer token", /\bBearer\s+[A-Za-z0-9._~+/-]{20,}=*/],
+ ["a password or key value", /\b(?:password|passwd|secret|api[_-]?key|token)\s*[:=]\s*["']?[^\s"']{8,}/i],
+];
+const EMAIL = /[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}/g;
+const PHONE = /(?:\+?1[\s.-]?)?\(?\b\d{3}\)?[\s.-]\d{3}[\s.-]\d{4}\b/g;
+
+function newMatches(re: RegExp, text: string, before: string): string[] {
+ const flags = re.flags.includes("g") ? re.flags : `${re.flags}g`;
+ const g = new RegExp(re.source, flags);
+ const old = new Set(before.match(g) ?? []);
+ return [...new Set(text.match(g) ?? [])].filter((m) => !old.has(m));
+}
+
+const bodyOf = (text: string) => text.replace(/^---\r?\n[\s\S]*?\r?\n---\r?\n?/, "").trim();
+
+export function evaluate(p: Proposed, ctx: RuleContext): Verdict {
+ const reasons: HeldReason[] = [];
+ const { current, settings } = ctx;
+ const live = current && !current.removed ? current : null;
+
+ // 1. The note must be valid. Nothing else is judged on a broken note.
+ if (!p.checked.ok) {
+ return {
+ decision: "hold",
+ reasons: [
+ {
+ code: "invalid",
+ message: p.checked.problems.map((x) => (x.field ? `${x.field}: ${x.message}` : x.message)).join("; "),
+ },
+ ],
+ };
+ }
+ const data = p.checked.data;
+
+ // 2. Settings notes live at one fixed path, so there is only ever one.
+ if (typeDef(p.checked.type)?.singleton && notePath(data) !== p.path) {
+ reasons.push({ code: "misplaced", message: `A ${p.checked.type} note belongs at ${notePath(data)}` });
+ }
+
+ // 3. Names and slugs must stay unambiguous.
+ if (ctx.nameTakenBy) {
+ reasons.push({ code: "duplicate-name", message: `Another note already uses this file name: ${ctx.nameTakenBy}` });
+ }
+ if (ctx.slugTakenBy) {
+ reasons.push({ code: "duplicate-slug", message: `Another ${p.checked.type} already uses the address "${p.slug}": ${ctx.slugTakenBy}` });
+ }
+
+ // 4. A large loss of text in one edit.
+ if (live) {
+ const before = bodyOf(live.text).length;
+ const after = bodyOf(p.text).length;
+ if (before >= settings.textLossMinChars && after < before * settings.textLossKeepRatio) {
+ reasons.push({
+ code: "text-loss",
+ message: `The text shrank from ${before} to ${after} characters in one edit`,
+ });
+ }
+ }
+
+ // 5. Anything that looks like a secret, or contact details from an agent.
+ const before = live?.text ?? "";
+ for (const [what, re] of SECRET_PATTERNS) {
+ if (newMatches(re, p.text, before).length) reasons.push({ code: "secret", message: `The note now contains what looks like ${what}` });
+ }
+ if (p.author === "agent") {
+ const allowed = new Set(settings.allowedEmails.map((e) => e.toLowerCase()));
+ const emails = newMatches(EMAIL, p.text, before).filter((e) => !allowed.has(e.toLowerCase()));
+ if (emails.length) reasons.push({ code: "contact", message: `An agent added an email address: ${emails.join(", ")}` });
+ if (newMatches(PHONE, p.text, before).length) reasons.push({ code: "contact", message: "An agent added a phone number" });
+ }
+
+ // 6. Agents stay inside their lane.
+ if (p.author === "agent") {
+ if (live && live.author !== "agent" && live.data.agentEdits !== true) {
+ reasons.push({ code: "agent-edit", message: "An agent changed a note you wrote, which needs your review" });
+ }
+ const publishing = data.publish === true && !(live?.publish ?? false);
+ if (publishing && !settings.agentAutoPublish.includes(p.checked.type)) {
+ reasons.push({ code: "agent-publish", message: `An agent wants to publish a ${p.checked.type}, which needs your approval` });
+ }
+ if (ctx.recentAgentChanges >= settings.agentBatchLimit) {
+ reasons.push({
+ code: "batch",
+ message: `More than ${settings.agentBatchLimit} agent changes in ${settings.agentBatchWindowMinutes} minutes`,
+ });
+ }
+ }
+
+ return reasons.length ? { decision: "hold", reasons } : { decision: "accept" };
+}
diff --git a/services/content/src/service.ts b/services/content/src/service.ts
new file mode 100644
index 0000000..c5ba942
--- /dev/null
+++ b/services/content/src/service.ts
@@ -0,0 +1,335 @@
+/**
+ * The content service core. Every change, from the vault or from the write
+ * API, goes through `propose`, which runs the rules and either accepts the
+ * change into the store or holds it for review.
+ *
+ * The HTTP API, the MCP server, the admin page and any later editing tool all
+ * call these methods; none of them write notes any other way.
+ */
+import { basename } from "node:path";
+import { checkNote, checkVault, formatNote, notePath, outgoingLinks, slugify, typeDef, type CheckedNote } from "@gk/schema";
+import { defaultRules, evaluate, type RuleSettings } from "./rules.ts";
+import type { Author, HeldChange, HeldReason, StoredNote, Store } from "./store.ts";
+import { hashText, isNotePath, type Vault } from "./vault.ts";
+
+export type Outcome =
+ | { status: "accepted"; path: string; note: StoredNote }
+ | { status: "held"; path: string; held: HeldChange }
+ | { status: "rejected"; path: string | null; reasons: HeldReason[] }
+ | { status: "unchanged"; path: string };
+
+export type Change =
+ | { op: "create"; data: Record; body?: string }
+ | { op: "update"; path: string; fields?: Record; body?: string };
+
+export interface ServiceOptions {
+ store: Store;
+ vault: Vault;
+ rules?: Partial;
+ /** Injectable clock, for tests. */
+ now?: () => Date;
+ /** Called whenever a change is held, for notifications. */
+ onHold?: (held: HeldChange) => void;
+}
+
+/** How long after a note disappears a new note can still claim its old address. */
+const MOVE_WINDOW_MS = 10 * 60_000;
+
+export class ContentService {
+ readonly store: Store;
+ readonly vault: Vault;
+ readonly rules: RuleSettings;
+ private readonly now: () => Date;
+ private readonly onHold?: (held: HeldChange) => void;
+
+ constructor(o: ServiceOptions) {
+ this.store = o.store;
+ this.vault = o.vault;
+ this.rules = { ...defaultRules, ...o.rules };
+ this.now = o.now ?? (() => new Date());
+ this.onHold = o.onHold;
+ }
+
+ private iso = () => this.now().toISOString();
+ private today = () => this.iso().slice(0, 10);
+
+ // ------------------------------------------------------------ from the vault
+
+ /** Read one file from the vault and take it in if it changed. */
+ ingest(path: string): Outcome {
+ const text = this.vault.read(path);
+ if (text === null) return this.removed(path);
+ const hash = hashText(text);
+ if (this.store.seenHash(path) === hash) return { status: "unchanged", path };
+ this.store.markSeen(path, hash);
+ const current = this.store.get(path);
+ if (current && !current.removed && current.hash === hash) return { status: "unchanged", path };
+ return this.propose(path, text, "greg", "vault");
+ }
+
+ /** The file is gone. The note leaves the site; its history stays. */
+ removed(path: string): Outcome {
+ this.store.markSeen(path, null);
+ this.store.supersede(path, this.iso());
+ const note = this.store.remove(path, "greg", this.iso());
+ return note ? { status: "accepted", path, note } : { status: "unchanged", path };
+ }
+
+ /**
+ * Compare the whole folder with what was seen before. Removals go first, so
+ * a note moved between folders frees its name before it is read again.
+ */
+ scan(): { changed: number; held: number } {
+ const present = new Set(this.vault.listNotes());
+ let changed = 0;
+ let held = 0;
+ for (const path of this.store.seenPaths()) {
+ if (!present.has(path)) {
+ this.removed(path);
+ changed++;
+ }
+ }
+ for (const path of present) {
+ const o = this.ingest(path);
+ if (o.status !== "unchanged") changed++;
+ if (o.status === "held") held++;
+ }
+ return { changed, held };
+ }
+
+ // ------------------------------------------------------------ the write API
+
+ /** One change from the API, made as `author`. Accepted changes are written into the vault. */
+ apply(change: Change, author: Author): Outcome {
+ let data: Record;
+ let body: string;
+ let path: string;
+
+ if (change.op === "create") {
+ data = { ...change.data, publish: change.data.publish ?? false, created: change.data.created ?? this.today() };
+ body = change.body ?? "";
+ if (!typeDef(String(data.type))) return this.reject(null, "invalid", `Unknown type "${String(data.type)}"`);
+ if (typeof data.title !== "string" || !data.title.trim()) return this.reject(null, "invalid", "title: A title is required");
+ path = notePath(data);
+ if (this.vault.exists(path) || (this.store.get(path) && !this.store.get(path)!.removed)) {
+ return this.reject(path, "exists", `A note already exists at ${path}; update it instead`);
+ }
+ } else {
+ path = change.path;
+ if (!isNotePath(path) || path.startsWith("/") || path.split("/").includes("..")) {
+ return this.reject(null, "invalid", "path: Not a note path inside the Site folder");
+ }
+ // Take in any edit made in Obsidian first, so it is never overwritten.
+ this.ingest(path);
+ const current = this.store.get(path);
+ if (!current || current.removed) return this.reject(path, "missing", `No note at ${path}`);
+ if (this.store.pendingFor(path)) {
+ return this.reject(path, "waiting", "This note has a change waiting for review; decide on that first");
+ }
+ data = { ...current.data };
+ for (const [k, v] of Object.entries(change.fields ?? {})) {
+ if (k === "type") return this.reject(path, "invalid", "type: A note's type cannot change");
+ if (v === null) delete data[k];
+ else data[k] = v;
+ }
+ body = change.body ?? current.body;
+ }
+
+ data.updated = this.today();
+ data.updatedBy = author;
+ const text = formatNote(data, body);
+ return this.propose(path, text, author, "api");
+ }
+
+ // ------------------------------------------------------------ review
+
+ acceptHeld(id: number): Outcome {
+ const h = this.store.heldById(id);
+ if (!h || h.status !== "pending") return this.reject(h?.path ?? null, "missing", "Nothing is waiting with that id");
+ const checked = checkNote(h.text);
+ if (!checked.ok) {
+ return this.reject(h.path, "invalid", "This change is not a valid note, so it cannot go live. Fix the note in Obsidian.");
+ }
+ const clash = this.clashes(h.path, checked);
+ if (clash.length) return { status: "rejected", path: h.path, reasons: clash };
+ if (h.source === "api") {
+ const onDisk = this.vault.read(h.path);
+ const current = this.store.get(h.path);
+ if (onDisk !== null && hashText(onDisk) !== (current && !current.removed ? current.hash : null)) {
+ return this.reject(h.path, "conflict", "The note changed in Obsidian since this was proposed");
+ }
+ this.writeToVault(h.path, h.text);
+ }
+ const note = this.acceptText(h.path, h.text, checked, h.author, "accept");
+ this.store.decide(id, "accepted", this.iso());
+ return { status: "accepted", path: h.path, note };
+ }
+
+ /**
+ * Turn a held change down. With `restore`, a held edit from Obsidian is
+ * replaced in the vault by the version the site is showing.
+ */
+ rejectHeld(id: number, opts: { restore?: boolean } = {}): Outcome {
+ const h = this.store.heldById(id);
+ if (!h || h.status !== "pending") return this.reject(h?.path ?? null, "missing", "Nothing is waiting with that id");
+ this.store.decide(id, "rejected", this.iso());
+ const current = this.store.get(h.path);
+ if (opts.restore && h.source === "vault" && current && !current.removed) this.writeToVault(h.path, current.text);
+ return current ? { status: "accepted", path: h.path, note: current } : { status: "unchanged", path: h.path };
+ }
+
+ /** Put an earlier accepted version back, in the vault and on the site. */
+ rollback(path: string, rev: number): Outcome {
+ const r = this.store.revision(path, rev);
+ if (!r) return this.reject(path, "missing", `No revision ${rev} for ${path}`);
+ const checked = checkNote(r.text);
+ if (!checked.ok) return this.reject(path, "invalid", "That version no longer passes the note rules");
+ const clash = this.clashes(path, checked);
+ if (clash.length) return { status: "rejected", path, reasons: clash };
+ this.store.supersede(path, this.iso());
+ this.writeToVault(path, r.text);
+ const note = this.acceptText(path, r.text, checked, "admin", "rollback");
+ return { status: "accepted", path, note };
+ }
+
+ // ------------------------------------------------------------ reading
+
+ /** Problems across the whole folder: links to notes that do not exist, and the like. */
+ problems() {
+ const notes = this.store.list().map((n) => ({ path: n.path, type: n.type, data: n.data, body: n.body }));
+ return checkVault(notes, this.vault.listAttachments());
+ }
+
+ // ------------------------------------------------------------ internals
+
+ private reject(path: string | null, code: string, message: string): Outcome {
+ return { status: "rejected", path, reasons: [{ code, message }] };
+ }
+
+ private slugOf(checked: CheckedNote): string {
+ if (!checked.ok) return "";
+ return String(checked.data.slug ?? slugify(String(checked.data.title)));
+ }
+
+ /** Name and slug clashes with other live notes. */
+ private clashes(path: string, checked: CheckedNote): HeldReason[] {
+ const reasons: HeldReason[] = [];
+ if (!checked.ok) return reasons;
+ const other = this.liveOther(this.store.byName(basename(path, ".md")), path);
+ if (other) reasons.push({ code: "duplicate-name", message: `Another note already uses this file name: ${other}` });
+ const def = typeDef(checked.type);
+ if (!def?.singleton) {
+ const s = this.liveOther(this.store.bySlug(checked.type, this.slugOf(checked)), path);
+ if (s) reasons.push({ code: "duplicate-slug", message: `Another ${checked.type} already uses this address: ${s}` });
+ }
+ return reasons;
+ }
+
+ /** Another live note's path, after making sure that note's file still exists. */
+ private liveOther(note: StoredNote | null, path: string): string | null {
+ if (!note || note.path === path) return null;
+ if (!this.vault.exists(note.path)) {
+ // Its file is gone (moved or deleted) and the watcher has not caught up.
+ this.removed(note.path);
+ return null;
+ }
+ return note.path;
+ }
+
+ private propose(path: string, text: string, author: Author, source: "vault" | "api"): Outcome {
+ const checked = checkNote(text);
+ const name = basename(path, ".md");
+ const slug = this.slugOf(checked);
+ const window = new Date(this.now().getTime() - this.rules.agentBatchWindowMinutes * 60_000).toISOString();
+ const current = this.store.get(path);
+ const nameTakenBy = checked.ok ? this.liveOther(this.store.byName(name), path) : null;
+ const slugTakenBy =
+ checked.ok && !typeDef(checked.type)?.singleton ? this.liveOther(this.store.bySlug(checked.type, slug), path) : null;
+
+ const verdict = evaluate(
+ { path, name, slug, text, author, checked },
+ {
+ current,
+ nameTakenBy,
+ slugTakenBy,
+ recentAgentChanges: author === "agent" ? this.store.changesSince("agent", window) : 0,
+ settings: this.rules,
+ },
+ );
+
+ if (verdict.decision === "accept" && checked.ok) {
+ if (source === "api") this.writeToVault(path, text);
+ const note = this.acceptText(path, text, checked, author, "accept");
+ return { status: "accepted", path, note };
+ }
+ if (verdict.decision === "hold" && source === "api" && verdict.reasons.some((r) => r.code === "invalid")) {
+ // An API caller gets the problems back at once instead of a broken note in the queue.
+ return { status: "rejected", path, reasons: verdict.reasons };
+ }
+ const reasons = verdict.decision === "hold" ? verdict.reasons : [];
+ const held = this.store.hold({
+ path,
+ author,
+ source,
+ text,
+ baseHash: current && !current.removed ? current.hash : null,
+ reasons,
+ at: this.iso(),
+ });
+ this.onHold?.(held);
+ return { status: "held", path, held };
+ }
+
+ private writeToVault(path: string, text: string) {
+ // Mark it seen first, so the watcher recognises the service's own write.
+ this.store.markSeen(path, hashText(text));
+ this.vault.write(path, text);
+ }
+
+ private acceptText(path: string, text: string, checked: CheckedNote & { ok: true }, author: Author, action: "accept" | "rollback"): StoredNote {
+ const at = this.iso();
+ const data = checked.data;
+ const slug = this.slugOf(checked);
+ const hash = hashText(text);
+ const vaultNote = { path, type: checked.type, data, body: checked.body };
+
+ return this.store.transaction(() => {
+ const prev = this.store.get(path);
+ // The old address keeps working after a rename or a slug change.
+ if (prev && !prev.removed && prev.type === checked.type && prev.slug !== slug) {
+ this.store.addRedirect(prev.type, prev.slug, path, at);
+ }
+ if (!prev || prev.removed) {
+ const since = new Date(this.now().getTime() - MOVE_WINDOW_MS).toISOString();
+ const moved = this.store
+ .recentlyRemoved(since)
+ .find((n) => n.path !== path && n.type === checked.type && (n.hash === hash || n.title === data.title));
+ if (moved) {
+ this.store.retarget(moved.path, path);
+ if (moved.slug !== slug) this.store.addRedirect(moved.type, moved.slug, path, at);
+ }
+ }
+ // A slug in use again belongs to its note, not to a redirect.
+ this.store.dropRedirect(checked.type, slug);
+ this.store.supersede(path, at);
+ return this.store.accept(
+ {
+ path,
+ type: checked.type,
+ name: basename(path, ".md"),
+ slug,
+ title: String(data.title),
+ publish: data.publish === true,
+ data,
+ body: checked.body,
+ text,
+ hash,
+ author,
+ acceptedAt: at,
+ },
+ action,
+ outgoingLinks(vaultNote),
+ );
+ });
+ }
+}
diff --git a/services/content/src/store.ts b/services/content/src/store.ts
new file mode 100644
index 0000000..f64569b
--- /dev/null
+++ b/services/content/src/store.ts
@@ -0,0 +1,382 @@
+/**
+ * The accepted store: the last good version of every note, its history,
+ * changes waiting for review, redirects, links and search.
+ *
+ * Everything the site shows comes from here, never from the vault directly.
+ * The history and review queue cannot be rebuilt from the vault, so this
+ * database carries the backup.dump=sqlite label on Cosmos.
+ */
+import { DatabaseSync } from "node:sqlite";
+import { mkdirSync } from "node:fs";
+import { dirname } from "node:path";
+
+export type Author = "greg" | "agent" | "admin";
+
+export interface StoredNote {
+ path: string;
+ type: string;
+ name: string;
+ slug: string;
+ title: string;
+ publish: boolean;
+ removed: boolean;
+ data: Record;
+ body: string;
+ text: string;
+ hash: string;
+ author: Author;
+ acceptedAt: string;
+ rev: number;
+}
+
+export interface Revision {
+ rev: number;
+ path: string;
+ hash: string;
+ text: string;
+ author: Author;
+ at: string;
+ action: "accept" | "remove" | "rollback" | "restore";
+}
+
+export interface HeldReason {
+ code: string;
+ message: string;
+}
+
+export interface HeldChange {
+ id: number;
+ path: string;
+ author: Author;
+ source: "vault" | "api";
+ /** The proposed note text. */
+ text: string;
+ /** Hash of the accepted version this change was made against, if any. */
+ baseHash: string | null;
+ reasons: HeldReason[];
+ at: string;
+ status: "pending" | "accepted" | "rejected" | "superseded";
+ decidedAt: string | null;
+}
+
+const SCHEMA = `
+create table if not exists notes (
+ path text primary key,
+ type text not null,
+ name text not null,
+ slug text not null,
+ title text not null,
+ publish integer not null,
+ removed integer not null default 0,
+ data text not null,
+ body text not null,
+ text text not null,
+ hash text not null,
+ author text not null,
+ accepted_at text not null,
+ rev integer not null
+);
+create index if not exists notes_type on notes(type, publish, removed);
+create index if not exists notes_name on notes(name collate nocase);
+create table if not exists revisions (
+ path text not null,
+ rev integer not null,
+ hash text not null,
+ text text not null,
+ author text not null,
+ at text not null,
+ action text not null,
+ primary key (path, rev)
+);
+create table if not exists held (
+ id integer primary key autoincrement,
+ path text not null,
+ author text not null,
+ source text not null,
+ text text not null,
+ base_hash text,
+ reasons text not null,
+ at text not null,
+ status text not null default 'pending',
+ decided_at text
+);
+create index if not exists held_status on held(status, path);
+create table if not exists redirects (
+ type text not null,
+ from_slug text not null,
+ to_path text not null,
+ at text not null,
+ primary key (type, from_slug)
+);
+create table if not exists links (
+ from_path text not null,
+ to_name text not null collate nocase,
+ primary key (from_path, to_name)
+);
+create table if not exists seen (
+ path text primary key,
+ hash text not null
+);
+create virtual table if not exists search using fts5(path unindexed, title, body, tokenize = 'porter unicode61');
+`;
+
+type Row = Record;
+
+const toNote = (r: Row): StoredNote => ({
+ path: r.path,
+ type: r.type,
+ name: r.name,
+ slug: r.slug,
+ title: r.title,
+ publish: !!r.publish,
+ removed: !!r.removed,
+ data: JSON.parse(r.data),
+ body: r.body,
+ text: r.text,
+ hash: r.hash,
+ author: r.author,
+ acceptedAt: r.accepted_at,
+ rev: r.rev,
+});
+
+const toHeld = (r: Row): HeldChange => ({
+ id: r.id,
+ path: r.path,
+ author: r.author,
+ source: r.source,
+ text: r.text,
+ baseHash: r.base_hash,
+ reasons: JSON.parse(r.reasons),
+ at: r.at,
+ status: r.status,
+ decidedAt: r.decided_at,
+});
+
+export class Store {
+ readonly db: DatabaseSync;
+
+ constructor(file: string) {
+ if (file !== ":memory:") mkdirSync(dirname(file), { recursive: true });
+ this.db = new DatabaseSync(file);
+ this.db.exec("pragma journal_mode = wal; pragma foreign_keys = on;");
+ this.db.exec(SCHEMA);
+ }
+
+ close() {
+ this.db.close();
+ }
+
+ transaction(fn: () => T): T {
+ this.db.exec("begin");
+ try {
+ const out = fn();
+ this.db.exec("commit");
+ return out;
+ } catch (e) {
+ this.db.exec("rollback");
+ throw e;
+ }
+ }
+
+ // ------------------------------------------------------------ notes
+
+ get(path: string): StoredNote | null {
+ const r = this.db.prepare("select * from notes where path = ?").get(path) as Row | undefined;
+ return r ? toNote(r) : null;
+ }
+
+ byName(name: string): StoredNote | null {
+ const r = this.db.prepare("select * from notes where name = ? collate nocase and removed = 0").get(name) as Row | undefined;
+ return r ? toNote(r) : null;
+ }
+
+ bySlug(type: string, slug: string): StoredNote | null {
+ const r = this.db.prepare("select * from notes where type = ? and slug = ? and removed = 0").get(type, slug) as Row | undefined;
+ return r ? toNote(r) : null;
+ }
+
+ list(opts: { type?: string; publishedOnly?: boolean; includeRemoved?: boolean; limit?: number; offset?: number } = {}): StoredNote[] {
+ const where: string[] = [];
+ const args: (string | number)[] = [];
+ if (opts.type) {
+ where.push("type = ?");
+ args.push(opts.type);
+ }
+ if (opts.publishedOnly) where.push("publish = 1");
+ if (!opts.includeRemoved) where.push("removed = 0");
+ const sql = `select * from notes ${where.length ? `where ${where.join(" and ")}` : ""} order by path limit ? offset ?`;
+ return (this.db.prepare(sql).all(...args, opts.limit ?? 10_000, opts.offset ?? 0) as Row[]).map(toNote);
+ }
+
+ count(): { notes: number; published: number; held: number } {
+ const c = this.db.prepare("select count(*) n, sum(publish) p from notes where removed = 0").get() as Row;
+ const h = this.db.prepare("select count(*) n from held where status = 'pending'").get() as Row;
+ return { notes: c.n, published: c.p ?? 0, held: h.n };
+ }
+
+ /** Save an accepted version and its revision. */
+ accept(note: Omit, action: Revision["action"], links: string[]): StoredNote {
+ const prev = this.get(note.path);
+ const rev = (prev?.rev ?? 0) + 1;
+ this.db
+ .prepare(
+ `insert into notes (path, type, name, slug, title, publish, removed, data, body, text, hash, author, accepted_at, rev)
+ values (?, ?, ?, ?, ?, ?, 0, ?, ?, ?, ?, ?, ?, ?)
+ on conflict(path) do update set type = excluded.type, name = excluded.name, slug = excluded.slug,
+ title = excluded.title, publish = excluded.publish, removed = 0, data = excluded.data, body = excluded.body,
+ text = excluded.text, hash = excluded.hash, author = excluded.author, accepted_at = excluded.accepted_at, rev = excluded.rev`,
+ )
+ .run(
+ note.path, note.type, note.name, note.slug, note.title, note.publish ? 1 : 0, JSON.stringify(note.data),
+ note.body, note.text, note.hash, note.author, note.acceptedAt, rev,
+ );
+ this.db
+ .prepare("insert into revisions (path, rev, hash, text, author, at, action) values (?, ?, ?, ?, ?, ?, ?)")
+ .run(note.path, rev, note.hash, note.text, note.author, note.acceptedAt, action);
+ this.db.prepare("delete from links where from_path = ?").run(note.path);
+ const addLink = this.db.prepare("insert or ignore into links (from_path, to_name) values (?, ?)");
+ for (const l of links) addLink.run(note.path, l);
+ this.db.prepare("delete from search where path = ?").run(note.path);
+ this.db.prepare("insert into search (path, title, body) values (?, ?, ?)").run(note.path, note.title, note.body);
+ return this.get(note.path)!;
+ }
+
+ /** The file is gone from the vault: hide the note, keep its history. */
+ remove(path: string, author: Author, at: string): StoredNote | null {
+ const prev = this.get(path);
+ if (!prev || prev.removed) return prev;
+ const rev = prev.rev + 1;
+ this.db.prepare("update notes set removed = 1, rev = ?, accepted_at = ?, author = ? where path = ?").run(rev, at, author, path);
+ this.db
+ .prepare("insert into revisions (path, rev, hash, text, author, at, action) values (?, ?, ?, ?, ?, ?, 'remove')")
+ .run(path, rev, prev.hash, prev.text, author, at);
+ this.db.prepare("delete from links where from_path = ?").run(path);
+ this.db.prepare("delete from search where path = ?").run(path);
+ return this.get(path);
+ }
+
+ /** Notes removed recently, newest first; used to recognise a move. */
+ recentlyRemoved(since: string): StoredNote[] {
+ return (this.db.prepare("select * from notes where removed = 1 and accepted_at >= ? order by accepted_at desc").all(since) as Row[]).map(toNote);
+ }
+
+ history(path: string): Revision[] {
+ return this.db.prepare("select * from revisions where path = ? order by rev desc").all(path) as unknown as Revision[];
+ }
+
+ revision(path: string, rev: number): Revision | null {
+ return (this.db.prepare("select * from revisions where path = ? and rev = ?").get(path, rev) as unknown as Revision) ?? null;
+ }
+
+ /** Accepted changes by an author since a time, for the batch guard. */
+ changesSince(author: Author, since: string): number {
+ const r = this.db.prepare("select count(*) n from revisions where author = ? and at >= ?").get(author, since) as Row;
+ const h = this.db.prepare("select count(*) n from held where author = ? and at >= ?").get(author, since) as Row;
+ return r.n + h.n;
+ }
+
+ // ------------------------------------------------------------ links, search, redirects
+
+ backlinks(name: string): StoredNote[] {
+ return (
+ this.db
+ .prepare("select n.* from links l join notes n on n.path = l.from_path where l.to_name = ? collate nocase and n.removed = 0 order by n.path")
+ .all(name) as Row[]
+ ).map(toNote);
+ }
+
+ search(query: string, opts: { publishedOnly?: boolean; limit?: number } = {}): StoredNote[] {
+ // Quote each word so punctuation in a query can never become FTS syntax.
+ const fts = query
+ .split(/\s+/)
+ .filter(Boolean)
+ .map((w) => `"${w.replace(/"/g, '""')}"*`)
+ .join(" ");
+ if (!fts) return [];
+ const rows = this.db
+ .prepare(
+ `select n.* from search s join notes n on n.path = s.path
+ where search match ? and n.removed = 0 ${opts.publishedOnly ? "and n.publish = 1" : ""}
+ order by bm25(search, 0, 4, 1) limit ?`,
+ )
+ .all(fts, opts.limit ?? 25) as Row[];
+ return rows.map(toNote);
+ }
+
+ addRedirect(type: string, fromSlug: string, toPath: string, at: string) {
+ this.db
+ .prepare("insert into redirects (type, from_slug, to_path, at) values (?, ?, ?, ?) on conflict(type, from_slug) do update set to_path = excluded.to_path, at = excluded.at")
+ .run(type, fromSlug, toPath, at);
+ }
+
+ /** Point every redirect that led to `fromPath` at `toPath`, so redirects never chain. */
+ retarget(fromPath: string, toPath: string) {
+ this.db.prepare("update redirects set to_path = ? where to_path = ?").run(toPath, fromPath);
+ }
+
+ dropRedirect(type: string, slug: string) {
+ this.db.prepare("delete from redirects where type = ? and from_slug = ?").run(type, slug);
+ }
+
+ redirect(type: string, slug: string): string | null {
+ const r = this.db.prepare("select to_path from redirects where type = ? and from_slug = ?").get(type, slug) as Row | undefined;
+ return (r?.to_path as string) ?? null;
+ }
+
+ redirects(): { type: string; fromSlug: string; toPath: string; at: string }[] {
+ return (this.db.prepare("select * from redirects order by at desc").all() as Row[]).map((r) => ({
+ type: r.type,
+ fromSlug: r.from_slug,
+ toPath: r.to_path,
+ at: r.at,
+ }));
+ }
+
+ // ------------------------------------------------------------ held changes
+
+ hold(h: Omit): HeldChange {
+ this.supersede(h.path, h.at);
+ const r = this.db
+ .prepare("insert into held (path, author, source, text, base_hash, reasons, at) values (?, ?, ?, ?, ?, ?, ?)")
+ .run(h.path, h.author, h.source, h.text, h.baseHash, JSON.stringify(h.reasons), h.at);
+ return this.heldById(Number(r.lastInsertRowid))!;
+ }
+
+ /** A newer change to the same note replaces whatever was waiting. */
+ supersede(path: string, at: string) {
+ this.db.prepare("update held set status = 'superseded', decided_at = ? where path = ? and status = 'pending'").run(at, path);
+ }
+
+ heldById(id: number): HeldChange | null {
+ const r = this.db.prepare("select * from held where id = ?").get(id) as Row | undefined;
+ return r ? toHeld(r) : null;
+ }
+
+ pendingFor(path: string): HeldChange | null {
+ const r = this.db.prepare("select * from held where path = ? and status = 'pending' order by id desc").get(path) as Row | undefined;
+ return r ? toHeld(r) : null;
+ }
+
+ heldList(status: HeldChange["status"] = "pending"): HeldChange[] {
+ return (this.db.prepare("select * from held where status = ? order by id desc").all(status) as Row[]).map(toHeld);
+ }
+
+ decide(id: number, status: "accepted" | "rejected", at: string) {
+ this.db.prepare("update held set status = ?, decided_at = ? where id = ?").run(status, at, id);
+ }
+
+ // ------------------------------------------------------------ what the watcher has seen
+
+ seenHash(path: string): string | null {
+ return ((this.db.prepare("select hash from seen where path = ?").get(path) as Row | undefined)?.hash as string) ?? null;
+ }
+
+ markSeen(path: string, hash: string | null) {
+ if (hash === null) this.db.prepare("delete from seen where path = ?").run(path);
+ else this.db.prepare("insert into seen (path, hash) values (?, ?) on conflict(path) do update set hash = excluded.hash").run(path, hash);
+ }
+
+ seenPaths(): string[] {
+ return (this.db.prepare("select path from seen").all() as Row[]).map((r) => r.path);
+ }
+}
diff --git a/services/content/src/vault.ts b/services/content/src/vault.ts
new file mode 100644
index 0000000..fbac3e8
--- /dev/null
+++ b/services/content/src/vault.ts
@@ -0,0 +1,95 @@
+/**
+ * The Site folder on disk. Only the content service reads or writes it.
+ */
+import { createHash } from "node:crypto";
+import { existsSync, mkdirSync, readFileSync, readdirSync, renameSync, statSync, writeFileSync } from "node:fs";
+import { basename, dirname, join, relative, sep } from "node:path";
+
+export const hashText = (text: string) => createHash("sha256").update(text).digest("hex");
+
+/**
+ * Whether a path under the Site folder can hold a note. Folders starting with
+ * `_` or `.` hold templates, attachments and Obsidian's own files; files
+ * starting with `_` are settings notes and do count. Sync conflict copies
+ * never count, so a conflict can never overwrite a page.
+ */
+export function isNotePath(rel: string): boolean {
+ const parts = rel.split(/[\\/]/);
+ const file = parts.pop() ?? "";
+ if (!file.endsWith(".md") || file.startsWith(".")) return false;
+ if (parts.some((p) => p.startsWith("_") || p.startsWith("."))) return false;
+ if (/\(conflicted copy|\.sync-conflict-/i.test(file)) return false;
+ return true;
+}
+
+export class Vault {
+ constructor(readonly dir: string) {}
+
+ read(rel: string): string | null {
+ try {
+ return readFileSync(join(this.dir, rel), "utf8");
+ } catch {
+ return null;
+ }
+ }
+
+ exists(rel: string): boolean {
+ return existsSync(join(this.dir, rel));
+ }
+
+ /** Write through a hidden temp file and a rename, so a reader never sees half a note. */
+ write(rel: string, text: string) {
+ const full = join(this.dir, rel);
+ mkdirSync(dirname(full), { recursive: true });
+ const tmp = join(dirname(full), `.${basename(full)}.${process.pid}.tmp`);
+ writeFileSync(tmp, text);
+ renameSync(tmp, full);
+ }
+
+ /** Every note path under the Site folder, relative, with `/` separators. */
+ listNotes(): string[] {
+ const out: string[] = [];
+ const walk = (d: string) => {
+ for (const e of readdirSync(d, { withFileTypes: true })) {
+ if (e.name.startsWith(".")) continue;
+ const full = join(d, e.name);
+ const rel = relative(this.dir, full).split(sep).join("/");
+ if (e.isDirectory()) {
+ if (!e.name.startsWith("_")) walk(full);
+ } else if (isNotePath(rel)) out.push(rel);
+ }
+ };
+ if (existsSync(this.dir)) walk(this.dir);
+ return out.sort();
+ }
+
+ /** File names in `_attachments`, for checking image links. */
+ listAttachments(): string[] {
+ const out: string[] = [];
+ const walk = (d: string) => {
+ if (!existsSync(d)) return;
+ for (const e of readdirSync(d, { withFileTypes: true })) {
+ if (e.isDirectory()) walk(join(d, e.name));
+ else out.push(e.name);
+ }
+ };
+ walk(join(this.dir, "_attachments"));
+ return out;
+ }
+
+ /** Absolute path of an attachment by file name, for serving images. */
+ attachmentPath(name: string): string | null {
+ const walk = (d: string): string | null => {
+ if (!existsSync(d)) return null;
+ for (const e of readdirSync(d, { withFileTypes: true })) {
+ const full = join(d, e.name);
+ if (e.isDirectory()) {
+ const found = walk(full);
+ if (found) return found;
+ } else if (e.name === name && statSync(full).isFile()) return full;
+ }
+ return null;
+ };
+ return walk(join(this.dir, "_attachments"));
+ }
+}
diff --git a/services/content/src/views.ts b/services/content/src/views.ts
new file mode 100644
index 0000000..b203187
--- /dev/null
+++ b/services/content/src/views.ts
@@ -0,0 +1,61 @@
+/**
+ * The shapes the HTTP API and the MCP server hand out. Both use these, so a
+ * note looks the same to the site, the admin page and an agent.
+ */
+import type { HeldChange, Revision, StoredNote } from "./store.ts";
+import type { Outcome } from "./service.ts";
+
+export const noteView = (n: StoredNote, opts: { text?: boolean } = {}) => ({
+ path: n.path,
+ type: n.type,
+ name: n.name,
+ slug: n.slug,
+ title: n.title,
+ publish: n.publish,
+ data: n.data,
+ body: n.body,
+ rev: n.rev,
+ acceptedAt: n.acceptedAt,
+ author: n.author,
+ ...(opts.text ? { text: n.text } : {}),
+});
+
+export const summaryView = (n: StoredNote) => ({
+ path: n.path,
+ type: n.type,
+ title: n.title,
+ slug: n.slug,
+ publish: n.publish,
+ acceptedAt: n.acceptedAt,
+});
+
+export const revisionView = (r: Revision, opts: { text?: boolean } = {}) => ({
+ rev: r.rev,
+ author: r.author,
+ at: r.at,
+ action: r.action,
+ ...(opts.text ? { text: r.text } : {}),
+});
+
+export const heldView = (h: HeldChange, opts: { text?: boolean } = {}) => ({
+ id: h.id,
+ path: h.path,
+ author: h.author,
+ source: h.source,
+ reasons: h.reasons,
+ at: h.at,
+ status: h.status,
+ decidedAt: h.decidedAt,
+ ...(opts.text ? { text: h.text } : {}),
+});
+
+export const outcomeView = (o: Outcome) => {
+ switch (o.status) {
+ case "accepted":
+ return { status: o.status, path: o.path, note: summaryView(o.note) };
+ case "held":
+ return { status: o.status, path: o.path, held: heldView(o.held) };
+ default:
+ return o;
+ }
+};
diff --git a/services/content/src/watcher.ts b/services/content/src/watcher.ts
new file mode 100644
index 0000000..2ebf74d
--- /dev/null
+++ b/services/content/src/watcher.ts
@@ -0,0 +1,76 @@
+/**
+ * Watches the Site folder and hands each changed file to the service after it
+ * has been quiet for a moment, so a sync tool writing a file in pieces is read
+ * once, whole. A full scan at start and on a timer catches anything the
+ * operating system's watch missed.
+ */
+import { watch, type FSWatcher } from "node:fs";
+import { isNotePath } from "./vault.ts";
+import type { ContentService } from "./service.ts";
+
+const SCAN = "\0scan";
+
+export interface WatcherOptions {
+ debounceMs?: number;
+ scanMinutes?: number;
+ /** Called after every full scan. */
+ onScan?: (result: { changed: number; held: number }) => void;
+ onError?: (err: unknown) => void;
+}
+
+export class Watcher {
+ private fsw?: FSWatcher;
+ private timer?: NodeJS.Timeout;
+ private readonly pending = new Map();
+
+ constructor(
+ private readonly service: ContentService,
+ private readonly dir: string,
+ private readonly o: WatcherOptions = {},
+ ) {}
+
+ start() {
+ this.scan();
+ this.fsw = watch(this.dir, { recursive: true }, (_event, file) => {
+ if (!file) return this.scan();
+ const rel = file.toString().split("\\").join("/");
+ if (isNotePath(rel)) this.queue(rel);
+ // A folder renamed or moved reports only the folder, so look at everything.
+ else if (!rel.endsWith(".md") && !rel.split("/").some((p) => p.startsWith(".") || p.startsWith("_"))) this.queue(SCAN);
+ });
+ this.fsw.on("error", (err) => this.o.onError?.(err));
+ const every = (this.o.scanMinutes ?? 5) * 60_000;
+ if (every > 0) this.timer = setInterval(() => this.scan(), every);
+ }
+
+ stop() {
+ this.fsw?.close();
+ clearInterval(this.timer);
+ for (const t of this.pending.values()) clearTimeout(t);
+ this.pending.clear();
+ }
+
+ scan() {
+ try {
+ this.o.onScan?.(this.service.scan());
+ } catch (err) {
+ this.o.onError?.(err);
+ }
+ }
+
+ private queue(rel: string) {
+ clearTimeout(this.pending.get(rel));
+ this.pending.set(
+ rel,
+ setTimeout(() => {
+ this.pending.delete(rel);
+ if (rel === SCAN) return this.scan();
+ try {
+ this.service.ingest(rel);
+ } catch (err) {
+ this.o.onError?.(err);
+ }
+ }, this.o.debounceMs ?? 2000),
+ );
+ }
+}
diff --git a/services/content/test/helpers.ts b/services/content/test/helpers.ts
new file mode 100644
index 0000000..3f6bac2
--- /dev/null
+++ b/services/content/test/helpers.ts
@@ -0,0 +1,78 @@
+import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
+import { tmpdir } from "node:os";
+import { dirname, join } from "node:path";
+import { notePath, writeNote } from "@gk/schema";
+import { ContentService } from "../src/service.ts";
+import { Store } from "../src/store.ts";
+import { Vault } from "../src/vault.ts";
+
+export interface Rig {
+ dir: string;
+ site: string;
+ store: Store;
+ vault: Vault;
+ service: ContentService;
+ clock: { t: number; advance(minutes: number): void };
+ /** Write a note into the vault the way Obsidian would, returning its path. */
+ put(data: Record, body?: string): string;
+ putText(path: string, text: string): void;
+ done(): void;
+}
+
+export function rig(): Rig {
+ const dir = mkdtempSync(join(tmpdir(), "gk-content-"));
+ const site = join(dir, "Site");
+ mkdirSync(site);
+ const store = new Store(join(dir, "content.db"));
+ const vault = new Vault(site);
+ const clock = {
+ t: Date.parse("2026-10-11T12:00:00Z"),
+ advance(minutes: number) {
+ this.t += minutes * 60_000;
+ },
+ };
+ const service = new ContentService({ store, vault, now: () => new Date(clock.t) });
+ const putText = (path: string, text: string) => {
+ mkdirSync(dirname(join(site, path)), { recursive: true });
+ writeFileSync(join(site, path), text);
+ };
+ return {
+ dir,
+ site,
+ store,
+ vault,
+ service,
+ clock,
+ put(data, body = "") {
+ const path = notePath(data);
+ putText(path, writeNote(data, body));
+ return path;
+ },
+ putText,
+ done() {
+ store.close();
+ rmSync(dir, { recursive: true, force: true });
+ },
+ };
+}
+
+export const book = (title: string, extra: Record = {}) => ({
+ type: "library-item",
+ title,
+ medium: "book",
+ state: "finished",
+ publish: true,
+ ...extra,
+});
+
+export const post = (title: string, extra: Record = {}) => ({
+ type: "post",
+ title,
+ date: "2026-10-01",
+ topic: "Making",
+ summary: "A summary.",
+ publish: true,
+ ...extra,
+});
+
+export const long = (n = 600) => "Paragraph of real writing. ".repeat(Math.ceil(n / 27)).slice(0, n);
diff --git a/services/content/test/http.test.ts b/services/content/test/http.test.ts
new file mode 100644
index 0000000..a1b5331
--- /dev/null
+++ b/services/content/test/http.test.ts
@@ -0,0 +1,96 @@
+import type { AddressInfo } from "node:net";
+import { afterAll, beforeAll, describe, expect, it } from "vitest";
+import { Client } from "@modelcontextprotocol/sdk/client/index.js";
+import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
+import { parseTokens } from "../src/config.ts";
+import { createHttpServer } from "../src/http.ts";
+import { book, post, rig, type Rig } from "./helpers.ts";
+
+const T = { reader: "r".repeat(24), agent: "a".repeat(24), editor: "e".repeat(24), admin: "d".repeat(24) };
+let r: Rig;
+let base: string;
+let server: ReturnType;
+
+beforeAll(async () => {
+ r = rig();
+ r.put({ type: "status-kinds", title: "Status kinds", publish: true, kinds: [{ label: "Reading", icon: "book" }] });
+ r.put(book("Mistborn"), "About [[Elantris]].");
+ r.put(book("Elantris"));
+ r.put(post("Secret draft", { publish: false }));
+ r.service.scan();
+ server = createHttpServer(r.service, { tokens: parseTokens(Object.entries(T).map(([k, v]) => `${k}:${v}`).join(",")) });
+ await new Promise((ok) => server.listen(0, "127.0.0.1", ok));
+ base = `http://127.0.0.1:${(server.address() as AddressInfo).port}`;
+});
+afterAll(() => {
+ server.close();
+ r.done();
+});
+
+const get = (path: string, token?: string) =>
+ fetch(base + path, { headers: token ? { authorization: `Bearer ${token}` } : {} });
+const post_ = (path: string, token: string, body: unknown) =>
+ fetch(base + path, { method: "POST", headers: { authorization: `Bearer ${token}`, "content-type": "application/json" }, body: JSON.stringify(body) });
+
+describe("HTTP API", () => {
+ it("needs a token for everything but health", async () => {
+ expect((await get("/health")).status).toBe(200);
+ expect((await get("/api/notes")).status).toBe(401);
+ expect((await get("/api/notes", "wrong".repeat(6))).status).toBe(401);
+ });
+
+ it("shows readers published notes only", async () => {
+ const reader = (await (await get("/api/notes?type=post", T.reader)).json()) as unknown[];
+ const editor = (await (await get("/api/notes?type=post", T.editor)).json()) as unknown[];
+ expect(reader).toHaveLength(0);
+ expect(editor).toHaveLength(1);
+ expect((await get("/api/note?path=Posts/Secret%20draft.md", T.reader)).status).toBe(404);
+ });
+
+ it("finds a note by slug with its backlinks", async () => {
+ const n = (await (await get("/api/note?type=library-item&slug=elantris", T.reader)).json()) as { title: string; backlinks: { title: string }[] };
+ expect(n.title).toBe("Elantris");
+ expect(n.backlinks.map((b) => b.title)).toEqual(["Mistborn"]);
+ });
+
+ it("keeps readers from writing and agents from reviewing", async () => {
+ expect((await post_("/api/changes", T.reader, { op: "create", data: book("X") })).status).toBe(403);
+ expect((await post_("/api/held/1/accept", T.agent, {})).status).toBe(403);
+ });
+
+ it("applies a change and reports rejections plainly", async () => {
+ const ok = await post_("/api/changes", T.editor, { op: "create", data: book("Warbreaker") });
+ expect(ok.status).toBe(200);
+ const bad = await post_("/api/changes", T.editor, { op: "create", data: { type: "library-item", title: "Y" } });
+ expect(bad.status).toBe(422);
+ });
+});
+
+describe("MCP server", () => {
+ it("lets an agent search, post a status and draft a post, but not publish it", async () => {
+ const client = new Client({ name: "test", version: "1.0.0" });
+ await client.connect(
+ new StreamableHTTPClientTransport(new URL(`${base}/mcp`), { requestInit: { headers: { authorization: `Bearer ${T.agent}` } } }),
+ );
+ const tools = (await client.listTools()).tools.map((t) => t.name).sort();
+ expect(tools).toEqual(["add_library_item", "draft_post", "get_note", "get_schema", "list_held", "search", "set_status", "update_fields"]);
+
+ const found = await client.callTool({ name: "search", arguments: { query: "mistborn" } });
+ expect(JSON.stringify(found.content)).toContain("Library/Books/Mistborn.md");
+
+ const status = await client.callTool({ name: "set_status", arguments: { kind: "reading", title: "Mistborn", subject: "Mistborn" } });
+ expect(status.isError).toBeFalsy();
+ const badKind = await client.callTool({ name: "set_status", arguments: { kind: "Juggling", title: "Balls" } });
+ expect(badKind.isError).toBe(true);
+
+ const draft = await client.callTool({
+ name: "draft_post",
+ arguments: { title: "Agent draft", topic: "Making", summary: "S.", body: "Text." },
+ });
+ expect(draft.isError).toBeFalsy();
+ const publish = await client.callTool({ name: "update_fields", arguments: { path: "Posts/Agent draft.md", fields: { publish: true } } });
+ expect(JSON.stringify(publish.content)).toContain("agent-publish");
+ expect(r.store.get("Posts/Agent draft.md")!.publish).toBe(false);
+ await client.close();
+ });
+});
diff --git a/services/content/test/migrated.test.ts b/services/content/test/migrated.test.ts
new file mode 100644
index 0000000..5c8ef39
--- /dev/null
+++ b/services/content/test/migrated.test.ts
@@ -0,0 +1,35 @@
+import { copyFileSync, mkdirSync } from "node:fs";
+import { dirname, join, resolve } from "node:path";
+import { afterAll, beforeAll, describe, expect, it } from "vitest";
+import { build, loadProducts } from "../../../tools/migrate/src/build.ts";
+import { rig, type Rig } from "./helpers.ts";
+
+let r: Rig;
+let total = 0;
+
+beforeAll(async () => {
+ const legacy = resolve(import.meta.dirname, "../../../legacy");
+ await loadProducts(legacy);
+ const result = build(legacy);
+ r = rig();
+ for (const n of result.notes) r.putText(n.path, n.text);
+ for (const a of result.attachments) {
+ mkdirSync(dirname(join(r.site, a.to)), { recursive: true });
+ copyFileSync(a.from, join(r.site, a.to));
+ }
+ total = result.notes.length;
+});
+afterAll(() => r.done());
+
+describe("the migrated Site folder", () => {
+ it("is taken in whole, with nothing held and no problems", () => {
+ expect(r.service.scan()).toEqual({ changed: total, held: 0 });
+ expect(r.store.count().notes).toBe(total);
+ expect(r.service.problems()).toEqual([]);
+ });
+
+ it("is searchable and linked", () => {
+ expect(r.store.search("Spirit Island").some((n) => n.type === "board-game")).toBe(true);
+ expect(r.store.backlinks("Magic The Gathering").length).toBeGreaterThan(0);
+ });
+});
diff --git a/services/content/test/service.test.ts b/services/content/test/service.test.ts
new file mode 100644
index 0000000..1af58c1
--- /dev/null
+++ b/services/content/test/service.test.ts
@@ -0,0 +1,188 @@
+import { readFileSync, renameSync, rmSync } from "node:fs";
+import { join } from "node:path";
+import { afterEach, beforeEach, describe, expect, it } from "vitest";
+import { evaluate, defaultRules } from "../src/rules.ts";
+import { book, long, post, rig, type Rig } from "./helpers.ts";
+
+let r: Rig;
+beforeEach(() => {
+ r = rig();
+});
+afterEach(() => r.done());
+
+const ok = (o: { status: string }) => expect(o.status).toBe("accepted");
+
+describe("from the vault", () => {
+ it("takes in valid notes and indexes them for search and links", () => {
+ r.put(book("Mistborn", { creator: "Brandon Sanderson" }), "Allomancy, heists and [[Elantris]].");
+ r.put(book("Elantris"));
+ expect(r.service.scan()).toEqual({ changed: 2, held: 0 });
+ expect(r.store.search("allomancy").map((n) => n.title)).toEqual(["Mistborn"]);
+ expect(r.store.backlinks("Elantris").map((n) => n.title)).toEqual(["Mistborn"]);
+ expect(r.service.scan()).toEqual({ changed: 0, held: 0 });
+ });
+
+ it("holds a broken edit and keeps showing the last good version", () => {
+ const path = r.put(book("Mistborn"));
+ ok(r.service.ingest(path));
+ r.putText(path, "---\ntype: library-item\ntitle: Mistborn\nmedium: scroll\n---\n");
+ const o = r.service.ingest(path);
+ expect(o.status).toBe("held");
+ expect(o.status === "held" && o.held.reasons[0]!.code).toBe("invalid");
+ expect(r.store.get(path)!.data.medium).toBe("book");
+ });
+
+ it("holds a large loss of text until it is accepted", () => {
+ const path = r.put(post("Building a bench"), long());
+ ok(r.service.ingest(path));
+ r.put(post("Building a bench"), "Short now.");
+ const o = r.service.ingest(path);
+ expect(o.status === "held" && o.held.reasons.map((x) => x.code)).toEqual(["text-loss"]);
+ expect(r.store.get(path)!.body.trim()).toBe(long());
+ ok(r.service.acceptHeld(o.status === "held" ? o.held.id : 0));
+ expect(r.store.get(path)!.body.trim()).toBe("Short now.");
+ });
+
+ it("holds a secret pasted into a note", () => {
+ const path = r.put(post("Homelab notes"), "Setup.");
+ ok(r.service.ingest(path));
+ r.put(post("Homelab notes"), "Setup. api_key = abcd1234efgh5678");
+ expect(r.service.ingest(path).status).toBe("held");
+ });
+
+ it("unpublishes a deleted note but keeps its history for a rollback", () => {
+ const path = r.put(book("Mistborn"));
+ ok(r.service.ingest(path));
+ rmSync(join(r.site, path));
+ r.service.scan();
+ expect(r.store.get(path)!.removed).toBe(true);
+ expect(r.store.list({ publishedOnly: true })).toHaveLength(0);
+ ok(r.service.rollback(path, 1));
+ expect(r.store.get(path)!.removed).toBe(false);
+ expect(r.vault.exists(path)).toBe(true);
+ });
+
+ it("keeps old addresses working after a slug change and a move", () => {
+ const path = r.put(post("Bench build", { slug: "bench" }));
+ ok(r.service.ingest(path));
+ r.put(post("Bench build", { slug: "workbench" }));
+ ok(r.service.ingest(path));
+ expect(r.store.redirect("post", "bench")).toBe(path);
+
+ // Renaming the file: the old path goes, the same note appears under a new name.
+ renameSync(join(r.site, path), join(r.site, "Posts/Workbench build.md"));
+ r.service.scan();
+ expect(r.store.redirect("post", "bench")).toBe("Posts/Workbench build.md");
+ });
+
+ it("holds a second note that takes a used address", () => {
+ ok(r.service.ingest(r.put(post("Bench build", { slug: "bench" }))));
+ const o = r.service.ingest(r.put(post("Another bench", { slug: "bench" })));
+ expect(o.status === "held" && o.held.reasons.map((x) => x.code)).toEqual(["duplicate-slug"]);
+ });
+
+ it("ignores conflict copies and template folders", () => {
+ r.putText("Posts/Bench (conflicted copy 2026-10-11).md", "junk");
+ r.putText("_templates/Post.md", "junk");
+ expect(r.service.scan()).toEqual({ changed: 0, held: 0 });
+ });
+});
+
+describe("the write API", () => {
+ it("writes an accepted change into the vault and skips it when the watcher sees it", () => {
+ const o = r.service.apply({ op: "create", data: book("Warbreaker") }, "agent");
+ ok(o);
+ const text = readFileSync(join(r.site, o.path!), "utf8");
+ expect(text).toContain("updatedBy: agent");
+ expect(r.service.ingest(o.path!)).toEqual({ status: "unchanged", path: o.path });
+ });
+
+ it("rejects invalid input at once instead of holding it", () => {
+ const o = r.service.apply({ op: "create", data: { type: "library-item", title: "X", medium: "scroll", state: "now" } }, "agent");
+ expect(o.status).toBe("rejected");
+ expect(r.store.heldList()).toHaveLength(0);
+ });
+
+ it("refuses paths outside the Site folder and type changes", () => {
+ expect(r.service.apply({ op: "update", path: "../secrets.md", fields: {} }, "admin").status).toBe("rejected");
+ const path = r.put(book("Mistborn"));
+ r.service.ingest(path);
+ expect(r.service.apply({ op: "update", path, fields: { type: "post" } }, "admin").status).toBe("rejected");
+ });
+
+ it("takes in an Obsidian edit before applying an API change on top of it", () => {
+ const path = r.put(book("Mistborn"));
+ r.service.ingest(path);
+ r.put(book("Mistborn", { verdict: "Great heist." }));
+ ok(r.service.apply({ op: "update", path, fields: { state: "now" } }, "greg"));
+ expect(r.store.get(path)!.data).toMatchObject({ verdict: "Great heist.", state: "now" });
+ });
+
+ it("holds an agent editing Greg's note, and writes it to the vault only once accepted", () => {
+ const path = r.put(book("Mistborn"));
+ r.service.ingest(path);
+ const before = readFileSync(join(r.site, path), "utf8");
+ const o = r.service.apply({ op: "update", path, fields: { verdict: "Meh." } }, "agent");
+ expect(o.status === "held" && o.held.reasons.map((x) => x.code)).toEqual(["agent-edit"]);
+ expect(readFileSync(join(r.site, path), "utf8")).toBe(before);
+ ok(r.service.acceptHeld(o.status === "held" ? o.held.id : 0));
+ expect(readFileSync(join(r.site, path), "utf8")).toContain("verdict: Meh.");
+ });
+
+ it("lets an agent edit a note marked agentEdits, and its own notes", () => {
+ const path = r.put(book("Mistborn", { agentEdits: true }));
+ r.service.ingest(path);
+ ok(r.service.apply({ op: "update", path, fields: { progress: "2/3" } }, "agent"));
+ const own = r.service.apply({ op: "create", data: book("Warbreaker") }, "agent");
+ ok(r.service.apply({ op: "update", path: own.path!, fields: { state: "now" } }, "agent"));
+ });
+
+ it("holds an agent publishing a post", () => {
+ const o = r.service.apply({ op: "create", data: post("Draft", { publish: false }), body: "Hi." }, "agent");
+ ok(o);
+ const pub = r.service.apply({ op: "update", path: o.path!, fields: { publish: true } }, "agent");
+ expect(pub.status === "held" && pub.held.reasons.map((x) => x.code)).toEqual(["agent-publish"]);
+ });
+
+ it("holds an agent adding contact details", () => {
+ const o = r.service.apply({ op: "create", data: post("Draft", { publish: false }), body: "Mail me at someone@example.com" }, "agent");
+ expect(o.status === "held" && o.held.reasons.map((x) => x.code)).toEqual(["contact"]);
+ });
+
+ it("holds an agent making too many changes at once", () => {
+ for (let i = 0; i < 10; i++) ok(r.service.apply({ op: "create", data: book(`Book ${i}`) }, "agent"));
+ expect(r.service.apply({ op: "create", data: book("Book 10") }, "agent").status).toBe("held");
+ r.clock.advance(11);
+ ok(r.service.apply({ op: "create", data: book("Book 11") }, "agent"));
+ });
+
+ it("refuses a change while another waits for review", () => {
+ const path = r.put(book("Mistborn"));
+ r.service.ingest(path);
+ r.service.apply({ op: "update", path, fields: { verdict: "Meh." } }, "agent");
+ expect(r.service.apply({ op: "update", path, fields: { verdict: "Good." } }, "greg").status).toBe("rejected");
+ });
+
+ it("rejecting a held vault edit can restore the live version into the vault", () => {
+ const path = r.put(book("Mistborn"));
+ r.service.ingest(path);
+ const good = readFileSync(join(r.site, path), "utf8");
+ r.putText(path, "---\ntype: library-item\n---\n");
+ const o = r.service.ingest(path);
+ r.service.rejectHeld(o.status === "held" ? o.held.id : 0, { restore: true });
+ expect(readFileSync(join(r.site, path), "utf8")).toBe(good);
+ });
+});
+
+describe("rules", () => {
+ it("only flags secrets that are new in this edit", () => {
+ const text = "---\ntype: post\n---\npassword = hunter2hunter2";
+ const checked = { ok: true as const, type: "post", data: { type: "post", title: "x", publish: false }, body: "" };
+ const current = { text, removed: false, author: "greg", data: {}, publish: false } as never;
+ const v = evaluate(
+ { path: "Posts/x.md", name: "x", slug: "x", text, author: "greg", checked: checked as never },
+ { current, nameTakenBy: null, slugTakenBy: null, recentAgentChanges: 0, settings: defaultRules },
+ );
+ expect(v.decision).toBe("accept");
+ });
+});
diff --git a/services/content/tsconfig.json b/services/content/tsconfig.json
new file mode 100644
index 0000000..7680f99
--- /dev/null
+++ b/services/content/tsconfig.json
@@ -0,0 +1,4 @@
+{
+ "extends": "../../tsconfig.base.json",
+ "include": ["src", "test"]
+}
diff --git a/vitest.config.ts b/vitest.config.ts
index 9ca545d..54d4e2a 100644
--- a/vitest.config.ts
+++ b/vitest.config.ts
@@ -1,5 +1,5 @@
import { defineConfig } from "vitest/config";
export default defineConfig({
- test: { include: ["packages/*/test/**/*.test.ts", "tools/*/test/**/*.test.ts"] },
+ test: { include: ["packages/*/test/**/*.test.ts", "services/*/test/**/*.test.ts", "tools/*/test/**/*.test.ts"] },
});
From 32cf13a5cb809e38396efa0b4eab6cd1ccefe06e Mon Sep 17 00:00:00 2001
From: Claude
Date: Sun, 11 Oct 2026 19:59:21 +0000
Subject: [PATCH 03/16] Add the admin page (phase 3)
services/admin is a client of the content service's HTTP API, served on
the tailnet only. It renders on the server with plain forms and no client
scripts, and holds the admin token so the browser never sees it.
- Review queue: reasons, line diff against the live version, accept,
reject, and reject with the live version put back into Obsidian
- Notes and drafts: search, preview, fields, backlinks, publish toggle
- History with compare and roll back
- Quick capture for a status or a Library item (optionally both)
- Store: orders open or closed per category, open slots and stock
- Overview: counts, last scan and error, scan now, sync job health
- Same-origin check on every post, strict CSP, sanitized previews
The content service gains /api/status, sync run reports (/api/syncs) and
lookup of a note by name.
Co-Authored-By: Claude Opus 5.5 (1M context)
Claude-Session: https://claude.ai/code/session_012aSbo2D1qP8cMFQCykf7kx
---
README.md | 6 +-
docs/admin.md | 49 ++++
docs/content-service.md | 5 +-
package-lock.json | 25 ++
package.json | 5 +-
services/admin/package.json | 16 ++
services/admin/public/admin.css | 79 +++++++
services/admin/src/client.ts | 136 +++++++++++
services/admin/src/diff.ts | 60 +++++
services/admin/src/html.ts | 46 ++++
services/admin/src/layout.ts | 49 ++++
services/admin/src/main.ts | 19 ++
services/admin/src/markdown.ts | 28 +++
services/admin/src/pages.ts | 289 +++++++++++++++++++++++
services/admin/src/server.ts | 357 +++++++++++++++++++++++++++++
services/admin/test/admin.test.ts | 152 ++++++++++++
services/admin/test/units.test.ts | 50 ++++
services/admin/tsconfig.json | 4 +
services/content/src/http.ts | 30 ++-
services/content/src/main.ts | 5 +-
services/content/src/service.ts | 12 +
services/content/src/store.ts | 47 ++++
services/content/test/http.test.ts | 11 +
23 files changed, 1474 insertions(+), 6 deletions(-)
create mode 100644 docs/admin.md
create mode 100644 services/admin/package.json
create mode 100644 services/admin/public/admin.css
create mode 100644 services/admin/src/client.ts
create mode 100644 services/admin/src/diff.ts
create mode 100644 services/admin/src/html.ts
create mode 100644 services/admin/src/layout.ts
create mode 100644 services/admin/src/main.ts
create mode 100644 services/admin/src/markdown.ts
create mode 100644 services/admin/src/pages.ts
create mode 100644 services/admin/src/server.ts
create mode 100644 services/admin/test/admin.test.ts
create mode 100644 services/admin/test/units.test.ts
create mode 100644 services/admin/tsconfig.json
diff --git a/README.md b/README.md
index ced09bb..167333b 100644
--- a/README.md
+++ b/README.md
@@ -13,11 +13,12 @@ with the decisions behind it, is the
| `packages/schema` | The one definition of every note type: fields, folders, file names, and the checks for one note and for the whole folder. The migration, the content service, the MCP server, the site and any later editing tool all use it. |
| `vault-kit` | What goes into the vault: the Obsidian templates (generated from the schema) and the Site guide. |
| `services/content` | The content service: watches the Site folder, runs the rules, keeps the last good version and its history, and serves the one write API. The MCP server for agents is part of it. See [docs/content-service.md](docs/content-service.md). |
+| `services/admin` | The admin page: the review queue with diffs, history and rollback, quick capture, store orders, draft previews and sync health. A client of the content service, on the tailnet only. See [docs/admin.md](docs/admin.md). |
| `tools/migrate` | The one-time migration from the old site's data into Site folder notes. |
| `docs` | How the pieces fit. Start with [docs/content-model.md](docs/content-model.md). |
| `legacy` | The old site, kept for reference only. Nothing outside it imports from it except the migration. |
-Coming in later phases: the admin page, the component library, and the new site.
+Coming in later phases: the component library and the new site.
## Commands
@@ -33,6 +34,9 @@ npm run schema:json # writes a JSON Schema per note type to packages/schema/jso
# the content service, against a copy of the Site folder
SITE_DIR=out/24.01_PersonalWebsite/Site DATA_DIR=data \
CONTENT_TOKENS="reader:<24+ chars>,admin:<24+ chars>" npm run content
+
+# the admin page, in a second terminal
+ADMIN_TOKEN= npm run admin # http://localhost:4320
```
## The old site
diff --git a/docs/admin.md b/docs/admin.md
new file mode 100644
index 0000000..71d3a43
--- /dev/null
+++ b/docs/admin.md
@@ -0,0 +1,49 @@
+# Admin page
+
+`services/admin` is where Greg decides what the rules held, puts back an
+earlier version, captures a status or a Library item, opens and closes store
+orders, previews drafts, and checks that the content service and the sync
+jobs are running.
+
+It is one more client of the content service's HTTP API, like the MCP server
+and any editing tool built later. It writes nothing itself: every action is a
+call to the write API, so the same rules apply, and changes are written into
+the vault so Obsidian shows them too. Its changes are recorded with the author
+`admin` ("Admin page" in history).
+
+## Pages
+
+| Page | What it does |
+| --- | --- |
+| Overview | Live and total notes, what waits for review, folder problems, the service's last scan and last error, a Scan now button, and each sync job's last run and last success. |
+| Review | Held changes with the rules' reasons and a line diff against the live version. Accept puts it live (and into the vault, for API changes). Reject keeps the live version; for an edit made in Obsidian, "Reject and put the live version back" also rewrites the file. Earlier decisions are listed by status. |
+| Notes, Drafts | Browse and search every note, or only the unpublished ones. |
+| A note | A preview of the body, its fields, what links to it, Publish or Unpublish, and its history. Any earlier version can be compared with the live one and rolled back. |
+| Capture | A status for the Now log, or a Library item with the option to post a status about it in the same step. |
+| Store | Orders open or closed per category, with the reopen date and turnaround, and open slots or stock per product line. |
+| Problems | Whole-folder problems, like links to notes that do not exist. |
+
+## Safety
+
+- **Tailnet only.** Publish it behind the "Tailnet only" access list; it has no
+ login of its own.
+- **The token stays on the server.** The admin page holds the content service's
+ admin token and calls the API itself; the browser never sees it.
+- **No posts from other sites.** Every form post must carry `Sec-Fetch-Site:
+ same-origin` (or a matching `Origin`), so a page elsewhere cannot make the
+ browser act on the admin page.
+- **Drafts cannot run code.** Previews show raw HTML as text and drop links that
+ are not http, https, mailto or relative. The page sends a content security
+ policy that allows no scripts at all; it works entirely with plain forms.
+
+## Running it
+
+| Variable | Default | |
+| --- | --- | --- |
+| `CONTENT_URL` | `http://localhost:4310` | The content service. |
+| `ADMIN_TOKEN` | required | The content service's admin token. |
+| `PORT`, `HOST` | `4320`, `0.0.0.0` | |
+
+```sh
+ADMIN_TOKEN=… npm run admin
+```
diff --git a/docs/content-service.md b/docs/content-service.md
index be91a21..f6512fd 100644
--- a/docs/content-service.md
+++ b/docs/content-service.md
@@ -79,13 +79,16 @@ roles in `CONTENT_TOKENS`.
| `GET /health` | anyone | Note, published and held counts. |
| `GET /api/schema` | all | Every note type with its JSON Schema. |
| `GET /api/notes?type=&q=&limit=&offset=&full=` | all | List or search. Readers see published notes only. |
-| `GET /api/note?path=` or `?type=&slug=` | all | One note with its backlinks. An old slug answers with `{ redirect }`. |
+| `GET /api/note?path=`, `?name=` or `?type=&slug=` | all | One note with its backlinks. An old slug answers with `{ redirect }`. |
| `GET /api/backlinks?name=` | all | Notes linking to a note. |
| `GET /api/redirects` | all | Every redirect. |
| `GET /attachments/` | all | An image from `_attachments`. |
| `GET /api/note/history?path=`, `/api/note/revision?path=&rev=` | agent, editor, admin | History and old versions. |
| `GET /api/problems` | agent, editor, admin | Broken links and other whole-folder problems. |
| `GET /api/held`, `/api/held/` | agent, editor, admin | The review queue; one entry comes with the current text for comparison. |
+| `GET /api/status` | agent, editor, admin | When the service started, its last full scan and last error, counts, and each sync job's latest run. |
+| `GET /api/syncs`, `?job=` | agent, editor, admin | Each sync job's latest run, or one job's recent runs. |
+| `POST /api/syncs` | agent, editor, admin | A sync job reports a run: `{ job, ok, message?, counts? }`. The last 50 runs per job are kept. |
| `POST /api/changes` | agent, editor, admin | One change. 422 with reasons when turned down. |
| `POST /api/held//accept`, `/reject` | editor, admin | Decide a held change. `{ "restore": true }` on reject puts the live version back into the vault. |
| `POST /api/note/rollback` | admin | `{ path, rev }` |
diff --git a/package-lock.json b/package-lock.json
index 7506337..44fe4d5 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -436,6 +436,10 @@
"node": ">=18"
}
},
+ "node_modules/@gk/admin": {
+ "resolved": "services/admin",
+ "link": true
+ },
"node_modules/@gk/content": {
"resolved": "services/content",
"link": true
@@ -1937,6 +1941,18 @@
"@jridgewell/sourcemap-codec": "^1.6.0"
}
},
+ "node_modules/marked": {
+ "version": "18.0.14",
+ "resolved": "https://registry.npmjs.org/marked/-/marked-18.0.14.tgz",
+ "integrity": "sha512-mBHK6FBHuBAlhgRe88w9F0O1AbwwXJUcQibUbC/QcdTbVGAD7aWza+xt3N6oT/jCZx3/OMeS+8rnuiHZcQ9s7A==",
+ "license": "MIT",
+ "bin": {
+ "marked": "bin/marked.js"
+ },
+ "engines": {
+ "node": ">= 20"
+ }
+ },
"node_modules/math-intrinsics": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
@@ -2863,6 +2879,15 @@
"zod": "^4.1.0"
}
},
+ "services/admin": {
+ "name": "@gk/admin",
+ "version": "0.1.0",
+ "dependencies": {
+ "@gk/schema": "*",
+ "marked": "18.0.14",
+ "tsx": "^4.19.0"
+ }
+ },
"services/content": {
"name": "@gk/content",
"version": "0.1.0",
diff --git a/package.json b/package.json
index 707ec47..295eaa8 100644
--- a/package.json
+++ b/package.json
@@ -12,13 +12,14 @@
"node": ">=22"
},
"scripts": {
- "typecheck": "tsc -p packages/schema && tsc -p tools/migrate && tsc -p services/content",
+ "typecheck": "tsc -p packages/schema && tsc -p tools/migrate && tsc -p services/content && tsc -p services/admin",
"test": "vitest run",
"check": "npm run typecheck && npm test",
"schema:json": "npm run json -w @gk/schema",
"templates": "npm run templates -w @gk/schema",
"migrate": "npm run migrate -w @gk/migrate --",
- "content": "npm run start -w @gk/content"
+ "content": "npm run start -w @gk/content",
+ "admin": "npm run start -w @gk/admin"
},
"devDependencies": {
"@types/node": "^22.10.0",
diff --git a/services/admin/package.json b/services/admin/package.json
new file mode 100644
index 0000000..fa5d793
--- /dev/null
+++ b/services/admin/package.json
@@ -0,0 +1,16 @@
+{
+ "name": "@gk/admin",
+ "version": "0.1.0",
+ "private": true,
+ "type": "module",
+ "description": "The admin page: the review queue, history and rollback, quick capture, store orders, draft previews and sync health. A client of the content service's HTTP API, reachable on the tailnet only.",
+ "scripts": {
+ "start": "node --import tsx src/main.ts",
+ "dev": "node --watch --import tsx src/main.ts"
+ },
+ "dependencies": {
+ "@gk/schema": "*",
+ "marked": "18.0.14",
+ "tsx": "^4.19.0"
+ }
+}
diff --git a/services/admin/public/admin.css b/services/admin/public/admin.css
new file mode 100644
index 0000000..fc25df6
--- /dev/null
+++ b/services/admin/public/admin.css
@@ -0,0 +1,79 @@
+/* The admin page follows the site's all-dark brand, kept plain: it is a tool. */
+:root {
+ --sky: #0b111c;
+ --midnight: #131c2b;
+ --panel: #182335;
+ --line: #2a3a52;
+ --text: #f4f2ed;
+ --muted: #a9b4c2;
+ --teal: #14a39a;
+ --teal-bright: #2bc2b7;
+ --gold: #c9a24a;
+ --gold-bright: #d8b35a;
+ --red: #f0857a;
+ --add: #12382f;
+ --remove: #45202a;
+ color-scheme: dark;
+}
+* { box-sizing: border-box; }
+body { margin: 0; background: var(--sky); color: var(--text); font: 15px/1.55 Montserrat, system-ui, sans-serif; }
+a { color: var(--teal-bright); text-underline-offset: 3px; }
+h1, h2, h3 { font-family: Cinzel, Georgia, serif; font-weight: 600; letter-spacing: 0.02em; }
+h1 { font-size: 1.6rem; margin: 0 0 4px; }
+h2 { font-size: 1.15rem; margin: 28px 0 10px; color: var(--gold-bright); }
+code, pre, .mono { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 13px; }
+header.top { display: flex; flex-wrap: wrap; align-items: center; gap: 4px 18px; padding: 12px 20px; background: var(--midnight); border-bottom: 1px solid var(--gold); }
+header.top .brand { font-family: Cinzel, serif; color: var(--gold-bright); text-decoration: none; margin-right: 8px; }
+header.top nav { display: flex; flex-wrap: wrap; gap: 4px 14px; }
+header.top nav a { color: var(--text); text-decoration: none; padding: 4px 0; border-bottom: 2px solid transparent; }
+header.top nav a[aria-current="page"] { border-color: var(--teal); }
+.count { display: inline-block; min-width: 20px; padding: 0 6px; margin-left: 4px; border-radius: 10px; background: var(--gold); color: var(--midnight); font-size: 12px; font-weight: 700; text-align: center; }
+main { max-width: 1100px; margin: 0 auto; padding: 24px 16px 64px; }
+.lede { color: var(--muted); margin: 0 0 18px; }
+.flash { padding: 10px 14px; border-left: 3px solid var(--teal); background: var(--panel); margin-bottom: 18px; }
+.flash.err { border-color: var(--red); }
+.flash ul { margin: 6px 0 0; padding-left: 20px; }
+.panel { background: var(--panel); border: 1px solid var(--line); border-radius: 10px; padding: 16px; margin-bottom: 16px; }
+.grid { display: grid; gap: 16px; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); }
+.stat { font-size: 1.9rem; font-family: Cinzel, serif; color: var(--gold-bright); }
+.muted { color: var(--muted); }
+table { width: 100%; border-collapse: collapse; }
+th, td { text-align: left; padding: 8px 10px; border-bottom: 1px solid var(--line); vertical-align: top; }
+th { color: var(--muted); font-weight: 600; font-size: 13px; }
+.table-wrap { overflow-x: auto; }
+.pill { display: inline-block; padding: 1px 10px; border-radius: 999px; font-size: 12px; border: 1px solid var(--line); white-space: nowrap; }
+.pill.live { border-color: var(--teal); color: var(--teal-bright); }
+.pill.draft { border-color: var(--gold); color: var(--gold-bright); }
+.pill.bad { border-color: var(--red); color: var(--red); }
+.reason { margin: 4px 0; }
+.reason code { color: var(--gold-bright); }
+form.inline { display: inline; }
+.actions { display: flex; flex-wrap: wrap; gap: 10px; align-items: center; margin: 14px 0; }
+button, .button { font: inherit; cursor: pointer; min-height: 36px; padding: 6px 18px; border-radius: 999px; border: 1px solid var(--gold); background: transparent; color: var(--text); text-decoration: none; display: inline-flex; align-items: center; }
+button.primary { background: var(--teal); border-color: var(--teal); color: var(--midnight); font-weight: 600; }
+button.danger { border-color: var(--red); color: var(--red); }
+button:focus-visible, a:focus-visible, input:focus-visible, select:focus-visible, textarea:focus-visible { outline: 2px solid var(--gold); outline-offset: 3px; }
+label { display: block; margin: 10px 0 4px; font-size: 13px; color: var(--muted); }
+label.check { display: flex; gap: 8px; align-items: center; color: var(--text); font-size: 15px; }
+input, select, textarea { font: inherit; width: 100%; padding: 8px 10px; border-radius: 10px; border: 1px solid var(--line); background: var(--sky); color: var(--text); }
+input[type="checkbox"] { width: auto; }
+input:focus, select:focus, textarea:focus { border-color: var(--teal); }
+.row { display: grid; gap: 0 14px; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); }
+.filters { display: flex; flex-wrap: wrap; gap: 10px; align-items: end; margin-bottom: 16px; }
+.filters > * { flex: 1 1 160px; }
+.filters button { flex: 0 0 auto; }
+.diff { background: var(--sky); border: 1px solid var(--line); border-radius: 10px; overflow-x: auto; padding: 8px 0; }
+.diff div { white-space: pre-wrap; word-break: break-word; padding: 0 12px; font-family: ui-monospace, monospace; font-size: 13px; }
+.diff .add { background: var(--add); }
+.diff .add::before { content: "+ "; color: var(--teal-bright); }
+.diff .remove { background: var(--remove); }
+.diff .remove::before { content: "- "; color: var(--red); }
+.diff .same::before { content: " "; }
+.diff .skip { color: var(--muted); font-style: italic; }
+.preview { background: var(--midnight); border: 1px solid var(--gold); border-radius: 10px; padding: 8px 22px; }
+.preview img { max-width: 100%; }
+dl.fields { display: grid; grid-template-columns: minmax(110px, max-content) 1fr; gap: 6px 16px; margin: 0; }
+dl.fields dt { color: var(--muted); }
+dl.fields dd { margin: 0; word-break: break-word; }
+dl.fields pre { margin: 0; white-space: pre-wrap; }
+input.num { width: 90px; }
diff --git a/services/admin/src/client.ts b/services/admin/src/client.ts
new file mode 100644
index 0000000..9798941
--- /dev/null
+++ b/services/admin/src/client.ts
@@ -0,0 +1,136 @@
+/**
+ * The content service's HTTP API, as the admin page uses it. The admin page
+ * holds the admin token on the server side; the browser never sees it.
+ */
+export class ApiError extends Error {
+ constructor(readonly status: number, message: string, readonly body: unknown) {
+ super(message);
+ }
+}
+
+export interface Summary {
+ path: string;
+ type: string;
+ title: string;
+ slug: string;
+ publish: boolean;
+ acceptedAt: string;
+}
+
+export interface Note extends Summary {
+ name: string;
+ data: Record;
+ body: string;
+ rev: number;
+ author: string;
+ text: string;
+ backlinks: Summary[];
+ pending: Held | null;
+}
+
+export interface Reason {
+ code: string;
+ message: string;
+}
+
+export interface Held {
+ id: number;
+ path: string;
+ author: string;
+ source: "vault" | "api";
+ reasons: Reason[];
+ at: string;
+ status: string;
+ decidedAt: string | null;
+ text?: string;
+ current?: string | null;
+}
+
+export interface Revision {
+ rev: number;
+ author: string;
+ at: string;
+ action: string;
+ text?: string;
+}
+
+export interface SyncJob {
+ job: string;
+ at: string;
+ ok: boolean;
+ message: string;
+ counts: Record;
+ lastOkAt?: string | null;
+}
+
+export interface Status {
+ startedAt: string;
+ lastScan: { at: string; changed: number; held: number } | null;
+ lastError: { at: string; message: string } | null;
+ counts: { notes: number; published: number; held: number };
+ syncs: SyncJob[];
+}
+
+export interface Problem {
+ path: string;
+ message: string;
+}
+
+export type Outcome =
+ | { status: "accepted"; path: string }
+ | { status: "held"; path: string; held: Held }
+ | { status: "rejected"; path: string | null; reasons: Reason[] }
+ | { status: "unchanged"; path: string };
+
+export type Change =
+ | { op: "create"; data: Record; body?: string }
+ | { op: "update"; path: string; fields?: Record; body?: string };
+
+export type FullNote = Omit;
+
+export class ContentApi {
+ constructor(private readonly base: string, private readonly token: string) {}
+
+ private async call(method: "GET" | "POST", path: string, body?: unknown, okStatuses = [200]): Promise {
+ const res = await fetch(new URL(path, this.base), {
+ method,
+ headers: { authorization: `Bearer ${this.token}`, ...(body !== undefined ? { "content-type": "application/json" } : {}) },
+ body: body !== undefined ? JSON.stringify(body) : undefined,
+ });
+ const data = (await res.json().catch(() => null)) as unknown;
+ if (!okStatuses.includes(res.status)) {
+ throw new ApiError(res.status, (data as { error?: string } | null)?.error ?? `The content service answered ${res.status}`, data);
+ }
+ return data as T;
+ }
+
+ private get = (path: string, params: Record = {}) => {
+ const u = new URLSearchParams();
+ for (const [k, v] of Object.entries(params)) if (v !== undefined && v !== "") u.set(k, String(v));
+ const s = u.toString();
+ return this.call("GET", s ? `${path}?${s}` : path);
+ };
+
+ status = () => this.get("/api/status");
+ notes = (p: { type?: string; q?: string; limit?: number; full?: boolean } = {}) =>
+ this.get("/api/notes", { type: p.type, q: p.q, limit: p.limit, full: p.full ? "true" : undefined });
+ fullNotes = (type: string) => this.get("/api/notes", { type, full: "true", limit: 1000 });
+ note = (path: string) => this.get("/api/note", { path });
+ noteByName = (name: string) => this.get("/api/note", { name });
+ history = (path: string) => this.get("/api/note/history", { path });
+ revision = (path: string, rev: number) => this.get("/api/note/revision", { path, rev });
+ held = (status = "pending") => this.get("/api/held", { status });
+ heldOne = (id: number) => this.get(`/api/held/${id}`);
+ problems = () => this.get("/api/problems");
+ syncRuns = (job: string) => this.get("/api/syncs", { job });
+
+ apply = (change: Change) => this.call("POST", "/api/changes", change, [200, 422]);
+ accept = (id: number) => this.call("POST", `/api/held/${id}/accept`, {}, [200, 422]);
+ reject = (id: number, restore: boolean) => this.call("POST", `/api/held/${id}/reject`, { restore }, [200, 422]);
+ rollback = (path: string, rev: number) => this.call("POST", "/api/note/rollback", { path, rev }, [200, 422]);
+ scan = () => this.call<{ changed: number; held: number }>("POST", "/api/scan", {});
+
+ /** Fetch an attachment to pass through to the browser. */
+ attachment = (name: string) =>
+ fetch(new URL(`/attachments/${encodeURIComponent(name)}`, this.base), { headers: { authorization: `Bearer ${this.token}` } });
+}
diff --git a/services/admin/src/diff.ts b/services/admin/src/diff.ts
new file mode 100644
index 0000000..b1dc19b
--- /dev/null
+++ b/services/admin/src/diff.ts
@@ -0,0 +1,60 @@
+/**
+ * A line diff for the review queue and history: what a change adds and
+ * removes, with unchanged lines folded down to a little context.
+ */
+export type DiffLine = { kind: "same" | "add" | "remove"; text: string } | { kind: "skip"; count: number };
+
+/** Notes are short, so a plain longest-common-subsequence table is enough. */
+const MAX_CELLS = 4_000_000;
+
+export function diffLines(before: string, after: string, context = 3): DiffLine[] {
+ const a = before === "" ? [] : before.replace(/\r\n/g, "\n").split("\n");
+ const b = after === "" ? [] : after.replace(/\r\n/g, "\n").split("\n");
+ let full: DiffLine[];
+ if (a.length * b.length > MAX_CELLS) {
+ // Too large to compare line by line: show it as replaced.
+ full = [...a.map((text) => ({ kind: "remove" as const, text })), ...b.map((text) => ({ kind: "add" as const, text }))];
+ } else {
+ const n = a.length;
+ const m = b.length;
+ const lcs = Array.from({ length: n + 1 }, () => new Uint32Array(m + 1));
+ for (let i = n - 1; i >= 0; i--) {
+ for (let j = m - 1; j >= 0; j--) {
+ lcs[i]![j] = a[i] === b[j] ? lcs[i + 1]![j + 1]! + 1 : Math.max(lcs[i + 1]![j]!, lcs[i]![j + 1]!);
+ }
+ }
+ full = [];
+ let i = 0;
+ let j = 0;
+ while (i < n && j < m) {
+ if (a[i] === b[j]) {
+ full.push({ kind: "same", text: a[i]! });
+ i++;
+ j++;
+ } else if (lcs[i + 1]![j]! >= lcs[i]![j + 1]!) full.push({ kind: "remove", text: a[i++]! });
+ else full.push({ kind: "add", text: b[j++]! });
+ }
+ while (i < n) full.push({ kind: "remove", text: a[i++]! });
+ while (j < m) full.push({ kind: "add", text: b[j++]! });
+ }
+ return fold(full, context);
+}
+
+function fold(lines: DiffLine[], context: number): DiffLine[] {
+ const changed = lines.map((l) => l.kind !== "same");
+ const keep = lines.map((_, i) => {
+ for (let k = Math.max(0, i - context); k <= Math.min(lines.length - 1, i + context); k++) if (changed[k]) return true;
+ return false;
+ });
+ const out: DiffLine[] = [];
+ let skipped = 0;
+ lines.forEach((l, i) => {
+ if (keep[i]) {
+ if (skipped) out.push({ kind: "skip", count: skipped });
+ skipped = 0;
+ out.push(l);
+ } else skipped++;
+ });
+ if (skipped) out.push({ kind: "skip", count: skipped });
+ return out;
+}
diff --git a/services/admin/src/html.ts b/services/admin/src/html.ts
new file mode 100644
index 0000000..81ef0eb
--- /dev/null
+++ b/services/admin/src/html.ts
@@ -0,0 +1,46 @@
+/**
+ * A tagged template that escapes every value unless it is already HTML. The
+ * admin page shows text agents wrote, so nothing reaches the page unescaped.
+ */
+export class Html {
+ constructor(readonly value: string) {}
+ toString() {
+ return this.value;
+ }
+}
+
+export const raw = (s: string) => new Html(s);
+
+export function escape(s: unknown): string {
+ return String(s ?? "")
+ .replace(/&/g, "&")
+ .replace(//g, ">")
+ .replace(/"/g, """)
+ .replace(/'/g, "'");
+}
+
+const part = (v: unknown): string => {
+ if (v instanceof Html) return v.value;
+ if (Array.isArray(v)) return v.map(part).join("");
+ if (v === null || v === undefined || v === false) return "";
+ return escape(v);
+};
+
+export function html(strings: TemplateStringsArray, ...values: unknown[]): Html {
+ let out = strings[0] ?? "";
+ values.forEach((v, i) => {
+ out += part(v) + (strings[i + 1] ?? "");
+ });
+ return new Html(out);
+}
+
+/** A query string from the set values only. */
+export function qs(params: Record): string {
+ const u = new URLSearchParams();
+ for (const [k, v] of Object.entries(params)) if (v !== undefined && v !== null && v !== "" && v !== false) u.set(k, String(v));
+ const s = u.toString();
+ return s ? `?${s}` : "";
+}
+
+export const when = (d: string | null | undefined) => (d ? d.replace("T", " ").slice(0, 16) + " UTC" : "never");
diff --git a/services/admin/src/layout.ts b/services/admin/src/layout.ts
new file mode 100644
index 0000000..7b8dc0c
--- /dev/null
+++ b/services/admin/src/layout.ts
@@ -0,0 +1,49 @@
+import { html, type Html } from "./html.ts";
+
+export interface Flash {
+ msg?: string;
+ err?: string;
+ reasons?: string[];
+}
+
+const nav: [string, string][] = [
+ ["/", "Overview"],
+ ["/review", "Review"],
+ ["/notes", "Notes"],
+ ["/drafts", "Drafts"],
+ ["/capture", "Capture"],
+ ["/store", "Store"],
+ ["/problems", "Problems"],
+];
+
+export function layout(p: { title: string; path: string; held: number; flash?: Flash; body: Html }): Html {
+ const current = (href: string) => (href === "/" ? p.path === "/" : p.path.startsWith(href));
+ return html`
+
+
+
+
+
+${p.title} · GregKilmer.com admin
+
+
+
+
+
+
+
+${p.flash?.msg ? html`${p.flash.msg}
` : ""}
+${p.flash?.err
+ ? html`${p.flash.err}${p.flash.reasons?.length ? html`
${p.flash.reasons.map((r) => html`${r} `)} ` : ""}
`
+ : ""}
+${p.body}
+
+
+`;
+}
diff --git a/services/admin/src/main.ts b/services/admin/src/main.ts
new file mode 100644
index 0000000..93dbe58
--- /dev/null
+++ b/services/admin/src/main.ts
@@ -0,0 +1,19 @@
+/**
+ * Starts the admin page. It talks to the content service with the admin
+ * token and must be published on the tailnet only.
+ */
+import { ContentApi } from "./client.ts";
+import { createAdminServer } from "./server.ts";
+
+const contentUrl = process.env.CONTENT_URL ?? "http://localhost:4310";
+const token = process.env.ADMIN_TOKEN;
+if (!token) throw new Error("ADMIN_TOKEN is required: the content service's admin token");
+const port = Number(process.env.PORT ?? 4320);
+const host = process.env.HOST ?? "0.0.0.0";
+
+const server = createAdminServer(new ContentApi(contentUrl, token));
+server.listen(port, host, () => console.log(`Admin page on ${host}:${port}, using ${contentUrl}`));
+
+const stop = () => server.close(() => process.exit(0));
+process.on("SIGTERM", stop);
+process.on("SIGINT", stop);
diff --git a/services/admin/src/markdown.ts b/services/admin/src/markdown.ts
new file mode 100644
index 0000000..ea2628b
--- /dev/null
+++ b/services/admin/src/markdown.ts
@@ -0,0 +1,28 @@
+/**
+ * Draft previews. Raw HTML in a note is shown as text and only http, https,
+ * mailto and relative links survive, so a draft an agent wrote cannot run
+ * anything on the admin page. Wikilinks become links to the note.
+ */
+import { Marked, type Tokens } from "marked";
+import { escape } from "./html.ts";
+
+const safeUrl = (href: string) => (/^(https?:|mailto:|\/|#|\.\/|\.\.\/)/i.test(href.trim()) || !/^[a-z][a-z0-9+.-]*:/i.test(href.trim()) ? href : "#");
+
+export function renderMarkdown(body: string, opts: { noteHref: (name: string) => string; imageSrc: (file: string) => string }): string {
+ const marked = new Marked({
+ gfm: true,
+ renderer: {
+ html: ({ text }: Tokens.HTML | Tokens.Tag) => escape(text),
+ link({ href, title, tokens }: Tokens.Link) {
+ const t = title ? ` title="${escape(title)}"` : "";
+ return `${this.parser.parseInline(tokens)} `;
+ },
+ image: ({ href, text }: Tokens.Image) => ` `,
+ },
+ });
+ // ![[file.png]] embeds and [[Note|label]] links, before Markdown sees them.
+ const withLinks = body
+ .replace(/!\[\[([^\]|]+)(?:\|[^\]]*)?\]\]/g, (_, f: string) => `)})`)
+ .replace(/\[\[([^\]|#]+)(?:#[^\]|]*)?(?:\|([^\]]+))?\]\]/g, (_, n: string, label?: string) => `[${label ?? n}](${opts.noteHref(n.trim())})`);
+ return marked.parse(withLinks, { async: false }) as string;
+}
diff --git a/services/admin/src/pages.ts b/services/admin/src/pages.ts
new file mode 100644
index 0000000..a752ec9
--- /dev/null
+++ b/services/admin/src/pages.ts
@@ -0,0 +1,289 @@
+/**
+ * Page bodies. Each takes data already fetched from the content service and
+ * returns HTML; nothing here talks to the network.
+ */
+import { libraryStates, media } from "@gk/schema";
+import type { FullNote, Held, Note, Problem, Revision, Status, Summary, SyncJob } from "./client.ts";
+import { diffLines } from "./diff.ts";
+import { html, qs, raw, when, type Html } from "./html.ts";
+import { renderMarkdown } from "./markdown.ts";
+
+export const noteHref = (path: string) => `/note${qs({ path })}`;
+
+const authorLabel: Record = { greg: "You", agent: "An agent", admin: "Admin page" };
+const who = (a: string) => authorLabel[a] ?? a;
+
+const publishPill = (publish: boolean) => (publish ? html`Live ` : html`Draft `);
+
+const reasonList = (reasons: Held["reasons"]) =>
+ reasons.length
+ ? reasons.map((r) => html`${r.code} ${r.message}
`)
+ : html`Waiting for review: no rule flagged it.
`;
+
+export function diffView(before: string, after: string): Html {
+ const lines = diffLines(before, after);
+ if (!lines.some((l) => l.kind === "add" || l.kind === "remove")) return html`No differences.
`;
+ return html`${lines.map((l) =>
+ l.kind === "skip" ? html`
${l.count} unchanged lines
` : html`
${l.text}
`,
+ )}
`;
+}
+
+export function preview(body: string): Html {
+ if (!body.trim()) return html`This note has no body text.
`;
+ return html`${raw(
+ renderMarkdown(body, {
+ noteHref: (name) => `/go${qs({ name })}`,
+ imageSrc: (file) => `/attachments/${encodeURIComponent(file)}`,
+ }),
+ )}
`;
+}
+
+// ------------------------------------------------------------ overview
+
+export function overview(s: Status, held: Held[], problems: Problem[]): Html {
+ const syncs = s.syncs;
+ return html`
+Overview
+What the site is serving, what is waiting for you, and whether the jobs that feed it are running.
+
+
${s.counts.published}
live notes of ${s.counts.notes}
+
+
+
+Content service
+
+
+ Running since ${when(s.startedAt)}
+ Last full scan ${s.lastScan ? html`${when(s.lastScan.at)}: ${s.lastScan.changed} changed, ${s.lastScan.held} held` : "not yet"}
+ Last error ${s.lastError ? html`${when(s.lastError.at)} ${s.lastError.message}` : "none"}
+
+
+
+Sync jobs
+${syncs.length
+ ? html`
+Job Last run Result Last success Counts
+${syncs.map(syncRow)}
`
+ : html`No sync job has reported yet. PersonalReader, BoardGameGeek and the store syncs report here once they run.
`}`;
+}
+
+const syncRow = (j: SyncJob) => html`
+ ${j.job}
+ ${when(j.at)}
+ ${j.ok ? html`OK ` : html`Failed `} ${j.message}
+ ${when(j.lastOkAt)}
+ ${Object.entries(j.counts).map(([k, v]) => `${k} ${v}`).join(", ")}
+ `;
+
+export function syncHistory(job: string, runs: SyncJob[]): Html {
+ return html`${job} Back to the overview
+When Result Counts
+${runs.map((r) => html`${when(r.at)} ${r.ok ? html`OK ` : html`Failed `} ${r.message} ${Object.entries(r.counts).map(([k, v]) => `${k} ${v}`).join(", ")} `)}
+
`;
+}
+
+// ------------------------------------------------------------ review
+
+const statuses = ["pending", "accepted", "rejected", "superseded"];
+
+export function reviewList(held: Held[], status: string): Html {
+ return html`Review
+Changes the rules held. The site keeps showing the last accepted version until you decide.
+${statuses.map((s) => (s === status ? html`${label(s)} ` : html`${label(s)} `))}
+${held.length
+ ? html`Note From Why When
+${held.map((h) => html`
+ ${h.path}
+ ${who(h.author)}${h.source === "vault" ? " in Obsidian" : ""}
+ ${h.reasons.map((r) => r.code).join(", ") || "review"}
+ ${when(h.at)} `)}
+
`
+ : html`${status === "pending" ? "Nothing is waiting." : "None."}
`}`;
+}
+
+const label = (s: string) => s[0]!.toUpperCase() + s.slice(1);
+
+export function reviewOne(h: Held): Html {
+ const pending = h.status === "pending";
+ return html`${h.path}
+${who(h.author)}${h.source === "vault" ? " in Obsidian" : " through the API"}, ${when(h.at)}. ${pending ? "" : html`${label(h.status)} ${when(h.decidedAt)} `}
+${reasonList(h.reasons)}
+${pending
+ ? html`
+
+
+ ${h.source === "vault" && h.current
+ ? html``
+ : ""}
+
`
+ : ""}
+${h.current === null ? "New note" : "Changes against the live version"}
+${diffView(h.current ?? "", h.text ?? "")}
+Open the live note
`;
+}
+
+// ------------------------------------------------------------ notes
+
+export function notesList(p: { title: string; lede: string; action: string; notes: Summary[]; types: string[]; type?: string; q?: string }): Html {
+ return html`${p.title}
+${p.lede}
+
+${p.notes.length
+ ? html`Title Type State Changed
+${p.notes.map((n) => html`${n.title} ${n.path}
${n.type} ${publishPill(n.publish)} ${when(n.acceptedAt)} `)}
+
`
+ : html`No notes match.
`}`;
+}
+
+const fieldValue = (v: unknown): Html =>
+ typeof v === "object" && v !== null ? html`${JSON.stringify(v, null, 2)} ` : html`${String(v)}`;
+
+export function noteView(n: Note, history: Revision[]): Html {
+ const shown = Object.entries(n.data).filter(([k]) => !["type", "title"].includes(k));
+ return html`${n.title}
+${publishPill(n.publish)} ${n.type} · ${n.path} · last changed by ${who(n.author).toLowerCase()} ${when(n.acceptedAt)}
+${n.pending ? html`` : ""}
+
+
+
+Preview
+${preview(n.body)}
+Fields
+${shown.map(([k, v]) => html`${k} ${fieldValue(v)} `)}
+Linked from
+${n.backlinks.length ? html`${n.backlinks.map((b) => html`${b.title} ${b.type} `)} ` : html`Nothing links here yet.
`}
+History
+${historyTable(n, history)}`;
+}
+
+function historyTable(n: Note, history: Revision[]): Html {
+ return html`Version Who What When
+${history.map((r) => html`
+ ${r.rev}${r.rev === n.rev ? html` current ` : ""}
+ ${who(r.author)} ${r.action} ${when(r.at)}
+ ${r.rev === n.rev || r.action === "remove" ? "" : html`Compare and roll back `}
+ `)}
+
`;
+}
+
+export function revisionView(n: Note, r: Revision): Html {
+ return html`${n.title}, version ${r.rev}
+${who(r.author)}, ${when(r.at)}. Showing what rolling back would change in the live version ${n.rev}.
+
+${diffView(n.text, r.text ?? "")}`;
+}
+
+// ------------------------------------------------------------ capture
+
+export function capture(kinds: string[], today: string): Html {
+ return html`Capture
+Add a line to the Now log or something to the Library. Both are written into the vault, so they show up in Obsidian too.
+`;
+}
+
+// ------------------------------------------------------------ store
+
+interface Category {
+ name: string;
+ ordersOpen: boolean;
+ reopens?: string;
+ turnaround?: string;
+}
+
+export function store(settings: FullNote | undefined, lines: FullNote[]): Html {
+ const categories = (settings?.data.categories as Category[] | undefined) ?? [];
+ return html`Store
+Open and close orders by category, and set how many order slots each made to order line has open.
+Categories
+${settings
+ ? html`${categories.map(
+ (c) => html`
`,
+ )}
`
+ : html`There is no Store/_settings note yet.
`}
+Product lines
+Line Category Sold Open slots or stock
+${lines.map((l) => {
+ const slots = l.data.slots as { open: number; total: number } | undefined;
+ const qty = l.data.quantity as number | undefined;
+ return html`
+ ${l.title} ${publishPill(l.publish)}
+ ${String(l.data.category)}
+ ${String(l.data.mode)}, ${String(l.data.channel)}
+ ${slots
+ ? html``
+ : qty !== undefined
+ ? html``
+ : html`Not counted here `}
+ `;
+ })}
+
`;
+}
+
+// ------------------------------------------------------------ problems
+
+export function problemsView(problems: Problem[]): Html {
+ return html`Problems
+Things that are wrong across the folder as a whole, like links to notes that do not exist. They do not stop a note going live.
+${problems.length
+ ? html`Note Problem
+${problems.map((p) => html`${p.path} ${p.message} `)}
`
+ : html`No problems.
`}`;
+}
diff --git a/services/admin/src/server.ts b/services/admin/src/server.ts
new file mode 100644
index 0000000..27bdaaa
--- /dev/null
+++ b/services/admin/src/server.ts
@@ -0,0 +1,357 @@
+/**
+ * The admin page's server. It renders HTML on the server and every action is
+ * a plain form post, so it works without client scripts. It holds the
+ * content service's admin token; the browser never sees it.
+ *
+ * It must only be reachable on the tailnet. On top of that, every form post
+ * has to come from this page itself, so another site cannot post to it.
+ */
+import { readFileSync } from "node:fs";
+import { createServer, type IncomingMessage, type ServerResponse } from "node:http";
+import { basename } from "node:path";
+import { Readable } from "node:stream";
+import { noteTypeNames } from "@gk/schema";
+import { ApiError, type ContentApi, type Outcome } from "./client.ts";
+import { html, qs, type Html } from "./html.ts";
+import { layout, type Flash } from "./layout.ts";
+import * as pages from "./pages.ts";
+
+const css = readFileSync(new URL("../public/admin.css", import.meta.url), "utf8");
+
+class Redirect {
+ constructor(readonly to: string) {}
+}
+
+type Form = URLSearchParams;
+type Page = { title: string; body: Html };
+
+export function createAdminServer(api: ContentApi, opts: { now?: () => Date } = {}) {
+ const today = () => (opts.now?.() ?? new Date()).toISOString().slice(0, 10);
+
+ /** Where to go after an action, carrying what happened. */
+ const after = (to: string, o: Outcome, saved: string) => {
+ const [path, query = ""] = to.split("?");
+ const p = new URLSearchParams(query);
+ if (o.status === "accepted" || o.status === "unchanged") p.set("msg", saved);
+ else if (o.status === "held") {
+ p.set("msg", "Saved for review. It is not live until you accept it.");
+ } else {
+ p.set("err", "Not saved.");
+ for (const r of o.reasons) p.append("r", r.message);
+ }
+ return new Redirect(`${path}?${p}`);
+ };
+
+ const pathOfOutcome = (o: Outcome) => (o.status === "rejected" ? null : o.path);
+
+ async function get(path: string, q: URLSearchParams): Promise {
+ switch (path) {
+ case "/": {
+ const [status, held, problems] = await Promise.all([api.status(), api.held(), api.problems()]);
+ return { title: "Overview", body: pages.overview(status, held, problems) };
+ }
+ case "/syncs": {
+ const job = q.get("job") ?? "";
+ return { title: job, body: pages.syncHistory(job, await api.syncRuns(job)) };
+ }
+ case "/review": {
+ const status = q.get("status") ?? "pending";
+ return { title: "Review", body: pages.reviewList(await api.held(status), status) };
+ }
+ case "/notes":
+ case "/drafts": {
+ const drafts = path === "/drafts";
+ const type = q.get("type") || undefined;
+ const text = q.get("q") || undefined;
+ let notes = await api.notes({ type, q: text, limit: 1000 });
+ if (drafts) notes = notes.filter((n) => !n.publish);
+ if (!text) notes = notes.sort((a, b) => b.acceptedAt.localeCompare(a.acceptedAt)).slice(0, 300);
+ return {
+ title: drafts ? "Drafts" : "Notes",
+ body: pages.notesList({
+ title: drafts ? "Drafts" : "Notes",
+ lede: drafts
+ ? "Notes that are not on the site yet. Open one to preview it and publish it."
+ : "Every note the site knows about, most recently changed first.",
+ action: path,
+ notes,
+ types: noteTypeNames,
+ type,
+ q: text,
+ }),
+ };
+ }
+ case "/note": {
+ const p = required(q, "path");
+ const [note, history] = await Promise.all([api.note(p), api.history(p)]);
+ return { title: note.title, body: pages.noteView(note, history) };
+ }
+ case "/note/revision": {
+ const p = required(q, "path");
+ const [note, rev] = await Promise.all([api.note(p), api.revision(p, Number(required(q, "rev")))]);
+ return { title: `${note.title}, version ${rev.rev}`, body: pages.revisionView(note, rev) };
+ }
+ case "/go": {
+ const note = await api.noteByName(required(q, "name"));
+ return new Redirect(pages.noteHref(note.path));
+ }
+ case "/capture": {
+ const kinds = (await api.fullNotes("status-kinds"))[0]?.data.kinds as { label: string }[] | undefined;
+ return { title: "Capture", body: pages.capture((kinds ?? []).map((k) => k.label), today()) };
+ }
+ case "/store": {
+ const [settings, lines] = await Promise.all([api.fullNotes("store-settings"), api.fullNotes("product-line")]);
+ return { title: "Store", body: pages.store(settings[0], lines.sort((a, b) => Number(a.data.order ?? 99) - Number(b.data.order ?? 99))) };
+ }
+ case "/problems":
+ return { title: "Problems", body: pages.problemsView(await api.problems()) };
+ }
+ const review = path.match(/^\/review\/(\d+)$/);
+ if (review) {
+ const h = await api.heldOne(Number(review[1]));
+ return { title: `Review ${h.path}`, body: pages.reviewOne(h) };
+ }
+ throw new ApiError(404, "There is no page here.", null);
+ }
+
+ async function post(path: string, f: Form): Promise {
+ switch (path) {
+ case "/scan": {
+ const r = await api.scan();
+ return new Redirect(`/${qs({ msg: `Scanned: ${r.changed} changed, ${r.held} held.` })}`);
+ }
+ case "/note/rollback": {
+ const p = required(f, "path");
+ const o = await api.rollback(p, Number(required(f, "rev")));
+ return after(pages.noteHref(p), o, "Rolled back. The site and the vault show that version again.");
+ }
+ case "/note/publish": {
+ const p = required(f, "path");
+ const publish = f.get("publish") === "1";
+ const o = await api.apply({ op: "update", path: p, fields: { publish } });
+ return after(pages.noteHref(p), o, publish ? "Published." : "Taken off the site.");
+ }
+ case "/capture/status": {
+ const subject = (f.get("subject") ?? "").trim().replace(/^\[\[|\]\]$/g, "");
+ const o = await api.apply({
+ op: "create",
+ data: {
+ type: "status",
+ title: required(f, "title"),
+ kind: required(f, "kind"),
+ date: f.get("date") || today(),
+ ...optional(f, ["note"]),
+ ...(subject ? { subject: `[[${subject}]]` } : {}),
+ ...(f.get("pinned") === "1" ? { pinned: true } : {}),
+ publish: f.get("publish") === "1",
+ },
+ });
+ return after("/capture", o, "Status posted.");
+ }
+ case "/capture/library": {
+ const year = f.get("year");
+ const o = await api.apply({
+ op: "create",
+ data: {
+ type: "library-item",
+ title: required(f, "title"),
+ medium: required(f, "medium"),
+ state: required(f, "state"),
+ ...optional(f, ["creator", "group", "verdict", "note"]),
+ ...(year ? { year: Number(year) } : {}),
+ publish: f.get("publish") === "1",
+ },
+ });
+ const added = pathOfOutcome(o);
+ if (f.get("alsoStatus") === "1" && o.status === "accepted" && added) {
+ const s = await api.apply({
+ op: "create",
+ data: {
+ type: "status",
+ title: required(f, "title"),
+ kind: required(f, "kind"),
+ date: today(),
+ subject: `[[${basename(added, ".md")}]]`,
+ publish: f.get("publish") === "1",
+ },
+ });
+ return after("/capture", s, "Added to the Library and posted a status.");
+ }
+ return after("/capture", o, "Added to the Library.");
+ }
+ case "/store/category": {
+ const [settings] = await api.fullNotes("store-settings");
+ if (!settings) throw new ApiError(404, "There is no Store/_settings note yet.", null);
+ const name = required(f, "name");
+ const categories = (settings.data.categories as Record[]).map((c) => {
+ if (c.name !== name) return c;
+ const next: Record = { ...c, ordersOpen: f.get("ordersOpen") === "1" };
+ for (const k of ["reopens", "turnaround"]) {
+ const v = (f.get(k) ?? "").trim();
+ if (v) next[k] = v;
+ else delete next[k];
+ }
+ return next;
+ });
+ const o = await api.apply({ op: "update", path: settings.path, fields: { categories } });
+ return after("/store", o, `${name} saved.`);
+ }
+ case "/store/stock": {
+ const p = required(f, "path");
+ let fields: Record;
+ if (f.has("open")) {
+ const note = await api.note(p);
+ fields = { slots: { ...(note.data.slots as object), open: Number(f.get("open")) } };
+ } else fields = { quantity: Number(required(f, "quantity")) };
+ const o = await api.apply({ op: "update", path: p, fields });
+ return after("/store", o, "Saved.");
+ }
+ }
+ const decide = path.match(/^\/review\/(\d+)\/(accept|reject)$/);
+ if (decide) {
+ const id = Number(decide[1]);
+ const o = decide[2] === "accept" ? await api.accept(id) : await api.reject(id, f.get("restore") === "1");
+ if (o.status === "rejected") return after(`/review/${id}`, o, "");
+ return new Redirect(`/review${qs({ msg: decide[2] === "accept" ? "Accepted. It is live." : "Rejected. The site keeps the live version." })}`);
+ }
+ throw new ApiError(404, "There is no action here.", null);
+ }
+
+ async function handle(req: IncomingMessage, res: ServerResponse) {
+ const url = new URL(req.url ?? "/", "http://admin");
+ const method = req.method ?? "GET";
+ res.setHeader("x-frame-options", "DENY");
+ res.setHeader("referrer-policy", "same-origin");
+ res.setHeader(
+ "content-security-policy",
+ "default-src 'self'; img-src 'self' data: https:; style-src 'self' https://fonts.googleapis.com; font-src https://fonts.gstatic.com; script-src 'none'; form-action 'self'; frame-ancestors 'none'",
+ );
+
+ if (url.pathname === "/admin.css") {
+ res.writeHead(200, { "content-type": "text/css; charset=utf-8", "cache-control": "max-age=300" });
+ return res.end(css);
+ }
+ if (url.pathname === "/health") {
+ res.writeHead(200, { "content-type": "application/json" });
+ return res.end('{"ok":true}');
+ }
+
+ const attachment = url.pathname.match(/^\/attachments\/([^/]+)$/);
+ if (method === "GET" && attachment) {
+ const r = await api.attachment(decodeURIComponent(attachment[1] ?? ""));
+ res.writeHead(r.status, { "content-type": r.headers.get("content-type") ?? "application/octet-stream", "x-content-type-options": "nosniff" });
+ if (r.body) Readable.fromWeb(r.body as never).pipe(res);
+ else res.end();
+ return;
+ }
+
+ if (method === "POST") {
+ if (!sameOrigin(req)) return text(res, 403, "Form posts must come from the admin page itself.");
+ const form = new URLSearchParams(await readBody(req));
+ const r = await post(url.pathname, form);
+ res.writeHead(303, { location: r.to });
+ return res.end();
+ }
+ if (method !== "GET") return text(res, 405, "Method not allowed");
+
+ const out = await get(url.pathname, url.searchParams);
+ if (out instanceof Redirect) {
+ res.writeHead(303, { location: out.to });
+ return res.end();
+ }
+ const held = (await api.held().catch(() => [])).length;
+ page(res, 200, out, url, held);
+ }
+
+ function page(res: ServerResponse, status: number, p: Page, url: URL, held: number) {
+ const flash: Flash = {
+ msg: url.searchParams.get("msg") ?? undefined,
+ err: url.searchParams.get("err") ?? undefined,
+ reasons: url.searchParams.getAll("r"),
+ };
+ res.writeHead(status, { "content-type": "text/html; charset=utf-8", "cache-control": "no-store" });
+ res.end(layout({ title: p.title, path: url.pathname, held, flash, body: p.body }).toString());
+ }
+
+ return createServer((req, res) => {
+ handle(req, res).catch(async (err: unknown) => {
+ if (res.headersSent) return res.end();
+ const url = new URL(req.url ?? "/", "http://admin");
+ if (err instanceof ApiError) {
+ const reasons = (err.body as { reasons?: { message: string }[] } | null)?.reasons?.map((r) => r.message) ?? [];
+ const message = err.status === 404 ? "Not found." : err.message;
+ // A failed form post goes back where it came from, with the reason.
+ if (req.method === "POST") {
+ const back = safeBack(req.headers.referer) ?? new URL("/", "http://admin");
+ for (const k of ["msg", "err", "r"]) back.searchParams.delete(k);
+ back.searchParams.set("err", message);
+ for (const r of reasons) back.searchParams.append("r", r);
+ res.writeHead(303, { location: back.pathname + back.search });
+ return res.end();
+ }
+ return page(res, err.status === 404 ? 404 : err.status === 400 ? 400 : 502, { title: "Not available", body: errorBody(message) }, url, 0);
+ }
+ console.error(err);
+ page(res, 500, { title: "Error", body: errorBody("Something went wrong. Is the content service running?") }, url, 0);
+ });
+ });
+}
+
+const errorBody = (message: string) => html`Not available ${message}
Back to the overview
`;
+
+function required(f: URLSearchParams, k: string): string {
+ const v = (f.get(k) ?? "").trim();
+ if (!v) throw new ApiError(400, `${k} is required`, null);
+ return v;
+}
+
+function optional(f: URLSearchParams, keys: string[]): Record {
+ const out: Record = {};
+ for (const k of keys) {
+ const v = (f.get(k) ?? "").trim();
+ if (v) out[k] = v;
+ }
+ return out;
+}
+
+/** Browsers send Sec-Fetch-Site or Origin with every form post; both must say this page. */
+function sameOrigin(req: IncomingMessage): boolean {
+ const site = req.headers["sec-fetch-site"];
+ if (site) return site === "same-origin" || site === "none";
+ const origin = req.headers.origin;
+ if (origin) {
+ try {
+ return new URL(origin).host === req.headers.host;
+ } catch {
+ return false;
+ }
+ }
+ // Neither header: not a browser (a script on the tailnet), which the network already limits.
+ return true;
+}
+
+/** The page a failed post came from, as a local path only. */
+function safeBack(referer: string | undefined): URL | null {
+ if (!referer) return null;
+ try {
+ return new URL(new URL(referer).pathname + new URL(referer).search, "http://admin");
+ } catch {
+ return null;
+ }
+}
+
+async function readBody(req: IncomingMessage): Promise {
+ const chunks: Buffer[] = [];
+ let size = 0;
+ for await (const c of req) {
+ size += (c as Buffer).length;
+ if (size > 200_000) throw new ApiError(413, "The form is too large", null);
+ chunks.push(c as Buffer);
+ }
+ return Buffer.concat(chunks).toString("utf8");
+}
+
+function text(res: ServerResponse, status: number, body: string) {
+ res.writeHead(status, { "content-type": "text/plain; charset=utf-8" });
+ res.end(body);
+}
diff --git a/services/admin/test/admin.test.ts b/services/admin/test/admin.test.ts
new file mode 100644
index 0000000..8a8c415
--- /dev/null
+++ b/services/admin/test/admin.test.ts
@@ -0,0 +1,152 @@
+import { existsSync, readFileSync } from "node:fs";
+import type { AddressInfo } from "node:net";
+import type { Server } from "node:http";
+import { join } from "node:path";
+import { afterAll, beforeAll, describe, expect, it } from "vitest";
+import { parseTokens } from "../../content/src/config.ts";
+import { createHttpServer } from "../../content/src/http.ts";
+import { book, rig, type Rig } from "../../content/test/helpers.ts";
+import { ContentApi } from "../src/client.ts";
+import { createAdminServer } from "../src/server.ts";
+
+const ADMIN = "d".repeat(24);
+let r: Rig;
+let content: Server;
+let admin: Server;
+let base: string;
+
+const listen = async (s: Server) => {
+ await new Promise((ok) => s.listen(0, "127.0.0.1", ok));
+ return `http://127.0.0.1:${(s.address() as AddressInfo).port}`;
+};
+
+beforeAll(async () => {
+ r = rig();
+ r.put({ type: "status-kinds", title: "Status kinds", publish: true, kinds: [{ label: "Reading", icon: "book" }, { label: "Playing", icon: "dice" }] });
+ r.put(
+ {
+ type: "store-settings",
+ title: "Store settings",
+ publish: true,
+ categories: [
+ { name: "Tabletop", ordersOpen: true, turnaround: "2 weeks" },
+ { name: "Paper", ordersOpen: false },
+ ],
+ },
+ );
+ r.put({
+ type: "product-line",
+ title: "Proxy cards",
+ publish: true,
+ category: "Tabletop",
+ mode: "made to order",
+ channel: "here",
+ summary: "Cards.",
+ slots: { open: 3, total: 5 },
+ priceTiers: [{ band: "1-10", price: "$1" }],
+ });
+ r.put(book("Mistborn"), "Allomancy.");
+ r.service.scan();
+ content = createHttpServer(r.service, { tokens: parseTokens(`admin:${ADMIN}`) });
+ const contentUrl = await listen(content);
+ admin = createAdminServer(new ContentApi(contentUrl, ADMIN), { now: () => new Date("2026-10-11T12:00:00Z") });
+ base = await listen(admin);
+});
+afterAll(() => {
+ admin.close();
+ content.close();
+ r.done();
+});
+
+const get = async (path: string) => {
+ const res = await fetch(base + path, { redirect: "manual" });
+ return { status: res.status, text: await res.text(), location: res.headers.get("location") };
+};
+const post = async (path: string, form: Record, headers: Record = {}) => {
+ const res = await fetch(base + path, {
+ method: "POST",
+ redirect: "manual",
+ headers: { "content-type": "application/x-www-form-urlencoded", "sec-fetch-site": "same-origin", ...headers },
+ body: new URLSearchParams(form).toString(),
+ });
+ return { status: res.status, location: res.headers.get("location") ?? "" };
+};
+
+describe("admin page", () => {
+ it("shows the overview", async () => {
+ const page = await get("/");
+ expect(page.status).toBe(200);
+ expect(page.text).toContain("live notes of 4");
+ expect(page.text).toContain("No sync job has reported yet");
+ });
+
+ it("refuses a form posted from another site", async () => {
+ expect((await post("/scan", {}, { "sec-fetch-site": "cross-site" })).status).toBe(403);
+ });
+
+ it("captures a Library item with a status, written into the vault", async () => {
+ const res = await post("/capture/library", { title: "Warbreaker", medium: "book", state: "now", publish: "1", alsoStatus: "1", kind: "Reading" });
+ expect(res.location).toContain("msg=");
+ expect(existsSync(join(r.site, "Library/Books/Warbreaker.md"))).toBe(true);
+ const status = r.store.list({ type: "status" })[0]!;
+ expect(status.data).toMatchObject({ kind: "Reading", subject: "[[Warbreaker]]", date: "2026-10-11" });
+ expect(status.author).toBe("admin");
+ });
+
+ it("sends a rejected capture back with the reasons", async () => {
+ const res = await post("/capture/library", { title: "Warbreaker", medium: "book", state: "now" });
+ expect(res.location).toMatch(/err=.*r=/);
+ });
+
+ it("reviews a held change with a diff and accepts it", async () => {
+ const o = r.service.apply({ op: "update", path: "Library/Books/Mistborn.md", fields: { verdict: "A heist." } }, "agent");
+ expect(o.status).toBe("held");
+ const id = o.status === "held" ? o.held.id : 0;
+ const list = await get("/review");
+ expect(list.text).toContain(`/review/${id}`);
+ const one = await get(`/review/${id}`);
+ expect(one.text).toContain('verdict: A heist.
');
+ expect(one.text).toContain("agent-edit");
+ const res = await post(`/review/${id}/accept`, {});
+ expect(res.location).toContain("Accepted");
+ expect(readFileSync(join(r.site, "Library/Books/Mistborn.md"), "utf8")).toContain("verdict: A heist.");
+ });
+
+ it("previews a note, shows its history and rolls it back", async () => {
+ const page = await get("/note?path=Library%2FBooks%2FMistborn.md");
+ expect(page.text).toContain("Allomancy.
");
+ expect(page.text).toContain("Compare and roll back");
+ const rev = await get("/note/revision?path=Library%2FBooks%2FMistborn.md&rev=1");
+ expect(rev.text).toContain('verdict: A heist.
');
+ await post("/note/rollback", { path: "Library/Books/Mistborn.md", rev: "1" });
+ expect(r.store.get("Library/Books/Mistborn.md")!.data.verdict).toBeUndefined();
+ });
+
+ it("publishes a draft", async () => {
+ r.put(book("Elantris", { publish: false }));
+ r.service.scan();
+ expect((await get("/drafts")).text).toContain("Elantris");
+ await post("/note/publish", { path: "Library/Books/Elantris.md", publish: "1" });
+ expect(r.store.get("Library/Books/Elantris.md")!.publish).toBe(true);
+ });
+
+ it("opens and closes orders and sets slots", async () => {
+ expect((await get("/store")).text).toContain("Proxy cards");
+ await post("/store/category", { name: "Paper", ordersOpen: "1", turnaround: "3 weeks" });
+ await post("/store/category", { name: "Tabletop" });
+ const cats = r.store.get("Store/_settings.md")!.data.categories;
+ expect(cats).toEqual([
+ { name: "Tabletop", ordersOpen: false },
+ { name: "Paper", ordersOpen: true, turnaround: "3 weeks" },
+ ]);
+ await post("/store/stock", { path: "Store/Lines/Proxy cards.md", open: "1" });
+ expect(r.store.get("Store/Lines/Proxy cards.md")!.data.slots).toEqual({ open: 1, total: 5 });
+ });
+
+ it("shows sync jobs once they report", async () => {
+ r.store.reportSync({ job: "personalreader", at: "2026-10-11T11:00:00Z", ok: false, message: "Suwayomi did not answer", counts: { updated: 0 } });
+ const page = await get("/");
+ expect(page.text).toContain("personalreader");
+ expect(page.text).toContain("Suwayomi did not answer");
+ });
+});
diff --git a/services/admin/test/units.test.ts b/services/admin/test/units.test.ts
new file mode 100644
index 0000000..662e559
--- /dev/null
+++ b/services/admin/test/units.test.ts
@@ -0,0 +1,50 @@
+import { describe, expect, it } from "vitest";
+import { diffLines } from "../src/diff.ts";
+import { html, raw } from "../src/html.ts";
+import { renderMarkdown } from "../src/markdown.ts";
+
+const md = (s: string) => renderMarkdown(s, { noteHref: (n) => `/go?name=${encodeURIComponent(n)}`, imageSrc: (f) => `/attachments/${f}` });
+
+describe("diff", () => {
+ it("marks added and removed lines and folds the rest", () => {
+ const before = ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j"].join("\n");
+ const after = ["a", "b", "c", "d", "e", "F", "g", "h", "i", "j"].join("\n");
+ const d = diffLines(before, after, 1);
+ expect(d).toEqual([
+ { kind: "skip", count: 4 },
+ { kind: "same", text: "e" },
+ { kind: "remove", text: "f" },
+ { kind: "add", text: "F" },
+ { kind: "same", text: "g" },
+ { kind: "skip", count: 3 },
+ ]);
+ });
+
+ it("shows a new note as all added", () => {
+ expect(diffLines("", "x\ny")).toEqual([
+ { kind: "add", text: "x" },
+ { kind: "add", text: "y" },
+ ]);
+ });
+});
+
+describe("html", () => {
+ it("escapes values but not nested html", () => {
+ expect(html`${""}${html`${"&"} `}${raw(" ")}
`.toString()).toBe("<b>&
");
+ });
+});
+
+describe("draft previews", () => {
+ it("never lets a draft run script", () => {
+ const out = md('\n\n[x](javascript:alert(1)) ');
+ expect(out).not.toMatch(/
+
+
diff --git a/services/site/src/components/store/Swatches.astro b/services/site/src/components/store/Swatches.astro
new file mode 100644
index 0000000..1c14798
--- /dev/null
+++ b/services/site/src/components/store/Swatches.astro
@@ -0,0 +1,94 @@
+---
+/**
+ * Color options as round swatches. With `names`, each swatch is listed with
+ * its name and stock; without, only the swatches show (names on hover and for
+ * screen readers).
+ */
+import type { Line } from "../../lib/store.ts";
+
+type Option = NonNullable[number];
+interface Props {
+ options: Option[];
+ names?: boolean;
+ class?: string;
+}
+const { options, names, class: className } = Astro.props;
+---
+{
+ names ? (
+
+ {options.map((o) => (
+
+ {o.hex ? : }
+ {o.name}
+ {!o.inStock && out of stock }
+
+ ))}
+
+ ) : (
+
+ {options.map((o) => (
+
+
+
+ {o.name}
+ {!o.inStock && ", out of stock"}
+
+
+ ))}
+
+ )
+}
+
+
diff --git a/services/site/src/components/store/Terms.astro b/services/site/src/components/store/Terms.astro
new file mode 100644
index 0000000..ca15c42
--- /dev/null
+++ b/services/site/src/components/store/Terms.astro
@@ -0,0 +1,49 @@
+---
+/** Order terms as ledger notes, each with its kind. */
+import Notice from "@gk/ui/components/Notice.astro";
+import type { Term } from "../../lib/store.ts";
+
+interface Props {
+ terms: Term[];
+}
+const { terms } = Astro.props;
+const kindLabel = { info: "Good to know", warning: "Please note", rule: "Rule" } as const;
+---
+
+ {
+ terms.map((t) => (
+
+
+ {kindLabel[t.kind]}:
+ {t.body}
+
+
+ ))
+ }
+
+
+
diff --git a/services/site/src/components/work/ChoicePills.astro b/services/site/src/components/work/ChoicePills.astro
new file mode 100644
index 0000000..783c146
--- /dev/null
+++ b/services/site/src/components/work/ChoicePills.astro
@@ -0,0 +1,75 @@
+---
+/**
+ * A single-choice question drawn as filter pills: real radio buttons inside
+ * gk-filter labels, so it works without a script and posts with the form.
+ */
+import Star from "@gk/ui/components/Star.astro";
+
+interface Props {
+ legend: string;
+ name: string;
+ options: string[];
+ /** The option checked at first. */
+ checked?: string;
+}
+const { legend, name, options, checked } = Astro.props;
+---
+
+ {legend}
+
+ {
+ options.map((o) => (
+
+
+
+ {o}
+
+ ))
+ }
+
+
+
+
diff --git a/services/site/src/components/work/FeaturedStudy.astro b/services/site/src/components/work/FeaturedStudy.astro
new file mode 100644
index 0000000..ff632f5
--- /dev/null
+++ b/services/site/src/components/work/FeaturedStudy.astro
@@ -0,0 +1,209 @@
+---
+/**
+ * The one gilt panel on Work: the featured case study with its role, summary,
+ * tech, key facts and a link to the full study.
+ */
+import Button from "@gk/ui/components/Button.astro";
+import Chip from "@gk/ui/components/Chip.astro";
+import GiltCorners from "@gk/ui/components/GiltCorners.astro";
+import Seal from "@gk/ui/components/Seal.astro";
+import Star from "@gk/ui/components/Star.astro";
+import TickScale from "@gk/ui/components/TickScale.astro";
+import type { Note } from "../../lib/content.ts";
+import { workGroup, workKicker } from "../../lib/work.ts";
+
+interface Props {
+ study: Note<"post">;
+ href: string | null;
+ core: Set;
+}
+const { study, href, core } = Astro.props;
+const d = study.data;
+const facts = d.facts ?? [];
+---
+
+
+
+
+
+
+
Featured · {workKicker(study)}
+
+
{d.title}
+
+
+ {d.role &&
{d.role} }
+ {d.period &&
{d.period} }
+ {d.summary &&
{d.summary}
}
+ {
+ d.tech?.length ? (
+
+ {d.tech.map((t) => (
+ {t}
+ ))}
+
+ ) : null
+ }
+ {href &&
Read the case study }
+
+ {
+ facts.length > 0 && (
+
+
+ {facts.map((f) => (
+
+
{f.label}
+
+ {f.value}
+
+
+
+ ))}
+
+
+ )
+ }
+
+
+
+
+
diff --git a/services/site/src/components/work/StudyCard.astro b/services/site/src/components/work/StudyCard.astro
new file mode 100644
index 0000000..79936af
--- /dev/null
+++ b/services/site/src/components/work/StudyCard.astro
@@ -0,0 +1,93 @@
+---
+/** A case study in the Work grid: kicker, title, role, summary, tech and a link. */
+import Chip from "@gk/ui/components/Chip.astro";
+import type { Note } from "../../lib/content.ts";
+import { workGroup, workKicker } from "../../lib/work.ts";
+
+interface Props {
+ study: Note<"post">;
+ href: string | null;
+ core: Set;
+}
+const { study, href, core } = Astro.props;
+const d = study.data;
+const Tag = href ? "a" : "article";
+---
+
+
+ {workKicker(study)}
+ {d.title}
+ {d.role && {d.role} }
+ {d.summary && {d.summary} }
+ {
+ d.tech?.length ? (
+
+ {d.tech.slice(0, 5).map((t) => (
+ {t}
+ ))}
+ {d.tech.length > 5 && +{d.tech.length - 5} }
+
+ ) : null
+ }
+ {
+ href && (
+
+ Read the case study
+
+
+ )
+ }
+
+
+
+
diff --git a/services/site/src/components/world/CtaBand.astro b/services/site/src/components/world/CtaBand.astro
new file mode 100644
index 0000000..10b45d4
--- /dev/null
+++ b/services/site/src/components/world/CtaBand.astro
@@ -0,0 +1,57 @@
+---
+/** The closing band on a world page: a line, a note and one or two buttons. */
+interface Props {
+ title: string;
+ note?: string;
+}
+const { title, note } = Astro.props;
+---
+
+
+
{title}
+ {note &&
{note}
}
+
+
+
+
+
diff --git a/services/site/src/components/world/NowStrip.astro b/services/site/src/components/world/NowStrip.astro
new file mode 100644
index 0000000..df5b042
--- /dev/null
+++ b/services/site/src/components/world/NowStrip.astro
@@ -0,0 +1,129 @@
+---
+/**
+ * The "Right now" / "At the table now" strip: a gold kicker over a tick scale
+ * and a row of gold-framed cards, each with a thumbnail, a label, a name and
+ * a short note.
+ */
+import Kicker from "@gk/ui/components/Kicker.astro";
+import Seal from "@gk/ui/components/Seal.astro";
+import type { SealName } from "@gk/ui/components/seals.ts";
+
+export interface NowItem {
+ id?: string;
+ label: string;
+ title: string;
+ note?: string;
+ href?: string | null;
+ img?: string;
+}
+interface Props {
+ id: string;
+ heading: string;
+ items: NowItem[];
+ seal?: SealName;
+}
+const { id, heading, items, seal = "mini-astrolabe" } = Astro.props;
+---
+
+ {heading}
+
+ {
+ items.map((it) => {
+ const Tag = it.href ? "a" : "div";
+ return (
+
+
+ {it.img ? (
+
+ ) : (
+
+ )}
+
+ {it.label}
+ {it.title}
+ {it.note && {it.note} }
+
+
+
+ );
+ })
+ }
+
+
+
+
diff --git a/services/site/src/components/world/WorldSection.astro b/services/site/src/components/world/WorldSection.astro
new file mode 100644
index 0000000..b4a0958
--- /dev/null
+++ b/services/site/src/components/world/WorldSection.astro
@@ -0,0 +1,70 @@
+---
+/**
+ * A page section on the world pages: a gold star, the Cinzel heading with the
+ * filament underline, an optional intro line, and an action at the right.
+ */
+import Star from "@gk/ui/components/Star.astro";
+
+interface Props {
+ id: string;
+ title: string;
+ intro?: string;
+ class?: string;
+}
+const { id, title, intro, class: className } = Astro.props;
+---
+
+
+
+
{title}
+
+ {intro &&
{intro}
}
+
+ {Astro.slots.has("action") &&
}
+
+
+
+
+
diff --git a/services/site/src/lib/blog.ts b/services/site/src/lib/blog.ts
new file mode 100644
index 0000000..3a5cf62
--- /dev/null
+++ b/services/site/src/lib/blog.ts
@@ -0,0 +1,242 @@
+/**
+ * What the Blog, topic, post and case study pages ask beyond the SiteIndex:
+ * topics, reading time, page-link chips, Now chips, related posts, the
+ * archive and the post's table of contents.
+ */
+import { slugify, topics } from "@gk/schema";
+import type { Note } from "./content.ts";
+import { formatDate, mediaSrc, type SiteIndex } from "./site.ts";
+
+export type Post = Note<"post">;
+export type Topic = (typeof topics)[number];
+
+export { topics };
+
+/** The address of a topic page. */
+export const topicHref = (topic: string) => `/blog/topic/${slugify(topic)}`;
+
+/** The topic a URL segment names, or undefined. */
+export const topicFromSlug = (slug: string | undefined): Topic | undefined => topics.find((t) => slugify(t) === slug);
+
+/** Where each topic lives on the site, for the topic page's sidebar. */
+export const topicHome: Record = {
+ Making: { label: "Workshop", href: "/workshop", blurb: "Crafts, tools and makes" },
+ Tabletop: { label: "Tabletop", href: "/tabletop", blurb: "Campaigns, systems, decks and card games" },
+ Library: { label: "Library", href: "/library", blurb: "Books, shows, anime and games" },
+ "Work and tech": { label: "Work", href: "/work", blurb: "Case studies and what I build" },
+ Life: { label: "Now", href: "/now", blurb: "Everything I am into right now" },
+};
+
+/** Minutes to read the body, at 220 words a minute. */
+export function readMinutes(body: string | undefined): number {
+ const words = (body ?? "")
+ .replace(//g, " ")
+ .split(/\s+/)
+ .filter(Boolean).length;
+ return Math.max(1, Math.round(words / 220));
+}
+
+/** "Oct 11, 2026 · 4 min read". */
+export const dateLine = (p: Post) => `${formatDate(p.data.date)} · ${readMinutes(p.body)} min read`;
+
+/** The top slot: the newest pinned post (Featured), else the newest post (Latest). */
+export function leadPost(posts: Post[]): { post: Post; label: "Featured" | "Latest" } | undefined {
+ const pinned = posts.find((p) => p.data.pinned);
+ if (pinned) return { post: pinned, label: "Featured" };
+ return posts[0] ? { post: posts[0], label: "Latest" } : undefined;
+}
+
+/** The section a note lives in, for chip labels like "Workshop · Leather". */
+const SECTION: Record = {
+ post: "Blog",
+ "library-item": "Library",
+ "board-game": "Shelf",
+ craft: "Workshop",
+ make: "Workshop",
+ station: "Workshop",
+ tool: "Workshop",
+ "tabletop-system": "Tabletop",
+ campaign: "Tabletop",
+ "card-game": "Tabletop",
+ deck: "Tabletop",
+ experience: "Resume",
+ status: "Now",
+ "product-line": "Store",
+};
+
+export function sectionOf(n: Note): string | undefined {
+ if (n.type === "post") return (n as Post).data.postType === "case study" ? "Work" : "Blog";
+ return SECTION[n.type];
+}
+
+/** A smaller label under the section, for the Connected pages panel. */
+const KIND: Record = {
+ "library-item": "Entry",
+ "board-game": "Board game",
+ craft: "Craft",
+ make: "Make",
+ station: "Station",
+ tool: "Tool",
+ "tabletop-system": "System",
+ campaign: "Campaign",
+ "card-game": "Card game",
+ deck: "Deck",
+ experience: "Role",
+ status: "Status",
+ "product-line": "Product line",
+};
+
+export function kindOf(n: Note): string | undefined {
+ if (n.type === "post") return (n as Post).data.postType === "case study" ? "Case study" : "Post";
+ return KIND[n.type];
+}
+
+/** "Workshop · Leather", or just the title for a page or unknown type. */
+export function chipLabel(n: Note): string {
+ const section = sectionOf(n);
+ return section && section !== n.title ? `${section} · ${n.title}` : n.title;
+}
+
+export interface PageLink {
+ note: Note;
+ href: string;
+ label: string;
+}
+
+/** The published pages a post links to, as chips. Statuses are left to the Now chips. */
+export function pageLinks(s: SiteIndex, post: Post): PageLink[] {
+ return s
+ .linksFrom(post)
+ .filter((n) => n.type !== "status" && n.path !== post.path)
+ .map((n) => ({ note: n, href: s.href(n), label: chipLabel(n) }))
+ .filter((l): l is PageLink => !!l.href);
+}
+
+/** The current statuses of the notes a post links to, one per kind and subject. */
+export function nowStatuses(s: SiteIndex, post: Post): Note<"status">[] {
+ const seen = new Set();
+ const out: Note<"status">[] = [];
+ for (const n of s.linksFrom(post)) {
+ const st = s.statusFor(n);
+ if (st && !seen.has(st.path)) {
+ seen.add(st.path);
+ out.push(st);
+ }
+ }
+ return out;
+}
+
+/** A note's first image: its cover, else its first photo or product image. */
+export function imageOf(n: Note): string | undefined {
+ const d = n.data as Record;
+ const photos = d.photos as string[] | undefined;
+ const images = d.images as { file: string }[] | undefined;
+ return mediaSrc((d.cover as string) ?? photos?.[0] ?? images?.[0]?.file);
+}
+
+/** Case-insensitive match on title and summary. */
+export function searchPosts(posts: Post[], q: string): Post[] {
+ const words = q.toLowerCase().split(/\s+/).filter(Boolean);
+ if (!words.length) return posts;
+ return posts.filter((p) => {
+ const hay = `${p.title} ${p.data.summary ?? ""}`.toLowerCase();
+ return words.every((w) => hay.includes(w));
+ });
+}
+
+/** Posts by month, newest first: [{ month: "2026-10", label: "October 2026", count }]. */
+export function archive(posts: Post[]): { month: string; label: string; count: number }[] {
+ const map = new Map();
+ for (const p of posts) {
+ const m = p.data.date.slice(0, 7);
+ map.set(m, (map.get(m) ?? 0) + 1);
+ }
+ return [...map.entries()]
+ .sort((a, b) => b[0].localeCompare(a[0]))
+ .map(([month, count]) => ({ month, label: formatDate(`${month}-01`, { month: "long", day: false }), count }));
+}
+
+/**
+ * Posts related to this one: posts that link to it or that it links to count
+ * most, then posts sharing linked pages, newest first among equals.
+ */
+export function relatedPosts(s: SiteIndex, post: Post, pool: Post[], limit = 3): Post[] {
+ const mine = new Set(s.linksFrom(post).map((n) => n.path));
+ const backlinks = new Set(s.backlinks(post, { type: "post" }).map((n) => n.path));
+ const scored = pool
+ .filter((p) => p.path !== post.path)
+ .map((p) => {
+ let score = backlinks.has(p.path) || mine.has(p.path) ? 10 : 0;
+ for (const n of s.linksFrom(p)) if (mine.has(n.path)) score += 1;
+ return { p, score };
+ })
+ .filter((x) => x.score > 0)
+ .sort((a, b) => b.score - a.score || b.p.data.date.localeCompare(a.p.data.date));
+ return scored.slice(0, limit).map((x) => x.p);
+}
+
+/** The posts before and after this one in a newest-first list. */
+export function neighbours(list: Post[], post: Post): { previous?: Post; next?: Post } {
+ const i = list.findIndex((p) => p.path === post.path);
+ if (i < 0) return {};
+ return { previous: list[i + 1], next: list[i - 1] };
+}
+
+/**
+ * Give each h2 in rendered HTML an id and return the list, for the
+ * "On this page" panel.
+ */
+export function withHeadings(html: string): { html: string; headings: { id: string; text: string }[] } {
+ const headings: { id: string; text: string }[] = [];
+ const used = new Set();
+ const out = html.replace(/([\s\S]*?)<\/h2>/g, (_, inner: string) => {
+ const text = inner.replace(/<[^>]+>/g, "").replace(/</g, "<").replace(/>/g, ">").replace(/'/g, "'").replace(/"/g, '"').replace(/&/g, "&").trim();
+ let id = slugify(text) || "section";
+ for (let n = 2; used.has(id); n++) id = `${slugify(text)}-${n}`;
+ used.add(id);
+ headings.push({ id, text });
+ return `${inner} `;
+ });
+ return { html: out, headings };
+}
+
+/** Escape text for XML. */
+export const xml = (s: string) =>
+ s.replace(/[&<>"']/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" })[c]!);
+
+export interface Connected {
+ href: string;
+ kicker: string;
+ title: string;
+ sub: string;
+ img?: string;
+}
+
+/**
+ * The pages a post links to and the pages that link back to it, each with
+ * its section, picture and a line on how it connects.
+ */
+export function connectedPages(s: SiteIndex, post: Post): Connected[] {
+ const seen = new Map();
+ for (const n of [...s.linksFrom(post), ...s.backlinks(post)]) {
+ if (n.path === post.path || n.type === "post" || n.type === "status" || !s.href(n)) continue;
+ seen.set(n.path, n);
+ }
+ return [...seen.values()].map((n) => {
+ const parts: string[] = [];
+ if (s.linksFrom(n).some((x) => x.path === post.path)) parts.push("Links back here");
+ if (n.type === "craft") {
+ const makes = s.all("make").filter((m) => s.byName(m.data.craft)?.path === n.path).length;
+ if (makes) parts.push(`${makes} ${makes === 1 ? "make" : "makes"}`);
+ }
+ const posts = s.backlinks(n, { type: "post" }).length;
+ if (posts > 1) parts.push(`${posts} posts`);
+ return {
+ href: s.href(n)!,
+ kicker: [sectionOf(n), kindOf(n)].filter(Boolean).join(" · "),
+ title: n.title,
+ sub: parts.join(" · "),
+ img: imageOf(n),
+ };
+ });
+}
diff --git a/services/site/src/lib/content.ts b/services/site/src/lib/content.ts
index b5d9e94..e4bfc0d 100644
--- a/services/site/src/lib/content.ts
+++ b/services/site/src/lib/content.ts
@@ -30,6 +30,8 @@ export interface Redirect {
const env = (k: string, fallback?: string) => process.env[k] ?? import.meta.env[k] ?? fallback;
const baseUrl = () => (env("CONTENT_URL", "http://localhost:4310") as string).replace(/\/$/, "");
const token = () => env("CONTENT_TOKEN", "") as string;
+/** Preview only: include unpublished notes. Needs an editor or admin token; never set in production. */
+const includeDrafts = () => env("CONTENT_INCLUDE_DRAFTS", "") === "true";
const refreshMs = () => Number(env("CONTENT_REFRESH_SECONDS", "30")) * 1000;
async function api(path: string): Promise {
@@ -44,7 +46,8 @@ async function api(path: string): Promise {
async function loadAll(): Promise {
const notes: Note[] = [];
for (let offset = 0; ; offset += 1000) {
- const page = await api(`/api/notes?full=true&published=true&limit=1000&offset=${offset}`);
+ const published = includeDrafts() ? "" : "&published=true";
+ const page = await api<(Note & { publish?: boolean })[]>(`/api/notes?full=true${published}&limit=1000&offset=${offset}`);
notes.push(...page);
if (page.length < 1000) break;
}
diff --git a/services/site/src/lib/home.ts b/services/site/src/lib/home.ts
new file mode 100644
index 0000000..4a3cfea
--- /dev/null
+++ b/services/site/src/lib/home.ts
@@ -0,0 +1,216 @@
+/**
+ * Helpers for the Landing, About and Now pages: how a status reads (its
+ * subject, a meta line, the page chip), how the Now page groups statuses, and
+ * small lookups for the landing doors.
+ */
+import { world } from "@gk/ui/components/nav.ts";
+import type { Note } from "./content.ts";
+import { brandIcon, mediaSrc, type SiteIndex } from "./site.ts";
+
+const MEDIUM_LABEL: Record = {
+ book: "Books",
+ "light novel": "Light novels",
+ webnovel: "Webnovels",
+ manga: "Manga",
+ manhwa: "Manhwa",
+ "video game": "Video games",
+ anime: "Anime",
+ TV: "TV",
+ movie: "Movies",
+};
+
+const SUB_LABEL: Partial> = {
+ "tabletop-system": "Systems",
+ campaign: "Campaigns",
+ "card-game": "Card games",
+ deck: "Decks",
+ "board-game": "The Shelf",
+ station: "Stations",
+ tool: "Tools",
+};
+
+const PAGE_LABEL: Record = {
+ ...Object.fromEntries(world.map((w) => [w.href, w.label])),
+ "/library/shelf": "The Shelf",
+ "/blog": "Blog",
+ "/store": "Store",
+ "/work": "Work",
+ "/resume": "Resume",
+ "/about": "About me",
+};
+
+/** "Library" for "/library", else the last path part with a capital. */
+export function pageLabel(path: string): string {
+ const clean = path.split("#")[0]!.replace(/\/$/, "") || "/";
+ if (PAGE_LABEL[clean]) return PAGE_LABEL[clean]!;
+ const last = clean.split("/").filter(Boolean).pop() ?? "Home";
+ return last.charAt(0).toUpperCase() + last.slice(1).replace(/-/g, " ");
+}
+
+export interface StatusView {
+ status: Note<"status">;
+ slug: string;
+ kind: string;
+ icon: string;
+ pinned: boolean;
+ title: string;
+ href: string | null;
+ subject?: Note;
+ /** A short line about the subject: creator, medium or platform. */
+ meta?: string;
+ note?: string;
+ date: string;
+ cover?: string;
+ chip?: { label: string; href: string };
+ upNext: boolean;
+}
+
+/** Everything a page shows about one status. */
+export function viewStatus(s: SiteIndex, status: Note<"status">): StatusView {
+ const d = status.data;
+ const link = d.subject ? s.link(d.subject) : null;
+ const subject = link?.note;
+ const kind = s.kind(d.kind);
+ const sd = (subject?.data ?? {}) as Record;
+
+ const metaParts: string[] = [];
+ if (subject?.type === "library-item") {
+ if (sd.creator) metaParts.push(String(sd.creator));
+ else if (sd.medium) metaParts.push(capital(String(sd.medium)));
+ if (sd.platform) metaParts.push(String(sd.platform));
+ } else if (subject?.type === "deck" && sd.format) {
+ metaParts.push(String(sd.format));
+ } else if (subject?.type === "campaign" && sd.platform) {
+ metaParts.push(String(sd.platform));
+ }
+
+ let sub: string | undefined;
+ if (subject?.type === "library-item") sub = MEDIUM_LABEL[String(sd.medium)];
+ else if (subject?.type === "make" || subject?.type === "craft") {
+ const craft = subject.type === "craft" ? subject : s.byName(String(sd.craft ?? ""));
+ sub = craft?.title;
+ } else if (subject) sub = SUB_LABEL[subject.type];
+
+ const page = kind.page;
+ const chip = page ? { label: sub ? `${pageLabel(page)} · ${sub}` : pageLabel(page), href: page } : undefined;
+
+ return {
+ status,
+ slug: status.slug,
+ kind: d.kind,
+ icon: brandIcon(kind.icon),
+ pinned: !!d.pinned,
+ title: link?.title ?? status.title,
+ href: link?.href ?? null,
+ subject,
+ meta: metaParts.join(" · ") || undefined,
+ note: d.note,
+ date: d.date,
+ cover: mediaSrc((sd.cover as string | undefined) ?? (status.data as { cover?: string }).cover),
+ chip,
+ upNext: isUpNext(d.kind, subject),
+ };
+}
+
+function isUpNext(kind: string, subject?: Note): boolean {
+ if (/up next|want to|queued|next/i.test(kind)) return true;
+ const sd = (subject?.data ?? {}) as Record;
+ return subject?.type === "library-item" && sd.state === "up next";
+}
+
+const capital = (x: string) => x.charAt(0).toUpperCase() + x.slice(1);
+
+/** The Now page's groups, in page order. */
+export const NOW_GROUPS = [
+ { key: "making", title: "Making" },
+ { key: "table", title: "At the table" },
+ { key: "playing", title: "Playing" },
+ { key: "reading", title: "Reading and listening" },
+ { key: "watching", title: "Watching" },
+ { key: "other", title: "Elsewhere" },
+ { key: "next", title: "Up next" },
+] as const;
+export type NowGroupKey = (typeof NOW_GROUPS)[number]["key"];
+
+const READ = new Set(["book", "light novel", "webnovel", "manga", "manhwa"]);
+const WATCH = new Set(["anime", "TV", "movie"]);
+const TABLE = new Set(["tabletop-system", "campaign", "card-game", "deck", "board-game"]);
+const MAKE = new Set(["craft", "make", "station", "tool"]);
+
+/** Which Now group a status belongs to: its subject decides, else its kind. */
+export function nowGroup(v: StatusView, s: SiteIndex): NowGroupKey {
+ if (v.upNext) return "next";
+ const t = v.subject?.type;
+ if (t && MAKE.has(t)) return "making";
+ if (t && TABLE.has(t)) return "table";
+ if (t === "library-item") {
+ const medium = String((v.subject!.data as Record).medium);
+ if (READ.has(medium)) return "reading";
+ if (WATCH.has(medium)) return "watching";
+ if (medium === "video game") return "playing";
+ }
+ const k = v.kind.toLowerCase();
+ if (/read|listen/.test(k)) return "reading";
+ if (/watch|caught up/.test(k)) return "watching";
+ if (/play/.test(k)) return "playing";
+ const page = s.kind(v.kind).page ?? "";
+ if (page.startsWith("/workshop")) return "making";
+ if (page.startsWith("/tabletop")) return "table";
+ return "other";
+}
+
+/** Every status grouped for the Now page, empty groups left out. */
+export function groupStatuses(s: SiteIndex): { key: NowGroupKey; title: string; items: StatusView[] }[] {
+ const views = s.statuses().map((st) => viewStatus(s, st));
+ return NOW_GROUPS.map((g) => ({ ...g, items: views.filter((v) => nowGroup(v, s) === g.key) })).filter((g) => g.items.length);
+}
+
+/** The newest status date, for "Last updated". */
+export function lastUpdated(s: SiteIndex): string | undefined {
+ return s
+ .statuses()
+ .map((x) => x.data.date)
+ .sort()
+ .pop();
+}
+
+/** The current role (an experience with no end), newest first. */
+export function currentRole(s: SiteIndex): Note<"experience"> | undefined {
+ return [...s.all("experience")].filter((e) => !e.data.end).sort((a, b) => b.data.start.localeCompare(a.data.start))[0];
+}
+
+/** A make photo for the My world door: a favorite first, then Galaxy dice, then any. */
+export function worldPhoto(s: SiteIndex): { src: string; alt: string } | undefined {
+ const makes = s.all("make").filter((m) => m.data.cover || m.data.photos?.length);
+ const pick =
+ makes.find((m) => m.data.favorite) ?? makes.find((m) => m.slug === "galaxy-dice") ?? makes[0];
+ if (!pick) return undefined;
+ const src = mediaSrc(pick.data.cover ?? pick.data.photos?.[0]);
+ return src ? { src, alt: pick.data.blurb ?? pick.title } : undefined;
+}
+
+/** Minutes to read a body at 220 words a minute, at least one. */
+export function readMinutes(body: string): number {
+ const words = body.trim().split(/\s+/).filter(Boolean).length;
+ return Math.max(1, Math.round(words / 220));
+}
+
+/** The featured post (pinned, else newest) and the two most recent after it. */
+export function blogStrip(s: SiteIndex): { featured?: Note<"post">; recent: Note<"post">[] } {
+ const posts = s.posts();
+ const featured = posts.find((p) => p.data.pinned) ?? posts[0];
+ return { featured, recent: posts.filter((p) => p !== featured).slice(0, 2) };
+}
+
+/** Split a Page text body into its intro (before the first heading) and the sections after. */
+export function splitIntro(body: string): { intro: string; sections: { heading: string; body: string }[] } {
+ const parts = body.split(/^##\s+/m);
+ const intro = parts.shift()?.trim() ?? "";
+ return {
+ intro,
+ sections: parts.map((p) => {
+ const [heading, ...rest] = p.split("\n");
+ return { heading: heading!.trim(), body: rest.join("\n").trim() };
+ }),
+ };
+}
diff --git a/services/site/src/lib/library.ts b/services/site/src/lib/library.ts
new file mode 100644
index 0000000..fd24c7f
--- /dev/null
+++ b/services/site/src/lib/library.ts
@@ -0,0 +1,164 @@
+/**
+ * The Library and The Shelf: how items group, filter and sort. The pages
+ * filter on the server from the query string (so they work without JS) and a
+ * small script repeats the same rules live in the browser.
+ */
+import type { Note } from "./content.ts";
+import type { SiteIndex } from "./site.ts";
+
+export type Item = Note<"library-item">;
+export type Game = Note<"board-game">;
+
+// ---------------------------------------------------------------- Library
+
+/** The list's three groups. Dropped items are left out. */
+export const sections = [
+ { key: "now", label: "Now", states: ["now"] },
+ { key: "next", label: "Up next", states: ["up next"] },
+ { key: "done", label: "Finished", states: ["finished", "caught up", "played"] },
+] as const;
+export type SectionKey = (typeof sections)[number]["key"];
+
+export const sectionOf = (item: Item): SectionKey | undefined =>
+ sections.find((s) => (s.states as readonly string[]).includes(item.data.state))?.key;
+
+/** The Type filter: media gathered the way the rail lists them. */
+export const mediaGroups = [
+ { key: "books", label: "Books", media: ["book", "light novel"] },
+ { key: "serials", label: "Webnovels, manga and manhwa", media: ["webnovel", "manga", "manhwa"] },
+ { key: "games", label: "Video games", media: ["video game"] },
+ { key: "screen", label: "Anime and TV", media: ["anime", "TV"] },
+ { key: "movies", label: "Movies", media: ["movie"] },
+] as const;
+export type MediaKey = (typeof mediaGroups)[number]["key"];
+
+export const mediaGroupOf = (item: Item): MediaKey | undefined =>
+ mediaGroups.find((g) => (g.media as readonly string[]).includes(item.data.medium))?.key;
+
+/** The short medium label on the right of a row. */
+export const mediumLabel = (medium: string) => (medium === "video game" ? "Game" : medium === "TV" ? "TV" : medium);
+
+/** How Finished is ordered: favorites, then items with a verdict, then A to Z. */
+export function byStanding(a: Item, b: Item): number {
+ return (
+ Number(!!b.data.favorite) - Number(!!a.data.favorite) ||
+ Number(!!b.data.verdict) - Number(!!a.data.verdict) ||
+ a.title.localeCompare(b.title)
+ );
+}
+
+export interface LibraryFilter {
+ type?: MediaKey;
+ q: string;
+ show: Set;
+ favorites: boolean;
+ blog: boolean;
+}
+
+export function libraryFilter(params: URLSearchParams): LibraryFilter {
+ const type = mediaGroups.find((g) => g.key === params.get("type"))?.key;
+ // A submitted form sends `f`; before that every group shows.
+ const submitted = params.has("f");
+ const show = new Set(
+ submitted ? (params.getAll("show").filter((v) => sections.some((s) => s.key === v)) as SectionKey[]) : sections.map((s) => s.key),
+ );
+ return {
+ type,
+ q: (params.get("q") ?? "").trim().toLowerCase(),
+ show,
+ favorites: params.get("fav") === "1",
+ blog: params.get("blog") === "1",
+ };
+}
+
+/** Text the search matches against. Kept in step with the row's visible text. */
+export const searchText = (parts: (string | number | undefined)[]) =>
+ parts
+ .filter((p) => p !== undefined && p !== "")
+ .join(" ")
+ .toLowerCase();
+
+/**
+ * The favorites constellation: favorites with a cover, taken in turn from each
+ * medium so the strip is not all one kind.
+ */
+export function constellation(items: Item[], max = 7): Item[] {
+ const favs = items.filter((i) => i.data.favorite && i.data.cover).sort(byStanding);
+ const byMedium = new Map();
+ for (const i of favs) byMedium.set(i.data.medium, [...(byMedium.get(i.data.medium) ?? []), i]);
+ const order = mediaGroups.flatMap((g) => g.media as readonly string[]);
+ const queues = [...byMedium.entries()].sort(([a], [b]) => order.indexOf(a) - order.indexOf(b)).map(([, q]) => q);
+ const out: Item[] = [];
+ while (out.length < max && queues.some((q) => q.length)) {
+ for (const q of queues) {
+ const next = q.shift();
+ if (next && out.length < max) out.push(next);
+ }
+ }
+ return out;
+}
+
+// ---------------------------------------------------------------- Shelf
+
+export const sorts = [
+ { key: "played", label: "Last played" },
+ { key: "az", label: "A to Z" },
+ { key: "added", label: "Recently added" },
+] as const;
+export type SortKey = (typeof sorts)[number]["key"];
+
+export function sortGames(games: Game[], sort: SortKey): Game[] {
+ const az = (a: Game, b: Game) => a.title.localeCompare(b.title);
+ const desc = (x?: string, y?: string) => (y ?? "").localeCompare(x ?? "");
+ const cmp: Record number> = {
+ played: (a, b) => desc(a.data.lastPlayed, b.data.lastPlayed) || az(a, b),
+ az,
+ added: (a, b) => desc(a.data.created, b.data.created) || az(a, b),
+ };
+ return [...games].sort(cmp[sort]);
+}
+
+export interface ShelfFilter {
+ q: string;
+ plays: Set;
+ owned: boolean;
+ want: boolean;
+ favorites: boolean;
+ sort: SortKey;
+}
+
+export function shelfFilter(params: URLSearchParams): ShelfFilter {
+ const submitted = params.has("f");
+ return {
+ q: (params.get("q") ?? "").trim().toLowerCase(),
+ plays: new Set(params.getAll("plays").map((p) => p.toLowerCase())),
+ owned: submitted ? params.get("owned") === "1" : true,
+ want: params.get("want") === "1",
+ favorites: params.get("fav") === "1",
+ sort: sorts.find((s) => s.key === params.get("sort"))?.key ?? "played",
+ };
+}
+
+/**
+ * Owned and Want to play add games to the list (either one); with neither
+ * ticked, every game shows. Favorites only and How it plays narrow it.
+ */
+export function gameShows(g: Game, f: ShelfFilter, text: string): boolean {
+ const owned = !!g.data.owned;
+ const want = !!g.data.wantToPlay;
+ if ((f.owned || f.want) && !((f.owned && owned) || (f.want && want))) return false;
+ if (f.favorites && !g.data.favorite) return false;
+ if (f.plays.size && !(g.data.howItPlays ?? []).some((p) => f.plays.has(p.toLowerCase()))) return false;
+ return !f.q || text.includes(f.q);
+}
+
+/** Every "how it plays" value on the shelf, A to Z. */
+export const playStyles = (games: Game[]) =>
+ [...new Set(games.flatMap((g) => g.data.howItPlays ?? []))].sort((a, b) => a.localeCompare(b));
+
+/** Statuses about a board game: what is on the table now. */
+export const onTheTable = (s: SiteIndex) =>
+ s
+ .statuses()
+ .map((status) => ({ status, game: s.byName(status.data.subject) }))
+ .filter((x): x is { status: Note<"status">; game: Game } => x.game?.type === "board-game");
diff --git a/services/site/src/lib/markdown.ts b/services/site/src/lib/markdown.ts
index 0457823..b512d57 100644
--- a/services/site/src/lib/markdown.ts
+++ b/services/site/src/lib/markdown.ts
@@ -32,6 +32,7 @@ export function renderMarkdown(
opts: { link: (name: string) => { title: string; href: string | null }; imageSrc: (file: string) => string | undefined },
): string {
const withLinks = body
+ .replace(//g, "")
.replace(/!\[\[([^\]|]+)(?:\|([^\]]*))?\]\]/g, (_, f: string, alt?: string) => `) ?? ""})`)
.replace(/\[\[([^\]|#]+)(?:#[^\]|]*)?(?:\|([^\]]+))?\]\]/g, (_, n: string, label?: string) => {
const l = opts.link(n.trim());
diff --git a/services/site/src/lib/resume-pdf.ts b/services/site/src/lib/resume-pdf.ts
new file mode 100644
index 0000000..cdbfe31
--- /dev/null
+++ b/services/site/src/lib/resume-pdf.ts
@@ -0,0 +1,215 @@
+/**
+ * The printable resume. Built from the same ResumeData as the /resume page,
+ * laid out as one plain column so applicant tracking systems read it in order.
+ * Paper colors: ink on white, with the brand gold and teal darkened for print.
+ */
+import PDFDocument from "pdfkit";
+import cinzelBold from "../assets/pdf-fonts/Cinzel-Bold.ttf?inline";
+import cinzelSemi from "../assets/pdf-fonts/Cinzel-SemiBold.ttf?inline";
+import montReg from "../assets/pdf-fonts/Montserrat-Regular.ttf?inline";
+import montMed from "../assets/pdf-fonts/Montserrat-Medium.ttf?inline";
+import montSemi from "../assets/pdf-fonts/Montserrat-SemiBold.ttf?inline";
+import { month, period, type ResumeData } from "./resume.ts";
+
+const fromDataUrl = (u: string) => Buffer.from(u.slice(u.indexOf(",") + 1), "base64");
+const FONTS = {
+ title: fromDataUrl(cinzelBold),
+ heading: fromDataUrl(cinzelSemi),
+ body: fromDataUrl(montReg),
+ medium: fromDataUrl(montMed),
+ strong: fromDataUrl(montSemi),
+};
+
+const INK = "#131c2b";
+const MUTED = "#566173";
+const GOLD = "#a8842f";
+const TEAL = "#0f7f78";
+const RULE = "#d9cfb8";
+
+const PAGE = { size: "LETTER" as const, margin: 50 };
+const SITE = "gregkilmer.com";
+
+export function renderResumePdf(r: ResumeData): Promise {
+ const doc = new PDFDocument({
+ size: PAGE.size,
+ margins: { top: PAGE.margin, bottom: PAGE.margin + 14, left: PAGE.margin, right: PAGE.margin },
+ bufferPages: true,
+ info: { Title: `${r.name} – Resume`, Author: r.name, Subject: r.headline, Keywords: "resume" },
+ lang: "en-US",
+ displayTitle: true,
+ });
+ for (const [name, data] of Object.entries(FONTS)) doc.registerFont(name, data);
+
+ const left = doc.page.margins.left;
+ const width = doc.page.width - left - doc.page.margins.right;
+ const chunks: Buffer[] = [];
+ doc.on("data", (c: Buffer) => chunks.push(c));
+ const done = new Promise((resolve, reject) => {
+ doc.on("end", () => resolve(Buffer.concat(chunks)));
+ doc.on("error", reject);
+ });
+
+ /** Starts a new page when fewer than `need` points remain. */
+ const room = (need: number) => {
+ if (doc.y + need > doc.page.height - doc.page.margins.bottom) doc.addPage();
+ };
+
+ const diamond = (x: number, y: number, s: number, color = GOLD) => {
+ doc.save().moveTo(x, y - s).lineTo(x + s, y).lineTo(x, y + s).lineTo(x - s, y).closePath().fill(color).restore();
+ };
+
+ /** A thin gold rule with a small star at its center. */
+ const starRule = (y: number) => {
+ const mid = left + width / 2;
+ doc.save().lineWidth(0.6).strokeColor(GOLD);
+ doc.moveTo(left, y).lineTo(mid - 10, y).stroke();
+ doc.moveTo(mid + 10, y).lineTo(left + width, y).stroke();
+ doc.restore();
+ diamond(mid, y, 4);
+ };
+
+ const section = (title: string) => {
+ room(60);
+ doc.moveDown(0.75);
+ const y = doc.y;
+ doc.font("heading").fontSize(11).fillColor(GOLD).text(title.toUpperCase(), left, y, { characterSpacing: 1.6 });
+ const ruleY = doc.y + 2;
+ doc.save().lineWidth(0.5).strokeColor(RULE).moveTo(left, ruleY).lineTo(left + width, ruleY).stroke().restore();
+ doc.y = ruleY + 8;
+ };
+
+ const bullet = (text: string, indent = 12) => {
+ doc.font("body").fontSize(9.5);
+ const h = doc.heightOfString(text, { width: width - indent, lineGap: 1.5 });
+ room(h + 4);
+ const y = doc.y;
+ diamond(left + 3.5, y + 6, 2.2);
+ doc.fillColor(INK).text(text, left + indent, y, { width: width - indent, lineGap: 1.5 });
+ doc.moveDown(0.15);
+ };
+
+ // Header
+ doc.font("title").fontSize(28).fillColor(INK).text(r.name.toUpperCase(), left, doc.y, { characterSpacing: 2 });
+ if (r.headline) doc.font("medium").fontSize(11).fillColor(TEAL).text(r.headline, { width });
+ // Only the professional profiles belong on paper; the hobby links stay on the site.
+ const profiles = r.socials.filter((s) => /github|gitea|linkedin/i.test(`${s.label} ${s.url}`));
+ const contact = [r.location, SITE, ...profiles.map((s) => s.url.replace(/^https?:\/\/(www\.)?/, "").replace(/\/$/, ""))].filter(Boolean).join(" · ");
+ doc.moveDown(0.3).font("body").fontSize(9).fillColor(MUTED).text(contact, { width });
+ if (r.availability) doc.font("medium").fontSize(9).fillColor(INK).text(r.availability, { width });
+ doc.moveDown(0.7);
+ starRule(doc.y);
+ doc.moveDown(0.6);
+
+ if (r.summary) {
+ doc.font("body").fontSize(10).fillColor(INK).text(r.summary.replace(/\s*\n\s*/g, " "), left, doc.y, { width, lineGap: 2 });
+ }
+
+ if (r.stats.length) {
+ section("Highlights");
+ const cols = Math.min(r.stats.length, 4);
+ const colW = width / cols;
+ let top = doc.y;
+ r.stats.forEach((st, i) => {
+ if (i > 0 && i % cols === 0) top = doc.y + 6;
+ const x = left + (i % cols) * colW;
+ doc.font("title").fontSize(16).fillColor(GOLD).text(st.value, x, top, { width: colW - 10 });
+ doc.font("strong").fontSize(9).fillColor(INK).text(st.label, x, doc.y, { width: colW - 10 });
+ if (st.detail) doc.font("body").fontSize(8.5).fillColor(MUTED).text(st.detail, x, doc.y, { width: colW - 10 });
+ });
+ doc.x = left;
+ }
+
+ if (r.roles.length) {
+ section("Experience");
+ r.roles.forEach((role, i) => {
+ const d = role.data;
+ if (i > 0) doc.moveDown(0.6);
+ room(64);
+ const y = doc.y;
+ const when = period(role);
+ doc.font("medium").fontSize(9).fillColor(GOLD);
+ const whenW = doc.widthOfString(when);
+ doc.text(when, left + width - whenW, y + 1.5, { lineBreak: false });
+ doc.font("strong").fontSize(11).fillColor(INK).text(d.role, left, y, { width: width - whenW - 16 });
+ const at = [d.company, d.location].filter(Boolean).join(" · ");
+ doc.font("medium").fontSize(9.5).fillColor(TEAL).text(at, left, doc.y, { width });
+ if (d.previousRoles?.length) {
+ doc.font("body").fontSize(8.5).fillColor(MUTED).text(`Previously ${d.previousRoles.join(", ")} at ${d.company}`, { width });
+ }
+ if (d.focus) doc.moveDown(0.2).font("body").fontSize(9).fillColor(MUTED).text(d.focus, { width, lineGap: 1 });
+ doc.moveDown(0.3);
+ for (const b of d.bullets ?? []) bullet(b);
+ if (d.tech?.length) {
+ doc.font("body").fontSize(8.5).fillColor(MUTED).text(d.tech.join(" · "), left + 12, doc.y, { width: width - 12 });
+ }
+ doc.x = left;
+ });
+ }
+
+ if (r.skills?.stack.length) {
+ section("Skills");
+ for (const g of r.skills.stack) {
+ const items = g.items.join(", ");
+ doc.font("body").fontSize(9.5);
+ room(doc.heightOfString(items, { width: width - 110 }) + 4);
+ const y = doc.y;
+ doc.font("strong").fontSize(9.5).fillColor(INK).text(g.label, left, y, { width: 100 });
+ const afterLabel = doc.y;
+ doc.font("body").fontSize(9.5).fillColor(INK).text(items, left + 110, y, { width: width - 110, lineGap: 1 });
+ doc.y = Math.max(doc.y, afterLabel) + 3;
+ }
+ if (r.skills.core?.length) {
+ room(30);
+ const y = doc.y;
+ doc.font("strong").fontSize(9.5).fillColor(INK).text("Strengths", left, y, { width: 100 });
+ doc.font("body").fontSize(9.5).fillColor(INK).text(r.skills.core.join(", "), left + 110, y, { width: width - 110, lineGap: 1 });
+ }
+ doc.x = left;
+ }
+
+ if (r.education?.degrees.length) {
+ section("Education");
+ for (const dg of r.education.degrees) {
+ room(40);
+ const y = doc.y;
+ doc.font("medium").fontSize(9).fillColor(GOLD);
+ const w = doc.widthOfString(dg.period);
+ doc.text(dg.period, left + width - w, y + 1.5, { lineBreak: false });
+ doc.font("strong").fontSize(10.5).fillColor(INK).text(dg.credential, left, y, { width: width - w - 16 });
+ doc.font("medium").fontSize(9.5).fillColor(TEAL).text(dg.institution, left, doc.y, { width });
+ if (dg.detail) doc.font("body").fontSize(9).fillColor(MUTED).text(dg.detail, { width });
+ doc.moveDown(0.4);
+ }
+ }
+
+ if (r.education?.certifications?.length) {
+ section("Certifications");
+ for (const c of r.education.certifications) {
+ room(20);
+ const line = [c.issuer, c.year].filter(Boolean).join(" · ");
+ doc.font("strong").fontSize(9.5).fillColor(INK).text(c.name, left, doc.y, { continued: !!line });
+ if (line) doc.font("body").fillColor(MUTED).text(` ${line}`);
+ }
+ }
+
+ // Footer on every page.
+ const range = doc.bufferedPageRange();
+ const updated = r.updated ? `Updated ${month(r.updated.slice(0, 7))}` : "";
+ for (let i = range.start; i < range.start + range.count; i++) {
+ doc.switchToPage(i);
+ const y = doc.page.height - PAGE.margin + 4;
+ const bottom = doc.page.margins.bottom;
+ doc.page.margins.bottom = 0;
+ doc.save().lineWidth(0.5).strokeColor(RULE).moveTo(left, y - 8).lineTo(left + width, y - 8).stroke().restore();
+ doc.font("body").fontSize(8).fillColor(MUTED);
+ doc.text([`${SITE}/resume`, updated].filter(Boolean).join(" · "), left, y, { lineBreak: false });
+ if (range.count > 1) {
+ const label = `${i - range.start + 1} of ${range.count}`;
+ doc.text(label, left + width - doc.widthOfString(label), y, { lineBreak: false });
+ }
+ doc.page.margins.bottom = bottom;
+ }
+
+ doc.end();
+ return done;
+}
diff --git a/services/site/src/lib/resume.ts b/services/site/src/lib/resume.ts
new file mode 100644
index 0000000..643b1dd
--- /dev/null
+++ b/services/site/src/lib/resume.ts
@@ -0,0 +1,49 @@
+/** The resume's data, shared by the page and the PDF so they never disagree. */
+import type { Note } from "./content.ts";
+import type { SiteIndex } from "./site.ts";
+
+export interface ResumeData {
+ name: string;
+ headline: string;
+ location?: string;
+ summary?: string;
+ availability?: string;
+ stats: { value: string; label: string; detail?: string }[];
+ roles: Note<"experience">[];
+ skills?: Note<"skills">["data"];
+ education?: Note<"education">["data"];
+ socials: { label: string; handle: string; url: string }[];
+ /** The newest change to any note the resume is built from (YYYY-MM-DD). */
+ updated: string;
+}
+
+/** "Mar 2019", from "2019-03". */
+export const month = (ym: string | undefined) => {
+ if (!ym) return "Present";
+ const [y, m] = ym.split("-").map(Number);
+ return new Date(Date.UTC(y!, (m ?? 1) - 1, 1)).toLocaleDateString("en-US", { timeZone: "UTC", month: "short", year: "numeric" });
+};
+
+export const period = (r: Note<"experience">) => `${month(r.data.start)} – ${month(r.data.end)}`;
+
+export function resumeData(s: SiteIndex): ResumeData {
+ const settings = s.settings();
+ const page = s.pageText("/resume");
+ const roles = [...s.all("experience")].sort((a, b) => (b.data.end ?? "9999").localeCompare(a.data.end ?? "9999") || b.data.start.localeCompare(a.data.start));
+ const current = roles.find((r) => !r.data.end) ?? roles[0];
+ const sources: Note[] = [...roles, ...(page ? [page] : []), ...[s.one("skills"), s.one("education")].filter((n): n is Note<"skills"> | Note<"education"> => !!n)];
+ const updated = sources.map((n) => n.acceptedAt?.slice(0, 10) ?? "").sort().at(-1) ?? "";
+ return {
+ name: settings?.name ?? "Greg Kilmer",
+ headline: page?.data.lede ?? current?.data.role ?? "",
+ location: settings?.location,
+ summary: page?.body?.trim() || undefined,
+ availability: settings?.availability,
+ stats: page?.data.stats ?? [],
+ roles,
+ skills: s.one("skills")?.data,
+ education: s.one("education")?.data,
+ socials: settings?.socials ?? [],
+ updated,
+ };
+}
diff --git a/services/site/src/lib/site.ts b/services/site/src/lib/site.ts
index 30c4d36..b40481d 100644
--- a/services/site/src/lib/site.ts
+++ b/services/site/src/lib/site.ts
@@ -218,10 +218,11 @@ export function mediaSrc(value: string | undefined): string | undefined {
return `/media/${encodeURIComponent(file.trim())}`;
}
-/** Status kinds name icons; older notes use Material names, mapped here to brand icons. */
+/** Notes name icons; older notes use Material names, mapped here to brand icons. */
const MDI: Record = {
"book-open-variant": "bookshelf",
"book-open-page-variant": "bookshelf",
+ notebook: "bookshelf",
headphones: "play",
"television-classic": "tv",
television: "tv",
@@ -229,11 +230,33 @@ const MDI: Record = {
"dice-multiple": "dice",
"hammer-wrench": "hammer",
school: "graduation",
+ "map-marker": "location",
+ "map-marker-outline": "location",
+ "briefcase-outline": "briefcase",
+ paw: "dog",
+ "cup-outline": "tea",
+ coffee: "tea",
+ "home-outline": "home",
+ "heart-outline": "heart",
+ "code-tags": "web",
+ laptop: "monitor",
+ "cards-playing": "proxy-cards",
+ "cards-playing-outline": "proxy-cards",
+ cards: "proxy-cards",
+ "printer-3d-nozzle": "print",
+ "printer-3d": "print",
+ printer: "print",
};
-export function brandIcon(name: string | undefined): string {
- if (!name) return "favorite";
- return MDI[name] ?? name;
+/** The brand icons in @gk/ui. */
+const ICONS = new Set(
+ Object.keys(import.meta.glob("../../../../packages/ui/src/assets/icons/*.svg")).map((p) => p.replace(/^.*\/(.+)\.svg$/, "$1")),
+);
+
+/** A brand icon for a note's icon name, or the fallback when none matches. */
+export function brandIcon(name: string | undefined, fallback = "favorite"): string {
+ const mapped = name ? (MDI[name] ?? name) : fallback;
+ return ICONS.has(mapped) ? mapped : fallback;
}
/** "Oct 11, 2026" from "2026-10-11". */
diff --git a/services/site/src/lib/store.ts b/services/site/src/lib/store.ts
new file mode 100644
index 0000000..18bcc9d
--- /dev/null
+++ b/services/site/src/lib/store.ts
@@ -0,0 +1,133 @@
+/**
+ * The Store's rules in one place: which lines show, in what order, whether a
+ * line can take an order, its status line and its price summary.
+ */
+import type { Note } from "./content.ts";
+import type { SiteIndex } from "./site.ts";
+import { brandIcon, formatDate } from "./site.ts";
+
+export type Line = Note<"product-line">;
+export type Category = Line["data"]["category"];
+export type Channel = Line["data"]["channel"];
+type Settings = Note<"store-settings">["data"];
+export type CategorySettings = Settings["categories"][number];
+export type Term = NonNullable[number];
+
+export const CATEGORIES: Category[] = ["Tabletop", "Paper", "Leather"];
+
+/** Proxy cards are sold locally only, never on Etsy. */
+export function isProxy(line: Line): boolean {
+ return /prox/i.test(`${line.title} ${line.slug} ${line.data.summary}`);
+}
+
+/**
+ * Where a line is sold. A proxy line a note marks as Etsy is treated as sold
+ * here, since proxies are never sold on Etsy.
+ */
+export function channelOf(line: Line): Channel {
+ if (line.data.channel === "Etsy" && isProxy(line)) return "here";
+ return line.data.channel;
+}
+
+/** The shop link for a line sold elsewhere, never for a proxy line on Etsy. */
+export function shopLink(line: Line): { href: string; label: string } | null {
+ const ch = channelOf(line);
+ if ((ch !== "Etsy" && ch !== "Patiun.com") || !line.data.channelUrl) return null;
+ return { href: line.data.channelUrl, label: `Shop on ${ch}` };
+}
+
+export interface StoreState {
+ lines: Line[];
+ groups: [Category, Line[]][];
+ settings: Settings | undefined;
+ category(name: Category): CategorySettings;
+ /** Categories that have a line sold here and are closed. */
+ closed: CategorySettings[];
+ /** True when some line sold here sits in an open category. */
+ anyOpen: boolean;
+ terms: Term[];
+}
+
+export function storeState(s: SiteIndex): StoreState {
+ const settings = s.one("store-settings")?.data;
+ const lines = [...s.all("product-line")].sort(
+ (a, b) =>
+ CATEGORIES.indexOf(a.data.category) - CATEGORIES.indexOf(b.data.category) ||
+ (a.data.order ?? 999) - (b.data.order ?? 999) ||
+ a.title.localeCompare(b.title),
+ );
+ const category = (name: Category): CategorySettings =>
+ settings?.categories.find((c) => c.name === name) ?? { name, ordersOpen: false };
+ const groups = CATEGORIES.map((c) => [c, lines.filter((l) => l.data.category === c)] as [Category, Line[]]).filter(([, ls]) => ls.length);
+ const sellingCats = CATEGORIES.filter((c) => lines.some((l) => l.data.category === c && channelOf(l) === "here"));
+ return {
+ lines,
+ groups,
+ settings,
+ category,
+ closed: sellingCats.map(category).filter((c) => !c.ordersOpen),
+ anyOpen: sellingCats.some((c) => category(c).ordersOpen),
+ terms: settings?.terms ?? [],
+ };
+}
+
+/** Whether a line has room: open slots when made to order, stock when in stock. */
+export function hasCapacity(line: Line): boolean {
+ return line.data.mode === "made to order" ? (line.data.slots?.open ?? 0) > 0 : (line.data.quantity ?? 0) > 0;
+}
+
+/** Whether the order button shows for this line. */
+export function canOrder(line: Line, st: StoreState): boolean {
+ return channelOf(line) === "here" && st.category(line.data.category).ordersOpen && hasCapacity(line);
+}
+
+/** A short status: slots, stock, or where it is sold. */
+export function statusText(line: Line): string {
+ const ch = channelOf(line);
+ if (ch === "coming soon") return "Coming soon";
+ if (ch !== "here") return `Sold on ${ch}`;
+ if (line.data.mode === "made to order") {
+ const sl = line.data.slots;
+ return sl ? `${Math.min(sl.open, sl.total)} of ${sl.total} ${sl.total === 1 ? "slot" : "slots"} open` : "Made to order";
+ }
+ const q = line.data.quantity ?? 0;
+ return q > 0 ? `${q} in stock` : "Sold out";
+}
+
+/** "Orders closed. Reopens Oct 11, 2026." for a category. */
+export function closedText(c: CategorySettings): string {
+ return c.reopens ? `Reopens ${formatDate(c.reopens)}.` : "";
+}
+
+const money = (p: string) => {
+ const m = /^\s*\$?\s*([\d,]+(?:\.\d+)?)\s*$/.exec(p);
+ return m ? Number(m[1]!.replace(/,/g, "")) : null;
+};
+
+/** The lowest price, its note, and up to two other priced tiers. */
+export function priceSummary(line: Line): { from: string; note?: string; more: string[] } | null {
+ const priced = line.data.priceTiers.map((t, i) => ({ ...t, i, n: money(t.price) })).filter((t) => t.n !== null);
+ if (!priced.length) return null;
+ const low = priced.reduce((a, b) => (b.n! < a.n! ? b : a));
+ const more = priced
+ .filter((t) => t !== low)
+ .sort((a, b) => b.n! - a.n!)
+ .slice(0, 2)
+ .sort((a, b) => a.i - b.i)
+ .map((t) => `${t.band} ${t.price}`);
+ return { from: low.price, note: low.note, more };
+}
+
+/** Option groups in note order, with their options. */
+export function optionGroups(line: Line): [string, NonNullable][] {
+ const map = new Map>();
+ for (const o of line.data.options ?? []) map.set(o.group, [...(map.get(o.group) ?? []), o]);
+ return [...map.entries()];
+}
+
+const CATEGORY_ICONS: Record = { Tabletop: "dice", Paper: "bookshelf", Leather: "leather" };
+
+/** A brand icon for a line: its own when it maps to one, else its category's. */
+export function lineIcon(line: Line): string {
+ return brandIcon(line.data.icon, CATEGORY_ICONS[line.data.category]);
+}
diff --git a/services/site/src/lib/work.ts b/services/site/src/lib/work.ts
new file mode 100644
index 0000000..292b44e
--- /dev/null
+++ b/services/site/src/lib/work.ts
@@ -0,0 +1,114 @@
+/** Helpers for the Work index and the Inquiries pages. */
+import type { Note } from "./content.ts";
+import type { SiteIndex } from "./site.ts";
+
+export type WorkGroup = "employer" | "personal" | "homelab";
+
+export const groupLabels: Record = {
+ employer: "Employer",
+ personal: "Personal",
+ homelab: "Homelab",
+};
+
+/**
+ * Which group a case study belongs to: employer work has an employer; a
+ * project without one is homelab when it is tagged or described as such,
+ * otherwise personal.
+ */
+export function workGroup(p: Note<"post">): WorkGroup {
+ if (p.data.employer) return "employer";
+ const tags = (p.data.tags ?? []).map((t) => t.toLowerCase());
+ if (tags.includes("homelab") || /\bhome\s?lab\b/i.test(`${p.data.summary} ${p.body}`)) return "homelab";
+ return "personal";
+}
+
+/** "Implico Inc." as "Implico" for kickers. */
+export function shortEmployer(name: string): string {
+ return name.replace(/,?\s+(Inc\.?|LLC|Ltd\.?|Corp\.?|GmbH)$/i, "").trim();
+}
+
+/** The gold kicker over a case study: "Employer · Implico", "Homelab". */
+export function workKicker(p: Note<"post">): string {
+ const g = workGroup(p);
+ return g === "employer" && p.data.employer ? `${groupLabels[g]} · ${shortEmployer(p.data.employer)}` : groupLabels[g];
+}
+
+/**
+ * The featured study: the newest one marked favorite or pinned, else the
+ * newest. Same-day ties go to employer work, then to the one with more facts.
+ */
+export function featuredStudy(studies: Note<"post">[]): Note<"post"> | undefined {
+ const marked = studies.filter((p) => p.data.favorite || p.data.pinned);
+ const pool = marked.length ? marked : studies;
+ const newest = pool[0]?.data.date;
+ return pool
+ .filter((p) => p.data.date === newest)
+ .sort((a, b) => Number(!!b.data.employer) - Number(!!a.data.employer) || (b.data.facts?.length ?? 0) - (a.data.facts?.length ?? 0))[0];
+}
+
+const groupOrder: WorkGroup[] = ["employer", "homelab", "personal"];
+
+/** The rest of the studies in group order (employer, homelab, personal), newest first within a group. */
+export function otherStudies(studies: Note<"post">[], featured: Note<"post"> | undefined): Note<"post">[] {
+ return studies
+ .filter((p) => p !== featured)
+ .map((p, i) => ({ p, i }))
+ .sort((a, b) => groupOrder.indexOf(workGroup(a.p)) - groupOrder.indexOf(workGroup(b.p)) || a.i - b.i)
+ .map((x) => x.p);
+}
+
+/** Tech the resume outlines as the core stack, lower-cased, for the teal-edged chips. */
+export function coreTech(s: SiteIndex): Set {
+ const stack = s.one("skills")?.data.stack ?? [];
+ return new Set(stack.flatMap((g) => g.core ?? []).map((t) => t.toLowerCase()));
+}
+
+// ---------------------------------------------------------------- Inquiries
+
+export type InquiryType = "job" | "freelance" | "patiun";
+
+export const inquiryTypes: { value: InquiryType; label: string; blurb: string; summary: string }[] = [
+ { value: "job", label: "A job or role", blurb: "Full-time roles and contracts", summary: "A job or role" },
+ { value: "freelance", label: "Freelance web work", blurb: "Sites, apps and integrations", summary: "Freelance web work" },
+ { value: "patiun", label: "Custom Patiun order", blurb: "Cards, deck boxes and more", summary: "Patiun custom order" },
+];
+
+/** `?type=` as an inquiry type; also accepts a few natural spellings. */
+export function parseInquiryType(raw: string | null | undefined): InquiryType | undefined {
+ const v = (raw ?? "").trim().toLowerCase();
+ if (!v) return undefined;
+ if (["job", "role", "jobs", "hire", "hiring"].includes(v)) return "job";
+ if (["freelance", "web", "freelance-web"].includes(v)) return "freelance";
+ if (["patiun", "order", "custom", "custom-order", "store"].includes(v)) return "patiun";
+ return undefined;
+}
+
+/**
+ * What the sent page shows under "What you sent". The inquiries API (phase 6)
+ * sets this as the short-lived `gk-inquiry` cookie (URL-encoded JSON) before
+ * redirecting to /inquiries/sent.
+ */
+export interface SentSummary {
+ type?: InquiryType;
+ name?: string;
+ email?: string;
+ /** Extra rows in order, like { label: "How many", value: "60 cards" }. */
+ rows?: { label: string; value: string }[];
+}
+
+export function parseSentSummary(raw: string | undefined): SentSummary | undefined {
+ if (!raw) return undefined;
+ try {
+ const v = JSON.parse(raw.startsWith("%7B") || raw.startsWith("%7b") ? decodeURIComponent(raw) : raw) as Record;
+ const str = (x: unknown) => (typeof x === "string" && x.trim() ? x.trim().slice(0, 300) : undefined);
+ const rows = Array.isArray(v.rows)
+ ? v.rows
+ .map((r) => ({ label: str((r as Record)?.label), value: str((r as Record)?.value) }))
+ .filter((r): r is { label: string; value: string } => !!r.label && !!r.value)
+ .slice(0, 8)
+ : [];
+ return { type: parseInquiryType(str(v.type)), name: str(v.name), email: str(v.email), rows };
+ } catch {
+ return undefined;
+ }
+}
diff --git a/services/site/src/lib/world.ts b/services/site/src/lib/world.ts
new file mode 100644
index 0000000..80cf142
--- /dev/null
+++ b/services/site/src/lib/world.ts
@@ -0,0 +1,97 @@
+/**
+ * Helpers for the Workshop, craft and Tabletop pages: ordering, craft seals,
+ * make photos, the sword rack, and which pill a state wears.
+ */
+import type { SealName } from "@gk/ui/components/seals.ts";
+import type { Note } from "./content.ts";
+import { mediaSrc, type SiteIndex } from "./site.ts";
+
+type Pill = "live" | "state" | "current" | "neutral";
+
+/** By `order`, notes without one last, then by title. */
+export function byOrder(notes: T[]): T[] {
+ const o = (n: Note) => ((n.data as { order?: number }).order ?? Number.MAX_SAFE_INTEGER);
+ return [...notes].sort((a, b) => o(a) - o(b) || a.title.localeCompare(b.title));
+}
+
+/** Newest `date` first; undated notes keep their order after the dated ones. */
+export function byDateDesc(notes: T[]): T[] {
+ const d = (n: Note) => String((n.data as { date?: string }).date ?? "");
+ return [...notes].sort((a, b) => d(b).localeCompare(d(a)));
+}
+
+/** A craft's own seal: dice crafts, leather, print and paper. Undefined means the family seal. */
+export function craftSeal(craft: Note<"craft">): SealName | undefined {
+ const text = `${craft.title} ${craft.data.blurb ?? ""}`.toLowerCase();
+ if (/leather/.test(text)) return "craft-leather";
+ if (/dice|resin/.test(text)) return "craft-dice";
+ if (/print|paper|bookbind|card|decal/.test(text)) return "craft-print";
+ return undefined;
+}
+
+/** A make's photos as image addresses (the `photos` list, else its `cover`). */
+export function makePhotos(make: Note<"make">): string[] {
+ const d = make.data as { photos?: string[]; cover?: string };
+ const list = d.photos?.length ? d.photos : d.cover ? [d.cover] : [];
+ return list.map((p) => mediaSrc(p)).filter((x): x is string => !!x);
+}
+
+/** Makes tagged for the sword rack. */
+export function isSword(make: Note<"make">): boolean {
+ const tags = make.data.tags ?? [];
+ return tags.some((t) => /sword/i.test(t));
+}
+
+/** The makes of a craft. */
+export function makesOf(s: SiteIndex, craft: Note<"craft">): Note<"make">[] {
+ return s.all("make").filter((m) => s.byName(m.data.craft)?.path === craft.path);
+}
+
+/** Build logs for a craft: posts linking to it or its makes. */
+export function buildLogs(s: SiteIndex, craft: Note<"craft">): Note<"post">[] {
+ return s.backlinks(craft, { type: "post" }) as Note<"post">[];
+}
+
+/** The tools of a craft, as notes where published, in the craft's order. */
+export function toolsOf(s: SiteIndex, craft: Note<"craft">): { title: string; note?: Note<"tool"> }[] {
+ return (craft.data.tools ?? []).map((t) => {
+ const l = s.link(t);
+ return { title: l.title, note: l.note?.type === "tool" ? (l.note as Note<"tool">) : undefined };
+ });
+}
+
+/** The tools placed at a station. */
+export function toolsAt(s: SiteIndex, station: Note<"station">): Note<"tool">[] {
+ return s.all("tool").filter((t) => s.byName(t.data.station)?.path === station.path);
+}
+
+export const craftPill = (status: string): Pill => (status === "Paused" ? "neutral" : "live");
+
+export const systemPill = (state: string): Pill => (state === "running" ? "live" : "neutral");
+
+/** Card game stances: the main event is gold, active ones live, the rest plain. */
+export function stancePill(stance: string): Pill {
+ const s = stance.toLowerCase();
+ if (s === "main event") return "current";
+ if (/getting into|exploring|playing|learning/.test(s)) return "live";
+ return "neutral";
+}
+
+/** "want to run" -> "Want to run". */
+export const sentence = (s: string) => s.charAt(0).toUpperCase() + s.slice(1);
+
+/** Order systems are grouped in. */
+export const systemStates = ["running", "played", "want to run", "want to play", "want to try"] as const;
+
+/** Statuses whose kind belongs to a page (Making and Learning for /workshop). */
+export function statusesFor(s: SiteIndex, page: string) {
+ return s.statuses().filter((st) => s.kind(st.data.kind).page === page);
+}
+
+/** Archidekt deck address. */
+export const archidektUrl = (id: number) => `https://archidekt.com/decks/${id}`;
+
+/** Deck's list address: Archidekt when set, else its own list link. */
+export function deckUrl(deck: Note<"deck">): string | undefined {
+ return deck.data.archidektId ? archidektUrl(deck.data.archidektId) : deck.data.listUrl;
+}
diff --git a/services/site/src/pages/404.astro b/services/site/src/pages/404.astro
new file mode 100644
index 0000000..c2487d4
--- /dev/null
+++ b/services/site/src/pages/404.astro
@@ -0,0 +1,162 @@
+---
+/**
+ * Off the star chart. Renders even when the content service is down: the
+ * only data it reads (an optional Page text note for /404) is wrapped.
+ */
+import Button from "@gk/ui/components/Button.astro";
+import PageShell from "@gk/ui/components/PageShell.astro";
+import Seal from "@gk/ui/components/Seal.astro";
+import Star from "@gk/ui/components/Star.astro";
+import StarPath from "@gk/ui/components/StarPath.astro";
+import TickScale from "@gk/ui/components/TickScale.astro";
+import { site } from "../lib/page.ts";
+
+Astro.response.status = 404;
+
+let lede = "This page isn't here. It may have moved when the site was rebuilt.";
+let timer: ReturnType | undefined;
+try {
+ const s = await Promise.race([site(), new Promise((_, reject) => (timer = setTimeout(() => reject(new Error("timeout")), 2000)))]);
+ lede = s.pageText("/404")?.data.lede ?? lede;
+} catch {
+ // The content service is unreachable; the page needs nothing from it.
+} finally {
+ clearTimeout(timer);
+}
+
+const groups = [
+ { id: "nf-work", label: "Work with me", links: [["Resume", "/resume"], ["Work", "/work"], ["Inquiries", "/inquiries"]] },
+ { id: "nf-world", label: "My world", links: [["Library", "/library"], ["Workshop", "/workshop"], ["Tabletop", "/tabletop"], ["Now", "/now"]] },
+ { id: "nf-also", label: "Also", links: [["Blog", "/blog"], ["Store", "/store"], ["About", "/about"]] },
+];
+---
+
+
+
+
+ 404 · Page not found
+ Off the star chart
+ {lede}
+
+ Back home
+ Blog
+
+
+
+
+
+
+ {
+ groups.map((g) => (
+
+
+
+ {g.label}
+
+
+
+ {g.links.map(([label, href]) => (
+
+ {label}
+
+ ))}
+
+
+ ))
+ }
+
+
+
+
diff --git a/services/site/src/pages/about.astro b/services/site/src/pages/about.astro
new file mode 100644
index 0000000..af06330
--- /dev/null
+++ b/services/site/src/pages/about.astro
@@ -0,0 +1,435 @@
+---
+/**
+ * About me: the "I am Greg" intro beside the portrait, the longer story with
+ * quick facts and what is happening right now, the two wings of the site, and
+ * where else to find Greg. Text from the Page text note for /about.
+ */
+import GiltCorners from "@gk/ui/components/GiltCorners.astro";
+import Icon from "@gk/ui/components/Icon.astro";
+import PageShell from "@gk/ui/components/PageShell.astro";
+import Seal from "@gk/ui/components/Seal.astro";
+import SectionHeading from "@gk/ui/components/SectionHeading.astro";
+import Star from "@gk/ui/components/Star.astro";
+import TickScale from "@gk/ui/components/TickScale.astro";
+import PageHero from "../components/PageHero.astro";
+import DoorLink from "../components/home/DoorLink.astro";
+import Portrait from "../components/home/Portrait.astro";
+import { splitIntro, viewStatus } from "../lib/home.ts";
+import { brandIcon } from "../lib/site.ts";
+import { site } from "../lib/page.ts";
+import { formatDate } from "../lib/site.ts";
+
+const s = await site();
+const text = s.pageText("/about");
+const settings = s.settings();
+const { intro, sections } = splitIntro(text?.body ?? "");
+const introHtml = s.render(intro);
+const facts = text?.data.quickFacts ?? [];
+const now = s.statuses().slice(0, 4).map((st) => viewStatus(s, st));
+const nowUpdated = now.map((v) => v.date).sort().pop();
+const socials = settings?.socials ?? [];
+const SOCIAL_ICONS: Record = { github: "web", linkedin: "briefcase", archidekt: "proxy-cards" };
+---
+
+
+
+
+
+ {introHtml ?
:
[PLACEHOLDER]
}
+
+
+
+
+
+
+ {
+ sections.map((sec, i) => (
+
+
+
+ {sec.heading}
+
+
+
+ ))
+ }
+
+
+
+
+
+
+
+
+ Two wings of the same house
+
+
+
+
+
+
+
For employers and clients
+
Work with me
+
+
+
+
For friends and fellow nerds
+
My world
+
+
+
+
+
+
+
+
diff --git a/services/site/src/pages/blog/[slug].astro b/services/site/src/pages/blog/[slug].astro
new file mode 100644
index 0000000..ead0c0a
--- /dev/null
+++ b/services/site/src/pages/blog/[slug].astro
@@ -0,0 +1,29 @@
+---
+/**
+ * One blog post in the all-dark reading layout. A case study's slug answers
+ * with a 301 to /work/; an old slug follows its redirect.
+ */
+import PostLayout from "../../components/blog/PostLayout.astro";
+import { topicHref } from "../../lib/blog.ts";
+import { site } from "../../lib/page.ts";
+
+const s = await site();
+const slug = Astro.params.slug ?? "";
+const post = s.bySlug("post", slug);
+if (!post) {
+ const to = s.redirect("post", slug);
+ return to ? Astro.redirect(to, 301) : Astro.rewrite("/404");
+}
+if (post.data.postType === "case study") return Astro.redirect(`/work/${post.slug}`, 301);
+
+const list = s.posts();
+---
+
diff --git a/services/site/src/pages/blog/index.astro b/services/site/src/pages/blog/index.astro
new file mode 100644
index 0000000..f91beda
--- /dev/null
+++ b/services/site/src/pages/blog/index.astro
@@ -0,0 +1,169 @@
+---
+/**
+ * The Blog: the featured or latest post, topic pills, search, the posts as
+ * rows, and the sidebar (Right now, Topics, Archive, RSS). Case studies live
+ * on Work and are not listed here.
+ */
+import PageShell from "@gk/ui/components/PageShell.astro";
+import Button from "@gk/ui/components/Button.astro";
+import FilterGroup from "@gk/ui/components/FilterGroup.astro";
+import Icon from "@gk/ui/components/Icon.astro";
+import SearchField from "@gk/ui/components/SearchField.astro";
+import PageHero from "../../components/PageHero.astro";
+import BlogLayout from "../../components/blog/BlogLayout.astro";
+import LeadPost from "../../components/blog/LeadPost.astro";
+import PostList from "../../components/blog/PostList.astro";
+import RightNowPanel from "../../components/blog/RightNowPanel.astro";
+import RowList from "../../components/blog/RowList.astro";
+import SidePanel from "../../components/blog/SidePanel.astro";
+import StarHeading from "../../components/blog/StarHeading.astro";
+import TopicsPanel from "../../components/blog/TopicsPanel.astro";
+import { archive, leadPost, searchPosts, topicHref, topics } from "../../lib/blog.ts";
+import { site } from "../../lib/page.ts";
+import { formatDate } from "../../lib/site.ts";
+
+const PER_PAGE = 10;
+const s = await site();
+const all = s.posts();
+const params = Astro.url.searchParams;
+const q = (params.get("q") ?? "").trim().slice(0, 100);
+const month = /^\d{4}-\d{2}$/.test(params.get("month") ?? "") ? params.get("month")! : "";
+const page = Math.max(1, Number.parseInt(params.get("page") ?? "1", 10) || 1);
+const filtered = !!(q || month);
+
+let list = searchPosts(all, q);
+if (month) list = list.filter((p) => p.data.date.startsWith(month));
+const lead = !filtered && page === 1 ? leadPost(all) : undefined;
+if (lead) list = list.filter((p) => p.path !== lead.post.path);
+const shown = list.slice((page - 1) * PER_PAGE, page * PER_PAGE);
+const more = list.length > page * PER_PAGE;
+
+const keep = (extra: Record) => {
+ const u = new URLSearchParams();
+ if (q) u.set("q", q);
+ if (month) u.set("month", month);
+ for (const [k, v] of Object.entries(extra)) u.set(k, String(v));
+ return `/blog?${u}`;
+};
+
+const text = s.pageText("/blog");
+const lede = text?.data.lede ?? "What I am making, learning, playing and building, and how it went.";
+const months = archive(all);
+const heading = q ? `Posts matching “${q}”` : month ? `Posts from ${formatDate(`${month}-01`, { month: "long", day: false })}` : lead ? "More posts" : "Posts";
+
+const empty = all.length === 0
+ ? {
+ emptyTitle: "The first posts are on their way",
+ emptyText: "Nothing is published here yet. Until then, the Now page shows what I am reading, playing and making.",
+ emptyAction: { label: "See what I am into now", href: "/now" },
+ }
+ : lead && list.length === 0 && !filtered
+ ? { emptyTitle: "That is the only post so far", emptyText: "More are on their way." }
+ : { emptyTitle: "No posts match", emptyText: "Try another word, or browse by topic.", emptyAction: { label: "Show every post", href: "/blog" } };
+---
+
+
+
+
+
+
+
+
+ {
+ lead && (
+
+ )
+ }
+
+
+
+
+ {heading}
+ {list.length > 0 && Sort: Newest }
+
+ ({ label: t, href: topicHref(t) }))]}
+ />
+
+
+ {
+ (more || page > 1) && (
+
+ )
+ }
+
+
+
+
+
+ {
+ months.length > 0 && (
+
+ ({ label: m.label, href: `/blog?month=${m.month}`, count: m.count, current: m.month === month }))} />
+
+ )
+ }
+
+
+
+
+
+
diff --git a/services/site/src/pages/blog/topic/[topic].astro b/services/site/src/pages/blog/topic/[topic].astro
new file mode 100644
index 0000000..c4bafd7
--- /dev/null
+++ b/services/site/src/pages/blog/topic/[topic].astro
@@ -0,0 +1,156 @@
+---
+/**
+ * A blog topic: its posts as rows, filterable by the page they link to, and
+ * a sidebar naming where the topic lives on the site and every topic.
+ */
+import PageShell from "@gk/ui/components/PageShell.astro";
+import Button from "@gk/ui/components/Button.astro";
+import FilterGroup from "@gk/ui/components/FilterGroup.astro";
+import Seal from "@gk/ui/components/Seal.astro";
+import BlogLayout from "../../../components/blog/BlogLayout.astro";
+import CrumbHero from "../../../components/blog/CrumbHero.astro";
+import PostList from "../../../components/blog/PostList.astro";
+import TopicsPanel from "../../../components/blog/TopicsPanel.astro";
+import { pageLinks, topicFromSlug, topicHome, topicHref } from "../../../lib/blog.ts";
+import { site } from "../../../lib/page.ts";
+
+const PER_PAGE = 10;
+const topic = topicFromSlug(Astro.params.topic);
+if (!topic) return Astro.rewrite("/404");
+
+const s = await site();
+const base = topicHref(topic);
+const posts = s.posts({ topic });
+
+// Filters: the pages this topic's posts link to, most posts first.
+const byPage = new Map();
+for (const p of posts) {
+ for (const l of pageLinks(s, p)) {
+ if (l.note.type === "post") continue;
+ const cur = byPage.get(l.note.path) ?? { label: l.note.title, slug: l.note.slug, count: 0 };
+ cur.count++;
+ byPage.set(l.note.path, cur);
+ }
+}
+const filters = [...byPage.values()].sort((a, b) => b.count - a.count || a.label.localeCompare(b.label)).slice(0, 8);
+const on = Astro.url.searchParams.get("on") ?? "";
+const active = filters.find((f) => f.slug === on);
+const page = Math.max(1, Number.parseInt(Astro.url.searchParams.get("page") ?? "1", 10) || 1);
+
+const list = active ? posts.filter((p) => pageLinks(s, p).some((l) => l.note.slug === active.slug)) : posts;
+const shown = list.slice((page - 1) * PER_PAGE, page * PER_PAGE);
+const more = list.length > page * PER_PAGE;
+const pageHref = (n: number) => `${base}?${new URLSearchParams({ ...(active ? { on: active.slug } : {}), page: String(n) })}`;
+
+const firstPage = (p: (typeof posts)[number]) => pageLinks(s, p).find((l) => l.note.type !== "post")?.note.title;
+const kicker = (p: (typeof posts)[number]) => {
+ const sub = active?.label ?? firstPage(p);
+ return sub ? `${topic} · ${sub}` : topic;
+};
+
+const home = topicHome[topic];
+const lede = s.pageText(base)?.data.lede;
+---
+
+
+
+
+
+
+
+ {
+ filters.length > 1 && (
+ ({ label: f.label, href: `${base}?on=${f.slug}`, active: f === active })),
+ ]}
+ />
+ )
+ }
+
+ {
+ (more || page > 1) && (
+
+ )
+ }
+
+
+
+
+
+
+ This topic lives in
+ {home.label}
+ {home.blurb}
+
+
+
+
+
+
+
+
diff --git a/services/site/src/pages/index.astro b/services/site/src/pages/index.astro
index f9121d6..359bdbf 100644
--- a/services/site/src/pages/index.astro
+++ b/services/site/src/pages/index.astro
@@ -1,8 +1,363 @@
---
+/**
+ * The landing: Greg's name and one line, the two doors (work first), the Now
+ * strip from the status log and the newest posts.
+ */
+import Button from "@gk/ui/components/Button.astro";
+import GiltCorners from "@gk/ui/components/GiltCorners.astro";
+import Kicker from "@gk/ui/components/Kicker.astro";
import PageShell from "@gk/ui/components/PageShell.astro";
+import Seal from "@gk/ui/components/Seal.astro";
+import SectionHeading from "@gk/ui/components/SectionHeading.astro";
+import StarChartArt from "@gk/ui/components/StarChartArt.astro";
+import TickScale from "@gk/ui/components/TickScale.astro";
+import BlogStrip from "../components/home/BlogStrip.astro";
+import DoorLink from "../components/home/DoorLink.astro";
+import NowCard from "../components/home/NowCard.astro";
+import Portrait from "../components/home/Portrait.astro";
+import { currentRole, viewStatus, worldPhoto } from "../lib/home.ts";
import { site } from "../lib/page.ts";
+
const s = await site();
+const settings = s.settings();
+const text = s.pageText("/");
+const role = currentRole(s);
+const skills = s.one("skills")?.data;
+
+const name = settings?.name ?? "Greg Kilmer";
+const roleTitle = role?.data.role.split(" / ").pop()?.trim();
+const eyebrow = [roleTitle, settings?.location].filter(Boolean).join(" · ");
+const intro = text?.data.lede ? `${text.data.lede}
` : s.render(text?.body);
+const studies = s.caseStudies().map((c) => c.title);
+const coreStack = skills?.stack.flatMap((g) => g.core ?? []) ?? [];
+const photo = worldPhoto(s);
+const statuses = s.statuses().slice(0, 5).map((st) => viewStatus(s, st));
+const listFormat = new Intl.ListFormat("en", { style: "long", type: "conjunction" });
---
-
- {s.notes.length} notes, {s.statuses().length} statuses.
+
+
+
+
+ {
+ eyebrow && (
+
+
+ {eyebrow}
+
+ )
+ }
+
{name}
+ {intro ?
:
[PLACEHOLDER]
}
+
+
+
+
+
+
+
+
+
+
For employers and clients
+
+
Work with me
+
+
+ {role?.data.focus &&
{role.data.focus}
}
+
+
+
+
+
+
+
+
+
+
+
For friends and fellow nerds
+
+
My world
+
+
+
+ Library
+ Blog
+ Making
+ Store
+
+
+
+
+
+
+
+
+ {
+ statuses.length ? (
+
+ {statuses.map((v) => (
+
+ ))}
+
+ ) : (
+ Nothing on the status log yet.
+ )
+ }
+
+
+
+
diff --git a/services/site/src/pages/inquiries/index.astro b/services/site/src/pages/inquiries/index.astro
new file mode 100644
index 0000000..0b24410
--- /dev/null
+++ b/services/site/src/pages/inquiries/index.astro
@@ -0,0 +1,421 @@
+---
+import Button from "@gk/ui/components/Button.astro";
+import Field from "@gk/ui/components/Field.astro";
+import HeroHorizon from "@gk/ui/components/HeroHorizon.astro";
+import PageShell from "@gk/ui/components/PageShell.astro";
+import Seal from "@gk/ui/components/Seal.astro";
+import Star from "@gk/ui/components/Star.astro";
+import StatusPill from "@gk/ui/components/StatusPill.astro";
+import ChoicePills from "../../components/work/ChoicePills.astro";
+import { site } from "../../lib/page.ts";
+import { inquiryTypes, parseInquiryType, type InquiryType } from "../../lib/work.ts";
+
+const s = await site();
+const settings = s.settings();
+const text = s.pageText("/inquiries");
+const selected = parseInquiryType(Astro.url.searchParams.get("type"));
+const lede = text?.data.lede ?? "Roles, freelance web work or a custom Patiun order. Pick one and the form asks the right questions.";
+const quickFacts = text?.data.quickFacts?.map((f) => f.text) ?? ["Replies within [PLACEHOLDER] days", "Read by me, not a bot"];
+const socials = settings?.socials ?? [];
+
+const placeholders: Record = {
+ none: "Tell me what you have in mind.",
+ job: "The role, the team, and anything I should know.",
+ freelance: "What you need built, who it is for, and any links.",
+ patiun: "Card list, deck colors, theme, or anything I should know.",
+};
+const timing = [
+ { value: "no-rush", label: "No rush" },
+ { value: "within-a-month", label: "Within a month" },
+ { value: "date", label: "A specific date" },
+];
+const env = (k: string) => (process.env[k] ?? import.meta.env[k]) as string | undefined;
+const turnstileKey = env("PUBLIC_TURNSTILE_SITE_KEY") ?? env("TURNSTILE_SITE_KEY");
+---
+
+ {turnstileKey && }
+
+
+
+
+
Inquiries
+
Let's make something
+
+
{lede}
+ {settings?.availability &&
{settings.availability} }
+
+
+ {
+ quickFacts.map((f) => (
+
+
+ {f}
+
+ ))
+ }
+ {
+ socials.length > 0 && (
+
+
+
+ Also on{" "}
+ {socials.map((x, i) => (
+ <>
+ {x.label}
+ {i < socials.length - 2 ? ", " : i === socials.length - 2 ? " and " : ""}
+ >
+ ))}
+
+
+ )
+ }
+
+
+
+
+
+
+
+
+
+
diff --git a/services/site/src/pages/inquiries/sent.astro b/services/site/src/pages/inquiries/sent.astro
new file mode 100644
index 0000000..eddf0cc
--- /dev/null
+++ b/services/site/src/pages/inquiries/sent.astro
@@ -0,0 +1,229 @@
+---
+/**
+ * The confirmation after an inquiry. The inquiries API sets the short-lived
+ * `gk-inquiry` cookie (URL-encoded JSON: type, name, email, rows) before it
+ * redirects here; the page shows it once and clears it. Without the cookie
+ * the page still reads correctly, showing `?type=` when given.
+ */
+import Button from "@gk/ui/components/Button.astro";
+import PageShell from "@gk/ui/components/PageShell.astro";
+import Seal from "@gk/ui/components/Seal.astro";
+import Star from "@gk/ui/components/Star.astro";
+import TickRing from "@gk/ui/components/TickRing.astro";
+import TickScale from "@gk/ui/components/TickScale.astro";
+import { inquiryTypes, parseInquiryType, parseSentSummary } from "../../lib/work.ts";
+
+const sent = parseSentSummary(Astro.cookies.get("gk-inquiry")?.value);
+if (sent) Astro.cookies.delete("gk-inquiry", { path: "/" });
+Astro.response.headers.set("Cache-Control", "private, no-store");
+
+const type = sent?.type ?? parseInquiryType(Astro.url.searchParams.get("type"));
+const typeLabel = inquiryTypes.find((t) => t.value === type)?.summary;
+const rows = [
+ ...(typeLabel ? [{ label: "Type", value: typeLabel }] : []),
+ ...(sent?.rows ?? []),
+ ...(sent?.email ? [{ label: "Reply to", value: sent.email }] : []),
+];
+const firstName = sent?.name?.split(/\s+/)[0];
+const steps = [
+ { title: "I read it", text: "Usually within a day or two." },
+ { title: "I reply", text: "With questions, a quote, or a time to talk." },
+ { title: "We settle the details", text: "Nothing starts until you say yes." },
+];
+---
+
+
+
+
+
+
Inquiry sent
+
Your note is on its way
+
Thanks{firstName ? `, ${firstName}` : ""}. I read every inquiry myself and reply within [PLACEHOLDER] days.
+
+ {
+ steps.map((st, i) => (
+
+ {i + 1}
+
+ {st.title}
+ {st.text}
+
+
+ ))
+ }
+
+
+
+
+ {
+ rows.length > 0 && (
+
+
+
+ What you sent
+
+
+
+ {rows.map((r) => (
+
+
{r.label}
+ {r.value}
+
+ ))}
+
+
+ )
+ }
+
+ While you wait
+
+ Blog
+ Workshop
+ Back home
+
+
+
+
+
+
+
diff --git a/services/site/src/pages/library/index.astro b/services/site/src/pages/library/index.astro
new file mode 100644
index 0000000..21d7458
--- /dev/null
+++ b/services/site/src/pages/library/index.astro
@@ -0,0 +1,255 @@
+---
+/**
+ * The Library: every book, series, show, film and video game. The favorites
+ * constellation, then the list grouped Now / Up next / Finished, filtered by
+ * medium, state, favorites and blog posts.
+ */
+import Divider from "@gk/ui/components/Divider.astro";
+import PageShell from "@gk/ui/components/PageShell.astro";
+import SearchField from "@gk/ui/components/SearchField.astro";
+import Constellation from "../../components/library/Constellation.astro";
+import FilterLayout from "../../components/library/FilterLayout.astro";
+import LibraryHero from "../../components/library/LibraryHero.astro";
+import LibraryRow from "../../components/library/LibraryRow.astro";
+import ListHeading from "../../components/library/ListHeading.astro";
+import RailGroup from "../../components/library/RailGroup.astro";
+import { byStanding, constellation, libraryFilter, mediaGroupOf, mediaGroups, sectionOf, sections, searchText } from "../../lib/library.ts";
+import { site } from "../../lib/page.ts";
+
+const s = await site();
+const f = libraryFilter(Astro.url.searchParams);
+const items = s.all("library-item").filter((i) => sectionOf(i));
+const lede = s.pageText("/library")?.data.lede ?? "What I'm on now, what's next, and what stuck with me. Short verdicts, no scores.";
+
+const shows = (i: (typeof items)[number]) =>
+ (!f.type || mediaGroupOf(i) === f.type) &&
+ f.show.has(sectionOf(i)!) &&
+ (!f.favorites || !!i.data.favorite) &&
+ (!f.blog || s.backlinks(i, { type: "post" }).length > 0) &&
+ (!f.q || searchText([i.title, i.data.creator, i.data.group, i.data.verdict, i.data.note, i.data.medium, i.data.platform, i.data.year, i.data.progress]).includes(f.q));
+
+const groups = sections.map((sec) => {
+ const list = items.filter((i) => sectionOf(i) === sec.key).sort(byStanding);
+ return { ...sec, list, visible: list.filter(shows).length };
+});
+const anyVisible = groups.some((g) => g.visible > 0);
+const dots = { now: "live", next: "open", done: "done" } as const;
+const typeHref = (key?: string) => {
+ const p = new URLSearchParams(Astro.url.searchParams);
+ if (key) p.set("type", key);
+ else p.delete("type");
+ const q = p.toString();
+ return q ? `/library?${q}` : "/library";
+};
+/** Finished rows shown before "Show everything finished". */
+const FIRST = 12;
+---
+
+
+
+
+
+
+
+
+
+ {f.type && }
+
+ Type
+ Everything
+ {mediaGroups.map((g) => {g.label} )}
+
+
+ {sections.map((sec) => {sec.label} )}
+ Favorites only
+ Has a blog post
+
+
+
+
+ {
+ groups.map((g) => (
+
+ {g.label}
+
+ {g.list.map((item) => )}
+
+ {g.key === "done" && g.list.length > FIRST && (
+
+ Show everything finished ({g.list.length})
+
+ )}
+
+ ))
+ }
+
Nothing in the Library matches these filters.
+
+
+
+
+
+
+
diff --git a/services/site/src/pages/library/shelf.astro b/services/site/src/pages/library/shelf.astro
new file mode 100644
index 0000000..04fe24c
--- /dev/null
+++ b/services/site/src/pages/library/shelf.astro
@@ -0,0 +1,369 @@
+---
+/**
+ * The Shelf: every board game. What is on the table (from the status log),
+ * then the collection, filtered by how it plays, owned, want to play and
+ * favorites, and sorted by last played, A to Z or recently added. Player
+ * counts and play times come from BoardGameGeek later; no filter for them yet.
+ */
+import Divider from "@gk/ui/components/Divider.astro";
+import FilterGroup from "@gk/ui/components/FilterGroup.astro";
+import GiltCorners from "@gk/ui/components/GiltCorners.astro";
+import PageShell from "@gk/ui/components/PageShell.astro";
+import SearchField from "@gk/ui/components/SearchField.astro";
+import Star from "@gk/ui/components/Star.astro";
+import StarChartArt from "@gk/ui/components/StarChartArt.astro";
+import TickScale from "@gk/ui/components/TickScale.astro";
+import FilterLayout from "../../components/library/FilterLayout.astro";
+import GameCard from "../../components/library/GameCard.astro";
+import LibraryHero from "../../components/library/LibraryHero.astro";
+import RailGroup from "../../components/library/RailGroup.astro";
+import { gameShows, onTheTable, playStyles, searchText, shelfFilter, sortGames, sorts, type Game } from "../../lib/library.ts";
+import { site } from "../../lib/page.ts";
+import { formatDate, mediaSrc } from "../../lib/site.ts";
+
+const s = await site();
+const f = shelfFilter(Astro.url.searchParams);
+const all = s.all("board-game");
+const games = sortGames(all, f.sort);
+const styles = playStyles(all);
+const owned = all.filter((g) => g.data.owned).length;
+const lede = s.pageText("/library/shelf")?.data.lede ?? "My board game collection. Filter by how it plays, sort by what hit the table last.";
+
+const text = (g: Game) => searchText([g.title, g.data.blurb, ...(g.data.howItPlays ?? [])]);
+const visible = new Set(games.filter((g) => gameShows(g, f, text(g))).map((g) => g.slug));
+
+// On the table: board-game statuses, then the most recent play not already listed.
+const table: { label: string; game: Game; note?: string; live: boolean }[] = onTheTable(s).map(({ status, game }) => ({
+ label: `${status.data.kind} now`,
+ game,
+ note: status.data.note,
+ live: true,
+}));
+const recent = sortGames(all, "played").find((g) => g.data.lastPlayed && !table.some((t) => t.game.path === g.path));
+if (recent) table.push({ label: "Played recently", game: recent, note: `Last played ${formatDate(recent.data.lastPlayed)}`, live: false });
+
+const sortHref = (key: string) => {
+ const p = new URLSearchParams(Astro.url.searchParams);
+ p.set("sort", key);
+ return `/library/shelf?${p}`;
+};
+---
+
+
+
+
+
+
+
+
{owned} {owned === 1 ? "game" : "games"} on the shelf
+
+
+
+
On the table
+
+
+ {
+ table.length > 0 ? (
+
+ ) : (
+
Nothing on the table right now.
+ )
+ }
+
+
+
+
+
+
+
+
+
+ {
+ styles.length > 0 && (
+
+ {styles.map((p) => {p} )}
+
+ )
+ }
+
+ Owned
+ Want to play
+ Favorites only
+
+
+
+
+
+
Showing {visible.size} {visible.size === 1 ? "game" : "games"}
+
+ Sort
+ ({ label: o.label, href: sortHref(o.key), active: o.key === f.sort, value: o.key }))} />
+
+
+
+ 0}>No games on the Shelf match these filters.
+
+
+
+
+
+
+
diff --git a/services/site/src/pages/now.astro b/services/site/src/pages/now.astro
new file mode 100644
index 0000000..4105e8b
--- /dev/null
+++ b/services/site/src/pages/now.astro
@@ -0,0 +1,262 @@
+---
+/**
+ * Now: every status in the log, grouped (making, at the table, playing,
+ * reading, watching, up next), each an anchor at its slug, with the
+ * Everything / Now / Up next filter.
+ */
+import FilterGroup from "@gk/ui/components/FilterGroup.astro";
+import PageShell from "@gk/ui/components/PageShell.astro";
+import SectionHeading from "@gk/ui/components/SectionHeading.astro";
+import Star from "@gk/ui/components/Star.astro";
+import TickScale from "@gk/ui/components/TickScale.astro";
+import { seals } from "@gk/ui/components/seals.ts";
+import PageHero from "../components/PageHero.astro";
+import StatusRow from "../components/home/StatusRow.astro";
+import { groupStatuses, lastUpdated, viewStatus } from "../lib/home.ts";
+import { site } from "../lib/page.ts";
+import { formatDate } from "../lib/site.ts";
+
+const s = await site();
+const text = s.pageText("/now");
+const groups = groupStatuses(s);
+const updated = lastUpdated(s);
+const lede = [text?.data.lede ?? "What I am making, reading, playing and watching right now.", updated && `Last updated ${formatDate(updated)}.`]
+ .filter(Boolean)
+ .join(" ");
+const intro = s.render(text?.body);
+const hasNext = groups.some((g) => g.key === "next");
+const wrapped = s
+ .all("status")
+ .filter((x) => /finish|wrapped|completed|done|caught up/i.test(x.data.kind))
+ .sort((a, b) => b.data.date.localeCompare(a.data.date))
+ .slice(0, 5)
+ .map((x) => viewStatus(s, x));
+---
+
+
+
+
+
+ Newest first · pinned on top
+
+
+
+
+ {intro &&
}
+ {
+ groups.length ? (
+ groups.map((g) => (
+
+
+
+ {g.title}
+
+
+ {g.items.map((v) => (
+
+ ))}
+
+
+ ))
+ ) : (
+
Nothing on the status log yet.
+ )
+ }
+ {!hasNext &&
Nothing queued up next right now.
}
+
+
+
+
+
+
+
+
+
diff --git a/services/site/src/pages/resume.astro b/services/site/src/pages/resume.astro
new file mode 100644
index 0000000..015042f
--- /dev/null
+++ b/services/site/src/pages/resume.astro
@@ -0,0 +1,559 @@
+---
+/**
+ * The resume: at a glance with the PDF, highlights, skills and education in
+ * the sidebar, and the experience timeline. Every word comes from the Resume
+ * notes and the /resume Page text; the PDF is built from the same data.
+ */
+import Button from "@gk/ui/components/Button.astro";
+import Chip from "@gk/ui/components/Chip.astro";
+import GiltCorners from "@gk/ui/components/GiltCorners.astro";
+import Icon from "@gk/ui/components/Icon.astro";
+import PageShell from "@gk/ui/components/PageShell.astro";
+import Seal from "@gk/ui/components/Seal.astro";
+import Star from "@gk/ui/components/Star.astro";
+import StarPath from "@gk/ui/components/StarPath.astro";
+import TickRing from "@gk/ui/components/TickRing.astro";
+import TickScale from "@gk/ui/components/TickScale.astro";
+import { site } from "../lib/page.ts";
+import { period, resumeData } from "../lib/resume.ts";
+import { formatDate } from "../lib/site.ts";
+
+const s = await site();
+const r = resumeData(s);
+const caseStudies = s.caseStudies();
+const isCore = (group: { core?: string[] }, item: string) => group.core?.some((c) => c.toLowerCase() === item.toLowerCase()) ?? false;
+---
+
+
+
+
RESUME AT A GLANCE
+
{r.name}
+
{[r.headline, r.location].filter(Boolean).join(" · ")}
+ {r.summary &&
{r.summary}
}
+
+
+
+
+ TAKE IT WITH YOU
+
+
+
+ Download PDF
+
+ {r.updated && PDF · updated {formatDate(r.updated)} }
+ Start an inquiry
+ {r.availability && {r.availability} }
+
+
+
+ {
+ r.stats.length > 0 && (
+
+ {r.stats.map((st) => (
+
+ {st.value}
+
+ {st.label}
+ {st.detail && {st.detail} }
+
+ ))}
+
+ )
+ }
+
+ {r.stats.length > 0 &&
}
+
+
+
+
+
+
+
Experience
+
+
+
+
+
+ {
+ r.roles.map((role, i) => (
+
+ {i === 0 && !role.data.end && }
+
+
+
+
+
{role.data.role}
+ {period(role)}
+
+ {role.data.company}{role.data.location ? ` · ${role.data.location}` : ""}
+ {role.data.previousRoles?.length ? (
+ Previously {role.data.previousRoles.join(" and ")} at {role.data.company}
+ ) : null}
+ {role.data.focus && {role.data.focus} }
+
+ {role.data.bullets.map((b) => (
+
+
+ {b}
+
+ ))}
+
+ {role.data.tech?.length ? (
+
+ {role.data.tech.map((t) => (
+ {t}
+ ))}
+
+ ) : null}
+
+ ))
+ }
+
+ {caseStudies.length > 0 && See the case studies on the Work page }
+
+
+
+
+
diff --git a/services/site/src/pages/resume.pdf.ts b/services/site/src/pages/resume.pdf.ts
new file mode 100644
index 0000000..4d15e51
--- /dev/null
+++ b/services/site/src/pages/resume.pdf.ts
@@ -0,0 +1,25 @@
+/** The resume as a PDF, generated from the same notes as /resume and rebuilt only when they change. */
+import type { APIRoute } from "astro";
+import { site } from "../lib/page.ts";
+import { resumeData } from "../lib/resume.ts";
+import { renderResumePdf } from "../lib/resume-pdf.ts";
+
+let cached: { key: string; pdf: Promise } | undefined;
+
+export const GET: APIRoute = async () => {
+ const r = resumeData(await site());
+ const key = JSON.stringify(r);
+ if (cached?.key !== key) cached = { key, pdf: renderResumePdf(r) };
+ const pdf = await cached.pdf.catch((err) => {
+ cached = undefined;
+ throw err;
+ });
+ const file = `${r.name.replace(/[^A-Za-z0-9]+/g, "-")}-Resume.pdf`;
+ return new Response(new Uint8Array(pdf), {
+ headers: {
+ "content-type": "application/pdf",
+ "content-disposition": `inline; filename="${file}"`,
+ "cache-control": "public, max-age=300",
+ },
+ });
+};
diff --git a/services/site/src/pages/rss.xml.ts b/services/site/src/pages/rss.xml.ts
new file mode 100644
index 0000000..c259928
--- /dev/null
+++ b/services/site/src/pages/rss.xml.ts
@@ -0,0 +1,53 @@
+/**
+ * The RSS 2.0 feed: blog posts and case studies, newest first, with absolute
+ * links from the configured site address or the request's origin.
+ */
+import type { APIRoute } from "astro";
+import { xml } from "../lib/blog.ts";
+import { site } from "../lib/page.ts";
+
+export const GET: APIRoute = async ({ site: configured, url }) => {
+ const s = await site();
+ const origin = (configured ?? new URL(url.origin)).href.replace(/\/$/, "");
+ const abs = (path: string) => new URL(path, `${origin}/`).href;
+ const settings = s.settings();
+ const name = settings?.name ?? "Greg Kilmer";
+ const posts = s.posts({ includeCaseStudies: true }).slice(0, 50);
+ const rfc822 = (iso: string) => new Date(`${iso}T12:00:00Z`).toUTCString();
+
+ const items = posts
+ .map((p) => {
+ const href = s.href(p);
+ if (!href) return "";
+ const link = abs(href);
+ const category = p.data.postType === "case study" ? "Case study" : p.data.topic;
+ return [
+ " - ",
+ `
${xml(p.title)} `,
+ ` ${xml(link)}`,
+ ` ${xml(link)} `,
+ ` ${rfc822(p.data.date)} `,
+ ` ${xml(category)} `,
+ p.data.summary ? ` ${xml(p.data.summary)} ` : "",
+ " ",
+ ]
+ .filter(Boolean)
+ .join("\n");
+ })
+ .filter(Boolean)
+ .join("\n");
+
+ const body = `
+
+
+ ${xml(name)}
+ ${xml(abs("/blog"))}
+
+ ${xml(settings?.tagline ?? "What I am making, learning, playing and building.")}
+ en-us
+${posts[0] ? ` ${rfc822(posts[0].data.date)} \n` : ""}${items}
+
+
+`;
+ return new Response(body, { headers: { "content-type": "application/rss+xml; charset=utf-8", "cache-control": "public, max-age=600" } });
+};
diff --git a/services/site/src/pages/store/[slug].astro b/services/site/src/pages/store/[slug].astro
new file mode 100644
index 0000000..19f9096
--- /dev/null
+++ b/services/site/src/pages/store/[slug].astro
@@ -0,0 +1,416 @@
+---
+/**
+ * One product line: gallery, summary and price, its slots or stock, the order
+ * button (or the closed or sold-out notice), specs, prices, options with
+ * swatches and stock, and its terms as ledger notes.
+ */
+import Button from "@gk/ui/components/Button.astro";
+import Chip from "@gk/ui/components/Chip.astro";
+import Notice from "@gk/ui/components/Notice.astro";
+import PageShell from "@gk/ui/components/PageShell.astro";
+import SectionHeading from "@gk/ui/components/SectionHeading.astro";
+import Star from "@gk/ui/components/Star.astro";
+import StarChartArt from "@gk/ui/components/StarChartArt.astro";
+import Breadcrumb from "../../components/store/Breadcrumb.astro";
+import LineStatus from "../../components/store/LineStatus.astro";
+import Swatches from "../../components/store/Swatches.astro";
+import Terms from "../../components/store/Terms.astro";
+import { site } from "../../lib/page.ts";
+import { formatDate, mediaSrc } from "../../lib/site.ts";
+import { canOrder, channelOf, hasCapacity, optionGroups, priceSummary, shopLink, storeState } from "../../lib/store.ts";
+
+const s = await site();
+const slug = Astro.params.slug ?? "";
+const line = s.bySlug("product-line", slug);
+if (!line) {
+ const to = s.redirect("product-line", slug);
+ if (to) return Astro.redirect(to, 301);
+ return Astro.rewrite("/404");
+}
+const st = storeState(s);
+const d = line.data;
+const cat = st.category(d.category);
+const ch = channelOf(line);
+const orderable = canOrder(line, st);
+const shop = shopLink(line);
+const price = priceSummary(line);
+const images = (d.images ?? []).map((im) => ({ ...im, src: mediaSrc(im.file) }));
+const lead = images[0];
+const groups = optionGroups(line);
+const body = s.render(line.body);
+const terms = [...(d.terms ?? []), ...st.terms];
+---
+
+
+
+
+
+
+
+
+
+ {lead?.label && {lead.label} }
+
+ {
+ images.length > 1 && (
+
+ {images.map((im, i) => (
+
+
+
+
+
+ ))}
+
+ )
+ }
+
+
+
+
{line.title}
+
{d.summary}
+ {
+ price && (
+
+ from
+ {price.from}
+ {[price.note, ...price.more].filter(Boolean).join(" · ")}
+
+ )
+ }
+
+ {
+ ch === "here" && !cat.ordersOpen && (
+
{cat.reopens ? `Reopens ${formatDate(cat.reopens)}.` : ""}
+ )
+ }
+ {
+ ch === "here" && cat.ordersOpen && !hasCapacity(line) && (
+
+ {d.mode === "made to order" ? "Slots reopen as current orders finish." : ""}
+
+ )
+ }
+ {ch === "coming soon" &&
Coming soon }
+
+ {orderable && Order request }
+ {shop && {shop.label} }
+ Something custom? Start an inquiry
+
+ {ch === "here" && cat.turnaround &&
{cat.turnaround} }
+
+
+
+ {body && }
+
+ {
+ (!!d.specs?.length || d.priceTiers.length > 0) && (
+
+ {!!d.specs?.length && (
+
+
+ Specs
+
+
+ {d.specs.map((sp) => (
+
+
{sp.label}
+ {sp.value}
+
+ ))}
+
+
+ )}
+ {d.priceTiers.length > 0 && (
+
+
+ Pricing
+
+
+ Prices for {line.title}
+
+ {d.priceTiers.map((t) => (
+
+ {t.band}
+ {t.price}
+ {t.note}
+
+ ))}
+
+
+
+ )}
+
+ )
+ }
+
+ {
+ groups.length > 0 && (
+
+
+ Options
+
+ {groups.map(([group, opts]) => (
+
+
{group}
+
+
+ ))}
+
+ )
+ }
+
+ {
+ terms.length > 0 && (
+
+
+ Before you order: requirements and policies
+
+
+
+
+
+ )
+ }
+
+
+
+
+
diff --git a/services/site/src/pages/store/index.astro b/services/site/src/pages/store/index.astro
new file mode 100644
index 0000000..e9a1a4d
--- /dev/null
+++ b/services/site/src/pages/store/index.astro
@@ -0,0 +1,356 @@
+---
+/**
+ * The Store: product lines grouped by category, each with its status, the
+ * store notice from Store settings, the in-stock colors, how ordering works
+ * and the store-wide terms.
+ */
+import Button from "@gk/ui/components/Button.astro";
+import Kicker from "@gk/ui/components/Kicker.astro";
+import Notice from "@gk/ui/components/Notice.astro";
+import SectionHeading from "@gk/ui/components/SectionHeading.astro";
+import PageShell from "@gk/ui/components/PageShell.astro";
+import Seal from "@gk/ui/components/Seal.astro";
+import Star from "@gk/ui/components/Star.astro";
+import TickRing from "@gk/ui/components/TickRing.astro";
+import TickScale from "@gk/ui/components/TickScale.astro";
+import LineCard from "../../components/store/LineCard.astro";
+import LineStatus from "../../components/store/LineStatus.astro";
+import Swatches from "../../components/store/Swatches.astro";
+import Terms from "../../components/store/Terms.astro";
+import { site } from "../../lib/page.ts";
+import { formatDate } from "../../lib/site.ts";
+import { channelOf, optionGroups, storeState } from "../../lib/store.ts";
+
+const s = await site();
+const st = storeState(s);
+const text = s.pageText("/store");
+const lede = text?.data.lede ?? st.settings?.capacityNote;
+
+const selling = st.lines.filter((l) => channelOf(l) === "here");
+const allClosed = st.closed.length > 0 && !st.anyOpen;
+const reopenText = (c: { reopens?: string }) => (c.reopens ? `Reopens ${formatDate(c.reopens)}.` : "");
+const turnarounds = [...new Set(st.groups.map(([c]) => st.category(c).turnaround).filter((t): t is string => !!t))];
+
+/** In-stock colors of lines sold here, one strip per line and option group. */
+const colorStrips = selling.flatMap((l) =>
+ optionGroups(l)
+ .map(([group, opts]) => ({ line: l, group, opts: opts.filter((o) => o.inStock && o.hex) }))
+ .filter((x) => x.opts.length),
+);
+---
+
+
+
+
+ {
+ allClosed ? (
+
+ {st.closed.map(reopenText).filter(Boolean).join(" ")}
+
+ ) : (
+ st.closed.map((c) => (
+
+ {reopenText(c)}
+
+ ))
+ )
+ }
+
A small workshop, not a factory
+ {lede &&
{lede}
}
+
+ {st.anyOpen && Start an order request }
+ Something custom? Start an inquiry
+
+
+ {
+ selling.length > 0 && (
+
+ {selling.map((l) => )}
+ {turnarounds.map((t) => {t} )}
+
+ )
+ }
+
+
+
+ {
+ st.groups.map(([cat, lines]) => {
+ const c = st.category(cat);
+ const id = `cat-${cat.toLowerCase()}`;
+ return (
+
+
+
+ {cat}
+
+ {lines.some((l) => channelOf(l) === "here") && (
+ {c.ordersOpen ? "Orders open" : `Orders closed${c.reopens ? ` · reopens ${formatDate(c.reopens)}` : ""}`}
+ )}
+
+
+ {lines.map((l) => (
+
+ ))}
+
+
+ );
+ })
+ }
+ {st.lines.length === 0 && [PLACEHOLDER]
}
+
+ {
+ colorStrips.map(({ line, group, opts }) => {
+ const id = `colors-${line.slug}-${group.toLowerCase().replace(/[^a-z0-9]+/g, "-")}`;
+ return (
+
+
+
{group} in stock
+
+ {line.title}
+
+
+ All colors
+
+ );
+ })
+ }
+
+
+ How ordering works
+
+
+ 1
+ Send a request
+ Use the order form with your card list or deck box colors. No payment is taken through the site.
+
+
+ 2
+ Confirm the details
+ I check the list and art, then confirm the total with you.
+
+
+ 3
+ I make it
+ {turnarounds.map((t) => {t} )}
+
+
+
+
+ {
+ st.terms.length > 0 && (
+
+
+
+ Before you order: requirements and policies
+
+
+
Each product line lists its own terms on its page.
+
+
+
+ )
+ }
+
+
+
diff --git a/services/site/src/pages/store/order.astro b/services/site/src/pages/store/order.astro
new file mode 100644
index 0000000..4e7b33d
--- /dev/null
+++ b/services/site/src/pages/store/order.astro
@@ -0,0 +1,176 @@
+---
+/**
+ * The order request form: lines that can take an order now, the chosen
+ * line's options, quantity, details, notes, contact and the terms. When no
+ * category takes orders, or every open line is full, a notice shows instead.
+ */
+import Button from "@gk/ui/components/Button.astro";
+import Notice from "@gk/ui/components/Notice.astro";
+import PageShell from "@gk/ui/components/PageShell.astro";
+import Star from "@gk/ui/components/Star.astro";
+import TickScale from "@gk/ui/components/TickScale.astro";
+import Breadcrumb from "../../components/store/Breadcrumb.astro";
+import LineStatus from "../../components/store/LineStatus.astro";
+import OrderForm from "../../components/store/OrderForm.astro";
+import { site } from "../../lib/page.ts";
+import { formatDate } from "../../lib/site.ts";
+import { canOrder, channelOf, storeState } from "../../lib/store.ts";
+
+const s = await site();
+const st = storeState(s);
+const selected = Astro.url.searchParams.get("line") ?? undefined;
+const orderable = st.lines.filter((l) => canOrder(l, st));
+const selling = st.lines.filter((l) => channelOf(l) === "here");
+const shown = orderable.length ? orderable : selling;
+const turnarounds = [...new Set(st.groups.map(([c]) => st.category(c).turnaround).filter((t): t is string => !!t))];
+const reopens = st.closed.filter((c) => c.reopens).map((c) => `${c.name} reopens ${formatDate(c.reopens)}.`);
+---
+
+
+
+ Order request
+ Send a request and I will confirm the details and the total with you. No payment is taken through the site.
+
+
+
+
+ {
+ orderable.length > 0 ? (
+
+ ) : (
+
+ {st.anyOpen ? (
+
Slots reopen as current orders finish.
+ ) : (
+
{reopens.join(" ")}
+ )}
+
{st.settings?.capacityNote}
+
+ Back to the Store
+ Something custom? Start an inquiry
+
+
+ )
+ }
+
+
+
+ {shown.map((l) => )}
+ {
+ (turnarounds.length > 0 || st.settings?.capacityNote) && (
+
+
+
+ Good to know
+
+
+ {turnarounds.map((t) => {t} )}
+ {orderable.length > 0 && st.settings?.capacityNote && {st.settings.capacityNote} }
+
+ )
+ }
+ {orderable.length > 0 && Something custom? Start an inquiry }
+
+
+
+
+
diff --git a/services/site/src/pages/tabletop.astro b/services/site/src/pages/tabletop.astro
new file mode 100644
index 0000000..2df6a6d
--- /dev/null
+++ b/services/site/src/pages/tabletop.astro
@@ -0,0 +1,448 @@
+---
+import FilterGroup from "@gk/ui/components/FilterGroup.astro";
+import Icon from "@gk/ui/components/Icon.astro";
+import PageShell from "@gk/ui/components/PageShell.astro";
+import Star from "@gk/ui/components/Star.astro";
+import StarChartArt from "@gk/ui/components/StarChartArt.astro";
+import StatusPill from "@gk/ui/components/StatusPill.astro";
+import PageHero from "../components/PageHero.astro";
+import NowStrip, { type NowItem } from "../components/world/NowStrip.astro";
+import WorldSection from "../components/world/WorldSection.astro";
+import type { Note } from "../lib/content.ts";
+import { plural, site } from "../lib/page.ts";
+import { slugify } from "@gk/schema";
+import { byOrder, deckUrl, makePhotos, sentence, stancePill, systemPill, systemStates } from "../lib/world.ts";
+
+const s = await site();
+const text = s.pageText("/tabletop");
+const intro = s.render(text?.body);
+
+const stateRank = (st: string) => systemStates.indexOf(st as (typeof systemStates)[number]);
+const systems = byOrder(s.all("tabletop-system")).sort((a, b) => stateRank(a.data.state) - stateRank(b.data.state));
+const states = systemStates.filter((st) => systems.some((x) => x.data.state === st));
+
+const campaigns = [...s.all("campaign")].sort((a, b) => a.title.localeCompare(b.title));
+const atTable = campaigns.filter((c) => c.data.status === "active" || c.data.status === "playtest");
+const pastCampaigns = campaigns.filter((c) => !atTable.includes(c));
+
+const games = byOrder(s.all("card-game"));
+const allDecks = [...s.all("deck")].sort((a, b) => a.title.localeCompare(b.title));
+const isFav = (d: Note<"deck">) => !!d.data.favorite;
+const decks = allDecks.some(isFav) ? allDecks.filter(isFav) : allDecks;
+const gameOf = (d: Note<"deck">) => s.link(d.data.game);
+const deckGroup = (d: Note<"deck">) => [shortGame(gameOf(d).title), d.data.format].filter(Boolean).join(" ");
+const deckGroups = [...new Set(decks.map(deckGroup))];
+
+/** "Magic: The Gathering" reads as MTG in kickers, as the design does. */
+function shortGame(title: string) {
+ return /^magic/i.test(title) ? "MTG" : title;
+}
+
+/** A status that points at a deck makes it the deck of the moment. */
+const deckStatus = s.statuses().find((st) => s.byName(st.data.subject)?.type === "deck");
+const deckOfMoment = deckStatus ? (s.byName(deckStatus.data.subject) as Note<"deck">) : undefined;
+
+const nowItems: NowItem[] = atTable.map((c) => {
+ const system = c.data.system ? s.link(c.data.system) : null;
+ const label = c.data.status === "playtest" ? "Playtest" : [system?.title, c.data.platform && `on ${c.data.platform}`].filter(Boolean).join(" · ");
+ return {
+ id: c.slug,
+ label: label || "Campaign",
+ title: c.title,
+ note: [c.data.schedule, c.data.blurb].filter(Boolean).join(" · ") || undefined,
+ };
+});
+if (deckOfMoment) {
+ nowItems.push({
+ label: ["Deck of the moment", deckGroup(deckOfMoment)].filter(Boolean).join(" · "),
+ title: deckOfMoment.title,
+ note: deckStatus?.data.note ?? deckOfMoment.data.take,
+ href: `#${deckOfMoment.slug}`,
+ });
+}
+
+const archidekt = s.settings()?.socials.find((x) => /archidekt/i.test(x.label));
+const shelfCount = s.all("board-game").length;
+const workshopImg = s.all("make").flatMap((m) => makePhotos(m))[0];
+---
+
+
+
+
+
+ {nowItems.length > 0 && }
+
+ {intro &&
}
+
+ {
+ systems.length > 0 && (
+
+ {states.length > 1 && (
+ ({ label: sentence(st), value: st }))]}
+ />
+ )}
+
+ {pastCampaigns.length > 0 && (
+
+
Earlier campaigns
+
+ {pastCampaigns.map((c) => (
+
+
+ {c.title}
+ {sentence(c.data.status)}
+ {c.data.blurb && {c.data.blurb} }
+
+ ))}
+
+
+ )}
+
+ )
+ }
+
+ {
+ decks.length > 0 && (
+
+ {archidekt && (
+
+ All my Magic decks on Archidekt
+
+
+
+
+ )}
+ {deckGroups.length > 1 && (
+ ({ label: g, value: g }))]}
+ />
+ )}
+
+ {decks.map((d) => {
+ const url = deckUrl(d);
+ const game = gameOf(d);
+ return (
+
+
+
+
+ );
+ })}
+
+
+ )
+ }
+
+ {
+ games.length > 0 && (
+
+
+
+ )
+ }
+
+
+
+
+
+ The Shelf
+ {shelfCount > 0 ? `${plural(shelfCount, "board game")}, filtered by how they play` : "Board games, filtered by how they play"}
+
+
+
+ {workshopImg ? : }
+
+ Workshop
+ The dice, proxies and terrain I make for the table
+
+
+
+
+
+ Tabletop posts
+ Session recaps, new games and deck techs
+
+
+
+
+
+
+
+
diff --git a/services/site/src/pages/work/[slug].astro b/services/site/src/pages/work/[slug].astro
new file mode 100644
index 0000000..862741f
--- /dev/null
+++ b/services/site/src/pages/work/[slug].astro
@@ -0,0 +1,35 @@
+---
+/**
+ * One case study: the post reading layout plus a facts strip (employer,
+ * role, period, key numbers, tech). A blog post's slug answers with a 301 to
+ * /blog/; an old slug follows its redirect.
+ */
+import FactsStrip from "../../components/blog/FactsStrip.astro";
+import PostLayout from "../../components/blog/PostLayout.astro";
+import { site } from "../../lib/page.ts";
+
+const s = await site();
+const slug = Astro.params.slug ?? "";
+const post = s.bySlug("post", slug);
+if (!post) {
+ const to = s.redirect("post", slug);
+ return to ? Astro.redirect(to, 301) : Astro.rewrite("/404");
+}
+if (post.data.postType !== "case study") return Astro.redirect(`/blog/${post.slug}`, 301);
+
+const list = s.caseStudies();
+const pool = s.posts({ includeCaseStudies: true });
+const d = post.data;
+---
+
+
+
diff --git a/services/site/src/pages/work/index.astro b/services/site/src/pages/work/index.astro
new file mode 100644
index 0000000..3e00cc8
--- /dev/null
+++ b/services/site/src/pages/work/index.astro
@@ -0,0 +1,174 @@
+---
+import Button from "@gk/ui/components/Button.astro";
+import FilterGroup from "@gk/ui/components/FilterGroup.astro";
+import Notice from "@gk/ui/components/Notice.astro";
+import PageShell from "@gk/ui/components/PageShell.astro";
+import PageHero from "../../components/PageHero.astro";
+import FeaturedStudy from "../../components/work/FeaturedStudy.astro";
+import StudyCard from "../../components/work/StudyCard.astro";
+import { site } from "../../lib/page.ts";
+import { coreTech, featuredStudy, groupLabels, otherStudies, workGroup, type WorkGroup } from "../../lib/work.ts";
+
+const s = await site();
+const text = s.pageText("/work");
+const studies = s.caseStudies();
+const featured = featuredStudy(studies);
+const rest = otherStudies(studies, featured);
+const core = coreTech(s);
+const groups = (["employer", "personal", "homelab"] as WorkGroup[]).filter((g) => studies.some((p) => workGroup(p) === g));
+const lede = text?.data.lede ?? "Employer work told as written case studies, plus the personal and homelab projects I build for myself.";
+---
+
+
+
+
+
+ {
+ groups.length > 1 && (
+
+ ({ label: groupLabels[g], value: g }))]}
+ />
+
+ )
+ }
+
+ {
+ studies.length === 0 && (
+
+ They are being written up now.
+
+ )
+ }
+
+ {featured && }
+
+ {
+ rest.length > 0 && (
+
+
+ {rest.map((p) => (
+
+ ))}
+
+ Nothing else in this group yet.
+
+ )
+ }
+
+
+
+
+
+
Have something like this in mind?
+
+
+
Jobs and freelance web work.
+
+
+ Start an inquiry
+ See the resume
+
+
+
+
+
+
+
+
diff --git a/services/site/src/pages/workshop/[slug].astro b/services/site/src/pages/workshop/[slug].astro
new file mode 100644
index 0000000..425d0b9
--- /dev/null
+++ b/services/site/src/pages/workshop/[slug].astro
@@ -0,0 +1,419 @@
+---
+import Button from "@gk/ui/components/Button.astro";
+import Chip from "@gk/ui/components/Chip.astro";
+import PageShell from "@gk/ui/components/PageShell.astro";
+import Seal from "@gk/ui/components/Seal.astro";
+import StarChartArt from "@gk/ui/components/StarChartArt.astro";
+import StatusPill from "@gk/ui/components/StatusPill.astro";
+import { pageSeals } from "@gk/ui/components/seals.ts";
+import CtaBand from "../../components/world/CtaBand.astro";
+import NowStrip, { type NowItem } from "../../components/world/NowStrip.astro";
+import WorldSection from "../../components/world/WorldSection.astro";
+import type { Note } from "../../lib/content.ts";
+import { plural, site } from "../../lib/page.ts";
+import { formatDate, mediaSrc } from "../../lib/site.ts";
+import { buildLogs, byDateDesc, byOrder, craftPill, craftSeal, makePhotos, makesOf, sentence, toolsOf } from "../../lib/world.ts";
+
+const s = await site();
+const slug = Astro.params.slug ?? "";
+const craft = s.bySlug("craft", slug);
+if (!craft) {
+ const to = s.redirect("craft", slug);
+ if (to) return Astro.redirect(to, 301);
+ return Astro.rewrite("/404");
+}
+
+const seal = craftSeal(craft);
+const artSeal = seal ?? pageSeals.workshop;
+const station = craft.data.station ? s.link(craft.data.station) : null;
+const tools = toolsOf(s, craft);
+const makes = byDateDesc(makesOf(s, craft));
+const logs = buildLogs(s, craft);
+const body = s.render(craft.body);
+const others = byOrder(s.all("craft")).filter((c) => c.path !== craft.path);
+
+const related = new Set([craft.path, ...makes.map((m) => m.path)]);
+const nowItems: NowItem[] = s
+ .statuses()
+ .filter((st) => {
+ const subject = s.byName(st.data.subject);
+ return subject && related.has(subject.path);
+ })
+ .map((st) => {
+ const subject = s.link(st.data.subject!);
+ const img = subject.note?.type === "make" ? makePhotos(subject.note as Note<"make">)[0] : undefined;
+ return {
+ id: st.slug,
+ label: st.data.kind === "Making" ? "On the bench" : st.data.kind,
+ title: subject.title,
+ note: st.data.note,
+ href: subject.note?.type === "make" ? `#${subject.note.slug}` : undefined,
+ img,
+ };
+ });
+
+const meta = [plural(makes.length, "make"), logs.length > 0 && plural(logs.length, "build log")].filter(Boolean).join(" · ");
+const postCover = (p: Note<"post">) => mediaSrc((p.data as { cover?: string }).cover);
+---
+
+
+
+ {craft.title}
+ {craft.data.blurb && {craft.data.blurb}
}
+
+
+
+ {body &&
}
+
+ {nowItems.length > 0 && }
+
+ {
+ tools.length > 0 && (
+
+
+
+ )
+ }
+
+ 1 ? "Newest first." : undefined}>
+ {
+ makes.length > 0 ? (
+
+ {makes.map((m) => {
+ const photos = makePhotos(m);
+ const log = s.backlinks(m, { type: "post" })[0];
+ return (
+
+
+ {photos.length > 1 && (
+
+ {photos.slice(1, 5).map((p, i) => (
+
+ ))}
+
+ )}
+
+ {m.title}
+ {m.data.blurb && {m.data.blurb} }
+ {(m.data.date || log) && (
+
+ {m.data.date && {formatDate(m.data.date)} }
+ {log && Build log }
+
+ )}
+
+
+ );
+ })}
+
+ ) : (
+ Nothing finished in this craft yet.
+ )
+ }
+
+
+ {
+ logs.length > 0 && (
+
+
+ {logs.map((p) => (
+
+
+
+
+ {p.data.topic}
+ {formatDate(p.data.date)}
+
+
{p.title}
+ {p.data.summary &&
{p.data.summary} }
+
+
+ ))}
+
+
+ )
+ }
+
+ {
+ others.length > 0 && (
+
+ Other crafts
+ {others.map((c) => {c.title} )}
+
+ )
+ }
+
+
+ Start an inquiry
+
+
+
+
diff --git a/services/site/src/pages/workshop/index.astro b/services/site/src/pages/workshop/index.astro
new file mode 100644
index 0000000..91d1f0d
--- /dev/null
+++ b/services/site/src/pages/workshop/index.astro
@@ -0,0 +1,481 @@
+---
+import Button from "@gk/ui/components/Button.astro";
+import GiltCorners from "@gk/ui/components/GiltCorners.astro";
+import PageShell from "@gk/ui/components/PageShell.astro";
+import Seal from "@gk/ui/components/Seal.astro";
+import Star from "@gk/ui/components/Star.astro";
+import StarChartArt from "@gk/ui/components/StarChartArt.astro";
+import StatusPill from "@gk/ui/components/StatusPill.astro";
+import TickRing from "@gk/ui/components/TickRing.astro";
+import PageHero from "../../components/PageHero.astro";
+import CtaBand from "../../components/world/CtaBand.astro";
+import NowStrip, { type NowItem } from "../../components/world/NowStrip.astro";
+import WorldSection from "../../components/world/WorldSection.astro";
+import type { Note } from "../../lib/content.ts";
+import { plural, site } from "../../lib/page.ts";
+import { buildLogs, byDateDesc, byOrder, craftPill, craftSeal, isSword, makePhotos, makesOf, statusesFor, toolsAt, toolsOf } from "../../lib/world.ts";
+
+const s = await site();
+const text = s.pageText("/workshop");
+const intro = s.render(text?.body);
+
+const crafts = byOrder(s.all("craft")).map((craft) => {
+ const makes = byDateDesc(makesOf(s, craft));
+ const photos = makes.flatMap((m) => makePhotos(m).slice(0, 1)).slice(0, 3);
+ return { craft, makes, photos, logs: buildLogs(s, craft).length, tools: toolsOf(s, craft).map((t) => t.title) };
+});
+
+const stations = byOrder(s.all("station")).map((station) => ({ station, tools: toolsAt(s, station) }));
+const loose = s.all("tool").filter((t) => !s.byName(t.data.station));
+
+const allMakes = s.all("make");
+const swords = allMakes.filter(isSword);
+const recent = byDateDesc(allMakes.filter((m) => !isSword(m)))
+ .sort((a, b) => Number(!!b.data.date) - Number(!!a.data.date) || Number(makePhotos(b).length > 0) - Number(makePhotos(a).length > 0))
+ .slice(0, 4);
+
+const nowItems: NowItem[] = statusesFor(s, "/workshop").map((st) => {
+ const subject = st.data.subject ? s.link(st.data.subject) : null;
+ const img = subject?.note?.type === "make" ? makePhotos(subject.note as Note<"make">)[0] : undefined;
+ return {
+ id: st.slug,
+ label: st.data.kind === "Making" ? "On the bench" : st.data.kind,
+ title: subject?.title ?? st.title,
+ note: st.data.note,
+ href: subject?.href,
+ img,
+ };
+});
+---
+
+
+
+
+
+ {intro &&
}
+
+ {nowItems.length > 0 && }
+
+ {
+ stations.length > 0 && (
+
+
+ {stations.map(({ station, tools }) => (
+
+
+ {station.data.order}
+ {station.title}
+
+ {station.data.where}
+ {tools.length > 0 && (
+
+ )}
+ {station.data.goodFor && {station.data.goodFor} }
+
+ ))}
+ {loose.length > 0 && (
+
+
+
+ Around the shop
+
+
+
+ )}
+
+
+ )
+ }
+
+
+
+
+
+ {
+ recent.length > 0 && (
+
+
+ {recent.map((m) => {
+ const found = s.byName(m.data.craft);
+ const craft = found?.type === "craft" ? (found as Note<"craft">) : undefined;
+ const log = s.backlinks(m, { type: "post" })[0];
+ return (
+
+
+
+ {m.title}
+
+
+ {craft &&
{craft.title} }
+
+ {m.data.blurb &&
{m.data.blurb} }
+ {log && (
+
+ Blog: {log.title}
+
+ )}
+
+
+ );
+ })}
+
+
+ )
+ }
+
+ {
+ swords.length > 0 && (
+
+
+
+ The sword rack
+
Blades from the stories I love
+
+ I always pick the sword class, and the spellsword when I can.
+
+
+
+ )
+ }
+
+
+ Visit the Store
+ Start an inquiry
+
+
+
+
diff --git a/vault-kit/24.01_PersonalWebsite/Site/_templates/Experience.md b/vault-kit/24.01_PersonalWebsite/Site/_templates/Experience.md
index dc961a5..11a80d2 100644
--- a/vault-kit/24.01_PersonalWebsite/Site/_templates/Experience.md
+++ b/vault-kit/24.01_PersonalWebsite/Site/_templates/Experience.md
@@ -4,7 +4,8 @@ title: "{{title}}"
publish: false # Only notes set to true appear on the site.
created: "{{date:YYYY-MM-DD}}" # optional
company:
-role:
+role: # The current or last title at this company.
+previousRoles: [] # optional. Earlier titles at the same company, newest first, shown as Previously.
start:
end: # optional. Leave out while it is your current role.
location: # optional
From 963ad0df23ad807bae41ed5d393f17d12a2a627f Mon Sep 17 00:00:00 2001
From: Claude
Date: Sun, 11 Oct 2026 21:58:21 +0000
Subject: [PATCH 09/16] Resume from Greg's interview, one-page PDF, store slots
closed
The experience notes, skills, education and a new /resume Page text now come
from Greg's Resume Interview answers (tools/migrate/src/seed.ts). Comcast and
Drexel move to a short Earlier list, and the page and PDF gain Selected
projects. Carrier Connect's numbers are Greg's corrected ones.
The PDF fits on one page and carries the email from site settings. Store
lines migrate with no open slots until Greg opens orders.
Schema: experience earlier, page projects, site-settings email.
Co-Authored-By: Claude Opus 5.5 (1M context)
Claude-Session: https://claude.ai/code/session_012aSbo2D1qP8cMFQCykf7kx
---
docs/site.md | 10 +-
packages/schema/src/types.ts | 15 ++
services/site/src/lib/resume-pdf.ts | 239 +++++++++++-------
services/site/src/lib/resume.ts | 13 +-
services/site/src/pages/resume.astro | 98 ++++++-
tools/migrate/src/build.ts | 12 +-
tools/migrate/src/seed.ts | 113 ++++++++-
tools/migrate/test/migrate.test.ts | 11 +-
.../Site/_templates/Experience.md | 1 +
9 files changed, 396 insertions(+), 116 deletions(-)
diff --git a/docs/site.md b/docs/site.md
index c14e226..9607c12 100644
--- a/docs/site.md
+++ b/docs/site.md
@@ -86,10 +86,14 @@ or admin token. Never set it where the public can reach the site.
## The resume PDF
`/resume.pdf` is drawn with pdfkit from the same `resumeData()` as `/resume`
-(`src/lib/resume.ts`), so the two never disagree. It is one plain column so
-applicant tracking systems read it in order, uses the brand TTFs in
+(`src/lib/resume.ts`), so the two never disagree. It reads top to bottom so
+applicant tracking systems take it in order, aims for one page (roles marked
+`earlier` print as a short list; skills share a row with education and
+projects), uses the brand TTFs in
`src/assets/pdf-fonts`, and is rebuilt only when the notes behind it change.
-The highlights row comes from the `/resume` Page text note's `stats`.
+The highlights row and Selected projects come from the `/resume` Page text note's
+`stats` and `projects`. Greg's Resume Interview answers live in
+`tools/migrate/src/seed.ts`.
## Where each page lives
diff --git a/packages/schema/src/types.ts b/packages/schema/src/types.ts
index b4508aa..a46bb6a 100644
--- a/packages/schema/src/types.ts
+++ b/packages/schema/src/types.ts
@@ -281,6 +281,10 @@ export const experience = def({
focus: line({ title: "Focus", description: "One line under the role." }).optional(),
bullets: strings({ title: "Bullets" }),
tech: strings({ title: "Tech" }).optional(),
+ earlier: z
+ .boolean()
+ .meta({ title: "Earlier role", description: "Shows in the short Earlier list: title, company, dates and the focus line only." })
+ .optional(),
}),
});
@@ -470,6 +474,7 @@ export const siteSettings = def({
tagline: line({ title: "Tagline" }),
lockup: line({ title: "Lockup line" }),
location: line({ title: "Location" }).optional(),
+ email: line({ title: "Email", description: "Printed on the resume PDF." }).optional(),
availability: line({ title: "Availability", description: "Shown on the resume and Inquiries, like Open to freelance web work." }).optional(),
socials: z
.array(z.object({ label: line({ title: "Label" }), handle: line({ title: "Handle" }), url: z.url().meta({ title: "Link" }) }))
@@ -501,6 +506,16 @@ export const page = def({
)
.meta({ title: "Highlights", description: "Big numbers on the page, like the resume highlights. Only real, checkable numbers." })
.optional(),
+ projects: z
+ .array(
+ z.object({
+ title: line({ title: "Project" }),
+ detail: line({ title: "Detail", description: "One line with a result or number worth stating." }),
+ link: line({ title: "Link", description: "A page on this site, like /work/cosmos, or a full address." }).optional(),
+ }),
+ )
+ .meta({ title: "Selected projects", description: "Personal projects the resume lists, like the homelab or this site." })
+ .optional(),
}),
});
diff --git a/services/site/src/lib/resume-pdf.ts b/services/site/src/lib/resume-pdf.ts
index cdbfe31..d6af089 100644
--- a/services/site/src/lib/resume-pdf.ts
+++ b/services/site/src/lib/resume-pdf.ts
@@ -1,6 +1,7 @@
/**
* The printable resume. Built from the same ResumeData as the /resume page,
- * laid out as one plain column so applicant tracking systems read it in order.
+ * laid out top to bottom (skills share a row with education and projects) so
+ * applicant tracking systems read it in order.
* Paper colors: ink on white, with the brand gold and teal darkened for print.
*/
import PDFDocument from "pdfkit";
@@ -9,7 +10,7 @@ import cinzelSemi from "../assets/pdf-fonts/Cinzel-SemiBold.ttf?inline";
import montReg from "../assets/pdf-fonts/Montserrat-Regular.ttf?inline";
import montMed from "../assets/pdf-fonts/Montserrat-Medium.ttf?inline";
import montSemi from "../assets/pdf-fonts/Montserrat-SemiBold.ttf?inline";
-import { month, period, type ResumeData } from "./resume.ts";
+import { listOf, month, period, type ResumeData } from "./resume.ts";
const fromDataUrl = (u: string) => Buffer.from(u.slice(u.indexOf(",") + 1), "base64");
const FONTS = {
@@ -26,8 +27,15 @@ const GOLD = "#a8842f";
const TEAL = "#0f7f78";
const RULE = "#d9cfb8";
-const PAGE = { size: "LETTER" as const, margin: 50 };
+const PAGE = { size: "LETTER" as const, margin: 36 };
const SITE = "gregkilmer.com";
+const TEXT = 9;
+
+/** A column of the page: where text starts and how wide it runs. */
+interface Col {
+ x: number;
+ w: number;
+}
export function renderResumePdf(r: ResumeData): Promise {
const doc = new PDFDocument({
@@ -40,8 +48,7 @@ export function renderResumePdf(r: ResumeData): Promise {
});
for (const [name, data] of Object.entries(FONTS)) doc.registerFont(name, data);
- const left = doc.page.margins.left;
- const width = doc.page.width - left - doc.page.margins.right;
+ const full: Col = { x: doc.page.margins.left, w: doc.page.width - doc.page.margins.left - doc.page.margins.right };
const chunks: Buffer[] = [];
doc.on("data", (c: Buffer) => chunks.push(c));
const done = new Promise((resolve, reject) => {
@@ -60,137 +67,177 @@ export function renderResumePdf(r: ResumeData): Promise {
/** A thin gold rule with a small star at its center. */
const starRule = (y: number) => {
- const mid = left + width / 2;
+ const mid = full.x + full.w / 2;
doc.save().lineWidth(0.6).strokeColor(GOLD);
- doc.moveTo(left, y).lineTo(mid - 10, y).stroke();
- doc.moveTo(mid + 10, y).lineTo(left + width, y).stroke();
+ doc.moveTo(full.x, y).lineTo(mid - 10, y).stroke();
+ doc.moveTo(mid + 10, y).lineTo(full.x + full.w, y).stroke();
doc.restore();
diamond(mid, y, 4);
};
- const section = (title: string) => {
- room(60);
- doc.moveDown(0.75);
+ const section = (title: string, c: Col = full) => {
+ room(50);
+ doc.moveDown(0.5);
const y = doc.y;
- doc.font("heading").fontSize(11).fillColor(GOLD).text(title.toUpperCase(), left, y, { characterSpacing: 1.6 });
- const ruleY = doc.y + 2;
- doc.save().lineWidth(0.5).strokeColor(RULE).moveTo(left, ruleY).lineTo(left + width, ruleY).stroke().restore();
- doc.y = ruleY + 8;
+ doc.font("heading").fontSize(10.5).fillColor(GOLD).text(title.toUpperCase(), c.x, y, { characterSpacing: 1.6, width: c.w });
+ const ruleY = doc.y + 1;
+ doc.save().lineWidth(0.5).strokeColor(RULE).moveTo(c.x, ruleY).lineTo(c.x + c.w, ruleY).stroke().restore();
+ doc.y = ruleY + 6;
};
- const bullet = (text: string, indent = 12) => {
- doc.font("body").fontSize(9.5);
- const h = doc.heightOfString(text, { width: width - indent, lineGap: 1.5 });
- room(h + 4);
+ const bullet = (text: string, c: Col = full, lead?: string) => {
+ const indent = 11;
+ const w = c.w - indent;
+ doc.font("body").fontSize(TEXT);
+ room(doc.heightOfString(lead ? `${lead} ${text}` : text, { width: w, lineGap: 1.2 }) + 3);
const y = doc.y;
- diamond(left + 3.5, y + 6, 2.2);
- doc.fillColor(INK).text(text, left + indent, y, { width: width - indent, lineGap: 1.5 });
- doc.moveDown(0.15);
+ diamond(c.x + 3.5, y + 5.5, 2.1);
+ if (lead) {
+ doc.font("strong").fillColor(INK).text(lead, c.x + indent, y, { width: w, continued: true, lineGap: 1.2 });
+ doc.font("body").text(` ${text}`, { lineGap: 1.2 });
+ } else {
+ doc.fillColor(INK).text(text, c.x + indent, y, { width: w, lineGap: 1.2 });
+ }
+ doc.moveDown(0.12);
+ };
+
+ /** A title on the left with its dates set flush right on the same line. */
+ const titled = (title: string, when: string, size: number, c: Col = full) => {
+ room(30);
+ const y = doc.y;
+ doc.font("medium").fontSize(8.5).fillColor(GOLD);
+ const whenW = when ? doc.widthOfString(when) : 0;
+ if (when) doc.text(when, c.x + c.w - whenW, y + (size - 8.5) / 2 + 0.5, { lineBreak: false });
+ doc.font("strong").fontSize(size).fillColor(INK).text(title, c.x, y, { width: c.w - whenW - 12 });
};
// Header
- doc.font("title").fontSize(28).fillColor(INK).text(r.name.toUpperCase(), left, doc.y, { characterSpacing: 2 });
- if (r.headline) doc.font("medium").fontSize(11).fillColor(TEAL).text(r.headline, { width });
+ doc.font("title").fontSize(24).fillColor(INK).text(r.name.toUpperCase(), full.x, doc.y, { characterSpacing: 2 });
+ if (r.availability) {
+ const y = doc.y;
+ doc.font("medium").fontSize(8.5).fillColor(INK);
+ const w = doc.widthOfString(r.availability);
+ doc.text(r.availability, full.x + full.w - w, y + 2.5, { lineBreak: false });
+ doc.x = full.x;
+ doc.y = y;
+ }
+ if (r.headline) doc.font("medium").fontSize(10.5).fillColor(TEAL).text(r.headline, full.x, doc.y, { width: full.w - 160 });
// Only the professional profiles belong on paper; the hobby links stay on the site.
const profiles = r.socials.filter((s) => /github|gitea|linkedin/i.test(`${s.label} ${s.url}`));
- const contact = [r.location, SITE, ...profiles.map((s) => s.url.replace(/^https?:\/\/(www\.)?/, "").replace(/\/$/, ""))].filter(Boolean).join(" · ");
- doc.moveDown(0.3).font("body").fontSize(9).fillColor(MUTED).text(contact, { width });
- if (r.availability) doc.font("medium").fontSize(9).fillColor(INK).text(r.availability, { width });
+ const contact = [r.location, r.email, SITE, ...profiles.map((s) => s.url.replace(/^https?:\/\/(www\.)?/, "").replace(/\/$/, ""))]
+ .filter(Boolean)
+ .join(" · ");
+ doc.moveDown(0.2).font("body").fontSize(8).fillColor(MUTED).text(contact, { width: full.w });
doc.moveDown(0.7);
starRule(doc.y);
- doc.moveDown(0.6);
+ doc.moveDown(0.5);
if (r.summary) {
- doc.font("body").fontSize(10).fillColor(INK).text(r.summary.replace(/\s*\n\s*/g, " "), left, doc.y, { width, lineGap: 2 });
+ doc.font("body").fontSize(9.5).fillColor(INK).text(r.summary.replace(/\s*\n\s*/g, " "), full.x, doc.y, { width: full.w, lineGap: 1.6 });
}
if (r.stats.length) {
- section("Highlights");
+ doc.moveDown(0.7);
const cols = Math.min(r.stats.length, 4);
- const colW = width / cols;
- let top = doc.y;
+ const colW = full.w / cols;
+ const top = doc.y;
+ let bottom = top;
r.stats.forEach((st, i) => {
- if (i > 0 && i % cols === 0) top = doc.y + 6;
- const x = left + (i % cols) * colW;
- doc.font("title").fontSize(16).fillColor(GOLD).text(st.value, x, top, { width: colW - 10 });
- doc.font("strong").fontSize(9).fillColor(INK).text(st.label, x, doc.y, { width: colW - 10 });
- if (st.detail) doc.font("body").fontSize(8.5).fillColor(MUTED).text(st.detail, x, doc.y, { width: colW - 10 });
+ const x = full.x + (i % cols) * colW;
+ const y = top + Math.floor(i / cols) * 44;
+ doc.font("title").fontSize(15).fillColor(GOLD).text(st.value, x, y, { width: colW - 10, lineBreak: false });
+ doc.font("strong").fontSize(8.5).fillColor(INK).text(st.label, x, y + 18, { width: colW - 10 });
+ if (st.detail) doc.font("body").fontSize(8).fillColor(MUTED).text(st.detail, x, doc.y, { width: colW - 10 });
+ bottom = Math.max(bottom, doc.y);
});
- doc.x = left;
+ doc.x = full.x;
+ doc.y = bottom;
}
if (r.roles.length) {
section("Experience");
r.roles.forEach((role, i) => {
const d = role.data;
- if (i > 0) doc.moveDown(0.6);
- room(64);
- const y = doc.y;
- const when = period(role);
- doc.font("medium").fontSize(9).fillColor(GOLD);
- const whenW = doc.widthOfString(when);
- doc.text(when, left + width - whenW, y + 1.5, { lineBreak: false });
- doc.font("strong").fontSize(11).fillColor(INK).text(d.role, left, y, { width: width - whenW - 16 });
+ if (i > 0) doc.moveDown(0.5);
+ titled(d.role, period(role), 10.5);
const at = [d.company, d.location].filter(Boolean).join(" · ");
- doc.font("medium").fontSize(9.5).fillColor(TEAL).text(at, left, doc.y, { width });
- if (d.previousRoles?.length) {
- doc.font("body").fontSize(8.5).fillColor(MUTED).text(`Previously ${d.previousRoles.join(", ")} at ${d.company}`, { width });
- }
- if (d.focus) doc.moveDown(0.2).font("body").fontSize(9).fillColor(MUTED).text(d.focus, { width, lineGap: 1 });
- doc.moveDown(0.3);
+ doc.font("medium").fontSize(9).fillColor(TEAL).text(at, full.x, doc.y, { width: full.w });
+ if (d.previousRoles?.length) doc.font("body").fontSize(8.5).fillColor(MUTED).text(`Previously ${listOf(d.previousRoles)}`, { width: full.w });
+ if (d.focus) doc.font("body").fontSize(8.5).fillColor(MUTED).text(d.focus, { width: full.w });
+ doc.moveDown(0.25);
for (const b of d.bullets ?? []) bullet(b);
- if (d.tech?.length) {
- doc.font("body").fontSize(8.5).fillColor(MUTED).text(d.tech.join(" · "), left + 12, doc.y, { width: width - 12 });
- }
- doc.x = left;
+ doc.x = full.x;
});
}
- if (r.skills?.stack.length) {
- section("Skills");
- for (const g of r.skills.stack) {
- const items = g.items.join(", ");
- doc.font("body").fontSize(9.5);
- room(doc.heightOfString(items, { width: width - 110 }) + 4);
+ if (r.earlier.length) {
+ section("Earlier");
+ r.earlier.forEach((role, i) => {
+ const d = role.data;
+ if (i > 0) doc.moveDown(0.3);
+ titled(d.role, period(role), 9.5);
+ doc.font("medium").fontSize(8.5).fillColor(TEAL).text(d.company, full.x, doc.y, { width: full.w });
+ if (d.focus) doc.font("body").fontSize(8.5).fillColor(MUTED).text(d.focus, { width: full.w });
+ });
+ }
+
+ // Skills on the left; education, certifications and projects on the right.
+ const gap = 22;
+ const leftCol: Col = { x: full.x, w: full.w * 0.55 };
+ const rightCol: Col = { x: full.x + leftCol.w + gap, w: full.w - leftCol.w - gap };
+ const labelW = 64;
+ const skillRows = (r.skills?.stack ?? []).map((g) => ({ label: g.label, items: g.items.join(", ") }));
+ // Measure both columns first: the block moves to a new page whole rather than splitting.
+ doc.fontSize(8.5);
+ const leftH = skillRows.reduce((h, g) => {
+ doc.font("strong");
+ const lh = doc.heightOfString(g.label, { width: labelW });
+ doc.font("body");
+ return h + Math.max(lh, doc.heightOfString(g.items, { width: leftCol.w - labelW, lineGap: 0.8 })) + 2;
+ }, 30);
+ const entries = (r.education?.degrees.length ?? 0) + (r.education?.certifications?.length ?? 0);
+ doc.font("body");
+ const rightH =
+ (entries ? 26 + entries * 28 : 0) +
+ (r.projects.length ? 26 : 0) +
+ r.projects.reduce((h, p) => h + doc.heightOfString(`${p.title} ${p.detail}`, { width: rightCol.w - 11, lineGap: 1.2 }) + 3, 0);
+ if (doc.y + Math.max(leftH, rightH) > doc.page.height - doc.page.margins.bottom) doc.addPage();
+ const start = doc.y;
+ let leftEnd = start;
+
+ if (skillRows.length) {
+ section("Skills", leftCol);
+ for (const { label, items } of skillRows) {
const y = doc.y;
- doc.font("strong").fontSize(9.5).fillColor(INK).text(g.label, left, y, { width: 100 });
+ doc.font("strong").fontSize(8.5).fillColor(INK).text(label, leftCol.x, y, { width: labelW });
const afterLabel = doc.y;
- doc.font("body").fontSize(9.5).fillColor(INK).text(items, left + 110, y, { width: width - 110, lineGap: 1 });
- doc.y = Math.max(doc.y, afterLabel) + 3;
+ doc.font("body").fontSize(8.5).fillColor(INK).text(items, leftCol.x + labelW, y, { width: leftCol.w - labelW, lineGap: 0.8 });
+ doc.y = Math.max(doc.y, afterLabel) + 2;
}
- if (r.skills.core?.length) {
- room(30);
- const y = doc.y;
- doc.font("strong").fontSize(9.5).fillColor(INK).text("Strengths", left, y, { width: 100 });
- doc.font("body").fontSize(9.5).fillColor(INK).text(r.skills.core.join(", "), left + 110, y, { width: width - 110, lineGap: 1 });
- }
- doc.x = left;
+ leftEnd = doc.y;
}
- if (r.education?.degrees.length) {
- section("Education");
- for (const dg of r.education.degrees) {
- room(40);
- const y = doc.y;
- doc.font("medium").fontSize(9).fillColor(GOLD);
- const w = doc.widthOfString(dg.period);
- doc.text(dg.period, left + width - w, y + 1.5, { lineBreak: false });
- doc.font("strong").fontSize(10.5).fillColor(INK).text(dg.credential, left, y, { width: width - w - 16 });
- doc.font("medium").fontSize(9.5).fillColor(TEAL).text(dg.institution, left, doc.y, { width });
- if (dg.detail) doc.font("body").fontSize(9).fillColor(MUTED).text(dg.detail, { width });
- doc.moveDown(0.4);
+ doc.y = start;
+ const edu = r.education;
+ if (edu?.degrees.length || edu?.certifications?.length) {
+ section("Education", rightCol);
+ for (const dg of edu?.degrees ?? []) {
+ titled(dg.credential, dg.period, 9.5, rightCol);
+ doc.font("medium").fontSize(8.5).fillColor(TEAL).text(dg.institution, rightCol.x, doc.y, { width: rightCol.w });
+ doc.moveDown(0.3);
+ }
+ for (const c of edu?.certifications ?? []) {
+ titled(c.name, c.year ?? "", 9.5, rightCol);
+ if (c.issuer) doc.font("medium").fontSize(8.5).fillColor(TEAL).text(c.issuer, rightCol.x, doc.y, { width: rightCol.w });
+ doc.moveDown(0.3);
}
}
-
- if (r.education?.certifications?.length) {
- section("Certifications");
- for (const c of r.education.certifications) {
- room(20);
- const line = [c.issuer, c.year].filter(Boolean).join(" · ");
- doc.font("strong").fontSize(9.5).fillColor(INK).text(c.name, left, doc.y, { continued: !!line });
- if (line) doc.font("body").fillColor(MUTED).text(` ${line}`);
- }
+ if (r.projects.length) {
+ section("Selected projects", rightCol);
+ for (const p of r.projects) bullet(p.detail, rightCol, p.title);
}
+ doc.x = full.x;
+ doc.y = Math.max(doc.y, leftEnd);
// Footer on every page.
const range = doc.bufferedPageRange();
@@ -200,12 +247,12 @@ export function renderResumePdf(r: ResumeData): Promise {
const y = doc.page.height - PAGE.margin + 4;
const bottom = doc.page.margins.bottom;
doc.page.margins.bottom = 0;
- doc.save().lineWidth(0.5).strokeColor(RULE).moveTo(left, y - 8).lineTo(left + width, y - 8).stroke().restore();
+ doc.save().lineWidth(0.5).strokeColor(RULE).moveTo(full.x, y - 8).lineTo(full.x + full.w, y - 8).stroke().restore();
doc.font("body").fontSize(8).fillColor(MUTED);
- doc.text([`${SITE}/resume`, updated].filter(Boolean).join(" · "), left, y, { lineBreak: false });
+ doc.text([`${SITE}/resume`, updated].filter(Boolean).join(" · "), full.x, y, { lineBreak: false });
if (range.count > 1) {
const label = `${i - range.start + 1} of ${range.count}`;
- doc.text(label, left + width - doc.widthOfString(label), y, { lineBreak: false });
+ doc.text(label, full.x + full.w - doc.widthOfString(label), y, { lineBreak: false });
}
doc.page.margins.bottom = bottom;
}
diff --git a/services/site/src/lib/resume.ts b/services/site/src/lib/resume.ts
index 643b1dd..20a25f1 100644
--- a/services/site/src/lib/resume.ts
+++ b/services/site/src/lib/resume.ts
@@ -8,8 +8,13 @@ export interface ResumeData {
location?: string;
summary?: string;
availability?: string;
+ email?: string;
stats: { value: string; label: string; detail?: string }[];
+ /** Roles shown in full, current first. */
roles: Note<"experience">[];
+ /** Roles marked earlier: a short list of title, company, dates and focus. */
+ earlier: Note<"experience">[];
+ projects: { title: string; detail: string; link?: string }[];
skills?: Note<"skills">["data"];
education?: Note<"education">["data"];
socials: { label: string; handle: string; url: string }[];
@@ -26,6 +31,9 @@ export const month = (ym: string | undefined) => {
export const period = (r: Note<"experience">) => `${month(r.data.start)} – ${month(r.data.end)}`;
+/** "A, B and C". */
+export const listOf = (items: string[]) => new Intl.ListFormat("en-US", { style: "long", type: "conjunction" }).format(items);
+
export function resumeData(s: SiteIndex): ResumeData {
const settings = s.settings();
const page = s.pageText("/resume");
@@ -39,8 +47,11 @@ export function resumeData(s: SiteIndex): ResumeData {
location: settings?.location,
summary: page?.body?.trim() || undefined,
availability: settings?.availability,
+ email: settings?.email,
stats: page?.data.stats ?? [],
- roles,
+ roles: roles.filter((r) => !r.data.earlier),
+ earlier: roles.filter((r) => r.data.earlier),
+ projects: page?.data.projects ?? [],
skills: s.one("skills")?.data,
education: s.one("education")?.data,
socials: settings?.socials ?? [],
diff --git a/services/site/src/pages/resume.astro b/services/site/src/pages/resume.astro
index 015042f..786aa49 100644
--- a/services/site/src/pages/resume.astro
+++ b/services/site/src/pages/resume.astro
@@ -15,7 +15,7 @@ import StarPath from "@gk/ui/components/StarPath.astro";
import TickRing from "@gk/ui/components/TickRing.astro";
import TickScale from "@gk/ui/components/TickScale.astro";
import { site } from "../lib/page.ts";
-import { period, resumeData } from "../lib/resume.ts";
+import { listOf, period, resumeData } from "../lib/resume.ts";
import { formatDate } from "../lib/site.ts";
const s = await site();
@@ -167,7 +167,7 @@ const isCore = (group: { core?: string[] }, item: string) => group.core?.some((c
{role.data.company}{role.data.location ? ` · ${role.data.location}` : ""}
{role.data.previousRoles?.length ? (
- Previously {role.data.previousRoles.join(" and ")} at {role.data.company}
+ Previously {listOf(role.data.previousRoles)}
) : null}
{role.data.focus && {role.data.focus} }
@@ -189,6 +189,43 @@ const isCore = (group: { core?: string[] }, item: string) => group.core?.some((c
))
}
+ {
+ r.earlier.length > 0 && (
+
+ )
+ }
+ {
+ r.projects.length > 0 && (
+
+ Selected projects
+
+ {r.projects.map((p) => (
+
+
+
+ {p.link ? {p.title} : {p.title} }
+ · {p.detail}
+
+
+ ))}
+
+
+ )
+ }
{caseStudies.length > 0 && See the case studies on the Work page }
@@ -556,4 +593,61 @@ const isCore = (group: { core?: string[] }, item: string) => group.core?.some((c
.role li :global(svg) {
margin-top: 7px;
}
+ .earlier,
+ .projects {
+ display: flex;
+ flex-direction: column;
+ gap: 12px;
+ }
+ .sub-head {
+ margin: 8px 0 0;
+ font-size: 16px;
+ letter-spacing: 0.08em;
+ color: var(--gk-gold-bright);
+ }
+ .earlier ul,
+ .projects ul {
+ margin: 0;
+ padding: 0;
+ list-style: none;
+ display: flex;
+ flex-direction: column;
+ gap: 12px;
+ }
+ .earlier li {
+ display: flex;
+ flex-direction: column;
+ gap: 4px;
+ padding: 14px 18px;
+ border: 1px solid var(--gk-lines);
+ border-radius: 10px;
+ background: var(--gk-midnight);
+ scroll-margin-top: 24px;
+ }
+ .earlier-role {
+ font-family: var(--gk-font-display);
+ font-size: 15px;
+ font-weight: 600;
+ letter-spacing: 0.04em;
+ color: var(--gk-text);
+ }
+ .earlier .company {
+ font-size: 14px;
+ }
+ .projects li {
+ display: flex;
+ gap: 10px;
+ font-size: 15px;
+ line-height: 1.55;
+ color: var(--gk-soft);
+ }
+ .projects li :global(svg) {
+ flex: none;
+ margin-top: 6px;
+ }
+ .projects strong,
+ .projects a {
+ color: var(--gk-text);
+ font-weight: 600;
+ }
diff --git a/tools/migrate/src/build.ts b/tools/migrate/src/build.ts
index cf7d449..500f984 100644
--- a/tools/migrate/src/build.ts
+++ b/tools/migrate/src/build.ts
@@ -263,13 +263,15 @@ export function build(legacyDir: string): BuildResult {
};
for (const file of readdirSync(expDir).sort()) {
const { frontmatter: e, body } = splitNote(readFileSync(join(expDir, file), "utf8")) as { frontmatter: any; body: string };
+ // Greg's Resume Interview answers replace the legacy wording field by field.
+ const answers = seed.experience[basename(file, ".md")] ?? {};
const [start, end] = String(e.period).split(" - ");
const bullets = body.split("\n").filter((l) => l.startsWith("- ")).map((l) => l.slice(2).trim());
const extra = body.split("\n").filter((l) => l.trim() && !l.startsWith("- ") && !/^Key contributions:?$/.test(l.trim())).join("\n");
add(
{
type: "experience",
- title: `${e.title} at ${e.company}`,
+ title: `${(answers.role as string | undefined) ?? e.title} at ${e.company}`,
publish: true,
company: e.company,
role: e.title,
@@ -278,10 +280,15 @@ export function build(legacyDir: string): BuildResult {
focus: e.focus,
bullets,
created: today,
+ ...answers,
},
extra,
);
}
+ {
+ const { body, ...rest } = seed.resumePage;
+ add({ type: "page", created: today, ...rest }, body);
+ }
add({ type: "skills", title: "Skills", publish: true, ...seed.skills });
add({ type: "education", title: "Education", publish: true, ...seed.education });
@@ -335,7 +342,8 @@ export function build(legacyDir: string): BuildResult {
summary: p.summary,
icon: p.icon,
order: i + 1,
- slots: store.slots[p.slotKey],
+ // Greg (2026-10-11): orders stay closed until he opens them, so no slot shows as open.
+ slots: { ...store.slots[p.slotKey], open: 0 },
images: p.images.map((im: any) => ({ file: attach(im.src, "Store"), alt: im.alt, label: im.label })),
specs: p.specs,
priceTiers: p.pricing,
diff --git a/tools/migrate/src/seed.ts b/tools/migrate/src/seed.ts
index 334da5a..c039031 100644
--- a/tools/migrate/src/seed.ts
+++ b/tools/migrate/src/seed.ts
@@ -16,6 +16,10 @@ export const site = {
tagline: "Developer. Maker. Storyteller.",
lockup: "Web Development · a Patiun LLC company",
location: "Philadelphia, PA",
+ /** legacy contact pages; Greg confirmed it for the resume PDF (Resume Interview, 2026-10-11). */
+ email: "greg@gregkilmer.com",
+ /** Resume Interview: open to offers but not looking, so the page says this. */
+ availability: "Open to freelance web work",
socials: [
{ label: "GitHub", handle: "Patiun", url: "https://github.com/patiun" },
{ label: "LinkedIn", handle: "Gregory Kilmer", url: "https://www.linkedin.com/in/gregory-kilmer-36646bb5/" },
@@ -222,11 +226,15 @@ export const favoriteBoardGames = ["Spirit Island", "Wyrmspan"];
// ---------------------------------------------------------------- Resume
-/** legacy/src/components/professional/SkillGrid.astro */
+/**
+ * legacy/src/components/professional/SkillGrid.astro, with the stack Greg added
+ * in the Resume Interview (2026-10-11). Core items are the ones he named for
+ * Implico's main stack.
+ */
export const skills = {
core: [
- "Cloud Architecture & Development",
- "Mobile Application Development",
+ "Software Architecture & System Design",
+ "Cloud & Mobile Application Development",
"SAP Business Technology Platform (BTP)",
"Team Leadership & Technical Mentorship",
"Cross-functional Communication",
@@ -237,14 +245,89 @@ export const skills = {
"Advanced automation and workflow engineering",
],
stack: [
- { label: "Languages", items: ["JavaScript", "TypeScript", "Java", "Python", "C#", "C", "SQL", "HTML", "CSS"] },
- { label: "Frameworks", items: ["React", "Vue", "Astro", "Flask"] },
- { label: "Cloud Platforms", items: ["SAP BTP", "Google Cloud Platform", "AWS"] },
- { label: "Tools", items: ["PostgreSQL", "VSCode", "Unity", "XCode", "Android Studio", "Git", "Gitea", "CI/CD pipelines"] },
+ {
+ label: "Languages",
+ items: ["JavaScript", "TypeScript", "Python", "Java", "C#", "C", "SQL", "Bash", "Awk", "HTML", "CSS"],
+ core: ["JavaScript", "Python"],
+ },
+ { label: "Frameworks", items: ["React", "React Native", "Node.js", "SAP CAP", "Vue", "Astro", "Flask"], core: ["React", "React Native", "SAP CAP"] },
+ {
+ label: "Cloud Platforms",
+ items: ["SAP BTP (Cloud Foundry)", "SAP Integration Suite", "SAP Build Apps", "SAP Build Work Zone", "Google Cloud Platform", "AWS"],
+ core: ["SAP BTP (Cloud Foundry)", "SAP Integration Suite"],
+ },
+ { label: "Mobile", items: ["iOS", "Android", "Xcode", "Android Studio"] },
+ { label: "Data", items: ["PostgreSQL", "NumPy", "Pandas"] },
+ { label: "Tools", items: ["Docker", "Git", "Gitea", "CI/CD pipelines", "VS Code", "Unity"] },
{ label: "AI/ML", items: ["Machine learning frameworks", "LLM integration", "Claude Code", "Hermes", "OpenClaw"] },
],
};
+/**
+ * Greg's Resume Interview answers (2026-10-11), written up as the experience
+ * notes. Keyed by the legacy file in src/content/experience; these fields
+ * replace the legacy ones. Numbers are his, as he gave them.
+ */
+export const experience: Record> = {
+ implico: {
+ role: "Team Lead, BTP Cloud & Mobile Apps and US Managed Services",
+ previousRoles: ["BTP Cloud and Mobile App Lead", "Lead Developer", "Software Developer"],
+ location: "Remote",
+ focus: "Leading the team behind Carrier Connect, an SAP logistics platform for third-party carriers.",
+ bullets: [
+ "Architected Carrier Connect, a suite of integration and management tools built for SDM for SAP that lets third-party carriers connect to a central purchase order system, track loading and delivery events, and streamline their documents.",
+ "Designed several integration paths into one delivery interface (REST APIs, Excel and CSV file transfer, and the companion Driver App), and personally built the delivery interface, the file transfer interfaces and the inventory polling and delivery system.",
+ "Carrier Connect runs at 99.999% uptime and handles 2,000+ orders a day for 500+ active users across 50+ companies, saving about 72 hours per delivery.",
+ "Lead a development team of 4, plus 6 extended members on the US Managed Services team.",
+ "Set the team's coding standards on Google's published best practices, along with code review, pull request, testing and code coverage standards.",
+ "Brought React and AI-assisted development to the team, and was the main author of its responsible AI guidelines: factual accuracy, reputation and transparency, human oversight, data security, and consequences for misuse.",
+ ],
+ tech: ["React Native", "React", "Python", "SAP CAP", "SAP BTP (Cloud Foundry)", "SAP Build Work Zone", "SAP Build Apps", "SAP Integration Suite", "iOS", "Android"],
+ },
+ drexel: {
+ role: "Assistant System Administrator, then Computer Lab Help Desk",
+ end: "2018-06",
+ earlier: true,
+ focus: "Maintained 4 Linux servers for the College of Computing and Informatics and automated every workstation in 3 labs and the laptop carts with Ansible.",
+ bullets: [
+ "Maintained the college's Linux server network (4 servers serving the whole College of Computing and Informatics) with Ansible, Bash, Python and MySQL.",
+ "Automated deployment and maintenance of every workstation in the 3 computer labs and the mobile laptop carts with Ansible.",
+ "Protected college-wide data with Ceph storage backups.",
+ "Stayed on at the Computer Lab help desk through June 2018, supporting faculty, staff and students.",
+ ],
+ },
+ comcast_1: {
+ role: "Junior Developer (co-op), then Senior Intern",
+ earlier: true,
+ focus: "Led a team of 10 co-ops building smart remote, call center and inventory tools, and built an NLP sentiment pipeline for leadership.",
+ bullets: [
+ "Hired back after the co-op as Senior Intern to run a team of 10 co-ops building smart remote, call center and inventory tools.",
+ "Built an NLP system that gathered remote speech input and reviews so leadership teams could gauge customer sentiment (Java, Groovy, Grails, Elasticsearch, MySQL).",
+ "Built call center and inventory features on the internal logistics platform for techs and warehouse teams (PHP, MySQL).",
+ "Worked in weeklong Agile sprints.",
+ ],
+ },
+};
+
+/** The /resume page's own text, from the Resume Interview (2026-10-11). */
+export const resumePage = {
+ title: "Resume",
+ page: "/resume",
+ publish: true,
+ lede: "Team Lead, SAP BTP Cloud & Mobile Apps",
+ stats: [
+ { value: "99.999%", label: "Uptime", detail: "Carrier Connect" },
+ { value: "2,000+", label: "Orders a day", detail: "Through Carrier Connect" },
+ { value: "500+", label: "Active users", detail: "Across 50+ companies" },
+ { value: "10", label: "People led", detail: "Development and Managed Services" },
+ ],
+ projects: [
+ { title: "Cosmos homelab", detail: "20+ Docker containers working with 17 AI agents on a private network." },
+ { title: "GregKilmer.com", detail: "This site, run from an Obsidian vault through a rule-checked content service and MCP server." },
+ ],
+ body: `I have a strong eye for software design and architecture, and a broad range of experience across cloud, app and game development. I stay current with the cutting edge of AI and use AI tools to speed up my workflow, but I'm skilled enough to do the work without them.`,
+};
+
/** legacy/src/components/professional/EducationHistory.astro and Certifications.astro */
export const education = {
degrees: [
@@ -255,14 +338,24 @@ export const education = {
detail: "Foundational coursework in software engineering, algorithms, and systems. Focuses in AI and human-computer interaction.",
},
],
- certifications: [{ name: "Planet 9 Development", issuer: "Neptune", year: "2019", status: "Active" as const }],
+ // Resume Interview: Neptune Software Planet 9, earned 2019.
+ certifications: [{ name: "Planet 9 Development", issuer: "Neptune Software", year: "2019", status: "Active" as const }],
};
/** Employer and facts for the legacy projects; facts from the Implico and Cosmos entries. */
export const caseStudies: Record = {
- "carrier-connect": { employer: "Implico Inc.", facts: [{ label: "Active users", value: "1,000+" }, { label: "Companies", value: "100" }] },
+ // Resume Interview (2026-10-11): Greg's corrected numbers.
+ "carrier-connect": {
+ employer: "Implico Inc.",
+ facts: [
+ { label: "Active users", value: "500+" },
+ { label: "Companies", value: "50+" },
+ { label: "Orders a day", value: "2,000+" },
+ { label: "Uptime", value: "99.999%" },
+ ],
+ },
"driver-app": { employer: "Implico Inc." },
- "cosmos-homelab": { facts: [{ label: "AI agents", value: "17" }] },
+ "cosmos-homelab": { facts: [{ label: "Docker containers", value: "20+" }, { label: "AI agents", value: "17" }] },
"placeholder-project": {},
};
diff --git a/tools/migrate/test/migrate.test.ts b/tools/migrate/test/migrate.test.ts
index 25d7588..d945d12 100644
--- a/tools/migrate/test/migrate.test.ts
+++ b/tools/migrate/test/migrate.test.ts
@@ -41,12 +41,19 @@ describe("migration", () => {
expect(find("library-item", "Cosmere")?.data).toMatchObject({ creator: "Brandon Sanderson", medium: "book" });
});
- it("keeps the store's real prices and slots", () => {
+ it("keeps the store's real prices, with no slot open until Greg opens orders", () => {
const proxies = find("product-line", "Proxy MTG Cards")!.data as any;
- expect(proxies.slots).toEqual({ open: 1, total: 3 });
+ expect(proxies.slots).toEqual({ open: 0, total: 3 });
expect(proxies.priceTiers).toContainEqual({ band: "100 card deck", price: "$40.00", note: "per set of cards" });
});
+ it("takes the resume from Greg's interview answers", () => {
+ const implico = find("experience", "Team Lead, BTP Cloud & Mobile Apps and US Managed Services at Implico Inc.")!.data as any;
+ expect(implico.bullets.join(" ")).toContain("500+ active users across 50+ companies");
+ expect(result.notes.filter((n) => n.data.type === "experience" && (n.data as any).earlier)).toHaveLength(2);
+ expect(find("page", "Resume")?.data).toMatchObject({ page: "/resume", stats: expect.any(Array), projects: expect.any(Array) });
+ });
+
it("logs status for everything in progress", () => {
const kinds = result.notes.filter((n) => n.data.type === "status").map((n) => `${n.data.kind}: ${n.data.title}`);
expect(kinds).toContain("Playing: Slay the Spire 2");
diff --git a/vault-kit/24.01_PersonalWebsite/Site/_templates/Experience.md b/vault-kit/24.01_PersonalWebsite/Site/_templates/Experience.md
index 11a80d2..790f180 100644
--- a/vault-kit/24.01_PersonalWebsite/Site/_templates/Experience.md
+++ b/vault-kit/24.01_PersonalWebsite/Site/_templates/Experience.md
@@ -12,6 +12,7 @@ location: # optional
focus: # optional. One line under the role.
bullets: []
tech: [] # optional
+earlier: false # optional. Shows in the short Earlier list: title, company, dates and the focus line only.
---
From b1aec13eba2e1e357dcbc42eb7ce7890f88ebd03 Mon Sep 17 00:00:00 2001
From: Claude
Date: Sun, 11 Oct 2026 22:13:55 +0000
Subject: [PATCH 10/16] Resume: customer work, network administration and team
management
Adds Greg's sales demos, customer presentations, requirement gathering,
training and network engineering, and the career path, yearly review and
resource planning side of leading the team. Wording is tightened so the PDF
stays on one page, and the PDF skips a role's focus line when it has bullets.
Co-Authored-By: Claude Opus 5.5 (1M context)
Claude-Session: https://claude.ai/code/session_012aSbo2D1qP8cMFQCykf7kx
---
services/site/src/lib/resume-pdf.ts | 3 ++-
tools/migrate/src/seed.ts | 9 +++++----
tools/migrate/test/migrate.test.ts | 1 +
3 files changed, 8 insertions(+), 5 deletions(-)
diff --git a/services/site/src/lib/resume-pdf.ts b/services/site/src/lib/resume-pdf.ts
index d6af089..72e379b 100644
--- a/services/site/src/lib/resume-pdf.ts
+++ b/services/site/src/lib/resume-pdf.ts
@@ -163,7 +163,8 @@ export function renderResumePdf(r: ResumeData): Promise {
const at = [d.company, d.location].filter(Boolean).join(" · ");
doc.font("medium").fontSize(9).fillColor(TEAL).text(at, full.x, doc.y, { width: full.w });
if (d.previousRoles?.length) doc.font("body").fontSize(8.5).fillColor(MUTED).text(`Previously ${listOf(d.previousRoles)}`, { width: full.w });
- if (d.focus) doc.font("body").fontSize(8.5).fillColor(MUTED).text(d.focus, { width: full.w });
+ // The focus line repeats what the bullets say, so paper skips it when there are bullets.
+ if (d.focus && !d.bullets?.length) doc.font("body").fontSize(8.5).fillColor(MUTED).text(d.focus, { width: full.w });
doc.moveDown(0.25);
for (const b of d.bullets ?? []) bullet(b);
doc.x = full.x;
diff --git a/tools/migrate/src/seed.ts b/tools/migrate/src/seed.ts
index c039031..331e9c5 100644
--- a/tools/migrate/src/seed.ts
+++ b/tools/migrate/src/seed.ts
@@ -275,10 +275,11 @@ export const experience: Record> = {
location: "Remote",
focus: "Leading the team behind Carrier Connect, an SAP logistics platform for third-party carriers.",
bullets: [
- "Architected Carrier Connect, a suite of integration and management tools built for SDM for SAP that lets third-party carriers connect to a central purchase order system, track loading and delivery events, and streamline their documents.",
+ "Architected Carrier Connect, an SDM for SAP tool suite that connects third-party carriers to a central purchase order system, tracks loading and delivery events, and streamlines documents.",
"Designed several integration paths into one delivery interface (REST APIs, Excel and CSV file transfer, and the companion Driver App), and personally built the delivery interface, the file transfer interfaces and the inventory polling and delivery system.",
"Carrier Connect runs at 99.999% uptime and handles 2,000+ orders a day for 500+ active users across 50+ companies, saving about 72 hours per delivery.",
- "Lead a development team of 4, plus 6 extended members on the US Managed Services team.",
+ "Lead a development team of 4, plus 6 extended members on the US Managed Services team, owning career paths, yearly reviews and resource planning.",
+ "Run sales demos and customer presentations, gather requirements, train customers and own their communication, and handle network engineering and administration.",
"Set the team's coding standards on Google's published best practices, along with code review, pull request, testing and code coverage standards.",
"Brought React and AI-assisted development to the team, and was the main author of its responsible AI guidelines: factual accuracy, reputation and transparency, human oversight, data security, and consequences for misuse.",
],
@@ -288,7 +289,7 @@ export const experience: Record> = {
role: "Assistant System Administrator, then Computer Lab Help Desk",
end: "2018-06",
earlier: true,
- focus: "Maintained 4 Linux servers for the College of Computing and Informatics and automated every workstation in 3 labs and the laptop carts with Ansible.",
+ focus: "Maintained the college's 4 Linux servers and automated every workstation in 3 labs and the laptop carts with Ansible.",
bullets: [
"Maintained the college's Linux server network (4 servers serving the whole College of Computing and Informatics) with Ansible, Bash, Python and MySQL.",
"Automated deployment and maintenance of every workstation in the 3 computer labs and the mobile laptop carts with Ansible.",
@@ -299,7 +300,7 @@ export const experience: Record> = {
comcast_1: {
role: "Junior Developer (co-op), then Senior Intern",
earlier: true,
- focus: "Led a team of 10 co-ops building smart remote, call center and inventory tools, and built an NLP sentiment pipeline for leadership.",
+ focus: "Led 10 co-ops building smart remote, call center and inventory tools, and built an NLP sentiment pipeline for leadership.",
bullets: [
"Hired back after the co-op as Senior Intern to run a team of 10 co-ops building smart remote, call center and inventory tools.",
"Built an NLP system that gathered remote speech input and reviews so leadership teams could gauge customer sentiment (Java, Groovy, Grails, Elasticsearch, MySQL).",
diff --git a/tools/migrate/test/migrate.test.ts b/tools/migrate/test/migrate.test.ts
index d945d12..5d61075 100644
--- a/tools/migrate/test/migrate.test.ts
+++ b/tools/migrate/test/migrate.test.ts
@@ -50,6 +50,7 @@ describe("migration", () => {
it("takes the resume from Greg's interview answers", () => {
const implico = find("experience", "Team Lead, BTP Cloud & Mobile Apps and US Managed Services at Implico Inc.")!.data as any;
expect(implico.bullets.join(" ")).toContain("500+ active users across 50+ companies");
+ expect(implico.bullets.join(" ")).toContain("sales demos");
expect(result.notes.filter((n) => n.data.type === "experience" && (n.data as any).earlier)).toHaveLength(2);
expect(find("page", "Resume")?.data).toMatchObject({ page: "/resume", stats: expect.any(Array), projects: expect.any(Array) });
});
From a834451def7a8ee477664a4ca41dbc396b173df7 Mon Sep 17 00:00:00 2001
From: Claude
Date: Sun, 11 Oct 2026 22:25:46 +0000
Subject: [PATCH 11/16] Resume: stronger wording throughout
A review pass over the resume: the summary leads with what Greg does, the
Implico bullets lead with ownership and put Carrier Connect's scale next to
the work, and the earlier roles lead with their strongest title. No new
facts; every number is Greg's.
Co-Authored-By: Claude Opus 5.5 (1M context)
Claude-Session: https://claude.ai/code/session_012aSbo2D1qP8cMFQCykf7kx
---
tools/migrate/src/seed.ts | 32 +++++++++++++++---------------
tools/migrate/test/migrate.test.ts | 2 +-
2 files changed, 17 insertions(+), 17 deletions(-)
diff --git a/tools/migrate/src/seed.ts b/tools/migrate/src/seed.ts
index 331e9c5..0fe086b 100644
--- a/tools/migrate/src/seed.ts
+++ b/tools/migrate/src/seed.ts
@@ -275,35 +275,35 @@ export const experience: Record> = {
location: "Remote",
focus: "Leading the team behind Carrier Connect, an SAP logistics platform for third-party carriers.",
bullets: [
- "Architected Carrier Connect, an SDM for SAP tool suite that connects third-party carriers to a central purchase order system, tracks loading and delivery events, and streamlines documents.",
- "Designed several integration paths into one delivery interface (REST APIs, Excel and CSV file transfer, and the companion Driver App), and personally built the delivery interface, the file transfer interfaces and the inventory polling and delivery system.",
- "Carrier Connect runs at 99.999% uptime and handles 2,000+ orders a day for 500+ active users across 50+ companies, saving about 72 hours per delivery.",
- "Lead a development team of 4, plus 6 extended members on the US Managed Services team, owning career paths, yearly reviews and resource planning.",
- "Run sales demos and customer presentations, gather requirements, train customers and own their communication, and handle network engineering and administration.",
- "Set the team's coding standards on Google's published best practices, along with code review, pull request, testing and code coverage standards.",
- "Brought React and AI-assisted development to the team, and was the main author of its responsible AI guidelines: factual accuracy, reputation and transparency, human oversight, data security, and consequences for misuse.",
+ "Architected Carrier Connect, an SDM for SAP platform that connects third-party carriers to a central purchase order system; it handles 2,000+ orders a day for 500+ active users at 50+ companies with 99.999% uptime.",
+ "Save about 72 hours per delivery by tracking loading and delivery events and streamlining documents in one system.",
+ "Built the core delivery interface, the file transfer interfaces and the inventory polling and delivery system, and designed the REST API, Excel and CSV, and Driver App integration paths that feed it.",
+ "Lead a 4-person development team and 6 extended US Managed Services members, owning career paths, yearly reviews and resource planning.",
+ "Run the customer side: sales demos, presentations, requirement gathering, training and ongoing communication. Also handle network engineering and administration.",
+ "Established the team's engineering standards on Google's published best practices: code review, pull requests, testing and code coverage.",
+ "Introduced React and AI-assisted development, and was lead author of the team's responsible AI guidelines covering accuracy, transparency, human oversight, data security and accountability.",
],
tech: ["React Native", "React", "Python", "SAP CAP", "SAP BTP (Cloud Foundry)", "SAP Build Work Zone", "SAP Build Apps", "SAP Integration Suite", "iOS", "Android"],
},
drexel: {
- role: "Assistant System Administrator, then Computer Lab Help Desk",
+ role: "Assistant System Administrator",
end: "2018-06",
earlier: true,
- focus: "Maintained the college's 4 Linux servers and automated every workstation in 3 labs and the laptop carts with Ansible.",
+ focus: "Ran the college's 4 Linux servers and automated 3 computer labs and the laptop carts with Ansible; stayed on at the lab help desk to 2018.",
bullets: [
- "Maintained the college's Linux server network (4 servers serving the whole College of Computing and Informatics) with Ansible, Bash, Python and MySQL.",
+ "Ran the Linux server network for the whole College of Computing and Informatics (4 servers) with Ansible, Bash, Python and MySQL.",
"Automated deployment and maintenance of every workstation in the 3 computer labs and the mobile laptop carts with Ansible.",
"Protected college-wide data with Ceph storage backups.",
"Stayed on at the Computer Lab help desk through June 2018, supporting faculty, staff and students.",
],
},
comcast_1: {
- role: "Junior Developer (co-op), then Senior Intern",
+ role: "Senior Intern and Junior Developer (co-op)",
earlier: true,
- focus: "Led 10 co-ops building smart remote, call center and inventory tools, and built an NLP sentiment pipeline for leadership.",
+ focus: "Ran a team of 10 co-ops building smart remote, call center and inventory tools; built an NLP pipeline turning customer speech and reviews into sentiment for leadership.",
bullets: [
"Hired back after the co-op as Senior Intern to run a team of 10 co-ops building smart remote, call center and inventory tools.",
- "Built an NLP system that gathered remote speech input and reviews so leadership teams could gauge customer sentiment (Java, Groovy, Grails, Elasticsearch, MySQL).",
+ "Built an NLP system that turned remote speech input and reviews into customer sentiment for leadership teams (Java, Groovy, Grails, Elasticsearch, MySQL).",
"Built call center and inventory features on the internal logistics platform for techs and warehouse teams (PHP, MySQL).",
"Worked in weeklong Agile sprints.",
],
@@ -323,10 +323,10 @@ export const resumePage = {
{ value: "10", label: "People led", detail: "Development and Managed Services" },
],
projects: [
- { title: "Cosmos homelab", detail: "20+ Docker containers working with 17 AI agents on a private network." },
- { title: "GregKilmer.com", detail: "This site, run from an Obsidian vault through a rule-checked content service and MCP server." },
+ { title: "Cosmos homelab", detail: "Runs 20+ Docker containers alongside 17 AI agents, all on a private network." },
+ { title: "GregKilmer.com", detail: "This site: pages fed from an Obsidian vault through a rule-checked content service, with an MCP server for AI agents." },
],
- body: `I have a strong eye for software design and architecture, and a broad range of experience across cloud, app and game development. I stay current with the cutting edge of AI and use AI tools to speed up my workflow, but I'm skilled enough to do the work without them.`,
+ body: `Software architect and team lead who takes SAP BTP cloud and mobile systems from the first customer demo through architecture, delivery and support. Broad background across cloud, app and game development. Uses AI tools to work faster, with the fundamentals to work without them and the guidelines to use them responsibly.`,
};
/** legacy/src/components/professional/EducationHistory.astro and Certifications.astro */
diff --git a/tools/migrate/test/migrate.test.ts b/tools/migrate/test/migrate.test.ts
index 5d61075..7aa695a 100644
--- a/tools/migrate/test/migrate.test.ts
+++ b/tools/migrate/test/migrate.test.ts
@@ -49,7 +49,7 @@ describe("migration", () => {
it("takes the resume from Greg's interview answers", () => {
const implico = find("experience", "Team Lead, BTP Cloud & Mobile Apps and US Managed Services at Implico Inc.")!.data as any;
- expect(implico.bullets.join(" ")).toContain("500+ active users across 50+ companies");
+ expect(implico.bullets.join(" ")).toContain("500+ active users at 50+ companies");
expect(implico.bullets.join(" ")).toContain("sales demos");
expect(result.notes.filter((n) => n.data.type === "experience" && (n.data as any).earlier)).toHaveLength(2);
expect(find("page", "Resume")?.data).toMatchObject({ page: "/resume", stats: expect.any(Array), projects: expect.any(Array) });
From 0d0b8e4085da7b2fc38c9c700f9c01f8db3c3da9 Mon Sep 17 00:00:00 2001
From: Claude
Date: Sun, 11 Oct 2026 22:36:19 +0000
Subject: [PATCH 12/16] Resume PDF on the Greg Kilmer letterhead
Each page of the PDF is laid over the brand letterhead with pdf-lib, both
kept as vectors. The letterhead carries the name, email, website and city, so
the resume's own header becomes the title, availability, profiles and date.
Type and two earlier-role lines are tightened so it stays on one page.
Co-Authored-By: Claude Opus 5.5 (1M context)
Claude-Session: https://claude.ai/code/session_012aSbo2D1qP8cMFQCykf7kx
---
docs/site.md | 5 +-
package-lock.json | 55 +++++++
services/site/package.json | 1 +
.../letterhead/Greg-Kilmer-Letterhead.pdf | Bin 0 -> 36235 bytes
services/site/src/lib/resume-pdf.ts | 136 ++++++++++--------
tools/migrate/src/seed.ts | 8 +-
6 files changed, 140 insertions(+), 65 deletions(-)
create mode 100644 services/site/src/assets/letterhead/Greg-Kilmer-Letterhead.pdf
diff --git a/docs/site.md b/docs/site.md
index 9607c12..558a843 100644
--- a/docs/site.md
+++ b/docs/site.md
@@ -86,7 +86,10 @@ or admin token. Never set it where the public can reach the site.
## The resume PDF
`/resume.pdf` is drawn with pdfkit from the same `resumeData()` as `/resume`
-(`src/lib/resume.ts`), so the two never disagree. It reads top to bottom so
+(`src/lib/resume.ts`), so the two never disagree. Each page is laid over the
+Greg Kilmer letterhead (`src/assets/letterhead`, a copy of
+`branding/letterhead/Greg-Kilmer-Letterhead.pdf`; re-copy it when the letterhead
+changes) with pdf-lib, which keeps both as vectors. It reads top to bottom so
applicant tracking systems take it in order, aims for one page (roles marked
`earlier` print as a short list; skills share a row with education and
projects), uses the brand TTFs in
diff --git a/package-lock.json b/package-lock.json
index a0f7d2c..9a2aead 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1780,6 +1780,36 @@
"url": "https://github.com/sponsors/oxc-project"
}
},
+ "node_modules/@pdf-lib/standard-fonts": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/@pdf-lib/standard-fonts/-/standard-fonts-1.0.0.tgz",
+ "integrity": "sha512-hU30BK9IUN/su0Mn9VdlVKsWBS6GyhVfqjwl1FjZN4TxP6cCw0jP2w7V3Hf5uX7M0AZJ16vey9yE0ny7Sa59ZA==",
+ "license": "MIT",
+ "dependencies": {
+ "pako": "^1.0.6"
+ }
+ },
+ "node_modules/@pdf-lib/standard-fonts/node_modules/pako": {
+ "version": "1.0.11",
+ "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz",
+ "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==",
+ "license": "(MIT AND Zlib)"
+ },
+ "node_modules/@pdf-lib/upng": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/@pdf-lib/upng/-/upng-1.0.1.tgz",
+ "integrity": "sha512-dQK2FUMQtowVP00mtIksrlZhdFXQZPC+taih1q4CvPZ5vqdxR/LKBaFg0oAfzd1GlHZXXSPdQfzQnt+ViGvEIQ==",
+ "license": "MIT",
+ "dependencies": {
+ "pako": "^1.0.10"
+ }
+ },
+ "node_modules/@pdf-lib/upng/node_modules/pako": {
+ "version": "1.0.11",
+ "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz",
+ "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==",
+ "license": "(MIT AND Zlib)"
+ },
"node_modules/@rolldown/binding-android-arm-eabi": {
"version": "1.2.13",
"resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm-eabi/-/binding-android-arm-eabi-1.2.13.tgz",
@@ -4880,6 +4910,30 @@
"url": "https://opencollective.com/express"
}
},
+ "node_modules/pdf-lib": {
+ "version": "1.17.1",
+ "resolved": "https://registry.npmjs.org/pdf-lib/-/pdf-lib-1.17.1.tgz",
+ "integrity": "sha512-V/mpyJAoTsN4cnP31vc0wfNA1+p20evqqnap0KLoRUN0Yk/p3wN52DOEsL4oBFcLdb76hlpKPtzJIgo67j/XLw==",
+ "license": "MIT",
+ "dependencies": {
+ "@pdf-lib/standard-fonts": "^1.0.0",
+ "@pdf-lib/upng": "^1.0.1",
+ "pako": "^1.0.11",
+ "tslib": "^1.11.1"
+ }
+ },
+ "node_modules/pdf-lib/node_modules/pako": {
+ "version": "1.0.11",
+ "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz",
+ "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==",
+ "license": "(MIT AND Zlib)"
+ },
+ "node_modules/pdf-lib/node_modules/tslib": {
+ "version": "1.14.1",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
+ "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==",
+ "license": "0BSD"
+ },
"node_modules/pdfkit": {
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/pdfkit/-/pdfkit-0.20.2.tgz",
@@ -6824,6 +6878,7 @@
"@gk/ui": "*",
"astro": "7.3.5",
"marked": "18.0.14",
+ "pdf-lib": "^1.17.1",
"pdfkit": "^0.20.2"
},
"devDependencies": {
diff --git a/services/site/package.json b/services/site/package.json
index af895a9..e1f7ea5 100644
--- a/services/site/package.json
+++ b/services/site/package.json
@@ -16,6 +16,7 @@
"@gk/ui": "*",
"astro": "7.3.5",
"marked": "18.0.14",
+ "pdf-lib": "^1.17.1",
"pdfkit": "^0.20.2"
},
"devDependencies": {
diff --git a/services/site/src/assets/letterhead/Greg-Kilmer-Letterhead.pdf b/services/site/src/assets/letterhead/Greg-Kilmer-Letterhead.pdf
new file mode 100644
index 0000000000000000000000000000000000000000..7594dca34f0bc5c313f85eeac8524204397fe787
GIT binary patch
literal 36235
zcmce;1#lce(x@qBX4Z%qM$F92ELk)nila-y7Ro#=_6JJxQNJudOnK=-sW{wXx5r6&P8
zWCa3QSvk1bc^Ft(X+E|OJw*re|GkQk5CX{F{I3t}|Mtbq1OP-}5mN-PC^3W6aX9uEb5{FS7$fS|Jo+!|AWoH
zmbC#u=KstZ*MDa1Kk|(GKeNTm_K(r>a{fm~dHy4#%sj0BC?5g+4?@WMZ<`+z!pzF?
z;mZGN`hcweR@Crv1OZrdlud0wX09Jo34UnfRfJN5M#1bU-uUhDK-mA?W``@czwvrT0#@+-DPdojE5}YKpx0*i(WK7_pzJlt(?0z=Hok
z;F}aL)rY=~{<)gNfaK5KT*v{sgV^}jx>C8C*s7wTe7rB8%w|^y%-ZoI%#?qJC6e`{Hw^rGKXQHsTwr
zU+!AE;%M?xC7UIea|QR?TyJA*>euMn^;fg_nyYlqEyr4M0`^j&y|@ZQ6m`NlyzEcJ
zaWrEjbRxah4`!GiWruWD=q^Xw?Q{$tNB7|hOaKjxoNf!Z8-Ms`d
zhw!>}Tyo^D9{Sc`Q3sP;K@r)K4iwc{W}N-S1FL!$0vOCTycDKbEWlrEjT+_EqPe`6KaWQ>vzeUHGAW~crPOFZ)@6o%k7&Uxtae#rO$T`@Ku|(W%A@LC;
zgZd2PBgnP(yEqi2q`Af-L`fC6c>-EBJ;jJuSty>^KcPVpm83fRHWAa;I^pK-rr_!L
z^{_GCpP2U6iW$=AM$oSZ)&kLj+vtZ*u-NQ!u~4pjeq{MrprCiy#0eS3f*gw8G_0&5
z0EU$f3}lpn;~yziWNcik4W3Ox{&}=pPChK3$LPh
zhS?!dBb9V(O~QJ|Kh3#OxNjDUwf8$!mp+giJy={xo(=Ld#3*NPE=b>5l`x{JT?$Ta
zco&?}PG^w;jzzyn_Tk|B=e{+#4JBqo8KQ472|*z|{TL%IHx*YzRdNDBxC^grU6k#-
zhj@=*%UWO2x{Tli2g<)X%Ffum%NEzWDJd@H221Fp9t-4ctJdLdE(~w`%E08CFBWW<
z$sp;tU3tcfQ(Q`^<5}1|3o$AozzrnSZd1!bjFaB;y(OD-siI8H-}X#eJLEL&6B3ce
zfgm00_jz2@Y%_3w;Cqyxzic1B}jq)L5xPlsjbP@74ycY_E
z_B;^=ROSs_wQ>E(61JbF7J{+XJ15b{MWkJdz)_o2Gz5xOKCbt;jum@_$g*a?^6@*?
z^-+_6dBKj>-G8#A@+4T6iVP5mv6Hcw%@^Q|nU5~A5Jh^xZJL50^Kn1N8z53C4!Wz*
z9rkJ6$}0EFhol(EDy(i($>p;ufWH*SfcLQqxDH
zLU9~x0h5?u_OS_)+2-b<7gCTqw>9)py;`%0G1PEM?!HN%H@!S*FuHBfL|HugzI<+1
zo?`euG%|ok_eO&>3_5~}Y~L>NRqOM057T0(OF?o}!U_kS5>M^M&pB7Y1Rh5n+sF$}
zBjlm6dI%?@JD=zPl@W%SwRJM2D(iXp?r(-^-ul^+x+8N%1T~=_!0wQKK@kBWSJa&eJC9(pH$FFBMn)4W&&>QhMGmmJjbOImyPsAuZ6}FwwkSHqHE?ut_
zLh!6oQbC#`S&^$ukd?Bwah^BJtSJE*<5sE4oD4Uv34XTxS9`uxVGX5uZZJ`c$3?3t
zYnyOA^@Yv&g{*5Raso*EZpSxUR`O&tUmf)*Q3}5&V{v2Nwxn?mwQ4Zs9OM{LW<=^b
zxpNjlG)PqzG)dGvAv8nPyxctKrW|qhorZK27r90+4@dn63X+BBFEh!_Yg
z94>C}}$wfetrGvI*%}UHL{Y%?N-IqMW);Nd~^{
zVSA)0?yyOO4Y~V1lS}*B7s93++;pfZEK`-t!Ah0@wePD<2t;{Yet9-_9iq#(|K4b08mfQW_Y{g5l;n2n-4iNjo~m7|4G&j=OtkEO9LdEKr(@L4
zMQc30>MCSOW7d>jb5Zbl=4&|K45MzBix8P{t*^yRI|_!=r~4?E6-$!#mJ0bV#V!m;JJ
zQ!=*s{#p)s*j-!FT_ZvStD^Yy07{M}Oe)8fhFS*AmdiUDNR4|_z<|-dcbt%|eX@)c
z9&n7eS_zS4CPcJ{t%)0MVQ~?t9wGuybzD9Lui6#2#}w|8;xRb*xOO3jH?bnhGds7$
zks(i^+1}l;8n@uRFxIS%Td5G9!9~W;L)6tDhA_jzR!{v=YfNC7T?|(U3GnqR4NeJe
zX=`I(O<=pz8KyrC8)
z24@rEVnmyj*ap&UAsvqey|Jy)0Q**juV8SN%F0#`Lv<*H1;UkT|G)(`vv(;U*1hCp
zRw?^CbW3Cx@Fss@A17@bJB+I5GxCU?G;k}qfis5XBLbWOsj)yc>A5i7LB=Vj^vqCn
zU_T*=2$*1EaH2LbA4A%t!8l7Dpq(Z|Hqb>0<6gttU!@#9jG$1XVW0y`!_hB?1D^^z+jB%I(
zK#GT*a0mYuRdQkVal}TuT{J4C^0AJ0~
z-?1{-qy0|$7`AFV9Tv8WooF;r$D$dac(91c2X%`Zyc!+YcIJea8iiD83Y`yNZ+6qd2
zIY%=n!#V{Bc^<~Ga)%>ed;{a370ZzaM<0M$e03?>h8K{*Xl%ZoXZNB~i4g_4;!X&BPM0kE3xYF0%
ztYG&zj^X^6eOpT`P}L{jC`dAV9MDU!_9-!*DJ?4T5~zp3PF-fm8(|b-7rZAt_aMDU
zr*>r|j_`7=Ewn6#l-Z!VT4u0(H@`H~CZT5z2PDd$8KZnbZZA&%0?Rz>1(j=YY(|Ok
zqJlcCvhYPsU|3>fA@Sr%MWBJ}a_t3{6Z#k~jaxW56Q*sgRc^o;zzJdU%!-@Ln_Z{8
zEf##>RT{@19!W`Yt8u$*i_Sf1p;9(7*bT>4MsIMJST6eOBw_9=Tmr(4%d#HC9RSK|
zfL`6wy?Wig<}|u8J6A3mPQwCW$K$raFAJPKS@p
z_-@+%ILI1eLcg{G`(1{Yckx1_yD`hyJgtI9KOt5@H+t#OO=-hCt$;^5GuC!7%H_=@
z?wNLP0|UoA+L(%7&%jx8Gy0AqR1kkLFmA9>|0??CDk0VF!N^)h5}HoBRF
z#@lE7gh4PmM^%B^oC?XB|e5*r$%1|LE
zw%>$eZ8R$ts$4e6n2(xxap+K|$&p8(#Qy@%C`$b-MS(ioyi3mZGgPuT&bpe=wVs@7
zOEEfFPb0aQO7bXKR@Ctt?S0gpBA|}}=?0y`Wkd8ttpaIg3eg&Ud&k*nG959X0)t-U
z2nNEY@#<8F5>g-9dOUYjT+_g76^~3bSVn?Gd?hU@vpUzDoeCO370ij#%IeRdLP=Bh
z;6i5M7K~>Sy3fL|2p+Yls{Fu4G{_8`9}kA!@%=e*{)U=H@AtHmm!2
zv+}Cd5x@MhAgT@(-!l^U9--3rOIo`bY7fC@i-Gdatu?;9pabf$`RS^7FfMgKRixZ|
zwx(P}kM!)gMCHfk9>e~2Gc?#nI~R-+tgMvxaS&dr1cNw70~9buNvC_S8&{GTT1z_e
znJ_cK%`yLW-YNKnFt`w{x}VQ`=f1fIUHpJY{`_}iUAMMwQv4{DrC#D^Y4CjV&pdrg
zp*l57>EO7@_jMNX;jNq+j%0zDRRmyqC2T6-jCa$qjQxiz44$GWBirA2*sjx47BOaH
zb;9^|zTBch-s8b1c5}FAbQlLR8IE6(s#bBNN0RQ@DcQChymm^m=lfr^KfI;~`cQqJ
z(09&iWH^PLtU>Hvm>l(AO+b80b^XI~s!g$C7W>|WK3VgaYTe!{r5M^Pe2;5YaBuR=
zF^q;7TXU^9u*d|UHi~v3cA4m@x`fRz{s|{f>JE=4ZrozC6<-G|L~m~PmcTp|_Vjy4
zGan~%N#Pl1kmriQVUh
z8r_EQ8=m1hZkJCT?p~UVh-pQJXSfBb)(g8;^*T#BXa2nIjA?sDcmT7iStkIrhEOUp
zZF2oZ-#~=X!NTbcS#p^Z_6?4OffpFub|Y$pnfwv$|VP(MWlY
z&QUK|Zr3qeDvea@=n?!uD!dW;X7p=ny^_0CL{yrtFmU%2-2K7J;CGZ-f_w|alC!Yx
zvNvq!fMyQu<3kfee#go>whyVYm*n9HLP=i7N6OhLi72%-06iI|YW*O=Ehii+5Stlq9X+F7lL%EIlwz~9txLxW)5
zG}E8pSyYqcwX{%1#8^zT!@7yTNzrZ$QG;PIerAr3V$%hf0`hEOdm8jUA!Fwk9T(+4
z(6d0XB5htqT@{q;HEq;mq*`S}RkIeY(ZT>YaqO&0ui-(o6&)zQ+`xP-37BKFk6|R@
zVe@ycRY(*TbIc;&6H#0pGh51cBfHPkYx!&$zgqMsL;R*s(ht;OZfpxxdmOxS;J(ls
znwK7|P8?5?{i>_0g*6-kbu-l;o^}9nqjy4EM^DSqTg6XBr%H$ztuXXGqFA{REw?=i
zbqF`n!cXGScZN*WGV)74wsnCQN<{3D^PXf$2wyI0(6i!x<|}kLWbM7Odg82@dw0}U
zBk|6UVY!~;Q0Tk(Ox=pXcI5%_pbSXW=({pkB0usRMXL*fjH2Y~q|v$sh<4B+vWjh~
z)FZ?mE%`L42(tO^DUs~pR$+{OI2gNRBD97*MS-laZ+*IGgG3~D=1Zv7#ip*loyrax
zQH@1}(i)!ENJfRHBp9&~7K6>X6sa}s6kOMk86MB0+-p%1dh?E69|eJEm1CcomZrd5
ztJ{4gZUV=$v5{f}k6XTZs!N2lmZG$lJA0qgnHq5e$)_<7jDxAZ~k8QRl99}$cvM&`w1uV}}#dtOyJBQf}Xp>^6;
zltwy9nqPsJR|~XZ70j5iLX*v!v04*LBIQd5i
zpGu}lWXuCS*vju1*+;XuM8ON9Yx!7mY&-Rot`Kfub#Vy2UoU{X39$r`^sVwgWI%k%6(8h&W#nGK7>jo_G?PSBb0
zg*YXAvfzjV?(TwXr2`qEC@WjepUrUZ%WB6mx$2~4bjKqKe+X4+BOxg+3|iD5#HD?r
z(pa_7*K&1jj$*0_Z2(7#(VWO=k26u(J1DQ+p``EQL~NV9yiw2(Ft^OO6gfP5q7lW*
zvXeT#l5pL;6iT9`f4JmK3MWDWE+k;lwolI1y`F%rTV&L<8L>kqo}dZyV^FC7KAWF|
z{^f-J^+a%7>cn-=4$TpOC;QaRVzi-~@&&`gs#7#Bu1pt&Y}
zzQAum;ldoyzEzdr6rX>+%2&lL!x1OO*nuGlx+Hnc+R<$!nwlT(gESH;W{0)ypV=F}
zgQItUSEv1Rmi-MQO_s1|>ck3?Zw|G?CYZfrlrpn=@?2uMaQu%DdH-ZE$XA-{@mYpS
z$AaJ*m%3*q7r(vXQ0w^?eI4;u{ow{|rFi1lk&O8?O
zWROsqZ45hbx1(*H+eq1Grkb%lC3Wd?1PH`W~8eG3#Hw7)uypz2+*6%b(N*Y_GW
zT6rUu2)yAvfcbd5I{AV|$d&JgxXacaC@qRKbySE@Kgz%)K>pPASUe)!1>L5A0_#bp
zpr0y)e@{!jEYBl*n5C!8MG+ow-z_lmFhH6_%mcpC6LYb;3d?X
zRx9IVvG}JGndMjQJ$%&A9ovb)rnjD~AqR8zMEBRO(pXl$*6*m1lGxP4HV>E=Yz2-S
z`s*k>Qii+6kl})?nL#j|Y`gB_XSN*_ZzB6a#2ct+3GQvfU;MZHTrpgyCKmF8oVOk#
z$=OCX4+A23CNGaOHadGTcRZ?TFj~m*i~qFCIi8nW0@7>LW*J0+Z3-mfJQ$%-%NB{NN&-2>o%jYRMBU(mmlHj@$C$EBmyEu
zrt~&x9UkGddCybHR^V;%Un>YcBXq$BPvvkLDhQEVNJUf*O(tw)IyLqYGoZ0_L=rAF
zssz*ryK6996aTp$VxbJHUwQd8`BnRJ_jo?dT(=OQ+T1}wI!MV|Af$6$blwQNwnoTXRQ;aaBcVgQcm3o74csq|BNsc*42u0x7
zP6xNSr38|SU#o~K9zfRfi5=|wq?h}W9!Mx5kgow<3V+j9*WH@lTsP-txZU!b9Lukr
zgGkF5NB0d9H!(^9*nW4SKjO;nWtus4&9l{tO`Sb{okzBVy2;n;nGP8)_?nfS3Q$k|
zVl(Ov;--tv^Om+*X1s)@2JZ>A^8z6CqU2*?cSh++q?<9)uiO`*2G8H)D4v??`0~so
zq}>xup5xmkrL|h#NIwm5pf-&Xc-to+leQO-!Y4G23F>7s%-n7td%_@VbQay6dZLZ-
zQp4?jviv$$SJ${`_w-aE0jXyH&cizCdwBb!4DwW0=zU=RGIGPT0O^{yMtw>&9A#%@
zB-o8P;p4$Nk9o$o-pyqf0q#P%hv%Q!0v600k`+&NI3#GJ8eqfzD2^goZg704DE(+i
zp@+#RM_uTYz>BtP-a4uXd4
z_?Bdltm%lJQxHg)k-sgbsqt
z&inH-v@~zriK?RkIBdQ}dK@4X6ulv>-@%;vBZW3ICDv-Kxg8=jfp!fxPWTqWUSu
zZ3i`z3v(HL1%>}8qPvZaJWTLl?6E*z;(#uHp%bC7kozte*q1GBu
zxTk)^My?lce0DGemEi_GWR`j|S6(jE#iX`Ge#?sf8xa!aE&U=%AaEEbMU4
z$c=IEL;kt8bJB3jr~SnN*ix%se&FELQTv{#h}H|usI9L1YmB5_pYFrcj2F#!`O_Gm+rW=Z9U)=
z+7Mg$sSNr~kl75)^+}T{P`r_c!|5v1Tu)&pl#YQJ~_l6cddU#;%D{R9Xo(j&VyPO
zUE@dh0Kds;cm})TZ}kv;%YkVV4;jXrCJm01V;&~9KVjU#POEm^l?H3P&eSe%#LRb4
z=+mybLC&)(kz@Gg4Xwxj?k#D~ulbDLc3ORgy?=Uhj*V8M01W7l=dbV$%iw?X?U~9c
zeS}djnGy45oL}@Df4Jz~BQ-fF
zVRQ{_6l9E&t^2G3N{7+3d(@y*O7$RLte
zko34W4%4p*v94!*!4#C}?CM<;s~8dl1|E#-2bSd+l^{y{
zor8({z(TxYk1mkMc^;))*W-@903Fzui=5XI9F;6Mq~pFE$nAO5pD~CkVq`3h)HpT&
zmRlYjn$U%zixwT#0nx`tHm-Q3Hr59%T1jT)=jeKuux{E`THt@>#mN19&CL4)Y#Um2
z12nRNVbpYV5DCE^ZJZV7xr;ib_lF^2X|dsidSs&^xI}FwTJ?8Ium;X3vAl4X<~$>U
zP~CJDlv4#+$j1I)Sv3|npy%cfBW_DGq}kwtuV(b`YE?A$<=+6z;6_o=^~P{L$_HEtuzr(4@xYP@?cI>?|$u9
z|FYI7y6N-o@kKkVa)US1KAMnA8+aPbmpTuNKPb1CMw@Rbg`V?J5APn;Y^X|Qig+xh
z?HJZgS$y0t>R$(M|K=gdluXGY*ib&VuiqS&(Z*X$Ib!;mwB@q3RV427L^lb%>@H9$
zXL9+WP_S$Pn%ZR7tB}+Ar$xl7?W>?qASZgICWCARghcd-;5Q>SG$l$-L$+J2Fi)Y1
z4Lg>pzDh|LkremyT0j|$CxRftM(QNRRgNwNWm8O>!qNsX#wra8u0$o$gr~sIz0QKT
zqo5RX#!g+e8H>~H*T}Rk>rJg*FW7;vK4dC**QXKsuEnzEF8c5{Y&F=byCt01T>~6g
z^3D^qy&>k~&|V0m1&L+4x1kLum$qY<#hTSW4Nas
zBW)R58*kfPbH7W5)s0s1u2OHpS}68yf&RLUS6zJJRjVEsc~8OU)^U*l?24+gM+3yf
z{7C*9Tb3*sTPpwX0*?(XYs#@)zyY(v27f}@2-(RW!mB571!iU%I>iwW)1>JFEgVw2
z5_nIZ>#W^!)$nxXuCBPu83a~FU^kS`%^-4idO9dwDzj?$nHc{|4)*0uk0QeRTZi;Q7UBwx+TtmsA&m1W2YA1O4JOL{%!7pKA^2isD+@7MeD|g`Q{2Tf
zYL7#=-T<8*bExeaWkNkm@$CP-jqiq*a`!dz`#vhgWHe9?Q6NNu*tG(Yr`fup2|Ba6
zw64bKz81$FQm2+=PyQ45(a=-n=jCDKEMuwvZ>p!4N->i#VShg~HOeT2(BCZ6
zqK+1yX}aBJo`Y*GHoRY`!Ug5%{BM5nRE&`#ubGlaPg%phcBu;XeDw;rJ6Xh3dnJNQ|KGz+5GA9
zQ&nMXN_jm&bRd1gl3PVYlh1_pDEse@(aT*k~{5rT}8n;+s!!&_6PW-hpv!d
zG*0~!6n89}=sBkr6YA0!3_^>Wm2?fYUvxV#b=mASGhaxn5k*cl9|IuG!dWhl8W(5@
z5{aZF9NOYYa;teCyY@tG4j$jZt}d>mnqbm#w)Urp8c`3~`9y?}7>|i%>%^Kz&(k5_
zD{Z&gg+wiJ<;jaMNAsB!&8veH8BT`yowz6r&@qa5LL2%oq0|Q-ACU}IarK1j!nG*&
zhzmeIEMLNfqHkBHh-e4LXZDEiouuBx0wU3qqKcQ^FqNIdcLjIi1$beZVmF9ON-@aV
zEAR@uV4Fk)H#eOlI*dc7#3b@e*r<(z&a*va#)xN+99u&icC=OVouH32xWU+E-GAS#
z)aK`LOLoZ;JMump^Zh=d;CZ~p#(O^#oYu%K%P`iR;=art$mP|8QhIAMF@{mR@4)E#
z%+7ik#B-IjW+<+Vmi@9qm@N=f{B`1bs0dGXC^=2o66VNakM_c)4cq|GCohmnnGf}m
z;iKbU@a-NXxblXd#*9;CQ$^6Wji`m~4}L~A8IZZ8SWLH~=k?=~_e;evF0Fv^MoIV%
zww;{!$-5+jKj!MXLc1hLRso5F!%k9CQa=ukpWbe{2`O|fHeLyb$
z^{#`asA+c&=|HSSsq_9ogk^x{Tgr#{4A^C9*k}U&tN*SYH@HhC-#a?*R>G(TfOq=%|l)`*&TbnWd-d-#31McyYODKQcB^A>y?i
z2#3Mw8_u+YE`b|OF`+BiFM+VCTOxL1@%`cPhchLV%-5%gAd5A5fFEqUTqzLEfn|rU
z;ZB6?R1f|>%xP*Gp*?Xw(BuH&X257+WyZ;}zI=|+lK;pOvgI+ZPhB3~3>wucf=hg$
zne7bvJZUSKY4BGbHcXdLm-!?%YyJRlj{BR``~r@pEkhGI+IWWSh~76H=qR3rW>sO{
z`a*)#QaI^6%oj&0QkA8q-eOAlw!3F2jX}tsyC;_(=7qJ~nNsCI7((RFBvoRqF<{Ip@X4Y%^RG`_n=vP0R`
zKYCW^ZRKizLYkV(Z3?$z+?e@j?pV;ZLiqWt2i*yx-98GZ6z|W2&I492pWV`k57*Z{
zk|6e{n4dP-+C7)OKc2zUD6hJqdrrlnS_Cm~td^w;fC@e>XBX1oq-q
zd7s=$cCVGbZhcO`{aL=c&CE>laL0&i{lZs3zisq#b)^+z&-UbId+xdO+n|%V?%+Yv
zxwB*1Uh2iY3fIS9;6k#ARNJrq>W6kG?F%Fr^Y?c^7z5`;FoNM?Sc>$*#r7}{QMz*p
z4YP<#2^-2yO9Kfkg(2M+LM6k;Bo@z>*pA{-lDd+hqVez=V4)fFBG37XDu_NMo3mChGt`g^I4(9*W_4%#
z;j!TUrH!VGe{lm7?yT(+n>82eI*L)5+FLTeil*hu9CnqSruON}U6hS7xvymB_IO4u
zk5?Prb&BCSy4&8m$7nm|bxO_oV&Qx3yXR5DN!^4=5L{+uH3F}%M0yoy=kfG-{Y|a6
z<9+e8!z=oQ_m|O(`{a76%YeVM<1nvFv7VHG@TGPh!`lK2{D9h=zP#wX}!L3rp!rdA36sr$#c}ynd&UMUthl~W2C7(UsD%y
zX97yU-EyDq<*mOdw<8%Z-XVy;=O#S9ub#BhBo@PXzkQi3-uFlhJpRL)_=sz_GqG~l
zTIT$Mg7F=dsl*H(%)%_iYD;@O;Bv?RZf;$`5E#UYiOrsjI+BkVWm
znbY^%xQ<7Q8*oV>!1Hr?r=@g21|rAtbnS9Ur}MKzE=IR_#ww+(BjO8~PUh;%X5S~D
zUwJL39?GEFFTr}jw1q?ji_M>WG9B#i$J7Dj^uovx2;XtkR~yf_aZ}!v&mLAAN9P{{
z^|OzEqAO#^b~|XjY_Y$tv%-5_x&LlAbfqE
zy_2_g-MSC%q6hw)At4gzZ`jefhyU)vsca#pOV$VUhC+F$Q?DeO6ytcz{j?=zjt
zZXw5*^B%L9FW+Iv-$v%&AsxM5z5WtA|AH0%#qI$A8$2s$>8+>RR$2uca60!G4y{%to%S(|90M8dDPS7qyQ0S5kUaA?a7
z_Ws_lt_4#80T1rgr2bEvSGPad{2%7YX@+16ykZ{Tu2y$j-vN0zQ5-UYu<1!|&g#eg_tC1=cx(c|B2Y
zSzMHnGP97fd_^91gp%21Ko}ucQF#9(8Xa^v=4a1V;l~&s-0gxWl?78v}Dn^5x
zX)?^1J!ra{Pwaa(i$if^o%f_HBAZ+|L68!5o8zw=;B*NPn<*vSbr^fm42P|~mRRE1
zFo!nQ!4@T<%C*|*2vD*;sM%MbloB*OTgD$sxXA=;&+RC5+})7E(+kvMLxS5anhrJ@
zaE=Vpd0-uWo!*gwCjN78CTS_MS{xxa4a4LR(^!Ji+euGps97*}!l|7YY@l;na-V`)!iNJxgUxFe8j8B;6vuK+Zx>c5
zekP@BPK1n~-%aAAW(sLLRkLc-mBPMsC&F?Z3cQqDtV!CKZWYjbMUsyQzRxQLrepMk
z=R+>5=#oI3G?9U&0mG`Z{`itphik-i?;1-;b$(y@oaR$du;wjnXddOWs1l|&&Q&Us
zp;;1lEpqpJG*-%*Rvcb^(zD$vn3VvOphhQ!WEN@Ka{#0i9WepV9{*LH%1BOJi(6JI
zK5qts=Q@%i>0BIx;4!I|GUChEWRJs3`uJEtYtl@k3p3V)W>xH(dxEP3nNmP1#56-g<%4>4p)_UATpT$*rIrlnheAcBa4B8s6%mB*7i62G;ZVBpu#n@(
z*V~|3M+oBFR;g2m(25$+-H3FVp}9D`D)!BfAf#%b@;gcv#6{6NIdJOKlz}D6f>^VlNTU^Fr43h
zEiZ;{m>mb=D~5L!5--ZWk+}AZqHK+RC8@d5vu>r*K&95J&vvebouX*|>QH)U3a+ga
zPmv?rxTsVqp86Gpn>8Q91E8)ka`9@K{yse9EIP<3km{URz49Ov44`4c{}k9W4HGS<
zo9tNDM#w!T3pd%$t;K%9;jp^`zohTlnuC^1(2N$`5MAjcuEazt0Un*5)ccHQnadIR
zgoHQ9)P;@5%kBIX4TPj!?tB|Ii*F&}Vq5g)OAXK(BT&~OD7O5PJxeXdQQb6GIo88k
zhhR!FlOiG`roiWQstAya;hZBBQBolv=cE3T8aRyS3$cpT7F?-4@qibEhj`7iRX%2B
zQssnoJ3q3{9MG0XVV+0q2E8!@{P_JBqhuo%9L5?pw1;Y%#;K$(uBT=CLuMnpKaZ#z
zXi;~Rzoy#Qpx%X~0NP{%YJkX5*}%=o&pnWBUl-Ww{zxw{|}nF)e$aW6>n(p2pMX-Fl=*>W_5WGlJ$9ZA7#zmstKNk*!?n
zhyw7@^N|PWjU^eYV;t`ySr0o
zjIZ$$;Wgt|namiY@b8;a
zylN`b)|Vdv!I8AmB{~B3V)687ns-W))_8YC;!wA~4*1#A^QY9t#VR!iI2U&adYJCI
zv?vwBh=f`FG4WL5KL+$X>7dMU#I$Rj#2hv92$aYd$p+2v1qr*BeCw+jTy(qi4RQpR
zVHo23c-pBqvgp)J(Jw59N`5}BuFB>@hu^*J2o(>c`fsBcccRY+348t~VF
zkALxtZad`6=?&91DctFhy2qcepDFHL!p-vWm((m=4LMLtX&e)pk!Y@bX64R>V`ZY*
z&$@w+6jJ}P8ZzhygWi%bbJ~|H0<~&+(9z58QWg^L7ub4?x0ad3a-PudJJ6;NqF%QD
zlQqA(F_k{QEOOy|+8Wk@m3C@ot#Ccq=nt_-6mst}lFkb}1=JDq89N955WX07*ZV
z;azF=o|}(ar43A{SE$^QLGUWlKj(dJ0!`R?D`b2-dL*Z>Jp20AA2UIQf7(Jnk0Q4)
z-jO@VGAuMC1bb(4@}nRR3!=5<1@|TkTKyTTa-wReg3&1Kw4gWw=xGq3cISAAeQABO(tjE(IhL3H>hZ~xEySLK6Y{{I+u<^}wv
zv;Tt4A6fc867>(X8R5Uc>%S1Q&IgwL4>J2hNe9RU_}KozKYw7$4)(5pHKc&R5A^w;
zW~6{0N$KB#d}vDn*#RHhzuFuC;6DiKei^99fdCF5
z$H$8R{&(p%kc*q^-}eTd*{52ctn~M>-*e87@<9BljH^COys1=vbDD)Z4X!0VDYNc2
z^8sqyQKt*kx^kHdPMRa#?{zuUcBBoWkor#fpn#>YF}lxJbpo+v2(45q{#33K-N>>KOwj30&&&wZByUHf
zzpmeC^7_F%L(&HpZvsaH3`ZT=B~IPe$7x9Yc(!WycrWt!@!!C~s%nyKIgdeon}vD(
zESS!~ShUhb7A=QsxSvkV0ffelhU*9lDZ`5j{V!zyS-ZnN71#|V=k(u5S2~iU7WIML
zNxei{JW)hOzZsd$NXn9W*|b<=BpCs4*zlB!Yjb?SSac*au$)#U0&x`fpRv3OCSLP4
zWtJBe*?u~G7;9ZuU!P*zWUq1ipsohVelqxDjp+1I?XcQC-EXmJB@Gnn`k}#&x@gsh
zVhco|xEjMWmJWanBp_yV`ULn`WBrj#i2U%nt&xn8h?PNnR%;0lq!-JY5Btjs)d-zR
zwKK{;GNA7Uk0&19g#R)wQ5h;ekgTU~8pn0D&_Ym0_g>zhAqLh2Crkfxkc1x=2&s6M|(&J1zb~%la
zXjRR5*&RJK#R~7aUUmfKaP_I1JqC_r+SkKP@vG!FxuU1`@1w>&oQFuyu&}S2)z`+3
zFN$>7466j+?xkhWumXtV8pP`J{*U6`GOCVc?G|20Ai*7iCs=|mSja+xOGuCq+}(q_
zTL^9;5ZomR?(QDk-QC>@+$MXUZ8_(>@BMzn#&suor2QKSsb@mN~ZeOoPZ;^oD*d3@XqW&4({s?q#2%
zIMd=E$RYCJyB12x$WbKJk
zN+FwX6L85>NLd7-rED_&p_k9rTuV!77{oU-;_#|eUk5E((!MLPsDCP#C4lMiW8<;V
z$l%7a(Mt-s*xnGDA9xlQ6jJ3nxgkt4%LaS~4_F0Z#c-b6>VPQtnZ!TRA6a^fV2|NM
zBo;qKpWcyCXQsxuDux~1g$==Q4iNrYcUlO^`bLWPw#0-^{n?9`k?SvBZ}>Z;%ZqJCq$`ay(%CDg?xp_IwNbcjUcz(gY}w?
zZh@1yEr_6nQ;R1LA!eg&a_1wZ3!DTRcv(Q^lTkC4{HHHjuO`#!bGF+Bi+r6#Wrj2#
zQWU_Xf9fQ2rQl6KZ4b@PwErv}!0woS_`|2`vu#-0IAJ)vLyuT*$ZeloB00wCX7N;e
z?%36=(%Tz3?q(!O5+9NPh+izl6PfsED-K^Csre_#>f%&YWt^|1o?25a^?8~uQ0fYs
zJFv+uYE~7#Jp4BX$zK$Jewz~Sr+i_38(nK-OF$0jHxocg+YFFu5%{^$2^pI^>%U{T
zpWf{Np%y^V#L(tmV@JS77ns1wIXM9E@BRe$I}8vG7C;xpR^Lnkuo(bR7$I%Tm-@zr
zM!-)TU|^~j2j*X6T>*2$cl!4;|9$1ZDXstj8uaJkz@!X7X~0>vO){wLOZo>#_+vwjH_)m@cLySg9T=>4+{jC8S0_@3Ih=~hJ
zYFh$&GkQP^jcoy$7a&{K$p&boxVeGFy}9&O-_RHUNS(-^2>{YY`hX6Sw6&hTwXwM&
z`Lll=24u?GSz5l+H@m-406vGrqf{_i0=TE@z8%+Pra$#YI
z{?%|%AM7=0CC30Q)JQXT%(kQz#oOZxrm(?VZ=g$_!(b(52-UT}^2;(r`f~f9i9yr`Fj=`uZ6G_qslY
zp}4fT%MRgbVTZ;?$B!?oDdlAhF{6%Cg4bmd+T71R3mIzroENQb5fhS)6N{k=nfRP1
z7nY#PFW$hOIeYW{I&gkk!+*0uf9r66^ql)H1N7+ssi%pU16n}$9pjJw#PIT8<3~>$
z=*h1di_+CDUoaLw8{g;RQ7j3M68LKQ{*85TR-<~-uD+|Y8ANsBe
zvZb1$({0~mP0nb`Q9jwn#Hh0MtF`>qo&4RG(Nx{-8r%A>kA%YHZ3}iT!EzV1CfDdH(4!S2!M+0Wy+VI$WZSL_`~>M-BmFlfosk2yIsG;=F>O=y9bAJs*m;
z*;7|OgFZ3S66C(VJGbh)<66J#_qcW0@AC#J1wFU`yXfc{+FVWVXMry9L9r1hsVC>U
zOnZo(WH1}${mLb$cPdU;Dg7`?g-pFAZ$|h~2h2fXiLr@1=l$^oVQ{05w08HURcuN?
zf($lnmrq}aDPND3p$gKT+Lk=o$`S7FLc-+sk#)4
zBa`8Ur_2KFc1}K?CXCshN1iz*#<-2N8KWo}B|}z+@rU(Htms?ayUs~9gjw|iq4xCk
z^y2e*8F%}VR(pfgSS?MM@=h$*(`PQgx}3{{=oxy
z-j~nZB_^toF&fXK(#Rh2)bbn$2`JDKH`h88&)CQo%JW8jx&
zF%J-#y09l>vltt^=Td*2%~!{iO3IBi2Db^%Q>BKjYVFZeJU7%a-8?bW0!!1PK2x(a
zzb`O+m!&cO9NVZv7FLRu3Tx^whaNi*_z46zwXO$=h;zQ@B^H^=gy%zR3=v{>3@eqP
z+hxto^$Mk&vVu=;hdAPCcJy}|U{UyW70FrM?YX79ROqMgP6rN_D=}A#F^Q_4CW|?2
zZyI&iwHcKt1kW{;5zJ=drE`xG4U*&%NQwpx3hOcxTJ(tSPrnT|pZ^%j&W4k|wWjlF
zRyJ6a13x{)u5BawN~bh6pCDvsUX7>9*hBqr_EzBXwnZ)x>iolye2CCm
z91&akEzx(QkgqYOQD3azaOUx(^5o3O(T>f1A;~k(CCoEI3I0e#kjLgQ>RuSF5DOFe
zppy^bVNY31)BYk+<)b^v@OAz)sQ<7dY_ewq%VXkGs!gMTj5QU)OvFv&D|ol)xeVN7
z<+=J6?pkP~~78%6}f}O>b47q6Upx
z3V(PO1%hgvOu?%j8fwI+;gKCr@v<&!7v$xBJX|J4aoz=$>-bGm^P`qK@1A}UNF1N@
zCX{p96_tY_Xh-R5YIZu&CdKR1A!HAI;zyrlg}Dy?K%B)K@VsZ4z`&C9I7SE!-x3CI
zEuvQP3Z^TzrSy~DL=edY)uDnZnWzK?%O6h(5%B7eH_3C6%w+_tdL^w>~sww;5-u*&NYM^{q&z
zA6l(oQTHPUvEj{h&GaAz=l#QL!hNDCK6CW4eUI1IZ(LklxF6c%dWhb6yFaEKD6vB{
zn|1RnXB{6^Y}J_>Q>nHRqA)X_8&)8D7%q*bQ8qnYqKN1>SF`;_)_%WU?e1*%N0pmP
ziKL%>9mj-;I;Zsv^YUVMg%Zq^5i3d6#>DsIrbIs*iwnT@%nI};5
zY19iRsqji>Z7fX;HH1afrKhJYNx@yjU9ZF-SG>=HHn1pU$gY
zx%5{n)tE^0&0w9`HemLnG0l@@2fgke_IW*fvM_X
zZN+bF#B7+-0%}sVQusv(@#d=7c?>hvAJxo;DP6Z-S?ov2&P!4^L>BG#IDE7%TD}=f
zUq9Pb)41wiTlDf;G`QRYIVqx@uhxwUrn$VHsXF%Z{&3<8CObZ%0@7yzD*-
zG8A6{O@36rB~({)=636UTS4Fl^Qxf6KDt1^u3RIDw|9JrLY_&|-4Jj>m^ZuK+x(oR
z8T{!agONn_ij_aG0km;FQ&!S^Dr^#24pvrdxY?~>w4k|^`c*q9-@{E+SC@Wlq7s$=+Z}q+Ri|G;{d$)}
z!}fP>{G^M-4l<{pUY@+dp7+@dF-rbSldYn
zB*Rcplc4T98~h&g5iNC+gKy;Zy;pK_s!Q|L$e|P#8PTJsL++;9zgkgn
zP>YwXZtQd3PVue!n}^bwVztyrU6&`t6x-)Ry7kAwry3p;19DQi{^=&PkY2&VON1wRU%P~p(jWy9rb3rERqQP0
zID!j1DpQVeSW{7(tjFgcO@&0gys{~ykiL-zqG6MugnMZ(8xwvyiV#F{6|lw?4|l8K
zCpXR?fIKgXM;giWo$=DqNilqzQivSbEgqGDJK>x1TGcW$o@bMMNARU(5)9SC$4x;}
z9sRfRw{l?l*l($(MwHUqQjMPq
zv(y(?#Y@mXk3}yIAeuxioWNY>uH^`(FNHNEhj(zoB~Dg04=e5I^88SwPLp^zN&90k
zC2ifD(Yv|Wr9%h=*|BAM{Y~O#yWV#)8W>gi8?14>vovryN_{i#In2h&Xe~*tG>6p6
zV$~tZ8DXUIg(7x=x2}K88FC}6wj9`pTNie1dkqu4h#d<(-=rVeJ_*b%e9yXdG?Kj@
zkQNCC4X0s!(~?uFMpH1xr3gi|8d!yg&lcQX$>m*g&IZz7OJC5()KWVOZi
z%Eq*eY+K^`DnPlM>T1?RSe7I#
z)6OA2ht)ZzMK(Gyz{n)*xo_m7JU`ACac#vsY^}J7R>4L~B(7>386BwE)H(WPm=wvc
zIPpsI9+vd_>rSyO&33_yQ-@1jSjf{9KxK;7?XFR>oxQZ5rb|}B7jf@rNY05vTa*ph
zB)9Qfy0{}8vNgB6tiJ^ff49~gFH9}Y-ONHk(4Tdbe^UI)-zu3gggezOll0JeCfg=?
z@1SeCMwV_?!^)sCZQs(ymyS8Y>BD!j(1Q{KSCQ5wWb7X8CwW5H_+MR>5+3i3qaQz*
z#%g;`ZXI@g>Rzt}(S4d0rxtK(knpyh3R^88FSxb8!vgY^S!(FR_pgjIi^x9oy2TP7
ztXTBEn7p^T3r{VEM89N|u}4(*KzgJ|_LB
zds4e~!5vafGQ-~NM{)c-Ufp0C|-IWB6Mo}yh>L2B!ar6VM?5N~r(Zuq;^
zw{9g`W`&d(4E*Uq`gXFO)+$YO?rq}oapt5ZpC^`727KWGrnJaK8q}Ys0*$^s6inWT
zz5e(}vG6Uv_e?=IMSFkSF}lJ=#j~TBfSP6j<0vZ>BG*F7w@=U;`sVT0OY0MLm7vle
zI#4lhJads|3eNR`jzMH1Yy^=S{!qd5wn6%f=qu#4Z-ZC%%Jyx#G7P;HQ;7xKM*O5~
zM_)#q-mI@Lt9`XNpTKQSR28>mWTf8Rx>&JQM|7UMSu(A(
zavYSxcge;FL0X^HDlUtF_ZcD<;ZW%^A>Ja=!?jt##gG-!(WQ+@#$0N}u5`gZWguSk
zrUG#;NF((k0iSaeE&|froimqza;DDMoZaI%Hj3nko4~9pXYTT{-V9Kfke8)<+OsBS
zSl)K=a;8XqKEGOK*CEHJ^VLvJ>gVg%s3TrN+=8tSg0XiWW=~-j4-F+{kqhLh&(M~g
z@lfF=j0EW5sK_o)#OWFq@A)_nEEt9D;1`I(R1HK~n=u6pePX}CZ2R;ad-?OS242nh
z>qFdI{URE=R}m;kNhQrEuWdfh&iFe4`3vf02@Bgq^c#891)q3iLUfwONx!c%zGe|WmBBtxp7QKngZ0(QuACE~
z_dCAX$IY&Y{8p!~Vk0(s-cw_$;>0bh%1ljqG8HH?*d0mu!t5DlLsyOteXEiw>cQMdbMv@NHm^*YFrT|N+gqUp!gGLs{lSiMv(QZQveUS(Xhcx!!{9G1Zn)$H4u
zzD|bS%{c){qA(i~{0L_O9&zZP=E7|ZZJ+!SG|1Vgp^C_Yvq9A{!@
zaS3`zZ62oJ*|#F6ElU#I!M@E2dc@_S*g<^HTec5b#fJ&dTX~)@Hbq)>Sgp41n0|qP
zo1&7kOTZO6OL(40x2!Qn8pV*!
zY6%nRK%$UA%(+QTs;D#c8Fq@3)#2%@9-?W^)nLmIl@8U+lR9j4P)Q8rb#
z?I(9kaFfT=i`*pcy2l4UPG?Wu0`BV6=Of0GeJrR4Eb1;zUm~_POx_wUARbh_HvX=FF!2tRbmVUKSIp?A
zhu|-m(Vy_-Z*b*rV5{J7V5{Uk{G)Get!?|C0atc_kNg90Wnly8&;L(=D=V0poPm`c
zAa~jCSzR#Xo~*rxpMFPNf2Vf;z!ZP+<@^l*yZ`@ZlK>p@H+J|>9PB^0><{eh|268$
z3?QdKbIBnbK!S|}z*yOU1T*9}bQQ|N4xp<54$IB~;H}IM;5P{KeTtp)9;5{zS!Tdj
zQb@KV0wGsrX1=cj
z!V3J%&I;7e!g7BpEbN^3Nw%L!j^BY@HYfns{qYd~mmZ)H=D!2GL%o4MU;x;4NqX9`
zo7$>m#^66-@&Nt1rP>V?A8f7_@M_Vtu{ImS=S9T{aO6CB5KRSMVe8;g}S1t
z7B^?d2R8^lMH205M7Rozsv(KGY45(-*CP@TO-s}FbK)n1V_Xg(9-Nz-2t?F#bqE$a
zjhgxTHQH%z&~rG7iAD+q95tbS;mfJ23Uhcy@VjZF!lw65E)3HRM4BaVr0G<4>C~tW
zwG%vRLh8*BqsATNW|(@pg69v-;{m^d{pi$
znMp5O;l$7kEISSP2$P`)@)|GMzGWpCm%e-z__`=5_?$AA2K(FRJ@FAkXZ}o6yd5g;
zVdY@XlEC^i*T%(MS*7;U+~X>^(*x3Xjfu-Mp96@H`{$b_%>z`DH0d;wB!3;&Kb;|d
z8P@mk{_l$Rzd*tNc?kS(;ozSh8o>Dc3mp8@o8m8B|G{~Ijhz+p
zH#nFFS&n$z?!4~DQKUU4{lWwL588OjHOSujmyep7i^+Mhx~1b_-0YR>vB*(6y(jXE
zZe*#+=p;>OI0JUM$l8pDj>n{|#{#A%#MQDTRnJS!ciMZc;?6q7Lv5s_hE^yQN>v*;
z-~)>$ZeIOMLAWwIA(4tpUbQ6C6
zjV?CG83S}CVn}h^%Qx|Z1a!jya%6N~#xi^kmVjGu16dxo-mL7PBkz$R6eht<+(vYD
zF7berG&ZjpKru9i3e@JYmHXEq>PO^t1|~c#3TIIsPA9ldvYX#7VO15$(OR-k3kuTAah0*?!Aa>7Z?;X
z5Y**%W$En^%okYx%u)|`ZP)r63$iVk0XJBX8yZH69#%OV>`Q#DN98^!#-2^ExCd6C
z+-Z024Hz#p0!luC^(j~k&%J4JIL{)cqMPXk4uvguTQ;G%mrmrUS%0U-ON3G5Exb?naC@)%zB~@=U}tzD;3x5$y+@HTo~9FD_|1l
z;xrr31uSR-1S8sJdgVoW_m~S20hFut2vj{v!-XVo)KBeVOGG5}f`OO^jhN@l)E6>A
zX=~5DbT?>F*Al-UlQ)_s2(BK)MgNAg>$OT%!n;=*S+**M_A8BO6Ge+jC5!I9Ne{TS
zYDCNO&&_?_yfI~o=d4t9D&iBG=qrw>8E$JiPDK*xosm8fZHu!?MdDkY(K4qLPTXZt
zN+p(`UdE%+&2TXNv4y+O5M8-n^+U0KP=Xz=O{b*>->2u>tGmvA^~xR+9yzz$SNJDc
zyXzIG`Z(F>v4J5%8$2jxkq#Ez6{y+WqOp)PG6Kxb=|##JIO!H-Ek~GvWl!P(l#T@j
zM-YlJ>2Y|Uk6#ZA?U{!n=Y#23*D9fhd%X`^;;
z9yl1;z4#=kltZ+@Wi-Ae`pg%rEEL4jlr@^CxF#SH{%BLBj$jBTg-oiMURVo_OZ(AN
zFx)W4Xqp>Vme!-Q7V#J#Dy8>UN}dn=8?hWy$lA5Bq?%BKwP4VS>qTM=YKcdLICl&x
zM~%#fH|SrOAJUFZUFd`7+Z5%Vr>P|NYN_g+2Khnb@T1}2Wt?C^PT^Q-x|ASeDC%-g
ziIBu}AcMX}Kg*C5h`zAa5LT)5NFk!)h`WZ&p$#Mm(x9#=v;o(%gP~DMf-i9
z<*M26Y@ZMN%&87{k416c*5=r@z%;w2SrwH44`eIq3N!TNv$BkS$?6(Gh+r&m&UVFJ
zTcl=K+BlDkjX}TlyWLHS{oPHc{fO+t2bZ^9UDx^^>t;SeIf)}ga?||~vo_OBRaJx2
zwEp*dp<`pAvm{8f$})Sr^%eT%{mYy2ryNNKJKw75<_3%M?V~E1cQZ^lNcQcbs<&oH
zIZ?H*c$Gkn(gPJ(83K5Xn=C6L)QO{}t1=AE!hXCQpDz0H*J}pwvF}uT5$>D`+@4!C
zMP9JjLAcZ+W6~|8xWtI!;qW#)XM7oL@iZ%4{mA6@7hDb>bb8<9)KZM7xQz(an`D!Z
zFp{U&5~AdPpI^UR&o(s7PF_Fsy~2K>PDA*fvQ`cFN|9qsJ-#rg4qwx8l?MSc(ZR3K
z2RAY0cq{xgGijguE_=7(mF9-yW@8Cw|042d!$Uu1;}#CJdOs7{_}cgGA4-~DQGl1|
z(uZCLXBEd0T0}97o6uTuxs9^1X;|dH(dp?e%4_Sux1Cj=Yay<%sW)C)u{a~9Ws0n6
zxH0NaI$LWO;*3t%7z(w3Ka0Y-dewT^)vAc9FVKJLwe7~n{erD*G-^~b#mDFfOQRiM
zn|q>Bd|WXy<5H;9IWMbrW~wawIrG~?_EL<5U-PBT
zob`o{j|Q5CCB0VP?7xbK(UhpYyEwcisueq9CpmLQ=h%I*l({XNRQn!c$&RB=B_Bd7
z|2;0s1$|~@&VAUcF}TGLU48^rQ4xJy{{0SbNAQzM>lBBoyy|M(>|vM6ubYMA$_iVj
zUZK6DTK1gf#X0XH1)O*ZYZT;q>9^4O$}bF6W0>vAMhd&LPSAv=Us)J&4%l8T3|QM7
zi`o$G@wpQ-sMkxCd?Plrcmnx+ewf7;LDXxy+fenw@>_{%5iXUYGXBlXRH?_NF5g6b$-Bd?n8}+cAHF|
z@M@aH_M7%HN2|BE;!HSnoMtZV%oU|?W|edAZ)%_+e6E>ZS5;k)C;HMrv?W$M7m;lr
zHF}^gCTc-O>Q27+!e#rI;q~ln(Cr7`+gsoEweLcwr-*cP=wst#-{9e8Kr>!U-jcH(
z`f;{7T6qeqD(NKCNNdhF5mTXFd(|-L^)Z$G8uM>C$08jGcL@oOP}rz7y!EgUKntc9
zX@ddDa3EP{aqIVqA?ct4rv#DyFgd|eI79W@Hm)B_2v;KI>@uKPS!jF`41V66ntXaK
z1I~1cj;iQwK;I8YbaSojk6YdP&^b4@y8O@qUd>M55?-Y@^Ti$>9~#b05(}O+togp{
zMXpkHN@ZB4WztJ@DZ>T_<&gIo9J=-ziJrco+uiNHM$d)!*`L&8a;kevGMzl-ZmhcZ
zq7ds{y|dVm+Q9qIvB3;BUQJuQY^%zeiO5rHDmOL)4EUw7Lk&*yj8M1mHP~vqsapg-
zx4VIu%BL*c5Duqw3GrufM==kw%)CG|c6aK?6(no*TVGOFN_p
zxXV-?j;nnieQ37R?QODM;+)6IQLMhYY$Kzh_$9qreeqf6Y~%$q5}{z?Ms`5$5p9iw
z7`wB5KPkT@tJ9y4Vr5(m5HaBF`}{k5EXy@i;$^6D+h$eYv1WrUQ&C_
z@$K$g_hHs@Q&};_n56q{7XLytlY|tHt4&I7&eXJ?l*RM9J{gSxsKb6|rV+Mv}k|u1IbJBTLn=i#jZzM^2dFRn`6T(8>3^GSOwo(V1qN1~^HSQ+d
z0=ONb%rkAn8`id6Mt50K%_mG@5}O6cQvhF4GRMTzuq|+(80Ik7bYA+C)R2a|&KeBNhuz
z0~3D4NlGcy8lU>{u@M`mh_1HLrkGfoSLS8agSq8UZKIG!8}BrG!urrqURxn!?CqqS
zJQRgum`CYmZoEszS@*nnj4Mk^HH08WG!RmKoheordpN!9PM(vi*#9t>B!ohXHENTY
zuh+*acZmKQISuYf3>jE>*}1Hvp*_#fXeM_^L4Gfhu;Ou=74ohV3K+JTOWq$w{jo=N
zlf_x~CBzD^yaR*kh0Z~ILm+4fgC{PaIQ&I3~0=RW=u^r${Ce
zth9f6c8LD+6UV!CeQu_1Gg6KaMk%cJU2o#iz+
z;+;j%+%EBvF}UI*N~&F5_T#uwQi@&XM|tp*as`OK|9>+W`-`999Py{>)dK68VN7OK%gSV%R26L5Uh{^dm;;bN0Du;
zsi_rI3hxBLn+T%`#sh&^W|tYka9MA`_e2!8ylMEHuyC+HDq^&`3@nx2vUI#>*5(hq
z{PF62km!z@TsfWfC#TxpWl>TN$C(dwlj5(XV)mSvxdaLc*0osagTKL7b8`$+b6`_H
z#GiYKUHt`mCcN|2_E8hFw}{G9U19u<&yMC23PK2i^cJ<$*i((In2DX#iu5jHDlbhj
z6JOe<2~roBOXYD)8$ZIXQ_62g8ss)!5$EAFr-2lkCQ=D1h;jOsJVb@9D{3MseIFpO
zL!LAEP*O^i(502!K4SD4O~{l}>_+0Qwm!oWk9nFy{_CeU`8ioQP;UgPB&~=zAq62d
zrL|`5&=lT)^<84-6LB66%+w~4Ve^+|G_3S8ITYVLTd`A!I;A9husARu<2!Oo8yDv~
zCvSc65aA{We>j0F^5)^k0F8I+)K7_-p#g)F;$#Rku?~+SteSnl@-VXWwU^rjZjo@s
zzj$_Ft(Tw`art(CWr;v^li}M#%uwP-HU0vnw3A4R1uw&zFp)9pAkN?&EXTg)GMjf_
zDeS$nr8Y7coWeTtSh4~>MGy$vb9Uv@*|$<^q!w;xyw+zgQRamGf%Pb_C}u(*qYIVM
z`*`c&iR`k>U7>KIg`Tn(L2Dma?B)lSMRq!;HzJjp)5L`o*GPT2))qKHv15qQjp4}j
z%HYe@H^Gpd?|O=HFMFQG+F-hAB#xP-t*JfoR>qfCAxYQW`i8I}OdB}t9P>mwFBQKd
zR&r}QJbg2g=GLD-=tYt%4<)9DQi@3QS4Rl8uvp<&TsxgDMvC2;@3?W4r{7Z)X&~AZ
zoQdkxS&&^>NfxzT$6lAimF2i~M^vMpH+1`B_L=4T?Ynxf>_fh$2df~gS3c{V8RWH}
zB8YI*Sdg@5d{x*fLP7l4^0~}td5wI5{ntq^Q7RMslth9tS}i!(W?o?nK#*~VLTSQp
z%(R(;AYA!NdKhaeP#i!Rv(8M83ISIL-J2=hi#?f#(0dg+`c{Xgu>C1=Hb~8}SJ{y>SznVJPipx@;eV!{Vs(I3U6Qesnfq;dR4FOKSQO74uFL%`=?v@Ph0FGPDJNF%RxT+dy
zMOsWr`5v=HmH~5d`FG`Q=~pA=Le8Fg@Ee!D_3=YSpikI7j@^+eecBxXsG-`F)wmpQ
zkH79qnm%95Fg4qWo2Q2NKf*&}
z6HLEirZk#=j3suGHeyup`4Lh_Bp>6aQ90i>G!+Eg&=RFbNOWrK0+`k`HU(oe;J8N2
zblK%JyqV3nI8k9H$pKM&Fli1$OvNW$E?hspt6n!=ec3*(=HyBue}X34%p&cE>6hM&
zByGu0PVAtxw3)+;c8#nrvWO#vxiLN>AYL~th2uBtd*aI9&$4|Z;#+Kq`=lfsL4z{s
zCP(mU(N<*7Y};BInJzag_fcreO0|q>lXYwHWZ$`G4Dm1Z^oA?~N4>L_u`>J4SH1I9li4KY+FM8rzHS5^7b{jaN
zHL){svcSI2CA5)bW14ZY4&>70^FcS1at$&X>To~Y
zHu!i49?w;x-wwd=onD!~KfkleU*v|lNS3X4$r14Izm*q8dK>P|$JU6La(1Wc*XzmG
zeapCV>Qrm$v3vbt{U-6EfY9ZlH_VM2#^dn3aC3P4?9hN;8SGi?er~nTT8qoI)t0yJ
z2ys^>tGV!dEEd10a*gh`kr)DF%cL~l1HB<9cH1tyB(bYJ;={WkwV~8>H7ykvxUUo^
zgZ#nsmw!(ln8WGGbrX3THX`=(_fF@&McU}c7`kJ3I+~`{yZfIz>jeP5fGfLoaAh*K-9-QEe8C90;j#-11=T#
z2UG%3Ff0&2Rt2a90{NXNW9I-kuYVC`kiW#a{5NlS2=p(qE{+N6zY%4K2fq$;o;_=1vLr%1
zrXp*f+iiB>vqL_%l`RV!Z5oN$?kNto*~d0t-rDhLz-e*yf;*nu1=+K5+**(AdO7X7
zDZ6XIE>~(f^&wWWNH^Gbt8&1c7w$};Xw7Vpc9fXkDY(ACTfo|tjdLf}Qm-a`XK3EY
z^5lJ|lft-NA_Uyhuy4XtqjtUDw`9$?9*g0XZ1E9SJXKB*>CIpOPI_QnK4m6l$hWVI
zIPo{0XDCHd2!y!We2<42BPekxcNio6-_Pn3eiW2b{%9m!H%|41mO3+j#J&Du`V90}mcJ2g
zQ!bWrqc8{u`!oAVv%m+EYPzxbAHHkt7Ap39gh9?9Ng&sV82+#|(4fme9L!YAlVObX
zN+3Y1aO)ceM$}u>u`E1oT(fOeqG1Imxg3V4Z^<$P6(aJT%lEv4?Wd%Eqz`ZQT)IKd
zlWh9y*4Z2!?vx*bq;lb9ti(4v)%w>FMPZgWK_ztCupy77QIygAyJ3R!)v71_;&Kl?
z>0#a@u`zy8Wp&;ZY;;6w41M5bNwHH12Sp+`6WBc(2x_jZ0v22N?aivN$K89-jvXzshVQfBWuSTo;Z5uX
zM}Z+{TvnB^Pu;;u@7OWkVnQe@yb70tn^})#CwOt6Tl;G3tjJ!6Cc%x@r(cJLZf*A2
zt4{9iga`CD@0?F!;udjByH4m4A$V~fwv^MYc5EP?^z@R&^Nm5;w>J|fxgq-!HNEB#
z=MZ)*NKA?Gk2p)+N_X+yE4$s@?Xhvjujm{f#&bDB@q$fFFy?C5b9B46Xz*|(ay7F;
z&FWt}62-OS?vzhwhzpT-tZ%XAer@V&j?k21WrxlP@&nRH8x8H}x;!4K&$>|wj^
zI+B`fh*Wr$ZH&rwQb34)X_M6-v%P)e`l{=zMz-9i3!ALZeLy&ea9#!v_jEEOlX5be
zpj1{RaVSn@a5E?OtgKoEQPRjxUh;s(ek8OT%fe?84kI1BE2m73oh}`PZDHK^m8HRY
z_U4sxT7Or?_Z=9bTF|REzP=B&*rQW$U`8l{C%*_{H-8a;+gcQ26eUN$d?pG!`12E_koJ2_emDwMT9h>-3_m#o@zs)nu)e?aWlB~M!-Z+3jY4-N~9qsqcY
zoa{v-*7l3N7;#8NogN5&R+|Px7i^%5H+7J^(=Pf>7_NGNq1?X?`a!IfcBf;PH3Yd(
zWT#_!quDA~C$c_e
z`A3wL3GU>W*H-80lR8&vFi9Pbblbvk?o1AaX!!gOV%pACgI2Bs_eCx~KMZTXSSOg5
zcQu5aXIxVa^!UCS!SQ-E;^F)|hL0A}M+Za{tBVBl1YJUb1$3l6t7LUILhlQn*KaNE
zt6qF3t|enfgt-t{Y)rrb^5k_C=O-UP`avO3%fa-cfg
zMUEP8!9|Zrg=-dHa^H$P6)e6Rt<Hf+-AoS9KMZXLC`eD7X6N4X=@yyggi%MWhN(M=5FUAxk^1_vc4nhwbCT(
ztR)mgCYjocr=M$;4xi#P$tCdSWhdlA&Az>s+V1+Qyb1Vh<7XVZwjDoBz|sy_s`{Xr
zM5KF2iYIB9x>SKfBYo7_r6Cm;^Mu9;T12icMIUTfNS=s|(?J*7eg55wc`eMY%ay4k
z)T*}}XlA$26mg5rRf;P_!g<1~I`*2nfyO&e>Av&Fb!+55b
z!03KxMJoeO(TboG)}2j{ONn`2TZJ#hKJLL|jNMIT>hxkp>wNVY&b#N8p@&1!PQe6I
zJ0x&&T%G=!_UuuygMsFxDzj{*o`L4s+Y{O|xs%GEOtC?;i_Dssd^`jAO*ska?oXS6
z#?4g2ESqm6?27z!0vA6&542}86Nqc3l5A_%V7>Vm_`y=VMSAfmj))w*VgY{39od4%
z3oj2ks=of7b;?yR8Wga9;1ZO@6fm0hhTyOs+pI!9SO2
ze_eSB|F-fJ);G~su#?p`2V`P?S#h#J8Ch6Z*#8>1&H`a$ghDu>z?$0;d790}hrPoDfDH1wLc2+xh4c)#peUsyxj7bUd#~kc
z5+(K2P8I7I9r6S=6+M71E|>BuMRxk@jHHPJNt(@_fI(
z=ELBeq|RGgrq6hKHpWTcoaga1FZW^e$3KNzqo#V}z)N3rKL4Pkmgx?9dv0O->$vzS
zzx03QiTH0i$NzR^^wS^s?<=F9zP^7hx*e_c4UhmuRCXlr9~(J48yoOg4aon&tqn1Vf$wu7|i_d@&Gj9@5h3{EPxKeKl1>$$6xYT@0WJ>+aL9U!EC==3y=pO
z5&tX)2)X_vkC~H&19&O@?R)?-aj&HHw>${*J)ZH8Jhpqk;J@cF18BxCbwGf(&oAWw
zAm^WX>}$yX==bpzQx9j}rib{&6fPEAv0EnFYdm|3$YulfL0qBSj4kRin5ouAR{{wA*GUxyR
literal 0
HcmV?d00001
diff --git a/services/site/src/lib/resume-pdf.ts b/services/site/src/lib/resume-pdf.ts
index 72e379b..7cb7821 100644
--- a/services/site/src/lib/resume-pdf.ts
+++ b/services/site/src/lib/resume-pdf.ts
@@ -1,10 +1,13 @@
/**
- * The printable resume. Built from the same ResumeData as the /resume page,
+ * The printable resume, set on the Greg Kilmer letterhead. Built from the same
+ * ResumeData as the /resume page,
* laid out top to bottom (skills share a row with education and projects) so
* applicant tracking systems read it in order.
* Paper colors: ink on white, with the brand gold and teal darkened for print.
*/
import PDFDocument from "pdfkit";
+import { PDFDocument as PdfLib } from "pdf-lib";
+import letterheadPdf from "../assets/letterhead/Greg-Kilmer-Letterhead.pdf?inline";
import cinzelBold from "../assets/pdf-fonts/Cinzel-Bold.ttf?inline";
import cinzelSemi from "../assets/pdf-fonts/Cinzel-SemiBold.ttf?inline";
import montReg from "../assets/pdf-fonts/Montserrat-Regular.ttf?inline";
@@ -27,9 +30,12 @@ const GOLD = "#a8842f";
const TEAL = "#0f7f78";
const RULE = "#d9cfb8";
-const PAGE = { size: "LETTER" as const, margin: 36 };
-const SITE = "gregkilmer.com";
-const TEXT = 9;
+/**
+ * The writing area inside the Greg Kilmer letterhead (branding/letterhead), in
+ * points: below its header rule and above its footer rule, between its margins.
+ */
+const PAGE = { size: "LETTER" as const, left: 54, right: 54, top: 112, bottom: 70 };
+const TEXT = 8.6;
/** A column of the page: where text starts and how wide it runs. */
interface Col {
@@ -37,10 +43,10 @@ interface Col {
w: number;
}
-export function renderResumePdf(r: ResumeData): Promise {
+function renderBody(r: ResumeData): Promise