diff --git a/.dockerignore b/.dockerignore index 425d95f..5733a53 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,17 +1,13 @@ -# Keep the Docker build context lean. -node_modules -dist -.astro +**/node_modules +**/dist +**/.astro +/data +services/*/data +out .git -.gitignore .env -.env.* -!.env.example -*.log -npm-debug.log* -.DS_Store -.vscode -.idea -docs -*.md -!README.md +**/.env +**/.env.* +!**/.env.example +legacy/node_modules +legacy/dist diff --git a/.gitea/workflows/deploy.yml b/.gitea/workflows/deploy.yml new file mode 100644 index 0000000..445b091 --- /dev/null +++ b/.gitea/workflows/deploy.yml @@ -0,0 +1,24 @@ +# Gitea Actions on Cosmos: Gitea mirrors GitHub, and this builds, checks and +# restarts the GregKilmer.com stack on each new commit to master. +# It runs on a host-mode runner on Cosmos labelled "cosmos" (docs/deploy.md). +name: Deploy GregKilmer.com + +on: + push: + branches: [master] + # A pull mirror's sync may not raise a push event; this catches new commits + # anyway. deploy.sh does nothing when the commit is already running. + schedule: + - cron: "*/15 * * * *" + workflow_dispatch: + +concurrency: + group: gregkilmer-deploy + cancel-in-progress: false + +jobs: + deploy: + runs-on: cosmos + steps: + - uses: actions/checkout@v4 + - run: ./deploy/deploy.sh diff --git a/.gitignore b/.gitignore index 5c155e1..a30ef5a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,30 +1,28 @@ -# 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/ +data/ -# misc -*.local +# Astro build output and generated types +packages/ui/dist/ +packages/ui/.astro/ +services/site/dist/ +services/site/.astro/ diff --git a/Dockerfile b/Dockerfile index 0dac769..fecebf7 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,53 +1,93 @@ -# ============================================================================= -# Dockerfile — Greg Kilmer Profile Site -# ============================================================================= -# Multi-stage build producing a minimal Node server image for the Cosmos homelab. +# Images for the GregKilmer.com stack on Cosmos. One file, one target per service: # -# Stage 1 (builder): install all deps, run astro build -> dist/server/entry.mjs -# Stage 2 (runtime): copy only dist + prod node_modules, run the Node server +# docker build --target check . runs `npm run check` (typecheck and tests) +# docker build --target content . the content service and MCP server +# docker build --target admin . the admin page (tailnet only) +# docker build --target site . the public site # -# The @astrojs/node standalone adapter emits a self-contained server, so the -# runtime image needs only node_modules + dist. Build once, run anywhere. -# ============================================================================= +# The Gitea Actions workflow builds check first, so a red check stops a deploy. +# See docs/deploy.md. -# ---------- Stage 1: build ---------- -FROM node:22.12.0-alpine AS builder +# Pinned by version (Node 22.20, released 2025-09). Override with --build-arg to +# pull through a mirror. +ARG NODE_IMAGE=node:22.20.0-alpine + +# ---------- package manifests, then every workspace's dependencies ---------- +FROM ${NODE_IMAGE} AS manifests WORKDIR /app +COPY package.json package-lock.json ./ +COPY packages/schema/package.json packages/schema/ +COPY packages/ui/package.json packages/ui/ +COPY services/content/package.json services/content/ +COPY services/admin/package.json services/admin/ +COPY services/site/package.json services/site/ +COPY tools/migrate/package.json tools/migrate/ -# Install deps. Copy manifests first to leverage Docker layer caching. -COPY package.json package-lock.json* ./ -RUN npm ci --no-audit --no-fund || npm install --no-audit --no-fund --legacy-peer-deps +FROM manifests AS deps +RUN npm ci --no-audit --no-fund -# Copy source and build. SITE_URL can be overridden at build time. +# ---------- typecheck and tests ---------- +FROM deps AS check COPY . . -ARG SITE_URL=https://gregkilmer.com -ENV SITE_URL=$SITE_URL -RUN npm run build +RUN npm run check -# Prune to production dependencies for the runtime stage. -RUN npm prune --omit=dev +# ---------- the site's server build ---------- +FROM deps AS site-build +COPY packages packages +COPY services/site services/site +RUN npm run build -w @gk/site -# ---------- Stage 2: runtime ---------- -FROM node:20-alpine AS runtime +# ---------- each service's production dependencies only ---------- +FROM manifests AS content-deps +RUN npm ci --omit=dev --no-audit --no-fund -w @gk/content && npm cache clean --force + +FROM manifests AS admin-deps +RUN npm ci --omit=dev --no-audit --no-fund -w @gk/admin && npm cache clean --force + +FROM manifests AS site-deps +RUN npm ci --omit=dev --no-audit --no-fund -w @gk/site && npm cache clean --force + +# ---------- shared runtime base ---------- +FROM ${NODE_IMAGE} AS runtime WORKDIR /app +ENV NODE_ENV=production HOST=0.0.0.0 +COPY package.json ./ +COPY packages/schema/package.json packages/schema/ +COPY packages/schema/src packages/schema/src -# Run as a non-root user for defense in depth. -RUN addgroup -S app && adduser -S app -G app +# ---------- content service ---------- +FROM runtime AS content +COPY --from=content-deps /app/node_modules node_modules +COPY services/content/package.json services/content/ +COPY services/content/src services/content/src +# The vault's Site folder and the data folder (SQLite and saved form attachments). +ENV PORT=4310 SITE_DIR=/vault/Site DATA_DIR=/data +EXPOSE 4310 +HEALTHCHECK --interval=30s --timeout=5s --start-period=20s --retries=3 \ + CMD wget -qO- http://127.0.0.1:4310/health >/dev/null || exit 1 +USER node +CMD ["node", "--disable-warning=ExperimentalWarning", "--import", "tsx", "services/content/src/main.ts"] -# Copy production node_modules and the built server + client assets. -COPY --from=builder /app/node_modules ./node_modules -COPY --from=builder /app/dist ./dist -COPY --from=builder /app/package.json ./package.json +# ---------- admin page ---------- +FROM runtime AS admin +COPY --from=admin-deps /app/node_modules node_modules +COPY services/admin/package.json services/admin/ +COPY services/admin/src services/admin/src +COPY services/admin/public services/admin/public +ENV PORT=4320 +EXPOSE 4320 +HEALTHCHECK --interval=30s --timeout=5s --start-period=20s --retries=3 \ + CMD wget -qO- http://127.0.0.1:4320/health >/dev/null || exit 1 +USER node +CMD ["node", "--import", "tsx", "services/admin/src/main.ts"] -# Astro Node standalone server reads HOST/PORT from env at runtime. -ENV HOST=0.0.0.0 +# ---------- public site ---------- +FROM runtime AS site +COPY --from=site-deps /app/node_modules node_modules +COPY --from=site-build /app/services/site/dist services/site/dist ENV PORT=4321 EXPOSE 4321 - -USER app - -# Healthcheck: the server responds 200 on /. -HEALTHCHECK --interval=30s --timeout=5s --start-period=15s --retries=3 \ - CMD wget -qO- http://127.0.0.1:${PORT}/ >/dev/null 2>&1 || exit 1 - -CMD ["node", "./dist/server/entry.mjs"] +HEALTHCHECK --interval=30s --timeout=5s --start-period=20s --retries=3 \ + CMD wget -qO- http://127.0.0.1:4321/ >/dev/null || exit 1 +USER node +CMD ["node", "services/site/dist/server/entry.mjs"] diff --git a/README.md b/README.md index 3cdaf10..91e93d4 100644 --- a/README.md +++ b/README.md @@ -1,156 +1,53 @@ -# 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), the Site guide and the stack's inventory note. | +| `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). | +| `packages/ui` | The component library: the approved element rules and ornament kit as Astro components, with the brand fonts, seals and icons, and a gallery page that shows them all. See [docs/components.md](docs/components.md). | +| `services/site` | The public site: every page rendered from the content service's published notes, the resume PDF, and the inquiry and order forms. See [docs/site.md](docs/site.md). | +| `deploy`, `Dockerfile`, `.gitea` | The Cosmos stack: images, compose file, deploy and rollback scripts, and the Gitea Actions workflow. See [docs/deploy.md](docs/deploy.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. | -## Highlights +Coming next: the cutover from the old site (phase 8). -- **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 +npm run check # typecheck (with astro check) and tests +npm run ui # the component gallery at http://localhost:4321 +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/ -# 2. Start the dev server with hot reload -npm run dev -# -> http://localhost:4321 +# 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 -# 3. Production build (runs astro check + build) -npm run build - -# 4. Run the built Node server -npm run serve -# -> http://localhost:4321 +# the admin page, in a second terminal +ADMIN_TOKEN= npm run admin # http://localhost:4320 ``` -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/deploy/admin.env.example b/deploy/admin.env.example new file mode 100644 index 0000000..0dea73d --- /dev/null +++ b/deploy/admin.env.example @@ -0,0 +1,3 @@ +# Admin page settings. On Cosmos: /home/greg/homelab/gregkilmer/admin.env +# The content service's admin token (the admin:… entry in content.env's CONTENT_TOKENS). +ADMIN_TOKEN= diff --git a/deploy/compose.yaml b/deploy/compose.yaml new file mode 100644 index 0000000..6f110cb --- /dev/null +++ b/deploy/compose.yaml @@ -0,0 +1,66 @@ +# The GregKilmer.com stack on Cosmos: content service, admin page and public site. +# +# Lives at /home/greg/homelab/gregkilmer/compose.yaml (the Gitea Actions deploy +# copies it there from deploy/compose.yaml on every build). Settings: +# .env stack settings: GK_TAG, VAULT_SITE, PUID, PGID (deploy/stack.env.example) +# content.env content service secrets (services/content/.env.example) +# admin.env admin page settings (deploy/admin.env.example) +# site.env public site secrets (services/site/.env.example) +# +# No service publishes a host port. Nginx Proxy Manager reaches them by +# container name on homelab_network. See docs/deploy.md. + +name: gregkilmer + +x-common: &common + restart: unless-stopped + user: "${PUID:-1000}:${PGID:-1000}" + networks: [homelab_network] + read_only: true + tmpfs: [/tmp] + security_opt: ["no-new-privileges:true"] + cap_drop: [ALL] + +services: + gk-content: + <<: *common + image: gregkilmer/content:${GK_TAG:?set GK_TAG in .env} + container_name: gk-content + env_file: content.env + environment: + SITE_DIR: /vault/Site + DATA_DIR: /data + volumes: + # The vault's Site folder. Read-write: accepted API and MCP changes are + # written back into the notes. + - ${VAULT_SITE:?set VAULT_SITE in .env}:/vault/Site + # content.db (notes, history, review queue, saved forms, sync cache) and + # submissions/ (form attachments). Both are backed up. + - ./data/content:/data + labels: + backup.dump: sqlite + + gk-admin: + <<: *common + image: gregkilmer/admin:${GK_TAG} + container_name: gk-admin + env_file: admin.env + environment: + CONTENT_URL: http://gk-content:4310 + depends_on: + gk-content: { condition: service_healthy } + + gk-site: + <<: *common + image: gregkilmer/site:${GK_TAG} + container_name: gk-site + env_file: site.env + environment: + CONTENT_URL: http://gk-content:4310 + depends_on: + gk-content: { condition: service_healthy } + +networks: + homelab_network: + name: homelab_network + external: true diff --git a/deploy/deploy.sh b/deploy/deploy.sh new file mode 100755 index 0000000..d9af268 --- /dev/null +++ b/deploy/deploy.sh @@ -0,0 +1,61 @@ +#!/usr/bin/env bash +# Builds the three images for the checked-out commit, runs the checks, and +# restarts the stack on it. If the new containers don't come up healthy, the +# stack goes back to the last good tag. Run from the repo root on Cosmos, by the +# Gitea Actions workflow or by hand. See docs/deploy.md. +set -euo pipefail + +STACK="${GK_STACK_DIR:-/home/greg/homelab/gregkilmer}" +ENV_FILE="$STACK/.env" +TAG="$(git rev-parse --short=12 HEAD)" +compose() { docker compose --project-directory "$STACK" -f "$STACK/compose.yaml" "$@"; } +setting() { grep -E "^$1=" "$ENV_FILE" | tail -n 1 | cut -d= -f2- || true; } +set_setting() { + if grep -qE "^$1=" "$ENV_FILE"; then sed -i "s|^$1=.*|$1=$2|" "$ENV_FILE"; else echo "$1=$2" >> "$ENV_FILE"; fi +} + +for f in .env content.env admin.env site.env; do + [ -f "$STACK/$f" ] || { echo "Missing $STACK/$f (see docs/deploy.md)"; exit 1; } +done + +CURRENT="$(setting GK_TAG)" +PREVIOUS="$(setting GK_PREVIOUS_TAG)" +if [ "$CURRENT" = "$TAG" ] && [ "${FORCE:-}" != "1" ]; then + echo "Already running $TAG; nothing to do (FORCE=1 to rebuild)." + exit 0 +fi + +echo "== Checks" +docker build --target check . + +echo "== Images for $TAG" +for t in content admin site; do + docker build --target "$t" -t "gregkilmer/$t:$TAG" . +done + +echo "== Deploy" +mkdir -p "$STACK/data/content" +cp deploy/compose.yaml "$STACK/compose.yaml" +set_setting GK_TAG "$TAG" +[ -n "$CURRENT" ] && set_setting GK_PREVIOUS_TAG "$CURRENT" + +if ! compose up -d --remove-orphans --wait --wait-timeout 120; then + echo "!! $TAG did not come up healthy" + compose ps + compose logs --tail 50 || true + if [ -n "$CURRENT" ]; then + echo "== Rolling back to $CURRENT" + set_setting GK_TAG "$CURRENT" + set_setting GK_PREVIOUS_TAG "$PREVIOUS" + compose up -d --wait --wait-timeout 120 + fi + exit 1 +fi + +echo "== Prune old images (keeping $TAG and ${CURRENT:-none})" +for t in content admin site; do + docker images "gregkilmer/$t" --format '{{.Tag}}' | while read -r old; do + [ "$old" = "$TAG" ] || [ "$old" = "$CURRENT" ] || docker rmi "gregkilmer/$t:$old" >/dev/null || true + done +done +echo "Deployed $TAG" diff --git a/deploy/rollback.sh b/deploy/rollback.sh new file mode 100755 index 0000000..79a4fec --- /dev/null +++ b/deploy/rollback.sh @@ -0,0 +1,12 @@ +#!/usr/bin/env bash +# Puts the stack back on the previous tag (GK_PREVIOUS_TAG) and swaps the two, +# so running it again rolls forward. See docs/deploy.md. +set -euo pipefail +STACK="${GK_STACK_DIR:-/home/greg/homelab/gregkilmer}" +ENV_FILE="$STACK/.env" +cur="$(grep -E '^GK_TAG=' "$ENV_FILE" | cut -d= -f2-)" +prev="$(grep -E '^GK_PREVIOUS_TAG=' "$ENV_FILE" | cut -d= -f2-)" +[ -n "$prev" ] || { echo "No GK_PREVIOUS_TAG in $ENV_FILE"; exit 1; } +sed -i "s|^GK_TAG=.*|GK_TAG=$prev|; s|^GK_PREVIOUS_TAG=.*|GK_PREVIOUS_TAG=$cur|" "$ENV_FILE" +docker compose --project-directory "$STACK" -f "$STACK/compose.yaml" up -d --wait --wait-timeout 120 +echo "Now running $prev (previous: $cur)" diff --git a/deploy/stack.env.example b/deploy/stack.env.example new file mode 100644 index 0000000..d7ad733 --- /dev/null +++ b/deploy/stack.env.example @@ -0,0 +1,9 @@ +# Stack settings for compose.yaml. On Cosmos: /home/greg/homelab/gregkilmer/.env +# The Gitea Actions deploy rewrites GK_TAG on every build; GK_PREVIOUS_TAG is the rollback. +GK_TAG= +GK_PREVIOUS_TAG= +# The vault's Site folder on the host. +VAULT_SITE=/home/greg/homelab/obsidian-vault/20-29_Makerspace/24_Homelab_Infrastructure/24.01_PersonalWebsite/Site +# The user the containers run as: the owner of the vault files (id -u greg, id -g greg). +PUID=1000 +PGID=1000 diff --git a/docs/admin.md b/docs/admin.md new file mode 100644 index 0000000..938d035 --- /dev/null +++ b/docs/admin.md @@ -0,0 +1,50 @@ +# 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, each sync job's last run and last success, and Library suggestions: series moved to Reading in PersonalReader with no Library item, each with Add to the Library (title, medium, AniList id, state now, published) and Dismiss. | +| 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. | +| Inbox | Inquiries and order requests from the site, unhandled first and newest first, with whether each email reached you. Open one to read it, reply by email, download its attachment, and mark it handled or not handled. | + +## 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/components.md b/docs/components.md new file mode 100644 index 0000000..a4cecb6 --- /dev/null +++ b/docs/components.md @@ -0,0 +1,79 @@ +# Component library + +`packages/ui` (`@gk/ui`) holds the approved element rules and ornament kit as +Astro components. The site imports them; nothing else defines how a button, +chip or seal looks. The rules come from the design canvas's Element rules and +Ornament kit boards, approved Oct 11. + +Run the gallery with `npm run ui` (or `npm run ui:build` for a static copy in +`packages/ui/dist`). It shows every component inside the real page shell. + +## Layout + +| Path | What it is | +| --- | --- | +| `src/styles/tokens.css` | Colors, fonts, spacing, radii, control sizes and the page width as `--gk-*` variables. | +| `src/styles/fonts.css` | Cinzel and Montserrat, self-hosted as Latin-subset WOFF2 (OFL, see `src/assets/fonts/LICENSE.md`). | +| `src/styles/base.css` | Page defaults: body, headings, body links, the focus ring, `.gk-wrap`, reduced motion. | +| `src/styles/elements.css` | The element rules as global `gk-*` classes, so Markdown output and plain HTML can use them too. | +| `src/lib/ornaments.ts` | The seeded geometry behind star paths, tick scales, tick rings and dust. Tested in `test/ornaments.test.ts`. | +| `src/components/` | The Astro components below. | +| `src/assets/` | Seals and motifs, the monogram, 86 stroke icons, the card tick strip, and the fonts. | + +## Element rules + +| Rule | Component | Notes | +| --- | --- | --- | +| I Buttons | `Button` | `variant` primary (teal pill), secondary (gold hairline) or text (filament underline on hover); `size="compact"` for 36px; `href` makes it a link. One primary per section. | +| II States | `StatusPill` | Round pill. `live` teal with a lit dot, `state` without the dot, `favorite` and `current` gold with a star. | +| II Labels | `Chip` | Square 6px chip. `core` gives the teal edge; `href` makes it a page link led by a violet star. | +| III Filters | `FilterGroup` | Separate 36px pills. Items with `href` are links (`aria-current`); items without are toggle buttons (`aria-pressed`) a page script drives. | +| IV Kickers | `Kicker`, `SectionHeading` | Gold capitals over a tick scale. `SectionHeading` adds the Cinzel heading and the filament underline. | +| IV Notices | `Notice` | The ledger note: soft gold panel, gold rule on the left. | +| V Fields | `Field`, `SearchField` | Input, select or text area in the 10px box, filament across the base on focus, with `hint` and `error` wired to `aria-describedby`. | +| VI Cards | `Card` | Linked cards turn gold on hover with a tick scale along the top. `featured` adds the gold border and gilt corners; one per page. | +| VII Links | (base.css) | Body links are teal and always underlined. | + +Shared: controls are 44px (36px compact), chips 26px, focus is a 2px gold +ring 3px out, disabled is 40% opacity. + +## Ornaments + +| Component | Use | +| --- | --- | +| `Star` | The four-point star. Gold, violet (only ever a star), teal, starlight or outline. | +| `StarPath` | Greg's star path line. `strength` calm, standard or wild; the same `seed` always draws the same path (it defaults to the page path). Two per page at most. | +| `Divider` | The header divider: gold line, large center star, star paths both sides. Header and footer only. | +| `HeroHorizon` | Gold hairline with a violet star, where a hero has no divider. | +| `TickScale` | Astrolabe ticks; with `value` it is a meter. | +| `TickRing` | A tick ring around a step number or small seal; `medallion` adds the disc and glow. | +| `SlotMeter` | Open order slots as stars over a tick meter. | +| `GiltCorners` | Corner brackets for the one featured panel per page. | +| `Seal`, `EdgeSeals` | A seal as an image; `EdgeSeals` places the page family's seal at the page edges at 6 to 7%. | +| `ConstellationDust` | Sparse static specks, about one in five gold. | +| `StarChartArt` | Placeholder art and the fallback for a missing image: star-chart grid, gold mat, a seal. | +| `Icon` | One of the 86 brand icons, in the current text color. | + +Each page family's edge seal is in `src/components/seals.ts` (`pageSeals`): +landing uses transmutation star paths, resume the Kepler nest, work the +squared circle, and so on. A craft page passes its own craft seal. + +## Page shell + +`PageShell` is every page's frame: the head (title, description), dust and +edge seals, `SiteHeader` (nebula, filament banner, the primary nav with the My +world menu, an optional hero in the `hero` slot, the divider), the page +content, and `SiteFooter`. The nav entries live in `src/components/nav.ts`; +pass `current` to mark the page's entry. + +```astro +--- +import PageShell from "@gk/ui/components/PageShell.astro"; +--- + + … + … + +``` + +The site is dark only for now; light mode comes in a later phase. 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/docs/content-service.md b/docs/content-service.md new file mode 100644 index 0000000..acceec6 --- /dev/null +++ b/docs/content-service.md @@ -0,0 +1,364 @@ +# 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. | + +A sync job writing notes (author `sync`) runs every agent rule above, the +`batch` limit counted for syncs on their own; see [Sync jobs](#sync-jobs). + +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. With `full=true` each note carries `synced` (see [Sync jobs](#sync-jobs)). | +| `GET /api/note?path=`, `?name=` or `?type=&slug=` | all | One note with its backlinks and `synced`. 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 }` | +| `POST /api/scan` | editor, admin | Compare the whole folder now. | +| `POST /api/submissions` | intake, admin | File a saved copy of an inquiry or order. Answers `{ id, receipt }`. | +| `POST /api/submissions//delivery` | intake (with the receipt, once), admin | `{ receipt, status: sent, failed or skipped, error? }`: how the email went. | +| `GET /api/submissions?handled=`, `/api/submissions/` | admin | Saved copies, unhandled first, newest first, with counts. | +| `POST /api/submissions//handled` | admin | `{ handled: true or false }` | +| `GET /api/submissions//attachment` | admin | The attached file, always as a download. | +| `/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, intake. 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 `{ content, event: "held", held }` when a change is held. `content` is a one-line message, so a Discord webhook URL works as is. | +| `SCAN_MINUTES` | `5` | Full scan interval, on top of the file watch. | +| `SUBMISSION_MAX_MB` | `10` | Largest attachment a form submission may carry. | +| `SYNC_JOBS` | none | Public sync jobs to run, comma separated: `archidekt`, `bgg`, `covers`. See [Sync jobs](#sync-jobs). | +| `SYNC__MINUTES` | per job | Overrides a job's interval, e.g. `SYNC_BGG_MINUTES=720`. | +| `SYNC__HEALTHCHECK_URL` | none | The job's Healthchecks ping URL, e.g. `SYNC_ARCHIDEKT_HEALTHCHECK_URL`. | +| `BGG_TOKEN` | none | BoardGameGeek application token, sent as `Authorization: Bearer`. Register an app with BGG if the XML API answers 401. | +| `TMDB_API_KEY` | none | TMDB v3 API key or v4 read access token. Without it, film and TV covers are skipped. | + +```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. + +## Sync jobs + +Outside sources sync on a schedule into a cache the site reads, so a source +being down never takes a page down. Code: [`services/content/src/sync/`](../services/content/src/sync/). + +| Job | Every | Reads | Stores per note | +| --- | --- | --- | --- | +| `archidekt` | hour | Deck notes with `archidektId`: `GET https://archidekt.com/api/decks//` | `commanderArt` (Scryfall art crop of the commander, else the deck's featured image), `colors` (color identity, WUBRG letters), `commanders`, `deckSize` (cards, maybeboard excluded), `format`, `deckUrl` | +| `bgg` | day | Board-game notes' `bggId`: XML API2 `thing`, 20 ids a request, 5 s apart, waits out `202` | `minPlayers`, `maxPlayers`, `playTime`, `minPlayTime`, `maxPlayTime`, `cover` (box art) | +| `covers` | 15 min | Library items with no `cover`: Open Library (books), TMDB (film, TV; needs `TMDB_API_KEY`), AniList (anime, manga, manhwa, light novels, webnovels; by `anilistId` when set) | `cover` | +| `suwayomi` | hour | PersonalReader's manga and manhwa: Suwayomi's GraphQL library (category, furthest chapter read and when, AniList tracker id), matched to Library items by `anilistId`; AniList for covers | `chapterProgress` (`title`, `status`, `chapter`, `readAt`), `cover` (AniList); changes `state` (below) | +| `kavita` | day | PersonalReader's light novels and books: Kavita's API, matched by `kavitaSeriesId`, or by `anilistId` against an anilist.co link in the series' metadata | `bookProgress` (`title`, `percent`, `readAt`); changes `state` (below) | +| `bambuddy` | hour | Filament spools in stock (**unverified API**, below) | `filamentStock` on product lines with a Filament option | +| `homebox` | hour | Homebox items with the labels or locations in `HOMEBOX_LABELS` / `HOMEBOX_LOCATIONS` | `materialStock` on product lines with a Leather or Paper option | + +The covers job looks every 15 minutes, so a new Library item gets a cover +soon after it appears, and sweeps slowly: at most 20 items a run, an item +with no cover found is asked again after a day, one with a cover after 30 +days. Only image addresses are stored; nothing is downloaded. Video games +have no cover source. + +### Homelab sources (Cosmos) + +These four run on Cosmos over `homelab_network`, are **read only** against +their source, and are enabled by their own URL and login alone (not +`SYNC_JOBS`). Code: `sync/jobs/{suwayomi,kavita,bambuddy,homebox}.ts`, +settings in `sync/jobs/homelab.ts`. None of them has been run against a real +server from this repository: they are written from each project's API +documentation (Bambuddy: from nothing, see below) and tested with +hand-written sample payloads in `services/content/test/sync/fixtures`. + +**PersonalReader (Suwayomi, Kavita)** is private. A job touches a Library +item only when the note carries the series' id: `anilistId` (Suwayomi's +AniList tracker record; Kavita's metadata link), or `kavitaSeriesId`. It never +matches by title. It caches only a title, a chapter or percent, when it was +read and an AniList cover: never a source site, a scan group, a thumbnail or +any address inside Suwayomi or Kavita. + +| Reader says | Library `state` | +| --- | --- | +| Suwayomi category Reading | `now` (a note at `caught up` stays) | +| Plan to read | `up next` | +| Completed | `finished` | +| Dropped, or any other category | left alone | +| Kavita: every page read | `finished` when the series is Completed or Ended, else `caught up` (a note at `finished` stays) | +| Kavita: some pages read | `now` (a note at `caught up` stays) | +| Kavita: unread and on Want to Read | `up next` | + +State changes go through `ctx.apply` as `sync`, so a note Greg wrote is held +for review (`agent-edit`), as is everything else in the rules above. A job +proposes each change once: it is not proposed again while it waits, nor after +Greg rejects it, until the reader says something different (the job keeps +that memo in its cache under `_memo`, which is not a note and never reaches +the site). + +- **Suwayomi** logs in with its own read-only account. `SUWAYOMI_AUTH=ui_login` + (the default with a user set) uses the GraphQL `login` mutation and sends the + access token as `Authorization: Bearer`; `basic` sends HTTP basic auth (for + `AUTH_MODE=basic_auth`); `none` sends nothing. Suwayomi's cookie-based + `simple_login` mode is not supported. It then reads `categories { nodes { + name mangas { nodes { title latestReadChapter { chapterNumber lastReadAt } + trackRecords { nodes { trackerId remoteId title lastChapterRead } } } } } }` + from `/api/graphql`; the AniList record is tracker id 2. Covers, + titles and media for suggestions come from one batched AniList query. +- **Library suggestions.** A Reading series with an AniList id and no Library + item is listed at `GET /api/suggestions` (editor, admin, agent) and on the + admin Overview, with one button that creates the Library item (title, + medium from AniList, `anilistId`, `state: now`, published) through + `POST /api/changes`, and a Dismiss (`POST /api/suggestions//dismiss`, + editor or admin; kept in the `suggestion_dismissals` table). The list itself + is the job's cache entry `_suggestions`; a series leaves it as soon as a + Library item carries its AniList id. +- **Kavita** exchanges `KAVITA_API_KEY` at `POST /api/Plugin/authenticate` for + a token, reads `GET /api/Series/`, `GET /api/Series/metadata?seriesId=`, + `POST /api/Series/all-v2` (only when a note needs an AniList match; one + metadata request per series, a quarter second apart) and + `POST /api/want-to-read/v2`. AniList matching applies to book, light novel + and webnovel notes only, so a manga note is Suwayomi's. + +**Store stock (Bambuddy, Homebox)** never writes notes. A job stores what is +in stock on every product line that has an option in a group it feeds +(`filamentStock` for Bambuddy's group, Filament by default; +`materialStock` for Homebox's), as `[{ group, options: [{ name, hex?, inStock }] }]`. +The site lays it under the note (`optionGroups` in +`services/site/src/lib/store.ts`, which the Store pages and the order check +both use): an option's own `inStock` and `hex` always win; an option that +leaves `inStock` out (it is optional) takes the synced value, and is out of +stock when the source does not have it; in-stock colors the note does not +list are added after the note's own. With nothing synced, options show +exactly as the note says (a missing `inStock` counts as in stock). + +- **Bambuddy: unverified.** Its API is not documented anywhere this project + has, and no request has been made to one. The endpoint is + `BAMBUDDY_SPOOLS_PATH` (default `/api/v1/inventory/spools`, a guess), and all + knowledge of the response is in `mapBambuddySpools` in + `sync/jobs/bambuddy.ts`: it accepts a list (or one under `items`, `spools`, + `data`, `results`) and reads color name, hex, material and grams left under + common spellings. Check it against Greg's Bambuddy and fix that one function + before relying on it. A color is in stock when some spool of it has at least + `BAMBUDDY_MIN_GRAMS` (default 50) left, or does not say. +- **Homebox** logs in at `POST /api/v1/users/login`, looks up the label and + location ids, and lists `GET /api/v1/items?labels=` (or + `locations=`). Each `HOMEBOX_LABELS` / `HOMEBOX_LOCATIONS` entry is + `name` or `name=Group` (`leather` feeds Leather). An item is in stock when + its quantity is above zero; a `#rrggbb` in its description is its swatch + color. A name that matches no label or location fails the run. + +| Variable | | +| --- | --- | +| `SUWAYOMI_URL` | Enables `suwayomi`, like `http://suwayomi:4567`. | +| `SUWAYOMI_USER`, `SUWAYOMI_PASS` | Its own read-only Suwayomi account. | +| `SUWAYOMI_AUTH` | `ui_login` (default with a user), `basic`, or `none`. | +| `KAVITA_URL`, `KAVITA_API_KEY` | Enable `kavita`, like `http://kavita:5000`; the key of a Kavita user that can see the libraries. | +| `BAMBUDDY_URL` | Enables `bambuddy`. | +| `BAMBUDDY_SPOOLS_PATH` | Endpoint path (default `/api/v1/inventory/spools`, unverified). | +| `BAMBUDDY_API_KEY`, `BAMBUDDY_AUTH_HEADER` | Sent as `Authorization: Bearer `, or as the raw key in the named header. | +| `BAMBUDDY_GROUP`, `BAMBUDDY_MATERIALS`, `BAMBUDDY_MIN_GRAMS` | Option group (Filament), materials to include (like `PLA,PETG`; all when empty), grams that count as in stock (50). | +| `HOMEBOX_URL`, `HOMEBOX_USER`, `HOMEBOX_PASS` | Enable `homebox`, with a read-only Homebox user. | +| `HOMEBOX_LABELS`, `HOMEBOX_LOCATIONS` | Which items count, `name` or `name=Group`, comma separated. Labels default to `leather,paper`. | + +Each also takes `SYNC__MINUTES` and `SYNC__HEALTHCHECK_URL` like any +job (`SYNC_SUWAYOMI_HEALTHCHECK_URL`, and so on). + +### How they run + +- **Enabled only when configured.** The public sources run when named in + `SYNC_JOBS`. A job that needs a URL or a login is enabled by those + variables alone. +- **Scheduler.** Each job runs once shortly after start (20 seconds apart, + so they do not all start together), then every interval. A job never runs + twice at once; a run past its timeout (10 minutes unless the job says + otherwise) is aborted and reported as failed. +- **Reporting.** Every run lands in `sync_runs` (`/api/syncs`, the admin + Overview). With `SYNC__HEALTHCHECK_URL` set, the job pings + `/start`, then `` or `/fail` with the message, as the nightly + backup does. +- **The cache.** Table `sync_cache` in `content.db`, keyed by job and note + path, holding the values as JSON and when they were fetched. A job replaces + a note's values only with values it fetched successfully, so a failed run + or a failed item keeps the last good values. Values for notes that leave a + job's scope (a deck loses its `archidektId`) are dropped. The cache can be + rebuilt by running the jobs again. + +### What the site gets + +`/api/notes?full=true` and `/api/note` give every note a `synced` object: +each job's values for that note merged together (jobs do not share keys). +The site lays `synced` under the note's `data`, so **a value set in the note +always wins** (a note's own `cover` beats a synced one), and a note with +nothing synced renders exactly as before. + +### Writing a job + +A job is a `SyncJob` ([`sync/types.ts`](../services/content/src/sync/types.ts)), +added to `enabledJobs` in [`sync/index.ts`](../services/content/src/sync/index.ts) +behind the settings it needs (read in [`sync/settings.ts`](../services/content/src/sync/settings.ts) +from the environment, and documented above): + +```ts +interface SyncJob { + name: string; // "bambuddy": sync_runs, the cache and SYNC_BAMBUDDY_* use it + everyMinutes: number; + timeoutMs?: number; // default 10 minutes + healthcheckUrl?: string; + run(ctx: SyncContext): Promise<{ message?: string; counts?: Record; ok?: boolean }>; +} +``` + +`run` gets a `SyncContext` and nothing else: + +| | | +| --- | --- | +| `ctx.notes(type)` | Live notes of a type, read only. | +| `ctx.cached(path)`, `ctx.set(path, values)` | This job's cache for a note. Call `set` only with values fetched successfully. | +| `ctx.retain(paths)` | Drop this job's values for notes outside `paths`. | +| `ctx.apply(change)` | Propose a note change as `sync` (below). | +| `ctx.fetch(url, init)` | `fetch` with the sync User-Agent, the run's abort signal and a 30 second timeout (`timeoutMs` to change). | +| `ctx.sleep(ms)`, `ctx.signal`, `ctx.now()`, `ctx.log(msg)` | Polite pauses, cancellation, the clock, logging. | + +Throw to fail the whole run; return `ok: false` when some items failed. A run +that throws changes nothing already cached. + +**Read-only sources** (Bambuddy filament, Homebox materials) store what they +read with `ctx.set` on the note it belongs to and add the keys to the site's +readers; they never write notes. + +**Jobs that change notes** (Suwayomi and Kavita progress for PersonalReader) +call `ctx.apply({ op: "update", path, fields })`, the same +`ContentService.apply` every client uses, with author `sync`, so the note +gets `updatedBy: sync`. A sync is held to every agent rule and can do nothing +an agent cannot: + +- it cannot delete, move or rename a note, or change its type; +- a change to a note the sync did not write (Greg's, an agent's) is held for + review unless the note has `agentEdits: true`; +- added email addresses or phone numbers, anything that looks like a secret, + and large text loss are held; +- it may publish only statuses and Library items; anything else is held; +- more than 10 sync changes in 10 minutes are held (`batch`), so a job that + updates many notes should change only what actually differs. + +The outcome says `accepted`, `held` or `rejected`; count those in the run's +`counts`. A note with a change waiting for review takes no further change +until Greg decides it, which `apply` answers as `rejected` with `waiting`. + +## Form submissions + +The site's Inquiries and Store order forms are emailed to Greg and saved here +as well (`services/content/src/submissions.ts`), so a lost email never loses a +request. Each copy keeps its kind (inquiry or order), type (inquiry type or +product line), name, email, the answers in form order, the attachment if any, +when it came in, how its email went (`pending`, `sent`, `failed`, `skipped` +when SMTP is not set up) and whether Greg has handled it. + +- The site files copies with an `intake` token. That token can do nothing + else: every other route, `/mcp` included, answers 403, and it can never read + a copy back, not even the one it just filed. It reports the email result + once, with the receipt it got when filing. +- Only the `admin` token lists, reads, downloads and marks copies handled + (the admin page's Inbox). +- Attachments are kept under `DATA_DIR/submissions` with a name the service + makes up (id, random part, a known extension), up to `SUBMISSION_MAX_MB`. + Allowed: png, jpg, jpeg, gif, webp, avif, heic, pdf, txt, csv, dek, dec. They + are only ever served as downloads. +- The rows live in `content.db`, so the `backup.dump=sqlite` label covers + them; back up `DATA_DIR/submissions` with the volume too. diff --git a/docs/deploy.md b/docs/deploy.md new file mode 100644 index 0000000..368c9d5 --- /dev/null +++ b/docs/deploy.md @@ -0,0 +1,114 @@ +# Deploying on Cosmos + +The stack is three containers on `homelab_network`, none with a host port: + +| Container | Image target | Port | Reached as | Who can reach it | +| --- | --- | --- | --- | --- | +| `gk-content` | `content` | 4310 | `content.gregkilmer.com` (API and `/mcp`), `http://gk-content:4310` on homelab_network | Tailnet only | +| `gk-admin` | `admin` | 4320 | `admin.gregkilmer.com` | Tailnet only | +| `gk-site` | `site` | 4321 | `beta.gregkilmer.com` until cutover, then `gregkilmer.com` | Tailnet only until cutover, then public through Cloudflare | + +The old site keeps serving gregkilmer.com until phase 8 (cutover). + +Files in the repo: + +- `Dockerfile`: one target per service, plus `check` (typecheck and every test). Node is pinned to `node:22.20.0-alpine`. +- `deploy/compose.yaml`: the stack. Containers run as the vault's owner, with a read-only root filesystem, no capabilities and `no-new-privileges`. +- `deploy/deploy.sh`: builds `check`, then the three images tagged with the commit, and restarts the stack. If the new containers aren't healthy within two minutes, it puts the last good tag back. Run it again on the same commit and it does nothing (`FORCE=1` rebuilds). +- `deploy/rollback.sh`: switches to the previous tag. Run it again to roll forward. +- `.gitea/workflows/deploy.yml`: runs `deploy.sh` on Cosmos for each new commit to master. + +## On Cosmos + +Everything lives in `/home/greg/homelab/gregkilmer`: + +``` +/home/greg/homelab/gregkilmer/ + compose.yaml copied from deploy/compose.yaml by every deploy + .env GK_TAG, GK_PREVIOUS_TAG, VAULT_SITE, PUID, PGID + content.env content service secrets and sync settings + admin.env ADMIN_TOKEN + site.env site secrets: tokens, Turnstile, SMTP + data/content/ content.db and submissions/ (backed up) +``` + +Kopia already covers `/home/greg`, so the `.env` files and `data/` are in the encrypted backups. + +## First-time setup + +1. **Folder and settings.** Run these from a checkout of the repo: + + ```sh + mkdir -p /home/greg/homelab/gregkilmer/data/content + cd /home/greg/homelab/gregkilmer + cp /deploy/stack.env.example .env + cp /services/content/.env.example content.env + cp /deploy/admin.env.example admin.env + cp /services/site/.env.example site.env + chmod 600 .env *.env + ``` + + In `.env`, check that `PUID` and `PGID` match `id -u greg` and `id -g greg`, so the content service can write notes back into the vault. + +2. **Tokens.** Make one per role with `openssl rand -hex 24`: + - In `content.env`, set `CONTENT_TOKENS=reader:,admin:,intake:,agent:`. + - In `admin.env`, set `ADMIN_TOKEN=`. + - In `site.env`, set `CONTENT_TOKEN=` and `CONTENT_INTAKE_TOKEN=`. + - Give agents ``. + + Leave `CONTENT_URL` out of `site.env`; compose sets it. + +3. **The rest of the settings.** Fill in Healthchecks, the Discord webhook, Turnstile, SMTP and the sync logins. The list is in `/mnt/project-files/website/phase6/setup-on-cosmos.md` and in each `.env.example`. + +4. **Gitea runner.** The workflow needs an `act_runner` on Cosmos with the label `cosmos` running in host mode (`cosmos:host`). It runs as a user in the `docker` group with write access to `/home/greg/homelab/gregkilmer`. + - A runner with the Docker socket is root-equivalent. Register it for this repository only, not for the whole instance. + +5. **Gitea mirror.** Mirror `github.com/Patiun/com.gregkilmer.www` into Gitea as a pull mirror and turn Actions on for it. + - A pull mirror's sync may not raise a push event. The workflow therefore also runs every 15 minutes and deploys only when master has moved. + - Check once that a new commit reaches Cosmos. If the schedule doesn't fire on a mirror either, run the workflow by hand from the Actions tab. + +6. **First deploy.** Run it from the Actions tab, or by hand from a checkout: `./deploy/deploy.sh`. Then check: + - `docker compose -f /home/greg/homelab/gregkilmer/compose.yaml ps` shows three healthy containers. + - `docker logs gk-content` says `Content service on 0.0.0.0:4310: N notes`. + +7. **Nginx Proxy Manager.** Add a proxy host for each hostname. Force SSL on each. For `content`, add `proxy_buffering off;` under Advanced, because MCP streams its responses. + + | Host | Forward to | Access list | + | --- | --- | --- | + | `admin.gregkilmer.com` | `http://gk-admin:4320` | Tailnet only | + | `content.gregkilmer.com` | `http://gk-content:4310` | Tailnet only | + | `beta.gregkilmer.com` | `http://gk-site:4321` | Tailnet only | + + - DNS: an A record for each pointing at 100.99.111.16 (TTL 300), the same as other tailnet hosts. + - Certificates: Let's Encrypt can't reach a tailnet-only host over HTTP, so use NPM's DNS challenge with your DNS provider. This is the same open item as the `*.patiun.com` certificate. + - Agents already on homelab_network can skip NPM and use `http://gk-content:4310/mcp`. + +8. **Turnstile.** Add `beta.gregkilmer.com` and `gregkilmer.com` to the widget's hostnames. + +9. **Healthchecks.** Create these checks: + - one for the content service's full scan (`HEALTHCHECK_URL`), every 5 minutes; + - one per sync job you turn on (Archidekt, Suwayomi, Bambuddy and Homebox hourly; BGG and Kavita daily; covers every 15 minutes). + +10. **Inventory note.** Copy `vault-kit/24.01_PersonalWebsite/GregKilmer.com stack.md` into the vault's `24.01_PersonalWebsite` folder, so `cosmos-inventory` stops listing the containers as undocumented. + +## Backups + +- `gk-content` carries `backup.dump=sqlite`. `cosmos-dump` finds `content.db` in its `/data` mount and copies it with `VACUUM INTO` before Kopia runs. + - `content.db` holds note history, the review queue, saved inquiries and orders, and the sync cache. + - The vault mount has no SQLite files, so nothing else is dumped. +- Form attachments are plain files in `data/content/submissions/`, which Kopia picks up with the rest of `/home/greg`. +- The plan called for a cache folder in `exclusions.conf`. None is needed: the sync cache and search index live inside `content.db` and are small. +- `gk-admin` and `gk-site` keep no data. + +## Cutover (phase 8) + +1. In NPM, point `gregkilmer.com` and `www.gregkilmer.com` at `http://gk-site:4321` with no access list, and retire the old `greg-profile-site` container. +2. Turn on Cloudflare's proxy for both records with SSL mode Full (strict). Cloudflare doesn't cache HTML by default, which is what the site needs: pages change within a minute of a note changing. +3. The site already trusts `X-Forwarded-Host` and `X-Forwarded-Proto` for gregkilmer.com and its subdomains (`security.allowedDomains` in `services/site/astro.config.mjs`), so form posts pass Astro's origin check behind the proxy. It reads the visitor's address from `CF-Connecting-IP` for the form rate limits. + +## Day to day + +- **Deploy:** merge to master on GitHub; Gitea mirrors it and the workflow deploys it. +- **Roll back:** `GK_STACK_DIR=/home/greg/homelab/gregkilmer ./deploy/rollback.sh` from a checkout, or set `GK_TAG` in `.env` to any image tag still on the host and run `docker compose up -d`. +- **Logs:** `docker logs gk-content`. Sync runs also appear on the admin page's Overview. +- **Restart one service:** `docker compose -f /home/greg/homelab/gregkilmer/compose.yaml restart gk-site`. diff --git a/docs/site.md b/docs/site.md new file mode 100644 index 0000000..a59cf96 --- /dev/null +++ b/docs/site.md @@ -0,0 +1,150 @@ +# The site + +`services/site` (`@gk/site`) is the public GregKilmer.com. It is an Astro app +rendered on request by the Node adapter. It reads only the content service, +with a reader token, and only published notes. + +## How a page gets its data + +```astro +--- +import PageShell from "@gk/ui/components/PageShell.astro"; +import { site } from "../lib/page.ts"; +const s = await site(); // the SiteIndex: every published note in memory +const games = s.all("board-game"); // typed: games[0].data.bggId is a number +--- +``` + +`src/lib/content.ts` loads every published note from the content service and +refreshes in the background every 30 seconds (`CONTENT_REFRESH_SECONDS`). If +the service is down, the last good copy keeps serving. + +`src/lib/site.ts` (`SiteIndex`) answers everything a page asks: + +| Method | Gives | +| --- | --- | +| `all(type)`, `one(type)`, `bySlug(type, slug)`, `byName(name or [[link]])` | Notes. | +| `href(note)` | The address that shows a note (see the URL map below). | +| `link("[[Name]]")` | `{ title, href, note }`; `href` is null when the note is not published, so render plain text. | +| `backlinks(note, { type })` | Notes linking to it, newest first. Links to a make count for its craft, a deck for its game, a campaign for its system. | +| `linksFrom(note)` | Notes it links to. | +| `posts({ topic })`, `caseStudies()` | Blog posts and case studies, newest first. | +| `statuses()`, `statusFor(note)` | The status log (newest per subject, pinned first) and a note's current status. | +| `kind(label)` | A status kind's brand icon and page. | +| `pageText(path)` | The Page text note for an address: lede, body, quick facts. | +| `settings()` | Site settings: name, tagline, availability, socials. | +| `render(markdown)` | Safe HTML with wikilinks resolved. | +| `redirect(type, slug)` | Where an old slug now lives; answer it with a 301. | + +`mediaSrc(value)` turns an image field into a `/media/` address, which +passes vault images through from the content service. `formatDate("2026-10-11")` +gives "Oct 11, 2026". + +## URL map + +| Address | Shows | +| --- | --- | +| `/` | Landing | +| `/about` | About me | +| `/resume`, `/resume.pdf` | Resume and its PDF | +| `/work`, `/work/` | Case studies and each study | +| `/inquiries`, `/inquiries/sent` | Inquiry form and its confirmation | +| `/library`, `/library/shelf` | The Library and The Shelf; items are anchors `#` | +| `/workshop`, `/workshop/` | Workshop and each craft; makes are anchors on their craft | +| `/tabletop` | Systems, campaigns, card games and decks as anchors | +| `/now` | Every status; each is an anchor | +| `/blog`, `/blog/topic/`, `/blog/`, `/rss.xml` | Blog, topics, posts, feed | +| `/store`, `/store/`, `/store/order`, `/store/order/sent` | Store, product lines, order form and its confirmation | +| anything else | 404, Off the star chart | + +## Rules for pages + +- Build from the components in `@gk/ui` (see [components.md](components.md)) and + the gk-* classes. Every page uses `PageShell` with its `family` (edge seal) + and `current` (nav entry); its hero goes in the `hero` slot. +- All dark. Panels are Midnight with a gold frame, not Starlight. +- Never invent facts or numbers. Where a note has no value, leave the element + out, or show `[PLACEHOLDER]` where the design needs text Greg has not + written yet. +- No phone numbers. Email addresses stay as they are in the notes. +- Lists of posts are fixed-height rows, not stretching cards. +- Small client scripts (filters, tabs) are fine; the page must read correctly + without them. + +## Running it locally + +```sh +# the content service, against a copy of the Site folder +SITE_DIR=… DATA_DIR=… CONTENT_TOKENS="reader:<24+ chars>" npm run content +# the site +CONTENT_URL=http://localhost:4310 CONTENT_TOKEN= npm run site # http://localhost:4330 +``` + +For a local preview of drafts, set `CONTENT_INCLUDE_DRAFTS=true` with an editor +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. Each page is laid over the +personal Greg Kilmer letterhead, the version without Patiun LLC +(`src/assets/letterhead`, a copy of `Greg-Kilmer-Personal-Letterhead.pdf` from the +branding letterheads; re-copy it when that 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 +`src/assets/pdf-fonts`, and is rebuilt only when the notes behind it change. +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 + +| Page | File | Helpers | +| --- | --- | --- | +| Landing, About, Now | `pages/index.astro`, `about.astro`, `now.astro` | `lib/home.ts`, `components/home` | +| Resume and PDF | `pages/resume.astro`, `resume.pdf.ts` | `lib/resume.ts`, `lib/resume-pdf.ts` | +| Work, case studies | `pages/work/` | `lib/work.ts`, `components/work`, `components/blog/PostLayout` | +| Inquiries, sent, 404 | `pages/inquiries/`, `404.astro` | `lib/work.ts` | +| Library, Shelf | `pages/library/` | `lib/library.ts`, `components/library` | +| Workshop, crafts, Tabletop | `pages/workshop/`, `tabletop.astro` | `lib/world.ts`, `components/world` | +| Blog, topics, posts, RSS | `pages/blog/`, `rss.xml.ts` | `lib/blog.ts`, `components/blog` | +| Store, lines, order | `pages/store/` | `lib/store.ts`, `components/store` | + +## Forms + +The Inquiries form posts to `POST /api/inquiries` and the order form to +`POST /api/orders` (`src/pages/api/`). Both are plain multipart posts that +answer with a 303 redirect, so they work without JavaScript. The rules are +plain functions in `src/lib/forms.ts` (tested in `services/site/test`); the +network calls are in `src/lib/forms-server.ts`. + +1. A filled honeypot field (`leave_blank`) gets the confirmation page and nothing else. +2. The answers are checked. Inquiries take only the chosen type's `job_*`, + `freelance_*` or `patiun_*` fields; others are ignored. +3. A per-address rate limit, then Cloudflare Turnstile when + `TURNSTILE_SECRET_KEY` is set (a missing or bad token, or Cloudflare being + unreachable, turns the post down). +4. Orders only: the line is checked against the live store notes, fetched + from the content service then and there. An order goes through only for a + line sold here, in a category with `ordersOpen: true`, with an open slot + (made to order) or stock, and with in-stock options. Otherwise it is + turned down, whatever was posted. +5. The copy is saved through the content service with `CONTENT_INTAKE_TOKEN`, + then emailed (SMTP via nodemailer) to `CONTACT_TO` or `ORDER_TO`, and the + email's result is recorded on the copy. A failed email still shows the + visitor the confirmation; the admin Inbox flags it. If saving fails the + email is still tried; only when neither works does the form show an error. +6. Success redirects to `/inquiries/sent` (cookie `gk-inquiry`) or + `/store/order/sent` (cookie `gk-order`): URL-encoded JSON, httpOnly, + SameSite Lax, ten minutes, secure in production. A turned-down post goes + back to the form with `?error=` and a `gk-inquiry-draft` or + `gk-order-draft` cookie holding the reason, the marked fields and the + answers (an attachment has to be added again). + +Settings are in `services/site/.env.example`: `CONTENT_INTAKE_TOKEN`, +`PUBLIC_TURNSTILE_SITE_KEY`, `TURNSTILE_SECRET_KEY`, `SMTP_HOST`, `SMTP_PORT`, +`SMTP_USER`, `SMTP_PASS`, `SMTP_FROM`, `SMTP_ALLOW_SELFSIGNED`, `CONTACT_TO`, +`ORDER_TO`, `CONTACT_RATE_LIMIT`, `CONTACT_RATE_WINDOW_MIN`, `ORDER_RATE_LIMIT`, +`ORDER_RATE_WINDOW_MIN`, `FORM_MAX_ATTACHMENT_MB`. The Turnstile widget needs +JavaScript; with the secret set, a visitor without it cannot send a form. diff --git a/legacy/.dockerignore b/legacy/.dockerignore new file mode 100644 index 0000000..425d95f --- /dev/null +++ b/legacy/.dockerignore @@ -0,0 +1,17 @@ +# Keep the Docker build context lean. +node_modules +dist +.astro +.git +.gitignore +.env +.env.* +!.env.example +*.log +npm-debug.log* +.DS_Store +.vscode +.idea +docs +*.md +!README.md 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/legacy/Dockerfile b/legacy/Dockerfile new file mode 100644 index 0000000..0dac769 --- /dev/null +++ b/legacy/Dockerfile @@ -0,0 +1,53 @@ +# ============================================================================= +# Dockerfile — Greg Kilmer Profile Site +# ============================================================================= +# Multi-stage build producing a minimal Node server image for the Cosmos homelab. +# +# Stage 1 (builder): install all deps, run astro build -> dist/server/entry.mjs +# Stage 2 (runtime): copy only dist + prod node_modules, run the Node server +# +# The @astrojs/node standalone adapter emits a self-contained server, so the +# runtime image needs only node_modules + dist. Build once, run anywhere. +# ============================================================================= + +# ---------- Stage 1: build ---------- +FROM node:22.12.0-alpine AS builder +WORKDIR /app + +# Install deps. Copy manifests first to leverage Docker layer caching. +COPY package.json package-lock.json* ./ +RUN npm ci --no-audit --no-fund || npm install --no-audit --no-fund --legacy-peer-deps + +# Copy source and build. SITE_URL can be overridden at build time. +COPY . . +ARG SITE_URL=https://gregkilmer.com +ENV SITE_URL=$SITE_URL +RUN npm run build + +# Prune to production dependencies for the runtime stage. +RUN npm prune --omit=dev + +# ---------- Stage 2: runtime ---------- +FROM node:20-alpine AS runtime +WORKDIR /app + +# Run as a non-root user for defense in depth. +RUN addgroup -S app && adduser -S app -G app + +# Copy production node_modules and the built server + client assets. +COPY --from=builder /app/node_modules ./node_modules +COPY --from=builder /app/dist ./dist +COPY --from=builder /app/package.json ./package.json + +# Astro Node standalone server reads HOST/PORT from env at runtime. +ENV HOST=0.0.0.0 +ENV PORT=4321 +EXPOSE 4321 + +USER app + +# Healthcheck: the server responds 200 on /. +HEALTHCHECK --interval=30s --timeout=5s --start-period=15s --retries=3 \ + CMD wget -qO- http://127.0.0.1:${PORT}/ >/dev/null 2>&1 || exit 1 + +CMD ["node", "./dist/server/entry.mjs"] 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..19c3bda 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,39 +1,44 @@ { - "name": "greg-kilmer-profile", - "version": "1.0.0", + "name": "gregkilmer-com", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "greg-kilmer-profile", - "version": "1.0.0", + "name": "gregkilmer-com", + "workspaces": [ + "packages/*", + "services/*", + "tools/*" + ], + "devDependencies": { + "@types/node": "^22.10.0", + "tsx": "^4.19.0", + "typescript": "^5.7.0", + "vitest": "^5.0.3" + }, + "engines": { + "node": ">=22" + } + }, + "node_modules/@astrojs/astro2tsx": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/@astrojs/astro2tsx/-/astro2tsx-0.1.2.tgz", + "integrity": "sha512-Uj/M9zbrhu5cIZEvWAGTbQJRKSxXWJ/E59BZwt7F+chrKhbfttW+9rkS/l+1QxoJP1KU3UnpM62M7yszfAI0WA==", + "dev": true, + "license": "MIT", "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" + "@emnapi/core": "1.11.3", + "@napi-rs/wasm-runtime": "1.2.4" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" } }, "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==", + "dev": true, "license": "MIT", "dependencies": { "@astrojs/language-server": "^2.16.7", @@ -48,36 +53,47 @@ "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==", + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/@astrojs/compiler-binding/-/compiler-binding-0.5.1.tgz", + "integrity": "sha512-oczyolLUl9mNrtuBuNjuvn5thpHrFLXAHAWGTRg0JDHQ8cGVsVeFekOu2uSoQGgainJIi+A3/9m0DyYwz3Nlnw==", "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" + "@astrojs/compiler-binding-android-arm64": "0.5.1", + "@astrojs/compiler-binding-darwin-arm64": "0.5.1", + "@astrojs/compiler-binding-darwin-x64": "0.5.1", + "@astrojs/compiler-binding-linux-arm64-gnu": "0.5.1", + "@astrojs/compiler-binding-linux-arm64-musl": "0.5.1", + "@astrojs/compiler-binding-linux-x64-gnu": "0.5.1", + "@astrojs/compiler-binding-linux-x64-musl": "0.5.1", + "@astrojs/compiler-binding-wasm32-wasi": "0.5.1", + "@astrojs/compiler-binding-win32-arm64-msvc": "0.5.1", + "@astrojs/compiler-binding-win32-x64-msvc": "0.5.1" + } + }, + "node_modules/@astrojs/compiler-binding-android-arm64": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/@astrojs/compiler-binding-android-arm64/-/compiler-binding-android-arm64-0.5.1.tgz", + "integrity": "sha512-nMb2nMIJtcWLNH+fV75Enmi+jp4tIOYOfsv4XtpgdOWkqc9P4uSSdK5u1xb340AzrXo5V18SxLl/nc3vcqtamQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" } }, "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==", + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/@astrojs/compiler-binding-darwin-arm64/-/compiler-binding-darwin-arm64-0.5.1.tgz", + "integrity": "sha512-5k4+nZ/WRt+dcVow4z7rBuI7iv9A58h/uQ91YJe1PrTivbcCkq3u2Ynx/AZJwII4CsZawBCeGpojkFjutZBhBQ==", "cpu": [ "arm64" ], @@ -91,9 +107,9 @@ } }, "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==", + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/@astrojs/compiler-binding-darwin-x64/-/compiler-binding-darwin-x64-0.5.1.tgz", + "integrity": "sha512-0Buw+YST9Ff2EqQyhRMQujuFq/dijLw3zXWhsvI0dJ2pGrT38iVoxTEca2eBX/PfhlwkVWSRJEKO5gMqKHyeHg==", "cpu": [ "x64" ], @@ -107,9 +123,9 @@ } }, "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==", + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/@astrojs/compiler-binding-linux-arm64-gnu/-/compiler-binding-linux-arm64-gnu-0.5.1.tgz", + "integrity": "sha512-x5LRpyCoMKuu3INr18cn42YhwSOaRpp9b5Ny3eJJ/sWVeejC16Zeshu1JlvlSmEZACLeWxU5JM0RX6T6nI+YyA==", "cpu": [ "arm64" ], @@ -123,9 +139,9 @@ } }, "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==", + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/@astrojs/compiler-binding-linux-arm64-musl/-/compiler-binding-linux-arm64-musl-0.5.1.tgz", + "integrity": "sha512-mL7tQEuDk3rGfaKmrO63nw6WBy1TPVioQGmVyjcAr+C+a/k3SVMYBytNSMqH9mZPT/Swj30JaZ2N842Z1NnOEQ==", "cpu": [ "arm64" ], @@ -139,9 +155,9 @@ } }, "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==", + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/@astrojs/compiler-binding-linux-x64-gnu/-/compiler-binding-linux-x64-gnu-0.5.1.tgz", + "integrity": "sha512-uLGc9jtxwiDe4cQyhNPQim4ECiB/YzbWlXssfLAv8r374kArfTwBHjwR2+j+XVyF71yCx5mF9YNRclRqdNBgeg==", "cpu": [ "x64" ], @@ -155,9 +171,9 @@ } }, "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==", + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/@astrojs/compiler-binding-linux-x64-musl/-/compiler-binding-linux-x64-musl-0.5.1.tgz", + "integrity": "sha512-G0cywinaQUc8v1MrytvYKUX7svTDMmYtOCZfxjwKPbJi59zIqM+Ll0rWgO0GQWLZH3XoPYo19ZwlP3Xt9ep9YA==", "cpu": [ "x64" ], @@ -171,25 +187,27 @@ } }, "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==", + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/@astrojs/compiler-binding-wasm32-wasi/-/compiler-binding-wasm32-wasi-0.5.1.tgz", + "integrity": "sha512-yMhLL8asmLydoAdnEZxAw6eLfNDs3YJl6jqn0pjPt6zrb2U5qIDLnii4/twThpIFODKkQ6YDpBRTOz/vSfZCxw==", "cpu": [ "wasm32" ], "license": "MIT", "optional": true, "dependencies": { - "@napi-rs/wasm-runtime": "^1.2.0" + "@emnapi/core": "1.11.3", + "@emnapi/runtime": "1.11.3", + "@napi-rs/wasm-runtime": "^1.2.4" }, "engines": { - "node": ">=14.0.0" + "node": "^20.19.0 || >=22.12.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==", + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/@astrojs/compiler-binding-win32-arm64-msvc/-/compiler-binding-win32-arm64-msvc-0.5.1.tgz", + "integrity": "sha512-aClIG6poFt3Wf/bZykrEAX2nAnKvBHW6RXiKUa69QjBtWH0/FOlOAjnFcxRNlhej2AMWNYFx0o+LGjwMcH8P7Q==", "cpu": [ "arm64" ], @@ -203,9 +221,9 @@ } }, "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==", + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/@astrojs/compiler-binding-win32-x64-msvc/-/compiler-binding-win32-x64-msvc-0.5.1.tgz", + "integrity": "sha512-/E6uBCagY70YpWqssBC+/FuniNSloeQPRgj7l3LDWiqjxIyTLnIgX1sTt18FU7jrigetpSnC9cHAM6b6IxSgPw==", "cpu": [ "x64" ], @@ -219,21 +237,21 @@ } }, "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==", + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/@astrojs/compiler-rs/-/compiler-rs-0.5.1.tgz", + "integrity": "sha512-1k1acnX7DepIiea9U9/Y+d1hdAFfylu1ZQQkkYgRSwBOBAsXInolyZuh/2tax+83vu/A+hOYP6P3+265RvMTvw==", "license": "MIT", "dependencies": { - "@astrojs/compiler-binding": "0.3.2" + "@astrojs/compiler-binding": "0.5.1" }, "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==", + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@astrojs/internal-helpers/-/internal-helpers-0.11.0.tgz", + "integrity": "sha512-3rzxJ+xbo0+8YyqOzLziIN32wmsHdCjEVz2sGOpRxJ+Ben/KiLph4ItxBy1abEL+E8fkRzqjg0rfXmaHJGw9JA==", "license": "MIT", "dependencies": { "@types/hast": "^3.0.4", @@ -247,20 +265,20 @@ } }, "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==", + "version": "2.17.2", + "resolved": "https://registry.npmjs.org/@astrojs/language-server/-/language-server-2.17.2.tgz", + "integrity": "sha512-WzqCBYo0RtKrkPLPMnTz2+UOQPosaFhoG/PvoSyPIgBU0BKeS6qeaYv7Rscq7P1LxJdoN0VosQF2h2TZTsIGHg==", + "dev": true, "license": "MIT", "dependencies": { - "@astrojs/compiler": "^2.13.1", + "@astrojs/astro2tsx": "^0.1.2", "@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", + "tinyglobby": "^0.2.17", "volar-service-css": "0.0.71", "volar-service-emmet": "0.0.71", "volar-service-html": "0.0.71", @@ -269,7 +287,7 @@ "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" + "vscode-uri": "^3.2.0" }, "bin": { "astro-ls": "bin/nodeServer.js" @@ -287,90 +305,30 @@ } } }, - "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==", + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@astrojs/markdown-satteri/-/markdown-satteri-0.4.2.tgz", + "integrity": "sha512-g4QehXnB/MzxQWnSf17NciFInMqEJy9948p8cc7zGg/r7995m0Ln5PMr3Xlnj24iey1LchaoZ3SzTOa3D4P/3w==", "license": "MIT", "dependencies": { - "@astrojs/internal-helpers": "0.10.2", + "@astrojs/internal-helpers": "0.11.0", "@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 - } + "satteri": "^0.10.3" } }, "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==", + "version": "11.1.6", + "resolved": "https://registry.npmjs.org/@astrojs/node/-/node-11.1.6.tgz", + "integrity": "sha512-jNZ10/vAT+HuXkX0E/tITsIYCSn3BmAvsooVAN1OjagG+FEzelpUWToy1bA3x6Irmu0VaknWRfReY+1twd35sQ==", "license": "MIT", "dependencies": { - "@astrojs/internal-helpers": "0.10.2", + "@astrojs/internal-helpers": "0.11.0", "send": "^1.2.1", "server-destroy": "^1.0.1" }, "peerDependencies": { - "astro": "^7.0.0" + "astro": "^7.2.1" } }, "node_modules/@astrojs/prism": { @@ -385,28 +343,6 @@ "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", @@ -426,144 +362,12 @@ "version": "0.2.4", "resolved": "https://registry.npmjs.org/@astrojs/yaml2ts/-/yaml2ts-0.2.4.tgz", "integrity": "sha512-8oddpOae35pJsXPQXhTkM0ypfKPskVsh2bCxRtbf7e+/Epw2nReakFYpLKjZMEr75CsoF203PMnCocpfz0s69A==", + "dev": true, "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", @@ -582,32 +386,10 @@ "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==", + "version": "7.29.9", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.9.tgz", + "integrity": "sha512-CjXrNHTnvqBVqHgdBysY3vk2T8tpJHb5/RMeHJBTyVa9xgugCB0CJTx/3oO8RV2QRQP391RWpB7D6hLjm8V9uA==", "license": "MIT", "dependencies": { "@babel/types": "^7.29.8" @@ -619,68 +401,6 @@ "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", @@ -695,9 +415,9 @@ } }, "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==", + "version": "0.10.5", + "resolved": "https://registry.npmjs.org/@bruits/satteri-darwin-arm64/-/satteri-darwin-arm64-0.10.5.tgz", + "integrity": "sha512-27KTVl4TJkVahMy/ohyA7qd4938G5UNneFUz/PsScYfpIhj0IVAS23mpcJXdPF44sa6nva198lmV/cKIb2YPyA==", "cpu": [ "arm64" ], @@ -708,9 +428,9 @@ ] }, "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==", + "version": "0.10.5", + "resolved": "https://registry.npmjs.org/@bruits/satteri-darwin-x64/-/satteri-darwin-x64-0.10.5.tgz", + "integrity": "sha512-IjnLe3nKspq6qaeqGgjT7MT8VrTV74yWRlaag7ZdNsI8TDAYZ0iPxMCo+9KQZHUk5EyVB+reBI/PFWL5KuFw9Q==", "cpu": [ "x64" ], @@ -721,9 +441,9 @@ ] }, "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==", + "version": "0.10.5", + "resolved": "https://registry.npmjs.org/@bruits/satteri-linux-arm64-gnu/-/satteri-linux-arm64-gnu-0.10.5.tgz", + "integrity": "sha512-glkYXZCJywjP13v67eAyAMSJdF+ncvEbYvgi/wOtffL9tQ27lr/zsyzUfgs+ovjJ9d8JNQKiXeiArJcX8PJL9w==", "cpu": [ "arm64" ], @@ -734,9 +454,9 @@ ] }, "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==", + "version": "0.10.5", + "resolved": "https://registry.npmjs.org/@bruits/satteri-linux-arm64-musl/-/satteri-linux-arm64-musl-0.10.5.tgz", + "integrity": "sha512-yWdgG1g17Nh2QyGVlFUxGRa3FEFwiMcpZEyMNWkbM3deC94cmVc+/i9OuyFpdKuWo3GkgoCtYVOoxk1uCnCZIA==", "cpu": [ "arm64" ], @@ -747,9 +467,9 @@ ] }, "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==", + "version": "0.10.5", + "resolved": "https://registry.npmjs.org/@bruits/satteri-linux-x64-gnu/-/satteri-linux-x64-gnu-0.10.5.tgz", + "integrity": "sha512-FVaLoPT1fBgGl0J+AYebyyXJYBachGl8Oyyrf1lye4RTqCB4S0Gwkj1uM9RJyThUOvx5VUmAT1CnNh1SFHA+kw==", "cpu": [ "x64" ], @@ -760,9 +480,9 @@ ] }, "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==", + "version": "0.10.5", + "resolved": "https://registry.npmjs.org/@bruits/satteri-linux-x64-musl/-/satteri-linux-x64-musl-0.10.5.tgz", + "integrity": "sha512-EHpVAx2bqW3GINHTKkljtxVfQmVDGWIuwOYOP5YghTj+0PkBa2o8oKPRtQ9Kbsr1Fye8jtUcDjhwj2jMNugZKg==", "cpu": [ "x64" ], @@ -773,9 +493,9 @@ ] }, "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==", + "version": "0.10.5", + "resolved": "https://registry.npmjs.org/@bruits/satteri-wasm32-wasi/-/satteri-wasm32-wasi-0.10.5.tgz", + "integrity": "sha512-ypz8c/Zmipxp4IoeDa228Gstv6TLzVmNs3yC6wKCoNSOjx1iwpgzu87Y3hTkXFdwChVGU85qeUDuOIarGUZQLw==", "cpu": [ "wasm32" ], @@ -784,7 +504,7 @@ "dependencies": { "@emnapi/core": "1.11.1", "@emnapi/runtime": "1.11.1", - "@napi-rs/wasm-runtime": "^1.1.6" + "@napi-rs/wasm-runtime": "^1.2.3" }, "engines": { "node": ">=14.0.0" @@ -822,9 +542,9 @@ } }, "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==", + "version": "0.10.5", + "resolved": "https://registry.npmjs.org/@bruits/satteri-win32-arm64-msvc/-/satteri-win32-arm64-msvc-0.10.5.tgz", + "integrity": "sha512-siTV88nb0LRqNpkL2gXboqCwVdq95sLtzMHS1/3eONV2gLbB3NAK46wmSMvCO/yquBvI2lvaFIfd8P12ecsxBw==", "cpu": [ "arm64" ], @@ -835,9 +555,9 @@ ] }, "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==", + "version": "0.10.5", + "resolved": "https://registry.npmjs.org/@bruits/satteri-win32-x64-msvc/-/satteri-win32-x64-msvc-0.10.5.tgz", + "integrity": "sha512-C3IfPvfvMXmlzBxaMPKFS1XiuV9pu2mC7YqkPk7PSvTgPZ8gbdASIpHpztDLvTTQjqZ0z1Ol8tK5X+V6XXC0wQ==", "cpu": [ "x64" ], @@ -860,9 +580,9 @@ } }, "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==", + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/@clack/core/-/core-1.5.1.tgz", + "integrity": "sha512-iHTrHA8MtVuLl2TfZySmcKv1qO2PoyC9Z7pfSDozEuV5vtY3/wcOPKJXlqJ5Oq2Cx5DDGQGAMVx6HZfRRoVEbQ==", "license": "MIT", "dependencies": { "fast-wrap-ansi": "^0.2.0", @@ -873,12 +593,12 @@ } }, "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==", + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/@clack/prompts/-/prompts-1.8.1.tgz", + "integrity": "sha512-dlT1m5e/0yUL0kRNcQn7yGLVThkgbB0Ga/1AmfDDC/8ik6AIiSf2QLQO2zPYvefsHP0aFgxO93cVLCCfDp7kzQ==", "license": "MIT", "dependencies": { - "@clack/core": "1.4.3", + "@clack/core": "1.5.1", "fast-string-width": "^3.0.2", "fast-wrap-ansi": "^0.2.0", "sisteransi": "^1.0.5" @@ -891,6 +611,7 @@ "version": "2.3.3", "resolved": "https://registry.npmjs.org/@emmetio/abbreviation/-/abbreviation-2.3.3.tgz", "integrity": "sha512-mgv58UrU3rh4YgbE/TzgLQwJ3pFsHHhCLqY20aJq+9comytTXUDNGG/SMtSeMJdkpxgXSXunBGLD8Boka3JyVA==", + "dev": true, "license": "MIT", "dependencies": { "@emmetio/scanner": "^1.0.4" @@ -900,6 +621,7 @@ "version": "2.1.8", "resolved": "https://registry.npmjs.org/@emmetio/css-abbreviation/-/css-abbreviation-2.1.8.tgz", "integrity": "sha512-s9yjhJ6saOO/uk1V74eifykk2CBYi01STTK3WlXWGOepyKa23ymJ053+DNQjpFcy1ingpaO7AxCcwLvHFY9tuw==", + "dev": true, "license": "MIT", "dependencies": { "@emmetio/scanner": "^1.0.4" @@ -909,6 +631,7 @@ "version": "0.4.1", "resolved": "https://registry.npmjs.org/@emmetio/css-parser/-/css-parser-0.4.1.tgz", "integrity": "sha512-2bC6m0MV/voF4CTZiAbG5MWKbq5EBmDPKu9Sb7s7nVcEzNQlrZP6mFFFlIaISM8X6514H9shWMme1fCm8cWAfQ==", + "dev": true, "license": "MIT", "dependencies": { "@emmetio/stream-reader": "^2.2.0", @@ -919,6 +642,7 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/@emmetio/html-matcher/-/html-matcher-1.3.0.tgz", "integrity": "sha512-NTbsvppE5eVyBMuyGfVu2CRrLvo7J4YHb6t9sBFLyY03WYhXET37qA4zOYUjBWFCRHO7pS1B9khERtY0f5JXPQ==", + "dev": true, "license": "ISC", "dependencies": { "@emmetio/scanner": "^1.0.0" @@ -928,26 +652,29 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/@emmetio/scanner/-/scanner-1.0.4.tgz", "integrity": "sha512-IqRuJtQff7YHHBk4G8YZ45uB9BaAGcwQeVzgj/zj8/UdOhtQpEIupUhSk8dys6spFIWVZVeK20CzGEnqR5SbqA==", + "dev": true, "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==", + "dev": true, "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==", + "dev": true, "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==", + "devOptional": true, "license": "MIT", - "optional": true, "dependencies": { "@emnapi/wasi-threads": "1.2.3", "tslib": "^2.4.0" @@ -957,8 +684,8 @@ "version": "1.11.3", "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.3.tgz", "integrity": "sha512-Xz4Tpyki7XyrpbUK1jR1AhdAdaXyhhY4lZ3neLodmhpuWfy2PAQN5B46sAiU4liOXGLkHypn/qU+jvfWSCYYLA==", + "devOptional": true, "license": "MIT", - "optional": true, "dependencies": { "tslib": "^2.4.0" } @@ -967,16 +694,16 @@ "version": "1.2.3", "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.3.tgz", "integrity": "sha512-ELEBe8PsLvvJ6QMr0zLt8ffvOHW/dc1m3CEzNMg7aJUv3bMaoDtw2TXyDAwkYBuroxxuHEwhRTLJSe5sya547g==", + "devOptional": true, "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==", + "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" ], @@ -990,9 +717,9 @@ } }, "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" ], @@ -1006,9 +733,9 @@ } }, "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" ], @@ -1022,9 +749,9 @@ } }, "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" ], @@ -1038,9 +765,9 @@ } }, "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" ], @@ -1054,9 +781,9 @@ } }, "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" ], @@ -1070,9 +797,9 @@ } }, "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" ], @@ -1086,9 +813,9 @@ } }, "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" ], @@ -1102,9 +829,9 @@ } }, "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" ], @@ -1118,9 +845,9 @@ } }, "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" ], @@ -1134,9 +861,9 @@ } }, "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" ], @@ -1150,9 +877,9 @@ } }, "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" ], @@ -1166,9 +893,9 @@ } }, "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" ], @@ -1182,9 +909,9 @@ } }, "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" ], @@ -1198,9 +925,9 @@ } }, "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" ], @@ -1214,9 +941,9 @@ } }, "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" ], @@ -1230,9 +957,9 @@ } }, "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" ], @@ -1246,9 +973,9 @@ } }, "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" ], @@ -1262,9 +989,9 @@ } }, "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" ], @@ -1278,9 +1005,9 @@ } }, "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" ], @@ -1294,9 +1021,9 @@ } }, "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" ], @@ -1310,9 +1037,9 @@ } }, "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" ], @@ -1326,9 +1053,9 @@ } }, "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" ], @@ -1342,9 +1069,9 @@ } }, "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" ], @@ -1358,9 +1085,9 @@ } }, "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" ], @@ -1374,9 +1101,9 @@ } }, "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" ], @@ -1389,47 +1116,41 @@ "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/admin": { + "resolved": "services/admin", + "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/@gk/content": { + "resolved": "services/content", + "link": true }, - "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/@gk/migrate": { + "resolved": "tools/migrate", + "link": true }, - "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/@gk/schema": { + "resolved": "packages/schema", + "link": true }, - "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/@gk/site": { + "resolved": "services/site", + "link": true + }, + "node_modules/@gk/ui": { + "resolved": "packages/ui", + "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/@img/colour": { "version": "1.1.0", @@ -1442,9 +1163,9 @@ } }, "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==", + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.35.5.tgz", + "integrity": "sha512-QRUlFQ0WxvdWyqqG/WtI3iupfD5rBzmCHXSdPsY91sAtVtTo7Q4cb6zOccZ3gqEqkr0f1As1ehLqmEpDsRf+lg==", "cpu": [ "arm64" ], @@ -1460,13 +1181,13 @@ "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-darwin-arm64": "1.3.2" + "@img/sharp-libvips-darwin-arm64": "1.3.4" } }, "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==", + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.35.5.tgz", + "integrity": "sha512-+BR255RhDlpygUpOc/Jdt1nT6DQ3XG/ERo5wbcdOf5Q320dKtPCKPLR1LJs9VGXRaMa8l1uUa0tkCNOXiAxZUw==", "cpu": [ "x64" ], @@ -1482,20 +1203,20 @@ "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-darwin-x64": "1.3.2" + "@img/sharp-libvips-darwin-x64": "1.3.4" } }, "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==", + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-freebsd-wasm32/-/sharp-freebsd-wasm32-0.35.5.tgz", + "integrity": "sha512-Y/z91nEZ4uIBX5X3nfTovjU9lHNKFYbL2lpHCLVNmXQK03VIZvXBBt0KxbPGp2SdGSF+2mQU4e+hQaWOt86iAw==", "license": "Apache-2.0", "optional": true, "os": [ "freebsd" ], "dependencies": { - "@img/sharp-wasm32": "0.35.3" + "@img/sharp-wasm32": "0.35.5" }, "engines": { "node": ">=20.9.0" @@ -1505,9 +1226,9 @@ } }, "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==", + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.3.4.tgz", + "integrity": "sha512-5R89nBYiRdUlSWJxPhO+GVtaXzXSxKnRu/xqMn3KTA3L9EB9Oy/P+Nn2f2vlhPuUdy/Zusb2DarbyTpGCfEDuw==", "cpu": [ "arm64" ], @@ -1521,9 +1242,9 @@ } }, "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==", + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.3.4.tgz", + "integrity": "sha512-iR2OKH80yi0U+dUplyh3/xdpFvps6YkCwsXenIJxqxR1v9o+xtKTGbS9H7cps+2Vxjc8B1j96p75NmTGjIhtpQ==", "cpu": [ "x64" ], @@ -1537,9 +1258,9 @@ } }, "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==", + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.3.4.tgz", + "integrity": "sha512-LmRtTsOHuvM2+wlO2Db37dx5MiZhB0FvSunciw48YjdOkZz9KAiRbm8ujeMOA1INqmei5NapFxYEK1D1ZSidmw==", "cpu": [ "arm" ], @@ -1553,9 +1274,9 @@ } }, "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==", + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.3.4.tgz", + "integrity": "sha512-Y3dgX/6lE2QhQb+Gxy0WZxfg9MEm/JBjamZpS2IklP7xIQoKN4hzAm7KcMVGtaVDt3neE9OKBC7vAfonA/Lr1A==", "cpu": [ "arm64" ], @@ -1569,9 +1290,9 @@ } }, "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==", + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.3.4.tgz", + "integrity": "sha512-Le6boB8Tai0Nis+gIxIpKx68UDVVIqdR8Tin5Yf1z2LJJQLDJvCDRqRu+jC2qCoD+eIomonmOwB4smBRxfVpYQ==", "cpu": [ "ppc64" ], @@ -1585,9 +1306,9 @@ } }, "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==", + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.3.4.tgz", + "integrity": "sha512-aHkkIEHPRdQEegJN20MLmGtxYD9R2wQr3Cwpddnu5+YKMt6Uzax7S9h5gpZTo8wyrGuZSlfQ63OevL5mTyOC7Q==", "cpu": [ "riscv64" ], @@ -1601,9 +1322,9 @@ } }, "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==", + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.3.4.tgz", + "integrity": "sha512-ra/mB6MikESDUO7Yg+Mi95bFBb9GsObURuhnOv3OqknjGe9sZrG8tCe9q0xSIGrtLgvgw0gKnFWcK4blSgQOuQ==", "cpu": [ "s390x" ], @@ -1617,9 +1338,9 @@ } }, "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==", + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.3.4.tgz", + "integrity": "sha512-GJ//SSXbnwSDes02umB3nDJLFcQzw8a18V8fyhqr6tV515tOEMdImjjxj1AoafMRz56F3PHgftnj1QEKSU1zkw==", "cpu": [ "x64" ], @@ -1633,9 +1354,9 @@ } }, "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==", + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.3.4.tgz", + "integrity": "sha512-hvulFwtjUcagsis6BBxHwGFwWoNZjgYmULGVrZcyfNbjA8hKILbRxGg15/7w5HDyXHXUos/j6baAWqnCyQ2DWA==", "cpu": [ "arm64" ], @@ -1649,9 +1370,9 @@ } }, "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==", + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.3.4.tgz", + "integrity": "sha512-6zXKeE/p39I1AmA3cJG35eyBGNqNddLnUXjhwBnsGjFPWqf5VKkDBEqaEkPDoTEtkxwi2vv8Tcr2mDyP4So7Fg==", "cpu": [ "x64" ], @@ -1665,9 +1386,9 @@ } }, "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==", + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.35.5.tgz", + "integrity": "sha512-LEaXK2WdXVK5ykcw0buWyPMsmLLL2vpHLD6yrNSW+JGEL3BZPA4tpKN6iaMc4AxTTAoaX/sU1rOL51lcIz48ZQ==", "cpu": [ "arm" ], @@ -1683,13 +1404,13 @@ "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-linux-arm": "1.3.2" + "@img/sharp-libvips-linux-arm": "1.3.4" } }, "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==", + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.35.5.tgz", + "integrity": "sha512-LYVx5JTsOM2CBzmxreh+nl64/3H6Xb09iSLknqH47z2T2DFFxDeFLP5y4dJwe6H7uGQlHPyEEtIqyo3DYsRwdQ==", "cpu": [ "arm64" ], @@ -1705,13 +1426,13 @@ "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-linux-arm64": "1.3.2" + "@img/sharp-libvips-linux-arm64": "1.3.4" } }, "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==", + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.35.5.tgz", + "integrity": "sha512-QVxAAq8evVRI9ia2vqgwrmWucn5Dfv+JdWzj75pD8omHLPSP7f8p20O8jxzjCcuCEQEOtYOZUmX1hkiZ0kdevA==", "cpu": [ "ppc64" ], @@ -1727,13 +1448,13 @@ "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-linux-ppc64": "1.3.2" + "@img/sharp-libvips-linux-ppc64": "1.3.4" } }, "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==", + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.35.5.tgz", + "integrity": "sha512-LtdreXguaavKODPIfzJ4kffx7UNt1omwtK0rch4EBbbSTXPnxWmYSayXdLJw0fJzQ97kHt1gL/yh4tvU+nCyRQ==", "cpu": [ "riscv64" ], @@ -1749,13 +1470,13 @@ "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-linux-riscv64": "1.3.2" + "@img/sharp-libvips-linux-riscv64": "1.3.4" } }, "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==", + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.35.5.tgz", + "integrity": "sha512-UZasTOFiYzotTsGOCu42BfUzP6Tu6Do/947iRm1RsLKvlllxwGcn4RN27LibGWceix4Y+Pmw3jsnTcCQIgWjqA==", "cpu": [ "s390x" ], @@ -1771,13 +1492,13 @@ "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-linux-s390x": "1.3.2" + "@img/sharp-libvips-linux-s390x": "1.3.4" } }, "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==", + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.35.5.tgz", + "integrity": "sha512-SxFtLTeJInhAA9Q836kux2vZNeOBQEx658qvbboZScr0wIARym3IcGmW7KpVD5sbVg0Ojy+udFQdayYIZyoNog==", "cpu": [ "x64" ], @@ -1793,13 +1514,13 @@ "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-linux-x64": "1.3.2" + "@img/sharp-libvips-linux-x64": "1.3.4" } }, "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==", + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.35.5.tgz", + "integrity": "sha512-9HbMclmI1zlNkFRs3z9/eBtDjfD0sGlrX1z6b1qwmiFY5ElDLh4BC0LPBdVp7z1DXFiKlIcznf+ZlsuZzLxQqg==", "cpu": [ "arm64" ], @@ -1815,13 +1536,13 @@ "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-linuxmusl-arm64": "1.3.2" + "@img/sharp-libvips-linuxmusl-arm64": "1.3.4" } }, "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==", + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.35.5.tgz", + "integrity": "sha512-4KOphqB035HrVdqLZfCgMzzERrQkkzOwRhl4OAkRO1YCldbaFjySXMaK534Mo0V+LndnlJk+sbUyLeU0ULyD1A==", "cpu": [ "x64" ], @@ -1837,17 +1558,17 @@ "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-linuxmusl-x64": "1.3.2" + "@img/sharp-libvips-linuxmusl-x64": "1.3.4" } }, "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==", + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.35.5.tgz", + "integrity": "sha512-Ptsga1su4tQx+LLF1ECS9U6nz5kmrXKo6XVbtR48Ke3ZRxxgaWBu7IDtEe1quo8hiupwm6WFqxVlXaSf7IINGQ==", "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", "optional": true, "dependencies": { - "@emnapi/runtime": "^1.11.1" + "@emnapi/runtime": "^1.11.3" }, "engines": { "node": ">=20.9.0" @@ -1857,16 +1578,16 @@ } }, "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==", + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-webcontainers-wasm32/-/sharp-webcontainers-wasm32-0.35.5.tgz", + "integrity": "sha512-hfhF/FmoQyTUkA0bIKFOtw536BQSeBMe6BF6QyWlrPxT754+TFLaZ7sKKTfvvM0yJgKgaYTwnFCIZ/GuDw5SUA==", "cpu": [ "wasm32" ], "license": "Apache-2.0", "optional": true, "dependencies": { - "@img/sharp-wasm32": "0.35.3" + "@img/sharp-wasm32": "0.35.5" }, "engines": { "node": ">=20.9.0" @@ -1876,9 +1597,9 @@ } }, "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==", + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.35.5.tgz", + "integrity": "sha512-X4t7g+7ZA5DKblCBEXGjUqqemj4vczING/5viFwAL8h4N3qYeyjwdCvRLHi4EdOUI+2Z7UFlp1VM+p/AuEtm6Q==", "cpu": [ "arm64" ], @@ -1895,9 +1616,9 @@ } }, "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==", + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.35.5.tgz", + "integrity": "sha512-5Zm82LoBc43nhwNybZlG7Y1KO//Zhsn306fQl29ZOuStHLGTo3BWL83q3cznX0poxSAMuYL1On/BHBxkBeKr6A==", "cpu": [ "ia32" ], @@ -1914,9 +1635,9 @@ } }, "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==", + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.35.5.tgz", + "integrity": "sha512-x76eH0vEiHlcMQu8Y8IenntaACtddpT6W0wmXtWrnKcnKI7ME5DdgqhAD6SEWOEl1v2zDvkZDhFA9KnURwpfqg==", "cpu": [ "x64" ], @@ -1932,94 +1653,79 @@ "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==", + "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==", "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==", + "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": { - "@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" + "@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" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "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/@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==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.2.4.tgz", + "integrity": "sha512-AJxoUD2/15ESHbvpcyjU274nsAPLuOtPHCk0vKJM5pj//Fg/B1FXNWjPnXTT9PymCYYiHo4zPj0ZomXBKhoy7g==", + "devOptional": true, "license": "MIT", - "optional": true, "dependencies": { "@tybys/wasm-util": "^0.10.3" }, @@ -2031,8 +1737,32 @@ "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" + "@emnapi/core": "^1.7.1 || ^2.0.0-alpha.4", + "@emnapi/runtime": "^1.7.1 || ^2.0.0-alpha.4" + } + }, + "node_modules/@noble/ciphers": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@noble/ciphers/-/ciphers-1.3.0.tgz", + "integrity": "sha512-2I0gnIVPtfnMw9ee9h1dJG7tp81+8Ob3OJb3Mv37rx5L40/b0i7djjCVvGOVqc9AEIQyvyu1i6ypKdFw8R8gQw==", + "license": "MIT", + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@noble/hashes": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", + "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==", + "license": "MIT", + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" } }, "node_modules/@oslojs/encoding": { @@ -2042,18 +1772,64 @@ "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==", "license": "MIT", "funding": { - "url": "https://github.com/sponsors/Boshen" + "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", + "integrity": "sha512-Vu/NICrgdRvUOeaJZIyB2FP63s6fO/4ud5l8dXmxDx9yDyn+mN0y+gsvsVviSli+T361Aqo0h46EuMoobrwawA==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" } }, "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==", + "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" ], @@ -2067,9 +1843,9 @@ } }, "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" ], @@ -2083,9 +1859,9 @@ } }, "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" ], @@ -2099,9 +1875,9 @@ } }, "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" ], @@ -2115,9 +1891,9 @@ } }, "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" ], @@ -2131,9 +1907,9 @@ } }, "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" ], @@ -2147,9 +1923,9 @@ } }, "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" ], @@ -2163,9 +1939,9 @@ } }, "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" ], @@ -2179,9 +1955,9 @@ } }, "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" ], @@ -2195,9 +1971,9 @@ } }, "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" ], @@ -2211,9 +1987,9 @@ } }, "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" ], @@ -2227,9 +2003,9 @@ } }, "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" ], @@ -2243,9 +2019,9 @@ } }, "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" ], @@ -2259,9 +2035,9 @@ } }, "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" ], @@ -2275,19 +2051,19 @@ } }, "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==", "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==", + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/@shikijs/core/-/core-4.5.0.tgz", + "integrity": "sha512-6CIgwq+bt2JZ3OSjb/nBkkwEf/CfpHcbam+Mg5Pllco7HAGRQ2/EFhZrrJtbyNTgKwknfVS+llsXGygiDGwJWw==", "license": "MIT", "dependencies": { - "@shikijs/primitive": "4.4.2", - "@shikijs/types": "4.4.2", + "@shikijs/primitive": "4.5.0", + "@shikijs/types": "4.5.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.5", "hast-util-to-html": "^9.0.5" @@ -2297,12 +2073,12 @@ } }, "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==", + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/@shikijs/engine-javascript/-/engine-javascript-4.5.0.tgz", + "integrity": "sha512-Ed6UdEj84LyYkwnxM5CvSMWAD+id9ExTYri0/+x41U9zrgNifPSS9lZm0JqBP0VfYI8yr8tY5nbNq9+p9R0kFA==", "license": "MIT", "dependencies": { - "@shikijs/types": "4.4.2", + "@shikijs/types": "4.5.0", "@shikijs/vscode-textmate": "^10.0.2", "oniguruma-to-es": "^4.3.6" }, @@ -2311,12 +2087,12 @@ } }, "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==", + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-4.5.0.tgz", + "integrity": "sha512-dKVmbJecVdB9recl8a/CU4jdpNXyjJQrIcesIkpS70s5HS02ohpnCSpMUbotPnEUzbAcLQ+S4hwZYbkBBiTcJw==", "license": "MIT", "dependencies": { - "@shikijs/types": "4.4.2", + "@shikijs/types": "4.5.0", "@shikijs/vscode-textmate": "^10.0.2" }, "engines": { @@ -2324,24 +2100,24 @@ } }, "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==", + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/@shikijs/langs/-/langs-4.5.0.tgz", + "integrity": "sha512-wwBTqwduXSz0X4v9Qa2EWeFaYJEw1clCncWjoRNQRbVDu3SPRTRWucnouYapKj3b8Q+sk/41Vbc4B5c8nuYzLA==", "license": "MIT", "dependencies": { - "@shikijs/types": "4.4.2" + "@shikijs/types": "4.5.0" }, "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==", + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/@shikijs/primitive/-/primitive-4.5.0.tgz", + "integrity": "sha512-UM7ShlA8oz0OWxLLO8BmO9Ip6fVqdkt8yF0EH7nOCqHfrx1eEAzF0ZUVK7L+I8NEgeu1PEWSRlxaiPPeCb4iDg==", "license": "MIT", "dependencies": { - "@shikijs/types": "4.4.2", + "@shikijs/types": "4.5.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.5" }, @@ -2350,21 +2126,21 @@ } }, "node_modules/@shikijs/themes": { - "version": "4.4.2", - "resolved": "https://registry.npmjs.org/@shikijs/themes/-/themes-4.4.2.tgz", - "integrity": "sha512-H0CFoL07ddDC2Dd6EdrPYNkRhUR6YCkJlnuYFceYYUJJA5TIm2b5B33qqiDYryBExgbKMndFJPb2u1gTuqO37g==", + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/@shikijs/themes/-/themes-4.5.0.tgz", + "integrity": "sha512-zGb87H3UBbpuI4yoh5UguB1XbU+2BDU3kflM7UfZ+Cy2hWo9lBtqjSVK20gmLY6ukYl040hAypGXMgud9vul8Q==", "license": "MIT", "dependencies": { - "@shikijs/types": "4.4.2" + "@shikijs/types": "4.5.0" }, "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==", + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-4.5.0.tgz", + "integrity": "sha512-drtPrtD8N0DENyD2cyt6Ajy+IWsiCLgMxODwLTpvH1UsN6a9jf90tNTPjqwHvA+0Lzu9e0txk8DjfqH1p9UJRw==", "license": "MIT", "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", @@ -2380,322 +2156,42 @@ "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", + "node_modules/@swc/helpers": { + "version": "0.5.23", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.23.tgz", + "integrity": "sha512-5lSsMOTXURePglDfvuAQUqkGek9Hg2kksOYay2m0+XR++b2NWYL/4sWyuvVBIs8oKnJaxkdi9whaL/sqN13afw==", + "license": "Apache-2.0", "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" + "tslib": "^2.8.0" } }, "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==", + "version": "0.10.4", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.4.tgz", + "integrity": "sha512-W3c4gRigFS0T/Ma4qIYF3GDAc5AQdHb1yL5znJT1Zv1YaD9Kitx656wBjvr19qbiosmZT8lWDM5BEMynUqX65A==", + "devOptional": true, "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==", + "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": { - "@babel/parser": "^7.20.7", - "@babel/types": "^7.20.7", - "@types/babel__generator": "*", - "@types/babel__template": "*", - "@types/babel__traverse": "*" + "@types/deep-eql": "*", + "assertion-error": "^2.0.1" } }, - "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/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/@types/estree": { "version": "1.0.9", @@ -2730,18 +2226,6 @@ "@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", @@ -2752,41 +2236,25 @@ } }, "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==", + "devOptional": 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/@types/pdfkit": { + "version": "0.17.6", + "resolved": "https://registry.npmjs.org/@types/pdfkit/-/pdfkit-0.17.6.tgz", + "integrity": "sha512-tIwzxk2uWKp0Cq9JIluQXJid77lYhF52EsIOwhsMF4iWLA6YneoBR1xVKYYdAysHuepUB0OX4tdwMiUDdGKmig==", + "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", @@ -2794,35 +2262,54 @@ "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==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.4.0.tgz", + "integrity": "sha512-1mEZtMKPM09vDmQt5y7YvmN2+DFTP7Tg0EWXdic8/C6VRnpb33e4ghisCIE3WZjsE2N8mf+QV1Zqh7ZFYLWInQ==", "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==", + "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": { - "@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" + "@jridgewell/trace-mapping": "0.3.31", + "@vitest/spy": "5.0.3", + "estree-walker": "^3.0.3", + "magic-string": "^1.2.3" }, - "engines": { - "node": "^20.19.0 || >=22.12.0" + "funding": { + "url": "https://opencollective.com/vitest" }, "peerDependencies": { - "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0" + "msw": "^2.4.9", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "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", + "funding": { + "url": "https://opencollective.com/vitest" } }, "node_modules/@volar/kit": { "version": "2.4.28", "resolved": "https://registry.npmjs.org/@volar/kit/-/kit-2.4.28.tgz", "integrity": "sha512-cKX4vK9dtZvDRaAzeoUdaAJEew6IdxHNCRrdp5Kvcl6zZOqb6jTOfk3kXkIkG3T7oTFXguEMt5+9ptyqYR84Pg==", + "dev": true, "license": "MIT", "dependencies": { "@volar/language-service": "2.4.28", @@ -2839,6 +2326,7 @@ "version": "2.4.28", "resolved": "https://registry.npmjs.org/@volar/language-core/-/language-core-2.4.28.tgz", "integrity": "sha512-w4qhIJ8ZSitgLAkVay6AbcnC7gP3glYM3fYwKV3srj8m494E3xtrCv6E+bWviiK/8hs6e6t1ij1s2Endql7vzQ==", + "dev": true, "license": "MIT", "dependencies": { "@volar/source-map": "2.4.28" @@ -2848,6 +2336,7 @@ "version": "2.4.28", "resolved": "https://registry.npmjs.org/@volar/language-server/-/language-server-2.4.28.tgz", "integrity": "sha512-NqcLnE5gERKuS4PUFwlhMxf6vqYo7hXtbMFbViXcbVkbZ905AIVWhnSo0ZNBC2V127H1/2zP7RvVOVnyITFfBw==", + "dev": true, "license": "MIT", "dependencies": { "@volar/language-core": "2.4.28", @@ -2865,6 +2354,7 @@ "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==", + "dev": true, "license": "MIT", "dependencies": { "@volar/language-core": "2.4.28", @@ -2877,12 +2367,14 @@ "version": "2.4.28", "resolved": "https://registry.npmjs.org/@volar/source-map/-/source-map-2.4.28.tgz", "integrity": "sha512-yX2BDBqJkRXfKw8my8VarTyjv48QwxdJtvRgUpNE5erCsgEUdI2DsLbpa+rOQVAJYshY99szEcRDmyHbF10ggQ==", + "dev": true, "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==", + "dev": true, "license": "MIT", "dependencies": { "@volar/language-core": "2.4.28", @@ -2894,6 +2386,7 @@ "version": "2.11.0", "resolved": "https://registry.npmjs.org/@vscode/emmet-helper/-/emmet-helper-2.11.0.tgz", "integrity": "sha512-QLxjQR3imPZPQltfbWRnHU6JecWTF1QSWhx3GAKQpslx7y3Dp6sIIXhKjiUJ/BR9FX8PVthjr9PD6pNwOJfAzw==", + "dev": true, "license": "MIT", "dependencies": { "emmet": "^2.4.3", @@ -2907,33 +2400,27 @@ "version": "2.3.1", "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-2.3.1.tgz", "integrity": "sha512-H8jvkz1O50L3dMZCsLqiuB2tA7muqbSg1AtGEkN0leAqGjsUzDJir3Zwr02BhqdcITPg3ei3mZ+HjMocAknhhg==", + "dev": true, "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==", + "dev": true, "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==", + "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", - "bin": { - "acorn": "bin/acorn" + "dependencies": { + "mime-types": "^3.0.0", + "negotiator": "^1.0.0" }, "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": ">= 0.6" } }, "node_modules/ajv": { @@ -2956,6 +2443,7 @@ "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==", + "dev": true, "license": "MIT", "peerDependencies": { "ajv": "^8.5.0" @@ -2966,10 +2454,28 @@ } } }, + "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/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==", + "dev": true, "license": "MIT", "peerDependencies": { "ajv": "^8.0.0-beta.0" @@ -2988,9 +2494,10 @@ } }, "node_modules/ansi-regex": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", - "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.4.0.tgz", + "integrity": "sha512-KzTVk2tCWAHtYrvvvaP8bJKJq2pVinhLcGEQdtLIYPbmNGNyYe8QwNaTUYQp2J7/vIsUKt5QCqAfUkYyG9DkOw==", + "dev": true, "license": "MIT", "engines": { "node": ">=12" @@ -3003,6 +2510,7 @@ "version": "6.2.3", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, "license": "MIT", "engines": { "node": ">=12" @@ -3051,34 +2559,25 @@ "node": ">= 0.4" } }, - "node_modules/array-iterate": { + "node_modules/assertion-error": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/array-iterate/-/array-iterate-2.0.1.tgz", - "integrity": "sha512-I1jXZMjAgCMmxT4qxXfPXa6SthSoE8h6gkSI9BGGNv8mP8G/v0blc+qFnZu6K42vTOiuME596QaLO0TP3Lk0xg==", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/astring": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/astring/-/astring-1.9.0.tgz", - "integrity": "sha512-LElXdjswlqjWrPpJFg1Fx4wpkOCxj1TDHlSV4PlaRxHGWko024xICaa97ZkMfs6DRKlCguiAI+rbXv5GWwXIkg==", - "license": "MIT", - "bin": { - "astring": "bin/astring" + "engines": { + "node": ">=12" } }, "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==", + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/astro/-/astro-7.3.5.tgz", + "integrity": "sha512-1p/ernaq/RuESbM5NMjWzbO6SiebMGrGcYlGjMfBhzzgM+n/X7AUmVyw8BqW2QYCdivpsHwJBcD1fqmlTZjZHg==", "license": "MIT", "dependencies": { - "@astrojs/compiler-rs": "^0.3.2", - "@astrojs/internal-helpers": "0.10.2", - "@astrojs/markdown-satteri": "0.3.5", + "@astrojs/compiler-rs": "^0.5.0", + "@astrojs/internal-helpers": "0.11.0", + "@astrojs/markdown-satteri": "0.4.2", "@astrojs/telemetry": "3.3.3", "@capsizecss/unpack": "^4.0.0", "@clack/prompts": "^1.1.0", @@ -3091,43 +2590,44 @@ "common-ancestor-path": "^2.0.0", "cookie": "^2.0.1", "devalue": "^5.8.1", - "diff": "^8.0.3", + "diff": "^9.0.0", "dset": "^3.1.4", "es-module-lexer": "^2.0.0", "esbuild": "^0.28.0", + "find-proc": "0.2.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", + "js-yaml": "^4.3.2", "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", + "obug": "^3.0.0", "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", + "smol-toml": "^1.8.0", + "svgo": "^4.1.0", + "tinyclip": "^1.0.1", "tinyexec": "^1.0.4", "tinyglobby": "^0.2.15", "ultrahtml": "^1.6.0", - "unifont": "~0.7.4", + "unifont": "~0.7.5", "unstorage": "^1.17.5", + "verkit": "^0.4.0", "vite": "^8.0.13", "vitefu": "^1.1.2", "xxhash-wasm": "^1.1.0", "yargs-parser": "^22.0.0", - "zod": "^4.3.6" + "zod": "^4.5.4" }, "bin": { "astro": "bin/astro.mjs" @@ -3142,10 +2642,10 @@ "url": "https://opencollective.com/astrodotbuild" }, "optionalDependencies": { - "sharp": "^0.34.0 || ^0.35.0" + "sharp": "^0.35.4" }, "peerDependencies": { - "@astrojs/markdown-remark": "7.2.2" + "@astrojs/markdown-remark": "^7.3.0" }, "peerDependenciesMeta": { "@astrojs/markdown-remark": { @@ -3153,25 +2653,30 @@ } } }, - "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==", + "node_modules/astro/node_modules/cookie": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-2.0.1.tgz", + "integrity": "sha512-yuToqVvRrj6pfDXREyQAAv8SkAEk/8GS3jQRTiUMm66TVtBYmqQeoEjL2Lmq8Rpo6271vH76InTChTitEAm65w==", "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.5" + "engines": { + "node": ">=22" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "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" - }, + "node_modules/astro/node_modules/obug": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/obug/-/obug-3.0.0.tgz", + "integrity": "sha512-5vvB5+W7ePv+p3uqxi+RcW1XAzLW0/hxt3/4X4Lc4qHudzOhmBiBwOY6DRob4WnanAEGvNcLjF+KNOufrUoEQw==", + "funding": [ + "https://github.com/sponsors/sxzz", + "https://opencollective.com/debug" + ], + "license": "MIT", "engines": { - "node": ">=10" + "node": ">=12.20.0" } }, "node_modules/axobject-query": { @@ -3193,16 +2698,61 @@ "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" + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/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": ">=6.0.0" + "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/boolbase": { @@ -3211,58 +2761,52 @@ "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" - } - ], + "node_modules/brotli": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/brotli/-/brotli-1.3.3.tgz", + "integrity": "sha512-oTKjJdShmDuGW94SyyaoQvAjf30dZaHnjJ8uAF+u2/vGJkJbJPJAT1gDiOJP5v1Zb6f9KEyW/1HpuaWIXtGHPg==", "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" + "base64-js": "^1.1.2" } }, - "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/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/ccount": { "version": "2.0.1", @@ -3274,14 +2818,14 @@ "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==", + "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", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" + "engines": { + "node": ">=18" } }, "node_modules/character-entities-html4": { @@ -3304,20 +2848,11 @@ "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==", + "dev": true, "license": "MIT", "dependencies": { "readdirp": "^4.0.1" @@ -3348,6 +2883,7 @@ "version": "9.0.1", "resolved": "https://registry.npmjs.org/cliui/-/cliui-9.0.1.tgz", "integrity": "sha512-k7ndgKhwoQveBL+/1tqGJYNz097I7WOvwbmmU2AR5+magtbjPWQTS1C5vzGkBC8Ym8UWRzfKUzUUqFLypY4Q+w==", + "dev": true, "license": "ISC", "dependencies": { "string-width": "^7.2.0", @@ -3362,6 +2898,7 @@ "version": "7.2.0", "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "dev": true, "license": "MIT", "dependencies": { "emoji-regex": "^10.3.0", @@ -3375,6 +2912,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/clone": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", + "integrity": "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==", + "license": "MIT", + "engines": { + "node": ">=0.8" + } + }, "node_modules/clsx": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", @@ -3384,16 +2930,6 @@ "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", @@ -3422,31 +2958,83 @@ "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": { + "node_modules/content-disposition": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-2.0.1.tgz", - "integrity": "sha512-yuToqVvRrj6pfDXREyQAAv8SkAEk/8GS3jQRTiUMm66TVtBYmqQeoEjL2Lmq8Rpo6271vH76InTChTitEAm65w==", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-2.0.1.tgz", + "integrity": "sha512-e+H0ZXHSWYrENhQzw1LPuP4oF5MzVKmDU6d3hxlvaPEYLLg62MxtQNPRx4SYSuYJSBUgnQIG4HIN2tEtNv7Dog==", "license": "MIT", "engines": { - "node": ">=22" + "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-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/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/crossws": { "version": "0.3.5", "resolved": "https://registry.npmjs.org/crossws/-/crossws-0.3.5.tgz", @@ -3457,16 +3045,16 @@ } }, "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==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-6.0.0.tgz", + "integrity": "sha512-rZZVSLle8v0+EY8QAkDWrKhpgt6SA5OtHsgBnsj6ZaLb5dmDVOWUDtQitd9ydxxvEjhewNudS6eTVU7uOyzvXw==", "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" + "css-what": "^7.0.0", + "domhandler": "^5.0.3", + "domutils": "^3.2.2", + "nth-check": "^2.1.1" }, "funding": { "url": "https://github.com/sponsors/fb55" @@ -3486,9 +3074,9 @@ } }, "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==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-7.0.0.tgz", + "integrity": "sha512-wD5oz5xibMOPHzy13CyGmogB3phdvcDaB5t0W/Nr5Z2O/agcB8YwOz6e2Lsp10pNDzBoDO9nVa3RGs/2BttpHQ==", "license": "BSD-2-Clause", "engines": { "node": ">= 6" @@ -3530,12 +3118,6 @@ "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", @@ -3553,19 +3135,6 @@ } } }, - "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", @@ -3606,9 +3175,9 @@ } }, "node_modules/devalue": { - "version": "5.9.0", - "resolved": "https://registry.npmjs.org/devalue/-/devalue-5.9.0.tgz", - "integrity": "sha512-RWrqdArjvPbsATEhOPUo6Wndc/iWnkWKlhIrdlF3zMMYo/c3CVtoaVAyLtWxz5h8nSlkHzxnzV2uLydPXmtF+A==", + "version": "5.9.4", + "resolved": "https://registry.npmjs.org/devalue/-/devalue-5.9.4.tgz", + "integrity": "sha512-sPAT4pztbu6586/hrhOnMKS17IJrvg12mXiSPSS3W5qDeN2RGgvZ0diZCm31dBbnevfVmujNO3IM2wrS4Y2Rhg==", "license": "MIT" }, "node_modules/devlop": { @@ -3624,10 +3193,16 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/dfa": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/dfa/-/dfa-1.2.0.tgz", + "integrity": "sha512-ED3jP8saaweFTjeGX8HQPjeC1YYyZs98jGNZx6IiBvxW7JG5v492kamAQB3m2wop07CvU/RQmzcKr6bgcC5D/Q==", + "license": "MIT" + }, "node_modules/diff": { - "version": "8.0.4", - "resolved": "https://registry.npmjs.org/diff/-/diff-8.0.4.tgz", - "integrity": "sha512-DPi0FmjiSU5EvQV0++GFDOJ9ASQUVFh5kD+OzOnYdi7n3Wpm9hWWGfB/O2blfHcMVTL5WkQXSnRiK9makhrcnw==", + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-9.0.0.tgz", + "integrity": "sha512-svtcdpS8CgJyqAjEQIXdb3OjhFVVYjzGAPO8WGCmRbrml64SPw/jJD4GoE98aR7r25A0XcgrK3F02yw9R/vhQw==", "license": "BSD-3-Clause", "engines": { "node": ">=0.3.1" @@ -3647,18 +3222,6 @@ "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", @@ -3709,22 +3272,31 @@ "node": ">=4" } }, + "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/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==", + "dev": true, "license": "MIT", "workspaces": [ "./packages/scanner", @@ -3741,6 +3313,7 @@ "version": "10.6.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", + "dev": true, "license": "MIT" }, "node_modules/encodeurl": { @@ -3752,23 +3325,10 @@ "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==", + "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" @@ -3777,48 +3337,46 @@ "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==", + "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", - "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" + "engines": { + "node": ">= 0.4" } }, - "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==", + "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", + "integrity": "sha512-poHGpORABojJJucnV9KbOavETW8lBVnphkW77ER5/BQ5Fz7oXSoCNek7IH3vR5nRjdsEz926ibFYX8KtLQmdyw==", + "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": { - "@types/estree-jsx": "^1.0.0", - "acorn": "^8.0.0", - "esast-util-from-estree": "^2.0.0", - "vfile-message": "^4.0.0" + "es-errors": "^1.3.0" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "engines": { + "node": ">= 0.4" } }, "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==", "hasInstallScript": true, "license": "MIT", "bin": { @@ -3828,38 +3386,39 @@ "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" + "@esbuild/aix-ppc64": "0.28.2", + "@esbuild/android-arm": "0.28.2", + "@esbuild/android-arm64": "0.28.2", + "@esbuild/android-x64": "0.28.2", + "@esbuild/darwin-arm64": "0.28.2", + "@esbuild/darwin-x64": "0.28.2", + "@esbuild/freebsd-arm64": "0.28.2", + "@esbuild/freebsd-x64": "0.28.2", + "@esbuild/linux-arm": "0.28.2", + "@esbuild/linux-arm64": "0.28.2", + "@esbuild/linux-ia32": "0.28.2", + "@esbuild/linux-loong64": "0.28.2", + "@esbuild/linux-mips64el": "0.28.2", + "@esbuild/linux-ppc64": "0.28.2", + "@esbuild/linux-riscv64": "0.28.2", + "@esbuild/linux-s390x": "0.28.2", + "@esbuild/linux-x64": "0.28.2", + "@esbuild/netbsd-arm64": "0.28.2", + "@esbuild/netbsd-x64": "0.28.2", + "@esbuild/openbsd-arm64": "0.28.2", + "@esbuild/openbsd-x64": "0.28.2", + "@esbuild/openharmony-arm64": "0.28.2", + "@esbuild/sunos-x64": "0.28.2", + "@esbuild/win32-arm64": "0.28.2", + "@esbuild/win32-ia32": "0.28.2", + "@esbuild/win32-x64": "0.28.2" } }, "node_modules/escalade": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -3871,104 +3430,11 @@ "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==", + "dev": true, "license": "MIT", "dependencies": { "@types/estree": "^1.0.0" @@ -3989,6 +3455,112 @@ "integrity": "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==", "license": "MIT" }, + "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", + "integrity": "sha512-KfYbmpRm0VbLjEvVa9yGwCi9GI34xvi7A/HXYWQO65CSD2u3MczUJSuwXKFIxlGsgBQizV9q5J9NHj4VG0n+pA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "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/extend": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", @@ -4017,9 +3589,9 @@ } }, "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==", + "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", @@ -4058,6 +3630,42 @@ } } }, + "node_modules/fflate": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.3.tgz", + "integrity": "sha512-tbZNuJrLwGUp3zshBtdy4W+ORxZuIh8a5ilyIEQDC5rY1f3U20JMry0Ll3WBzU58EZKsEuJFXhb5gwv8CsPvgA==", + "license": "MIT" + }, + "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/find-proc": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/find-proc/-/find-proc-0.2.0.tgz", + "integrity": "sha512-a6h2nTrgB3g5Wrn4au9Ux4kMk7IJtyF6oA8uGn+mk3weLZssUtc1cp6meHIzzuW2o1srrBAxzkmXLbqjRkgB6w==", + "license": "MIT", + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, "node_modules/flattie": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/flattie/-/flattie-1.1.1.tgz", @@ -4076,6 +3684,23 @@ "fontkitten": "^1.0.2" } }, + "node_modules/fontkit": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/fontkit/-/fontkit-2.0.4.tgz", + "integrity": "sha512-syetQadaUEDNdxdugga9CpEYVaQIxOwk7GlwZWWZ19//qW4zE5bknOKeMBDYAASwnpaSHKJITRLMF9m1fp3s6g==", + "license": "MIT", + "dependencies": { + "@swc/helpers": "^0.5.12", + "brotli": "^1.3.2", + "clone": "^2.1.2", + "dfa": "^1.2.0", + "fast-deep-equal": "^3.1.3", + "restructure": "^3.0.0", + "tiny-inflate": "^1.0.3", + "unicode-properties": "^1.4.0", + "unicode-trie": "^2.0.0" + } + }, "node_modules/fontkitten": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/fontkitten/-/fontkitten-1.0.3.tgz", @@ -4088,31 +3713,13 @@ "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==", + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", "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 - } + "engines": { + "node": ">= 0.6" } }, "node_modules/fresh": { @@ -4138,28 +3745,30 @@ "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==", + "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", - "engines": { - "node": ">=6.9.0" + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/get-caller-file": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, "license": "ISC", "engines": { "node": "6.* || 8.* || >= 10.*" } }, "node_modules/get-east-asian-width": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.6.0.tgz", - "integrity": "sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA==", + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.7.0.tgz", + "integrity": "sha512-XjH1AECxf0giL2V1aU8vKyRR2ppRUb5c0EvT7zuJTokQ74bNo52zOtghqdWIqrhUD79fo3x0WfKZdOqxF6LG1Q==", + "dev": true, "license": "MIT", "engines": { "node": ">=18" @@ -4168,6 +3777,43 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "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/get-tsconfig": { "version": "5.0.0-beta.4", "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-5.0.0-beta.4.tgz", @@ -4189,11 +3835,17 @@ "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/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/h3": { "version": "1.15.11", @@ -4212,121 +3864,28 @@ "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==", + "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", - "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" + "engines": { + "node": ">= 0.4" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "url": "https://github.com/sponsors/ljharb" } }, - "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==", + "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": { - "@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" + "function-bind": "^1.1.2" }, - "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" + "engines": { + "node": ">= 0.4" } }, "node_modules/hast-util-to-html": { @@ -4352,68 +3911,6 @@ "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", @@ -4427,21 +3924,13 @@ "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==", + "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", - "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" + "engines": { + "node": ">=16.9.0" } }, "node_modules/html-escaper": { @@ -4461,9 +3950,9 @@ } }, "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==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.3.0.tgz", + "integrity": "sha512-M5t5LlJpS1UHMjvwRQVdFHvPISGeLAxNcrWuJkeGh0KxsqCHZ1O3NXZU/8x7cD0BDcGW8kapxMKTvwlqrNkHkA==", "license": "BSD-2-Clause" }, "node_modules/http-errors": { @@ -4486,17 +3975,45 @@ "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/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/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/iron-webcrypto": { "version": "1.2.1", @@ -4507,40 +4024,6 @@ "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", @@ -4556,16 +4039,6 @@ "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", @@ -4578,25 +4051,31 @@ "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": { + "node_modules/is-promise": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "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/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==", + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.2.tgz", + "integrity": "sha512-SFNOvSJ+Dgf/9An904Yx+CgSlIPCkIpao4qo51lpee25TIRejdH3rhR4EZMGoNx3/TP3O+wzWuiTFl4sqbltzA==", "funding": [ { "type": "github", @@ -4615,35 +4094,17 @@ "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/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/jsonc-parser": { "version": "3.3.1", @@ -4655,15 +4116,16 @@ "version": "4.1.5", "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==", + "dev": true, "license": "MIT", "engines": { "node": ">=6" } }, "node_modules/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==", "license": "MPL-2.0", "dependencies": { "detect-libc": "^2.0.3" @@ -4676,23 +4138,23 @@ "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" ], @@ -4710,9 +4172,9 @@ } }, "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" ], @@ -4730,9 +4192,9 @@ } }, "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" ], @@ -4750,9 +4212,9 @@ } }, "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" ], @@ -4770,9 +4232,9 @@ } }, "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" ], @@ -4790,9 +4252,9 @@ } }, "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" ], @@ -4810,9 +4272,9 @@ } }, "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" ], @@ -4830,9 +4292,9 @@ } }, "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" ], @@ -4850,9 +4312,9 @@ } }, "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" ], @@ -4870,9 +4332,9 @@ } }, "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" ], @@ -4890,9 +4352,9 @@ } }, "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" ], @@ -4909,38 +4371,47 @@ "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==", + "node_modules/linebreak": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/linebreak/-/linebreak-1.1.0.tgz", + "integrity": "sha512-MHp03UImeVhB7XZtjd0E4n6+3xr5Dq/9xI/5FptGk5FrbDR3zagPa2DS6U8ks/3HjbKWG9Q1M2ufOzxV2qLYSQ==", "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" + "dependencies": { + "base64-js": "0.0.8", + "unicode-trie": "^2.0.0" + } + }, + "node_modules/linebreak/node_modules/base64-js": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-0.0.8.tgz", + "integrity": "sha512-3XSA2cR/h/73EzlXXdU6YNycmYI7+kicTxks4eJg2g39biHR84slg2+des+p7iHYhbRg/udIS4TD53WabcOUkw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" } }, "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" + "version": "11.5.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.3.tgz", + "integrity": "sha512-U4N8FgzmWxc8k1VH8Kr6lQg18U7Fjvby6wXHVRX/ZZ7IwWbRMgrRbP0Wrb5q5NVinryp4SQampHKdvtecItxUg==", + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" } }, "node_modules/magic-string": { - "version": "0.30.21", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", - "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-1.4.3.tgz", + "integrity": "sha512-z12OxmaPGE0F4xlpGdjuAUckipLpQlTAuAmyGhNoD7ODpYUzvDfvtxUbjM/jwznyP178oju/KDdyi220wNJ0RQ==", "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==", + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.5.5.tgz", + "integrity": "sha512-UicdXN8zQ3JHlxVq+28afMXPr1z7WNY6+7EJnzTdQWkTAlMLF5fNCCKxJHBQwGaNGR11581EiQmQzx73+MvszA==", "license": "MIT", "dependencies": { "@babel/parser": "^7.29.7", @@ -4948,273 +4419,25 @@ "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==", + "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", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", "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": ">= 0.4" } }, "node_modules/mdast-util-to-hast": { @@ -5238,470 +4461,35 @@ "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" - } - ], + "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", - "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" + "engines": { + "node": ">= 0.8" }, "funding": { "type": "opencollective", - "url": "https://opencollective.com/unified" + "url": "https://opencollective.com/express" } }, - "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": { + "node_modules/merge-descriptors": { "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==", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", + "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", "license": "MIT", - "dependencies": { - "micromark-util-types": "^2.0.0" + "engines": { + "node": ">=18" }, "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" + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/micromark-util-character": { @@ -5724,107 +4512,6 @@ "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", @@ -5841,85 +4528,6 @@ ], "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", @@ -5941,28 +4549,6 @@ "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", @@ -5980,9 +4566,9 @@ "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==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.3.tgz", + "integrity": "sha512-oxB2Ik03hI0gv+VNn9tnh1t1YEe9MDPptViAEgfdf3YQHsn0pzGTgCdlSCJXcwhqm8phaEuM7zeEu3QQzVBrPg==", "funding": [ { "type": "GitHub Sponsors", @@ -6020,21 +4606,6 @@ "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", @@ -6054,12 +4625,13 @@ "version": "0.4.1", "resolved": "https://registry.npmjs.org/muggle-string/-/muggle-string-0.4.1.tgz", "integrity": "sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==", + "dev": true, "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==", "funding": [ { "type": "github", @@ -6074,6 +4646,35 @@ "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/neotraverse": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/neotraverse/-/neotraverse-1.0.1.tgz", @@ -6108,22 +4709,13 @@ "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==", + "version": "10.0.10", + "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-10.0.10.tgz", + "integrity": "sha512-He9XskOFms62SyAKkLu8CcGcYHAo+BSHSsORI8s4PJH8qZgZY4L4lDfvyN0twvmbpyG+eGrxpyBlskj9d9rJ8A==", "license": "MIT-0", "engines": { - "node": ">=6.0.0" + "node": ">=20.0.0" } }, "node_modules/normalize-path": { @@ -6147,10 +4739,32 @@ "url": "https://github.com/fb55/nth-check?sponsor=1" } }, + "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.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" @@ -6172,9 +4786,9 @@ } }, "node_modules/ohash": { - "version": "2.0.11", - "resolved": "https://registry.npmjs.org/ohash/-/ohash-2.0.11.tgz", - "integrity": "sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==", + "version": "2.0.12", + "resolved": "https://registry.npmjs.org/ohash/-/ohash-2.0.12.tgz", + "integrity": "sha512-65S/5gk9YSsaRjcyf7Nfa6h/d3E8/1gslpXfI4W7Dxn/oap8IKRuNT5VXkLQ1YFKIEg4apRY4Pj6aiwFzrDdmw==", "license": "MIT" }, "node_modules/on-finished": { @@ -6189,6 +4803,15 @@ "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/oniguruma-parser": { "version": "0.12.2", "resolved": "https://registry.npmjs.org/oniguruma-parser/-/oniguruma-parser-0.12.2.tgz", @@ -6207,9 +4830,9 @@ } }, "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==", + "version": "7.3.3", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-7.3.3.tgz", + "integrity": "sha512-SKgVvDU5TNIxQ4r30U75BNLC6a+6GOEjngrk/ysM5+Zu3oJuk1SC5ApzIzY/7PjCEjoVPMWJ0zho/4QUiEB7TQ==", "license": "MIT", "dependencies": { "yocto-queue": "^1.2.1" @@ -6222,9 +4845,9 @@ } }, "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==", + "version": "9.3.5", + "resolved": "https://registry.npmjs.org/p-queue/-/p-queue-9.3.5.tgz", + "integrity": "sha512-Lki/zhXFVmDhesBUn/yHRLW9Bte7mE7xoFHzTR8sIomV7a2z+LxYAPkGvdfidNiu4fKSfjtawixxSHcZci5QcQ==", "license": "MIT", "dependencies": { "eventemitter3": "^5.0.4", @@ -6238,9 +4861,9 @@ } }, "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==", + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-7.0.2.tgz", + "integrity": "sha512-prbX4Z3YszrFNgH+MW5Zoeq3baXrMtP/MQnFeET90UB/GtGcGDQ5Usg9OCy6ETjTTntOw1SL2z9fMPUppN3Guw==", "license": "MIT", "engines": { "node": ">=20" @@ -6250,72 +4873,90 @@ } }, "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==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/package-manager-detector/-/package-manager-detector-1.9.0.tgz", + "integrity": "sha512-zAMfbta9nPA8AKkA4WdmNAT8R8s6Dk0/xwPsqNnRidMFcLo1YkLuPDTgs2f64XdzS8vbLNnoJ+uUIkgbz7oRHA==", "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==", + "node_modules/pako": { + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/pako/-/pako-0.2.9.tgz", + "integrity": "sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA==", "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==", + "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", - "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" + "engines": { + "node": ">= 0.8" } }, "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==", + "dev": true, "license": "MIT" }, + "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/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", + "integrity": "sha512-Q/w03ICAQyXfHNfTsg1udp0ADerdBN0s7a6XSPL7J7Ro6ABnafoBOCDBstIO9U02mvzHEV8HrvFIw5iV5ejIRA==", + "license": "MIT", + "dependencies": { + "@noble/ciphers": "^1.3.0", + "@noble/hashes": "^1.8.0", + "fflate": "^0.8.3", + "fontkit": "^2.0.4", + "linebreak": "^1.1.0", + "png-js": "^2.0.0" + } + }, "node_modules/piccolore": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/piccolore/-/piccolore-0.1.3.tgz", @@ -6329,9 +4970,9 @@ "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==", + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz", + "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==", "license": "MIT", "engines": { "node": ">=12" @@ -6340,10 +4981,27 @@ "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/png-js": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/png-js/-/png-js-2.0.0.tgz", + "integrity": "sha512-GdzJuUMc6ZSpxFJWVxtOH1bzYHym+TOnveqUjb+VJIbZWbZzyiRGFiKhbiielfpYbgMlhHVhsJ0FTazfuRFkMA==", + "dependencies": { + "fflate": "^0.8.2" + } + }, "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==", "funding": [ { "type": "opencollective", @@ -6360,18 +5018,19 @@ ], "license": "MIT", "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==", + "version": "3.9.10", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.9.10.tgz", + "integrity": "sha512-skGHTH1Yb9QvaTC4hWnRTzEYtCpApF0UMBPz+t/0ZPzP8ihefo/btKd+QA7pUE0fEwA1SbS/NmhyEcstdXOIdA==", + "dev": true, "license": "MIT", "bin": { "prettier": "bin/prettier.cjs" @@ -6411,6 +5070,39 @@ "url": "https://github.com/sponsors/wooorm" } }, + "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/radix3": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/radix3/-/radix3-1.1.2.tgz", @@ -6430,40 +5122,26 @@ "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==", + "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": { - "scheduler": "^0.27.0" + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.7.0", + "unpipe": "~1.0.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": ">= 0.10" } }, "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==", + "dev": true, "license": "MIT", "engines": { "node": ">= 14.18.0" @@ -6473,73 +5151,6 @@ "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", @@ -6564,150 +5175,11 @@ "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==", + "dev": true, "license": "MIT" }, "node_modules/require-from-string": { @@ -6728,36 +5200,11 @@ "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/restructure": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/restructure/-/restructure-3.0.2.tgz", + "integrity": "sha512-gSfoiOEA0VPE6Tukkrr7I0RBdE0s7H1eFCDBk05l1KIQT1UIKNc5JZy6jdyW6eYH3aR3g5b3PuL77rq0hvwtAw==", + "license": "MIT" }, "node_modules/retext-smartypants": { "version": "6.2.0", @@ -6774,28 +5221,13 @@ "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==", "license": "MIT", "dependencies": { - "@oxc-project/types": "=0.143.0", + "@oxc-project/types": "=0.153.0", "@rolldown/pluginutils": "^1.0.0" }, "bin": { @@ -6805,49 +5237,66 @@ "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" + "@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/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==", + "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/satteri": { - "version": "0.9.5", - "resolved": "https://registry.npmjs.org/satteri/-/satteri-0.9.5.tgz", - "integrity": "sha512-ZuWVl+vnM64y+/TtX8Kosv2c00W+hLQiiwnEL6H0UKVVrxFqMw4D2CJHHQaouVd89OAhtBBfjWLqhKi3TVUV4w==", + "version": "0.10.5", + "resolved": "https://registry.npmjs.org/satteri/-/satteri-0.10.5.tgz", + "integrity": "sha512-Ao1LKpAEa9Wdg0otgbVKViZHEq9ebdXe4DMrp3s9vQAU0HNIuHnFEuMuOcm0ZIXyV0Yzxj91NvhLpvXZJO/5ZQ==", "license": "MIT", "dependencies": { "@types/estree-jsx": "^1.0.5", - "@types/hast": "^3.0.4", + "@types/hast": "^3.0.5", "@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" + "@bruits/satteri-darwin-arm64": "0.10.5", + "@bruits/satteri-darwin-x64": "0.10.5", + "@bruits/satteri-linux-arm64-gnu": "0.10.5", + "@bruits/satteri-linux-arm64-musl": "0.10.5", + "@bruits/satteri-linux-x64-gnu": "0.10.5", + "@bruits/satteri-linux-x64-musl": "0.10.5", + "@bruits/satteri-wasm32-wasi": "0.10.5", + "@bruits/satteri-win32-arm64-msvc": "0.10.5", + "@bruits/satteri-win32-x64-msvc": "0.10.5" } }, "node_modules/sax": { @@ -6859,19 +5308,17 @@ "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==", + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "devOptional": true, "license": "ISC", "bin": { "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" } }, "node_modules/send": { @@ -6900,6 +5347,25 @@ "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/server-destroy": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/server-destroy/-/server-destroy-1.0.1.tgz", @@ -6913,9 +5379,9 @@ "license": "ISC" }, "node_modules/sharp": { - "version": "0.35.3", - "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.35.3.tgz", - "integrity": "sha512-ej0zVHuZGHCiABXcNxeYhpRnPNPAcvbG8RMdBAhDAxLKkCRVSpK3Iyu7qbqw3JMzoj0REeM6f3tJLtVwl0023Q==", + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.35.5.tgz", + "integrity": "sha512-Ywn4OnzGukp7CDMrp08RQ50YKmuwG47brZgIVPTvBaaAfQlRlygrRqSrxdCiL9M+LlzLBiJ68IR1QqvzHyjC7g==", "license": "Apache-2.0", "optional": true, "dependencies": { @@ -6930,31 +5396,31 @@ "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" + "@img/sharp-darwin-arm64": "0.35.5", + "@img/sharp-darwin-x64": "0.35.5", + "@img/sharp-freebsd-wasm32": "0.35.5", + "@img/sharp-libvips-darwin-arm64": "1.3.4", + "@img/sharp-libvips-darwin-x64": "1.3.4", + "@img/sharp-libvips-linux-arm": "1.3.4", + "@img/sharp-libvips-linux-arm64": "1.3.4", + "@img/sharp-libvips-linux-ppc64": "1.3.4", + "@img/sharp-libvips-linux-riscv64": "1.3.4", + "@img/sharp-libvips-linux-s390x": "1.3.4", + "@img/sharp-libvips-linux-x64": "1.3.4", + "@img/sharp-libvips-linuxmusl-arm64": "1.3.4", + "@img/sharp-libvips-linuxmusl-x64": "1.3.4", + "@img/sharp-linux-arm": "0.35.5", + "@img/sharp-linux-arm64": "0.35.5", + "@img/sharp-linux-ppc64": "0.35.5", + "@img/sharp-linux-riscv64": "0.35.5", + "@img/sharp-linux-s390x": "0.35.5", + "@img/sharp-linux-x64": "0.35.5", + "@img/sharp-linuxmusl-arm64": "0.35.5", + "@img/sharp-linuxmusl-x64": "0.35.5", + "@img/sharp-webcontainers-wasm32": "0.35.5", + "@img/sharp-win32-arm64": "0.35.5", + "@img/sharp-win32-ia32": "0.35.5", + "@img/sharp-win32-x64": "0.35.5" }, "peerDependenciesMeta": { "@types/node": { @@ -6962,31 +5428,39 @@ } } }, - "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" + "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": ">=10" + "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/shiki": { - "version": "4.4.2", - "resolved": "https://registry.npmjs.org/shiki/-/shiki-4.4.2.tgz", - "integrity": "sha512-P8F/dFhRevaw2uSdeIYlq/5SXZNY85DPtmXQ947gD1Zj2JqO5AkNvVVBar0Me9JkFx3uzVud/qOtP5ek9NEQGA==", + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/shiki/-/shiki-4.5.0.tgz", + "integrity": "sha512-OcRW72ngQvc+MSVWBRIovC/BTW589iUE/Vxav7VeM4RcY9MUjqB+IR6ILG1k9nuW6LiG/nct9uma4/MBtnZbmQ==", "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/core": "4.5.0", + "@shikijs/engine-javascript": "4.5.0", + "@shikijs/engine-oniguruma": "4.5.0", + "@shikijs/langs": "4.5.0", + "@shikijs/themes": "4.5.0", + "@shikijs/types": "4.5.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.5" }, @@ -6994,6 +5468,78 @@ "node": ">=20" } }, + "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/sisteransi": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", @@ -7001,9 +5547,9 @@ "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==", + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/smol-toml/-/smol-toml-1.9.1.tgz", + "integrity": "sha512-j7WyPdf0CJoutMzMoPLF/bVXHtKZIT9Rb4eGeEbWw8X2t5oirjPgpL+OpaENZrILYaXqYwnVH+qgUwVbl+pUJQ==", "license": "BSD-3-Clause", "engines": { "node": ">= 18" @@ -7012,19 +5558,10 @@ "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==", + "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==", "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" @@ -7049,10 +5586,18 @@ "node": ">= 0.8" } }, + "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/string-width": { - "version": "8.2.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-8.2.2.tgz", - "integrity": "sha512-GaPUh5gfdrYzqeVNZvUfT23vYYxXzKYidUcnMtJg/3rxRV63EFZy3k6xfKlmfeJD0176lnUV/Usr3XcwSvFzpg==", + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-8.3.0.tgz", + "integrity": "sha512-ZbmZM0JCihQN91dWnxoipT2KOEyHqEyfRXUyjuRhW8b/xnqPDoq4gWEVApTVa9db2wN8mmoikgFBbjh71+cGeQ==", + "dev": true, "license": "MIT", "dependencies": { "get-east-asian-width": "^1.5.0", @@ -7083,6 +5628,7 @@ "version": "7.2.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "dev": true, "license": "MIT", "dependencies": { "ansi-regex": "^6.2.2" @@ -7094,37 +5640,19 @@ "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==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/svgo/-/svgo-4.1.0.tgz", + "integrity": "sha512-bkxnTg1kSU0guhIBmibA6UUhrQmPVA1XsQLN+ylCd+UWzbnLkySOcXpyk1mrl05f+pcaCx2eHb+sp6BgMZWX+Q==", "license": "MIT", "dependencies": { "commander": "^11.1.0", - "css-select": "^5.1.0", + "css-select": "^6.0.0", "css-tree": "^3.0.1", - "css-what": "^6.1.0", + "css-what": "^7.0.0", "csso": "^5.0.5", "picocolors": "^1.1.1", - "sax": "^1.5.0" + "sax": "1.6.1" }, "bin": { "svgo": "bin/svgo.js" @@ -7137,44 +5665,35 @@ "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/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": ">=20.0.0" + } + }, "node_modules/tinyclip": { - "version": "0.1.15", - "resolved": "https://registry.npmjs.org/tinyclip/-/tinyclip-0.1.15.tgz", - "integrity": "sha512-uo33abH+Ays0xYaDysoBt494Hb3hsEczMpcC0MwFl773pazORx4fmvKhclhR1wonUbB6vvpRsvVMwnhfqeMc+A==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tinyclip/-/tinyclip-1.0.3.tgz", + "integrity": "sha512-nTv2raKFMzGG6JJp0lpNDsRPdfFE6UHlu2rVEjXo8vkx1ZS3LW2CVXqIaLu8xZlu+6tbFs9V58Qc/Jmk/Ii7wA==", "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==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.3.1.tgz", + "integrity": "sha512-GCvB3aoys96IuDFBMcTB46JOR6mdMtAToqwiW8JlWhsoh1mhHi/xn9ss/Dg7N555GiJyEt2qzoG/NHCwM6h1EA==", "license": "MIT", "engines": { "node": ">=18" @@ -7231,16 +5750,66 @@ "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", "license": "0BSD" }, + "node_modules/tsx": { + "version": "4.23.15", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.23.15.tgz", + "integrity": "sha512-Yiex1Ovn8z2xPpOWckIiysV1SSyRMY9BkLF++q0yKiDxCqRhosKfMg3janKkiLBwZ5c/YryloKwGZcrEmtwxKw==", + "license": "MIT", + "dependencies": { + "esbuild": "~0.28.0" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "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/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==", + "dev": true, "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", @@ -7254,23 +5823,12 @@ "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==", + "dev": true, "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", @@ -7289,12 +5847,42 @@ "integrity": "sha512-Ql87qFHB3s/De2ClA9e0gsnS6zXG27SkTiSJwjCc9MebbfapQfuPzumMIUMi38ezPZVNFcHI9sUIepeQfw8J8Q==", "license": "MIT" }, + "node_modules/undici": { + "version": "8.11.2", + "resolved": "https://registry.npmjs.org/undici/-/undici-8.11.2.tgz", + "integrity": "sha512-u4UB2/IrKdU6lFxumHmmo1a3fCQO5tzQllRorfoRS63txhrB7xTpSn1PftwC4qEHkOaqP95fCWW4lJzwErwzhQ==", + "license": "MIT", + "engines": { + "node": ">=22.19.0" + } + }, "node_modules/undici-types": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-8.3.0.tgz", - "integrity": "sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ==", + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "devOptional": true, "license": "MIT" }, + "node_modules/unicode-properties": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/unicode-properties/-/unicode-properties-1.4.1.tgz", + "integrity": "sha512-CLjCCLQ6UuMxWnbIylkisbRj31qxHPAurvena/0iwSVbQ2G1VY5/HjV0IRabOEbDHlzZlRdCrD4NhB0JtU40Pg==", + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.0", + "unicode-trie": "^2.0.0" + } + }, + "node_modules/unicode-trie": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-trie/-/unicode-trie-2.0.0.tgz", + "integrity": "sha512-x7bc76x0bm4prf1VLg79uhAzKw8DVboClSN5VxJuQ+LKDOVEW9CdH+VY7SP+vX7xCYQqzzgQpFqz15zeLvAtZQ==", + "license": "MIT", + "dependencies": { + "pako": "^0.2.5", + "tiny-inflate": "^1.0.0" + } + }, "node_modules/unified": { "version": "11.0.5", "resolved": "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz", @@ -7315,28 +5903,14 @@ } }, "node_modules/unifont": { - "version": "0.7.4", - "resolved": "https://registry.npmjs.org/unifont/-/unifont-0.7.4.tgz", - "integrity": "sha512-oHeis4/xl42HUIeHuNZRGEvxj5AaIKR+bHPNegRq5LV1gdc3jundpONbjglKpihmJf+dswygdMJn3eftGIMemg==", + "version": "0.7.5", + "resolved": "https://registry.npmjs.org/unifont/-/unifont-0.7.5.tgz", + "integrity": "sha512-ULe/Cs+ZIsq+dcFofNkhqielCrUJnb5mr+Yc4EBM2VlL+6OZR6+cjtI2mT1bJvRBrVncqHAbLURxmPLcCXzWMg==", "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" + "ohash": "^2.0.11", + "undici": "^8.0.0" } }, "node_modules/unist-util-is": { @@ -7352,20 +5926,6 @@ "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", @@ -7379,33 +5939,6 @@ "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", @@ -7434,19 +5967,6 @@ "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", @@ -7461,6 +5981,15 @@ "url": "https://opencollective.com/unified" } }, + "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/unstorage": { "version": "1.17.5", "resolved": "https://registry.npmjs.org/unstorage/-/unstorage-1.17.5.tgz", @@ -7572,15 +6101,6 @@ "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", @@ -7594,34 +6114,25 @@ "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" - } - ], + "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", - "dependencies": { - "escalade": "^3.2.0", - "picocolors": "^1.1.1" + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/verkit": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/verkit/-/verkit-0.4.1.tgz", + "integrity": "sha512-mMVzj0TXExtVdlDEq+Mzp0eyOuyznNpFobNM3uAqe3HVm/Ps2c+u17BligMkZjZCA6EiXpoon8iRTer3iu40SQ==", + "license": "MIT", + "engines": { + "node": ">=18.12.0" }, - "bin": { - "update-browserslist-db": "cli.js" - }, - "peerDependencies": { - "browserslist": ">= 4.21.0" + "funding": { + "url": "https://github.com/sponsors/sxzz" } }, "node_modules/vfile": { @@ -7638,20 +6149,6 @@ "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", @@ -7667,15 +6164,15 @@ } }, "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==", "license": "MIT", "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 +6189,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,255 +6240,6 @@ } } }, - "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", @@ -8011,10 +6259,94 @@ } } }, + "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": { + "@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": { + "@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": { + "@edge-runtime/vm": { + "optional": true + }, + "@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-css": { "version": "0.0.71", "resolved": "https://registry.npmjs.org/volar-service-css/-/volar-service-css-0.0.71.tgz", "integrity": "sha512-wRRFt9BpjMKCazcgOh67MSjUjiWUCAh99DyYSDIOTuxaRjEtDC7PpB0k1Y1wbJIW/pVtMUSVbpPo3UGSm0Byxw==", + "dev": true, "license": "MIT", "dependencies": { "vscode-css-languageservice": "^6.3.0", @@ -8034,6 +6366,7 @@ "version": "0.0.71", "resolved": "https://registry.npmjs.org/volar-service-emmet/-/volar-service-emmet-0.0.71.tgz", "integrity": "sha512-zqjzt6bN95e3CUstBm0PBFAJnrfz0ZAARka87fart46/gNCLLuP3Vujy8V/J8HEziTFLnfkgIASLFYPUhonJcA==", + "dev": true, "license": "MIT", "dependencies": { "@emmetio/css-parser": "^0.4.1", @@ -8054,6 +6387,7 @@ "version": "0.0.71", "resolved": "https://registry.npmjs.org/volar-service-html/-/volar-service-html-0.0.71.tgz", "integrity": "sha512-e8tHPhgQ7ooLfudAEIku+kgd9pWkq3SSz8RbnQDI1+Eb8wbenkLGHqoirLqz5ORLV6wIMr2Iv08RWBG5eOcgpw==", + "dev": true, "license": "MIT", "dependencies": { "vscode-html-languageservice": "^5.3.0", @@ -8073,6 +6407,7 @@ "version": "0.0.71", "resolved": "https://registry.npmjs.org/volar-service-prettier/-/volar-service-prettier-0.0.71.tgz", "integrity": "sha512-Rz7JVH3qD108UCdmIEiZvOBNljMt2nLFdbN8AXcDfn7xD9F5I2aCIsDVqBbXw21PsnxG0b7MfwtNF+zPS/NKUg==", + "dev": true, "license": "MIT", "dependencies": { "vscode-uri": "^3.0.8" @@ -8094,6 +6429,7 @@ "version": "0.0.71", "resolved": "https://registry.npmjs.org/volar-service-typescript/-/volar-service-typescript-0.0.71.tgz", "integrity": "sha512-yTtM/BVT6hoyEYnDtaCyAtNhdNeS/mhTTABlBOdw3NNiRBUin3IznFJpgfjer4c6RYopiPjjQjc9VFhxVl1mLw==", + "dev": true, "license": "MIT", "dependencies": { "path-browserify": "^1.0.1", @@ -8116,6 +6452,7 @@ "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==", + "dev": true, "license": "MIT", "dependencies": { "vscode-uri": "^3.0.8" @@ -8129,22 +6466,11 @@ } } }, - "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==", + "dev": true, "license": "MIT", "dependencies": { "vscode-uri": "^3.0.8", @@ -8163,6 +6489,7 @@ "version": "6.3.10", "resolved": "https://registry.npmjs.org/vscode-css-languageservice/-/vscode-css-languageservice-6.3.10.tgz", "integrity": "sha512-eq5N9Er3fC4vA9zd9EFhyBG90wtCCuXgRSpAndaOgXMh1Wgep5lBgRIeDgjZBW9pa+332yC9+49cZMW8jcL3MA==", + "dev": true, "license": "MIT", "dependencies": { "@vscode/l10n": "^0.0.18", @@ -8175,12 +6502,14 @@ "version": "3.17.5", "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.17.5.tgz", "integrity": "sha512-Ld1VelNuX9pdF39h2Hgaeb5hEZM2Z3jUrrMgWQAu82jMtZp7p3vJT3BzToKtZI7NgQssZje5o0zryOrhQvzQAg==", + "dev": true, "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==", + "dev": true, "license": "MIT", "dependencies": { "@vscode/l10n": "^0.0.18", @@ -8193,6 +6522,7 @@ "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==", + "dev": true, "license": "MIT", "dependencies": { "jsonc-parser": "^3.0.0", @@ -8206,9 +6536,10 @@ } }, "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==", + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-9.0.3.tgz", + "integrity": "sha512-qETYf9PCl1lVfwR+oBmMfc4vP2nqLNA35ABxorw3j06+xlKRgDVxbLgKm4Y/xPL0YWdeDjLoI4ImICIT3nB0zQ==", + "dev": true, "license": "MIT", "engines": { "node": ">=14.0.0" @@ -8218,6 +6549,7 @@ "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==", + "dev": true, "license": "MIT", "dependencies": { "vscode-languageserver-protocol": "3.17.5" @@ -8227,31 +6559,35 @@ } }, "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==", + "version": "3.18.4", + "resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.18.4.tgz", + "integrity": "sha512-CCXbyr99dafRJzJH/0Ryk7ly5T8XPmSR0//OuF2w4m+9JiOin7zcAT5iET8GRE7Z9taFBejb3GbM0a0mbjdbrg==", + "dev": true, "license": "MIT", "dependencies": { - "vscode-jsonrpc": "9.0.1", - "vscode-languageserver-types": "3.18.0" + "vscode-jsonrpc": "9.0.3", + "vscode-languageserver-types": "3.18.4" } }, "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==", + "version": "1.0.15", + "resolved": "https://registry.npmjs.org/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.15.tgz", + "integrity": "sha512-Gsfr9S8LU3S6885cS+NaleasJ0n3V5y/SOTqHCifQiXWFw3G9w9/YItDfwHrlCz+dSZWWZ8ilYVMcJe7Sz7Sxw==", + "dev": true, "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==", + "version": "3.18.4", + "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.18.4.tgz", + "integrity": "sha512-nQsacoiijvl0fRf2q28uFDrAVwRhdCa3QveiF7/CX0r18mLR5FU/1WskSsCKOjmEbtteKUnY8x8Yzxop+k0zwg==", + "dev": true, "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==", + "dev": true, "license": "MIT", "engines": { "node": ">=14.0.0" @@ -8261,6 +6597,7 @@ "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==", + "dev": true, "license": "MIT", "dependencies": { "vscode-jsonrpc": "8.2.0", @@ -8271,34 +6608,56 @@ "version": "3.17.5", "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.17.5.tgz", "integrity": "sha512-Ld1VelNuX9pdF39h2Hgaeb5hEZM2Z3jUrrMgWQAu82jMtZp7p3vJT3BzToKtZI7NgQssZje5o0zryOrhQvzQAg==", + "dev": true, "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==", + "dev": true, "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==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.2.0.tgz", + "integrity": "sha512-m2gXo3bn0G1kT9InzMf07fTbqMbGtyckj3bH5ktLO+1Ssv+yiATZ4dhwaQv9UZWxJh6E9IFGnQyjgWVDWVBDrg==", + "dev": true, "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==", + "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", + "integrity": "sha512-Tb2FUhB4vUsGQlfSquQLYkApkuPAFQXGFzxWKHHumVz2dK+X1RUm/HnID4+TfIGYJ1kTcwOaCk/buYCEJr6YjQ==", + "dev": true, "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=20.11" } }, "node_modules/wrap-ansi": { "version": "9.0.2", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz", "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==", + "dev": true, "license": "MIT", "dependencies": { "ansi-styles": "^6.2.1", @@ -8316,6 +6675,7 @@ "version": "7.2.0", "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "dev": true, "license": "MIT", "dependencies": { "emoji-regex": "^10.3.0", @@ -8329,6 +6689,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "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/xxhash-wasm": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/xxhash-wasm/-/xxhash-wasm-1.1.0.tgz", @@ -8339,21 +6705,16 @@ "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, "license": "ISC", "engines": { "node": ">=10" } }, - "node_modules/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" @@ -8369,6 +6730,7 @@ "version": "1.23.0", "resolved": "https://registry.npmjs.org/yaml-language-server/-/yaml-language-server-1.23.0.tgz", "integrity": "sha512-3qVyCOexLCWw06PQa5kRPwvMWMZ/eZeCRWUvgD6a0OkqL/4iCnxy2WumbWifa937Uo5xhyWJ0uxlU39ljhNh7A==", + "dev": true, "license": "MIT", "dependencies": { "@vscode/l10n": "^0.0.18", @@ -8392,12 +6754,14 @@ "version": "0.5.8", "resolved": "https://registry.npmjs.org/request-light/-/request-light-0.5.8.tgz", "integrity": "sha512-3Zjgh+8b5fhRJBQZoy+zbVKpAQGLyka0MPgW3zruTF4dFFJ8Fqcfu9YsAvi/rvdcaTeWG3MkbZv4WKxAn/84Lg==", + "dev": true, "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==", + "dev": true, "license": "ISC", "bin": { "yaml": "bin.mjs" @@ -8410,9 +6774,10 @@ } }, "node_modules/yargs": { - "version": "18.1.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-18.1.0.tgz", - "integrity": "sha512-2rAgRKu54VsHkqI0/tYkmluGXHD4KW7yZoycuqDQ15QOTnc2VVfy0nN/1eMhnQLO00A+dwtK20xuCnc1YGeUyg==", + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-18.2.0.tgz", + "integrity": "sha512-9OpKOLeaoNFecEp7P6iYbzze/5CqWoH7N3SMs/6y1XF4nMvFMspEGZzJ6uFi9MmQwXhyzqYoSEKO3tvA3Z/o2w==", + "dev": true, "license": "MIT", "dependencies": { "cliui": "^9.0.1", @@ -8448,14 +6813,23 @@ } }, "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/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" + } + }, "node_modules/zwitch": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", @@ -8465,6 +6839,72 @@ "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" + } + }, + "packages/ui": { + "name": "@gk/ui", + "version": "0.1.0", + "dependencies": { + "astro": "7.3.5" + }, + "devDependencies": { + "@astrojs/check": "0.9.10", + "typescript": "^5.9.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", + "dependencies": { + "@gk/schema": "*", + "@modelcontextprotocol/sdk": "^1.32.1", + "tsx": "^4.19.0", + "zod": "^4.1.0" + } + }, + "services/site": { + "name": "@gk/site", + "version": "0.1.0", + "dependencies": { + "@astrojs/node": "11.1.6", + "@gk/schema": "*", + "@gk/ui": "*", + "astro": "7.3.5", + "marked": "18.0.14", + "nodemailer": "10.0.10", + "pdf-lib": "^1.17.1", + "pdfkit": "^0.20.2" + }, + "devDependencies": { + "@astrojs/check": "0.9.10", + "@types/pdfkit": "^0.17.6", + "typescript": "^5.9.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..594d166 100644 --- a/package.json +++ b/package.json @@ -1,39 +1,33 @@ { - "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/*", + "services/*", + "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 && tsc -p services/content && tsc -p services/admin && npm run check -w @gk/ui && npm run check -w @gk/site", + "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", + "admin": "npm run start -w @gk/admin", + "ui": "npm run dev -w @gk/ui", + "ui:build": "npm run build -w @gk/ui", + "site": "npm run dev -w @gk/site" + }, + "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..2d1a060 --- /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", "sync"]) + .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..84e3f48 --- /dev/null +++ b/packages/schema/src/notes.ts @@ -0,0 +1,122 @@ +/** + * 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; +} + +/** + * Format a note's text without checking it, so a caller can run checkNote on + * the result and report problems field by field. + */ +export function formatNote(data: 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)); + 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}`); + } + return formatNote(data, body); +} + +/** 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..2d47c79 --- /dev/null +++ b/packages/schema/src/types.ts @@ -0,0 +1,557 @@ +/** + * 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(), + kavitaSeriesId: z + .number() + .int() + .meta({ title: "Kavita series id", description: "Links a book or light novel to its series in Kavita, for 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(), + tags: z + .array(line({ title: "Tag" })) + .meta({ title: "Tags", description: "Like sword rack, which puts the make on the Workshop's sword rack." }) + .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(), + favorite: z.boolean().meta({ title: "Favorite", description: "Shows first among the decks." }).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", description: "The current or last title at this company." }), + previousRoles: strings({ + title: "Earlier titles", + description: "Earlier titles at the same company, newest first, shown as Previously.", + }).optional(), + 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(), + earlier: z + .boolean() + .meta({ title: "Earlier role", description: "Shows in the short Earlier list: title, company, dates and the focus line only." }) + .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" }), + core: strings({ title: "Core items", description: "Items from this group outlined as the core stack." }).optional(), + }), + ) + .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", description: "Leave out to follow the synced stock (Bambuddy filament, Homebox leather and paper)." }) + .optional(), + }), + ) + .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(), + email: line({ title: "Email", description: "Printed on the resume PDF." }).optional(), + replyTime: line({ title: "Reply time", description: "How soon you answer inquiries, like 2 days." }).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(), + stats: z + .array( + z.object({ + value: line({ title: "Value", description: "Like 1,000+." }), + label: line({ title: "Label", description: "Like Active users." }), + detail: line({ title: "Detail" }).optional(), + }), + ) + .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(), + }), +}); + +/** 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/packages/ui/astro.config.mjs b/packages/ui/astro.config.mjs new file mode 100644 index 0000000..a794175 --- /dev/null +++ b/packages/ui/astro.config.mjs @@ -0,0 +1,12 @@ +// The gallery: one static page that shows every component, for review +// against the design canvas. The site itself imports the components. +import { defineConfig } from "astro/config"; + +export default defineConfig({ + output: "static", + build: { format: "file", assets: "assets" }, + devToolbar: { enabled: false }, + // Bundle astro's own cookie (2.x) into the prerender step; left external it + // resolves to the older copy express hoists to the workspace root. + vite: { environments: { prerender: { resolve: { noExternal: ["cookie"] } } } }, +}); diff --git a/packages/ui/package.json b/packages/ui/package.json new file mode 100644 index 0000000..34c39d1 --- /dev/null +++ b/packages/ui/package.json @@ -0,0 +1,25 @@ +{ + "name": "@gk/ui", + "version": "0.1.0", + "private": true, + "type": "module", + "description": "The GregKilmer.com component library: the approved element rules and the ornament kit as Astro components, plus a gallery page to review them.", + "exports": { + "./components/*": "./src/components/*", + "./styles/*": "./src/styles/*", + "./lib/*": "./src/lib/*", + "./assets/*": "./src/assets/*" + }, + "scripts": { + "dev": "astro dev", + "build": "astro build", + "check": "astro check" + }, + "dependencies": { + "astro": "7.3.5" + }, + "devDependencies": { + "@astrojs/check": "0.9.10", + "typescript": "^5.9.0" + } +} diff --git a/packages/ui/src/assets/brand/gk-monogram.svg b/packages/ui/src/assets/brand/gk-monogram.svg new file mode 100644 index 0000000..71e7942 --- /dev/null +++ b/packages/ui/src/assets/brand/gk-monogram.svg @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/packages/ui/src/assets/brand/patiun-glyph-gold.svg b/packages/ui/src/assets/brand/patiun-glyph-gold.svg new file mode 100644 index 0000000..37360af --- /dev/null +++ b/packages/ui/src/assets/brand/patiun-glyph-gold.svg @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/packages/ui/src/assets/brand/patiun-glyph-teal.svg b/packages/ui/src/assets/brand/patiun-glyph-teal.svg new file mode 100644 index 0000000..2d9be7b --- /dev/null +++ b/packages/ui/src/assets/brand/patiun-glyph-teal.svg @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/packages/ui/src/assets/fonts/Cinzel-Bold.woff2 b/packages/ui/src/assets/fonts/Cinzel-Bold.woff2 new file mode 100644 index 0000000..dfcf840 Binary files /dev/null and b/packages/ui/src/assets/fonts/Cinzel-Bold.woff2 differ diff --git a/packages/ui/src/assets/fonts/Cinzel-Regular.woff2 b/packages/ui/src/assets/fonts/Cinzel-Regular.woff2 new file mode 100644 index 0000000..49737b2 Binary files /dev/null and b/packages/ui/src/assets/fonts/Cinzel-Regular.woff2 differ diff --git a/packages/ui/src/assets/fonts/Cinzel-SemiBold.woff2 b/packages/ui/src/assets/fonts/Cinzel-SemiBold.woff2 new file mode 100644 index 0000000..b8d785c Binary files /dev/null and b/packages/ui/src/assets/fonts/Cinzel-SemiBold.woff2 differ diff --git a/packages/ui/src/assets/fonts/LICENSE.md b/packages/ui/src/assets/fonts/LICENSE.md new file mode 100644 index 0000000..adc5b00 --- /dev/null +++ b/packages/ui/src/assets/fonts/LICENSE.md @@ -0,0 +1,10 @@ +# Fonts + +Subset to Latin and served as WOFF2 from the static TTFs in the brand source +(`branding/source/patiun-brand-source.zip`, `fonts/static`). + +- **Cinzel**, copyright 2012 The Cinzel Project Authors (Natanael Gama). +- **Montserrat**, copyright 2011 The Montserrat Project Authors. + +Both are licensed under the SIL Open Font License, Version 1.1: +https://openfontlicense.org/open-font-license-official-text/ diff --git a/packages/ui/src/assets/fonts/Montserrat-Bold.woff2 b/packages/ui/src/assets/fonts/Montserrat-Bold.woff2 new file mode 100644 index 0000000..da978ab Binary files /dev/null and b/packages/ui/src/assets/fonts/Montserrat-Bold.woff2 differ diff --git a/packages/ui/src/assets/fonts/Montserrat-Medium.woff2 b/packages/ui/src/assets/fonts/Montserrat-Medium.woff2 new file mode 100644 index 0000000..49ec636 Binary files /dev/null and b/packages/ui/src/assets/fonts/Montserrat-Medium.woff2 differ diff --git a/packages/ui/src/assets/fonts/Montserrat-Regular.woff2 b/packages/ui/src/assets/fonts/Montserrat-Regular.woff2 new file mode 100644 index 0000000..819b8ad Binary files /dev/null and b/packages/ui/src/assets/fonts/Montserrat-Regular.woff2 differ diff --git a/packages/ui/src/assets/fonts/Montserrat-SemiBold.woff2 b/packages/ui/src/assets/fonts/Montserrat-SemiBold.woff2 new file mode 100644 index 0000000..720ddb2 Binary files /dev/null and b/packages/ui/src/assets/fonts/Montserrat-SemiBold.woff2 differ diff --git a/packages/ui/src/assets/icons/alert.svg b/packages/ui/src/assets/icons/alert.svg new file mode 100644 index 0000000..6ed51d3 --- /dev/null +++ b/packages/ui/src/assets/icons/alert.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/animation.svg b/packages/ui/src/assets/icons/animation.svg new file mode 100644 index 0000000..47a1124 --- /dev/null +++ b/packages/ui/src/assets/icons/animation.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/arrow-left.svg b/packages/ui/src/assets/icons/arrow-left.svg new file mode 100644 index 0000000..3d55b09 --- /dev/null +++ b/packages/ui/src/assets/icons/arrow-left.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/arrow-right.svg b/packages/ui/src/assets/icons/arrow-right.svg new file mode 100644 index 0000000..d6cc291 --- /dev/null +++ b/packages/ui/src/assets/icons/arrow-right.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/bookshelf.svg b/packages/ui/src/assets/icons/bookshelf.svg new file mode 100644 index 0000000..7bcbab1 --- /dev/null +++ b/packages/ui/src/assets/icons/bookshelf.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/brain.svg b/packages/ui/src/assets/icons/brain.svg new file mode 100644 index 0000000..e3462e5 --- /dev/null +++ b/packages/ui/src/assets/icons/brain.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/briefcase.svg b/packages/ui/src/assets/icons/briefcase.svg new file mode 100644 index 0000000..552922f --- /dev/null +++ b/packages/ui/src/assets/icons/briefcase.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/calendar.svg b/packages/ui/src/assets/icons/calendar.svg new file mode 100644 index 0000000..1fd22d3 --- /dev/null +++ b/packages/ui/src/assets/icons/calendar.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/castle.svg b/packages/ui/src/assets/icons/castle.svg new file mode 100644 index 0000000..c8f8531 --- /dev/null +++ b/packages/ui/src/assets/icons/castle.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/chart-bar.svg b/packages/ui/src/assets/icons/chart-bar.svg new file mode 100644 index 0000000..0e2e2a4 --- /dev/null +++ b/packages/ui/src/assets/icons/chart-bar.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/chart-line.svg b/packages/ui/src/assets/icons/chart-line.svg new file mode 100644 index 0000000..3888848 --- /dev/null +++ b/packages/ui/src/assets/icons/chart-line.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/check.svg b/packages/ui/src/assets/icons/check.svg new file mode 100644 index 0000000..283f68d --- /dev/null +++ b/packages/ui/src/assets/icons/check.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/chess.svg b/packages/ui/src/assets/icons/chess.svg new file mode 100644 index 0000000..c1267af --- /dev/null +++ b/packages/ui/src/assets/icons/chess.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/chevron-down.svg b/packages/ui/src/assets/icons/chevron-down.svg new file mode 100644 index 0000000..092ecf1 --- /dev/null +++ b/packages/ui/src/assets/icons/chevron-down.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/chevron-left.svg b/packages/ui/src/assets/icons/chevron-left.svg new file mode 100644 index 0000000..1dda76b --- /dev/null +++ b/packages/ui/src/assets/icons/chevron-left.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/chevron-right.svg b/packages/ui/src/assets/icons/chevron-right.svg new file mode 100644 index 0000000..95e22a1 --- /dev/null +++ b/packages/ui/src/assets/icons/chevron-right.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/clapper.svg b/packages/ui/src/assets/icons/clapper.svg new file mode 100644 index 0000000..6bf345e --- /dev/null +++ b/packages/ui/src/assets/icons/clapper.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/clipboard-check.svg b/packages/ui/src/assets/icons/clipboard-check.svg new file mode 100644 index 0000000..bf00624 --- /dev/null +++ b/packages/ui/src/assets/icons/clipboard-check.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/clipboard.svg b/packages/ui/src/assets/icons/clipboard.svg new file mode 100644 index 0000000..34ae58c --- /dev/null +++ b/packages/ui/src/assets/icons/clipboard.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/clock.svg b/packages/ui/src/assets/icons/clock.svg new file mode 100644 index 0000000..320ef86 --- /dev/null +++ b/packages/ui/src/assets/icons/clock.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/close.svg b/packages/ui/src/assets/icons/close.svg new file mode 100644 index 0000000..023ed40 --- /dev/null +++ b/packages/ui/src/assets/icons/close.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/cloud.svg b/packages/ui/src/assets/icons/cloud.svg new file mode 100644 index 0000000..280a147 --- /dev/null +++ b/packages/ui/src/assets/icons/cloud.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/cog.svg b/packages/ui/src/assets/icons/cog.svg new file mode 100644 index 0000000..184934f --- /dev/null +++ b/packages/ui/src/assets/icons/cog.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/commission.svg b/packages/ui/src/assets/icons/commission.svg new file mode 100644 index 0000000..ef0771f --- /dev/null +++ b/packages/ui/src/assets/icons/commission.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/contact.svg b/packages/ui/src/assets/icons/contact.svg new file mode 100644 index 0000000..c181ae4 --- /dev/null +++ b/packages/ui/src/assets/icons/contact.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/cube.svg b/packages/ui/src/assets/icons/cube.svg new file mode 100644 index 0000000..e6e2b58 --- /dev/null +++ b/packages/ui/src/assets/icons/cube.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/devices.svg b/packages/ui/src/assets/icons/devices.svg new file mode 100644 index 0000000..5461fc8 --- /dev/null +++ b/packages/ui/src/assets/icons/devices.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/dice.svg b/packages/ui/src/assets/icons/dice.svg new file mode 100644 index 0000000..7861738 --- /dev/null +++ b/packages/ui/src/assets/icons/dice.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/dog.svg b/packages/ui/src/assets/icons/dog.svg new file mode 100644 index 0000000..e6144ed --- /dev/null +++ b/packages/ui/src/assets/icons/dog.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/external.svg b/packages/ui/src/assets/icons/external.svg new file mode 100644 index 0000000..3811d02 --- /dev/null +++ b/packages/ui/src/assets/icons/external.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/factory.svg b/packages/ui/src/assets/icons/factory.svg new file mode 100644 index 0000000..0074a8d --- /dev/null +++ b/packages/ui/src/assets/icons/factory.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/favorite.svg b/packages/ui/src/assets/icons/favorite.svg new file mode 100644 index 0000000..4f87ef5 --- /dev/null +++ b/packages/ui/src/assets/icons/favorite.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/film.svg b/packages/ui/src/assets/icons/film.svg new file mode 100644 index 0000000..455d170 --- /dev/null +++ b/packages/ui/src/assets/icons/film.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/gallery.svg b/packages/ui/src/assets/icons/gallery.svg new file mode 100644 index 0000000..14526dd --- /dev/null +++ b/packages/ui/src/assets/icons/gallery.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/gamepad.svg b/packages/ui/src/assets/icons/gamepad.svg new file mode 100644 index 0000000..4a8f3ec --- /dev/null +++ b/packages/ui/src/assets/icons/gamepad.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/gauge.svg b/packages/ui/src/assets/icons/gauge.svg new file mode 100644 index 0000000..63f146c --- /dev/null +++ b/packages/ui/src/assets/icons/gauge.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/gem.svg b/packages/ui/src/assets/icons/gem.svg new file mode 100644 index 0000000..d81f880 --- /dev/null +++ b/packages/ui/src/assets/icons/gem.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/graduation.svg b/packages/ui/src/assets/icons/graduation.svg new file mode 100644 index 0000000..3e1e353 --- /dev/null +++ b/packages/ui/src/assets/icons/graduation.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/grid.svg b/packages/ui/src/assets/icons/grid.svg new file mode 100644 index 0000000..79ef3cc --- /dev/null +++ b/packages/ui/src/assets/icons/grid.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/hammer.svg b/packages/ui/src/assets/icons/hammer.svg new file mode 100644 index 0000000..9d6c0e5 --- /dev/null +++ b/packages/ui/src/assets/icons/hammer.svg @@ -0,0 +1 @@ + diff --git a/packages/ui/src/assets/icons/heart.svg b/packages/ui/src/assets/icons/heart.svg new file mode 100644 index 0000000..12d8feb --- /dev/null +++ b/packages/ui/src/assets/icons/heart.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/hearth.svg b/packages/ui/src/assets/icons/hearth.svg new file mode 100644 index 0000000..2748662 --- /dev/null +++ b/packages/ui/src/assets/icons/hearth.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/home.svg b/packages/ui/src/assets/icons/home.svg new file mode 100644 index 0000000..066389c --- /dev/null +++ b/packages/ui/src/assets/icons/home.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/info.svg b/packages/ui/src/assets/icons/info.svg new file mode 100644 index 0000000..c6011a3 --- /dev/null +++ b/packages/ui/src/assets/icons/info.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/leather.svg b/packages/ui/src/assets/icons/leather.svg new file mode 100644 index 0000000..17321b4 --- /dev/null +++ b/packages/ui/src/assets/icons/leather.svg @@ -0,0 +1 @@ + diff --git a/packages/ui/src/assets/icons/link.svg b/packages/ui/src/assets/icons/link.svg new file mode 100644 index 0000000..18eecc7 --- /dev/null +++ b/packages/ui/src/assets/icons/link.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/list-text.svg b/packages/ui/src/assets/icons/list-text.svg new file mode 100644 index 0000000..b9b216f --- /dev/null +++ b/packages/ui/src/assets/icons/list-text.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/list.svg b/packages/ui/src/assets/icons/list.svg new file mode 100644 index 0000000..df76b9a --- /dev/null +++ b/packages/ui/src/assets/icons/list.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/location.svg b/packages/ui/src/assets/icons/location.svg new file mode 100644 index 0000000..e626fa1 --- /dev/null +++ b/packages/ui/src/assets/icons/location.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/lock.svg b/packages/ui/src/assets/icons/lock.svg new file mode 100644 index 0000000..f61bcba --- /dev/null +++ b/packages/ui/src/assets/icons/lock.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/menu.svg b/packages/ui/src/assets/icons/menu.svg new file mode 100644 index 0000000..0c62e6a --- /dev/null +++ b/packages/ui/src/assets/icons/menu.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/monitor.svg b/packages/ui/src/assets/icons/monitor.svg new file mode 100644 index 0000000..2d68708 --- /dev/null +++ b/packages/ui/src/assets/icons/monitor.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/our-story.svg b/packages/ui/src/assets/icons/our-story.svg new file mode 100644 index 0000000..2fc2230 --- /dev/null +++ b/packages/ui/src/assets/icons/our-story.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/palette.svg b/packages/ui/src/assets/icons/palette.svg new file mode 100644 index 0000000..eb1ee9b --- /dev/null +++ b/packages/ui/src/assets/icons/palette.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/people.svg b/packages/ui/src/assets/icons/people.svg new file mode 100644 index 0000000..32690e1 --- /dev/null +++ b/packages/ui/src/assets/icons/people.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/person-details.svg b/packages/ui/src/assets/icons/person-details.svg new file mode 100644 index 0000000..888d050 --- /dev/null +++ b/packages/ui/src/assets/icons/person-details.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/person.svg b/packages/ui/src/assets/icons/person.svg new file mode 100644 index 0000000..13f5005 --- /dev/null +++ b/packages/ui/src/assets/icons/person.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/play.svg b/packages/ui/src/assets/icons/play.svg new file mode 100644 index 0000000..8523a47 --- /dev/null +++ b/packages/ui/src/assets/icons/play.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/price.svg b/packages/ui/src/assets/icons/price.svg new file mode 100644 index 0000000..149c88c --- /dev/null +++ b/packages/ui/src/assets/icons/price.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/print.svg b/packages/ui/src/assets/icons/print.svg new file mode 100644 index 0000000..8e2c5be --- /dev/null +++ b/packages/ui/src/assets/icons/print.svg @@ -0,0 +1 @@ + diff --git a/packages/ui/src/assets/icons/proxy-cards.svg b/packages/ui/src/assets/icons/proxy-cards.svg new file mode 100644 index 0000000..6861ec3 --- /dev/null +++ b/packages/ui/src/assets/icons/proxy-cards.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/robot.svg b/packages/ui/src/assets/icons/robot.svg new file mode 100644 index 0000000..011a19d --- /dev/null +++ b/packages/ui/src/assets/icons/robot.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/round-knife.svg b/packages/ui/src/assets/icons/round-knife.svg new file mode 100644 index 0000000..d2b425a --- /dev/null +++ b/packages/ui/src/assets/icons/round-knife.svg @@ -0,0 +1 @@ + diff --git a/packages/ui/src/assets/icons/ruler.svg b/packages/ui/src/assets/icons/ruler.svg new file mode 100644 index 0000000..9fcbd20 --- /dev/null +++ b/packages/ui/src/assets/icons/ruler.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/scissors.svg b/packages/ui/src/assets/icons/scissors.svg new file mode 100644 index 0000000..62dd57d --- /dev/null +++ b/packages/ui/src/assets/icons/scissors.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/search.svg b/packages/ui/src/assets/icons/search.svg new file mode 100644 index 0000000..66ac09e --- /dev/null +++ b/packages/ui/src/assets/icons/search.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/server.svg b/packages/ui/src/assets/icons/server.svg new file mode 100644 index 0000000..a3f85f7 --- /dev/null +++ b/packages/ui/src/assets/icons/server.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/shapes.svg b/packages/ui/src/assets/icons/shapes.svg new file mode 100644 index 0000000..3e7c91b --- /dev/null +++ b/packages/ui/src/assets/icons/shapes.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/shield-moon.svg b/packages/ui/src/assets/icons/shield-moon.svg new file mode 100644 index 0000000..39b996f --- /dev/null +++ b/packages/ui/src/assets/icons/shield-moon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/shop.svg b/packages/ui/src/assets/icons/shop.svg new file mode 100644 index 0000000..ef50438 --- /dev/null +++ b/packages/ui/src/assets/icons/shop.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/split.svg b/packages/ui/src/assets/icons/split.svg new file mode 100644 index 0000000..4eaae0a --- /dev/null +++ b/packages/ui/src/assets/icons/split.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/staff.svg b/packages/ui/src/assets/icons/staff.svg new file mode 100644 index 0000000..f0afc14 --- /dev/null +++ b/packages/ui/src/assets/icons/staff.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/swords.svg b/packages/ui/src/assets/icons/swords.svg new file mode 100644 index 0000000..b346dbc --- /dev/null +++ b/packages/ui/src/assets/icons/swords.svg @@ -0,0 +1 @@ + diff --git a/packages/ui/src/assets/icons/target.svg b/packages/ui/src/assets/icons/target.svg new file mode 100644 index 0000000..303f4da --- /dev/null +++ b/packages/ui/src/assets/icons/target.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/tea.svg b/packages/ui/src/assets/icons/tea.svg new file mode 100644 index 0000000..1a7421d --- /dev/null +++ b/packages/ui/src/assets/icons/tea.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/timeline.svg b/packages/ui/src/assets/icons/timeline.svg new file mode 100644 index 0000000..787326e --- /dev/null +++ b/packages/ui/src/assets/icons/timeline.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/toolbox.svg b/packages/ui/src/assets/icons/toolbox.svg new file mode 100644 index 0000000..883ca46 --- /dev/null +++ b/packages/ui/src/assets/icons/toolbox.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/translate.svg b/packages/ui/src/assets/icons/translate.svg new file mode 100644 index 0000000..85c5007 --- /dev/null +++ b/packages/ui/src/assets/icons/translate.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/trophy.svg b/packages/ui/src/assets/icons/trophy.svg new file mode 100644 index 0000000..f4f9c72 --- /dev/null +++ b/packages/ui/src/assets/icons/trophy.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/tv.svg b/packages/ui/src/assets/icons/tv.svg new file mode 100644 index 0000000..a19d096 --- /dev/null +++ b/packages/ui/src/assets/icons/tv.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/unavailable.svg b/packages/ui/src/assets/icons/unavailable.svg new file mode 100644 index 0000000..0fc9fa7 --- /dev/null +++ b/packages/ui/src/assets/icons/unavailable.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/verified.svg b/packages/ui/src/assets/icons/verified.svg new file mode 100644 index 0000000..e00d469 --- /dev/null +++ b/packages/ui/src/assets/icons/verified.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/wand.svg b/packages/ui/src/assets/icons/wand.svg new file mode 100644 index 0000000..7ee2cea --- /dev/null +++ b/packages/ui/src/assets/icons/wand.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/web.svg b/packages/ui/src/assets/icons/web.svg new file mode 100644 index 0000000..4253d1b --- /dev/null +++ b/packages/ui/src/assets/icons/web.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/wind.svg b/packages/ui/src/assets/icons/wind.svg new file mode 100644 index 0000000..a1a1084 --- /dev/null +++ b/packages/ui/src/assets/icons/wind.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/icons/wrench.svg b/packages/ui/src/assets/icons/wrench.svg new file mode 100644 index 0000000..d134340 --- /dev/null +++ b/packages/ui/src/assets/icons/wrench.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/motifs/astrolabe.svg b/packages/ui/src/assets/motifs/astrolabe.svg new file mode 100644 index 0000000..9e90190 --- /dev/null +++ b/packages/ui/src/assets/motifs/astrolabe.svg @@ -0,0 +1,2 @@ +0306090120150180210240270300330♈︎♉︎♊︎♋︎♌︎♍︎♎︎♏︎♐︎♑︎♒︎♓︎ + \ No newline at end of file diff --git a/packages/ui/src/assets/motifs/craft-seal-dice.svg b/packages/ui/src/assets/motifs/craft-seal-dice.svg new file mode 100644 index 0000000..dc7ab1d --- /dev/null +++ b/packages/ui/src/assets/motifs/craft-seal-dice.svg @@ -0,0 +1,2 @@ +DICE ✦ AQUA ✦ RESIN ✦ PATIUN ✦ DICE ✦ AQUA ✦ RESIN ✦ PATIUN ✦ 20🜄 + \ No newline at end of file diff --git a/packages/ui/src/assets/motifs/craft-seal-leather.svg b/packages/ui/src/assets/motifs/craft-seal-leather.svg new file mode 100644 index 0000000..6782fb6 --- /dev/null +++ b/packages/ui/src/assets/motifs/craft-seal-leather.svg @@ -0,0 +1,2 @@ +LEATHER ✦ TERRA ✦ HIDE ✦ PATIUN ✦ LEATHER ✦ TERRA ✦ HIDE ✦ PATIUN ✦ 🜃 + \ No newline at end of file diff --git a/packages/ui/src/assets/motifs/craft-seal-print.svg b/packages/ui/src/assets/motifs/craft-seal-print.svg new file mode 100644 index 0000000..5501cb6 --- /dev/null +++ b/packages/ui/src/assets/motifs/craft-seal-print.svg @@ -0,0 +1,2 @@ +PRINT ✦ IGNIS ✦ LAYER ✦ PATIUN ✦ PRINT ✦ IGNIS ✦ LAYER ✦ PATIUN ✦ 🜂 + \ No newline at end of file diff --git a/packages/ui/src/assets/motifs/filament-banner.svg b/packages/ui/src/assets/motifs/filament-banner.svg new file mode 100644 index 0000000..5f9de4d --- /dev/null +++ b/packages/ui/src/assets/motifs/filament-banner.svg @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/packages/ui/src/assets/motifs/filament-transmutation.svg b/packages/ui/src/assets/motifs/filament-transmutation.svg new file mode 100644 index 0000000..208377c --- /dev/null +++ b/packages/ui/src/assets/motifs/filament-transmutation.svg @@ -0,0 +1,2 @@ +SOLVE ET COAGULA ✦ DICE · LEATHER · PRINT ✦ PATIUN ✦ SOLVE ET COAGULA ✦ DICE · LEATHER · PRINT ✦ PATIUN ✦ ☉☽☿♀♂♃♄ + \ No newline at end of file diff --git a/packages/ui/src/assets/motifs/kepler-nest-seal.svg b/packages/ui/src/assets/motifs/kepler-nest-seal.svg new file mode 100644 index 0000000..3fc1a42 --- /dev/null +++ b/packages/ui/src/assets/motifs/kepler-nest-seal.svg @@ -0,0 +1,2 @@ +HARMONICES MUNDI ✦ PATIUN ✦ DICE · LEATHER · PRINT ✦ HARMONICES MUNDI ✦ PATIUN ✦ DICE · LEATHER · PRINT ✦ + \ No newline at end of file diff --git a/packages/ui/src/assets/motifs/layered-seal.svg b/packages/ui/src/assets/motifs/layered-seal.svg new file mode 100644 index 0000000..cba54ca --- /dev/null +++ b/packages/ui/src/assets/motifs/layered-seal.svg @@ -0,0 +1,2 @@ +♈︎♉︎♊︎♋︎♌︎♍︎♎︎♏︎♐︎♑︎♒︎♓︎PATIUN ✦ PATIUN ✦ PATIUN ✦ PATIUN ✦ PATIUN ✦ PATIUN ✦ 🜂🜄🜁🜃 + \ No newline at end of file diff --git a/packages/ui/src/assets/motifs/mini-astrolabe.svg b/packages/ui/src/assets/motifs/mini-astrolabe.svg new file mode 100644 index 0000000..e94d01c --- /dev/null +++ b/packages/ui/src/assets/motifs/mini-astrolabe.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/motifs/mini-layered.svg b/packages/ui/src/assets/motifs/mini-layered.svg new file mode 100644 index 0000000..5856587 --- /dev/null +++ b/packages/ui/src/assets/motifs/mini-layered.svg @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/packages/ui/src/assets/motifs/mini-tome.svg b/packages/ui/src/assets/motifs/mini-tome.svg new file mode 100644 index 0000000..31ab718 --- /dev/null +++ b/packages/ui/src/assets/motifs/mini-tome.svg @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/packages/ui/src/assets/motifs/mini-transmutation.svg b/packages/ui/src/assets/motifs/mini-transmutation.svg new file mode 100644 index 0000000..8b285c0 --- /dev/null +++ b/packages/ui/src/assets/motifs/mini-transmutation.svg @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/packages/ui/src/assets/motifs/seven-metals-dipper.svg b/packages/ui/src/assets/motifs/seven-metals-dipper.svg new file mode 100644 index 0000000..03f556f --- /dev/null +++ b/packages/ui/src/assets/motifs/seven-metals-dipper.svg @@ -0,0 +1,2 @@ +♄♃♂♀☿☽☉ +POLARIS · PLATINUM \ No newline at end of file diff --git a/packages/ui/src/assets/motifs/squared-circle-seal.svg b/packages/ui/src/assets/motifs/squared-circle-seal.svg new file mode 100644 index 0000000..4a03b8e --- /dev/null +++ b/packages/ui/src/assets/motifs/squared-circle-seal.svg @@ -0,0 +1,2 @@ +QUADRATURA CIRCULI ✦ SOLVE ET COAGULA ✦ PATIUN ✦ QUADRATURA CIRCULI ✦ SOLVE ET COAGULA ✦ PATIUN ✦ 🜂🜁🜄🜃🜍☿🜔 + \ No newline at end of file diff --git a/packages/ui/src/assets/motifs/transmutation-seal-star-paths.svg b/packages/ui/src/assets/motifs/transmutation-seal-star-paths.svg new file mode 100644 index 0000000..a3f4485 --- /dev/null +++ b/packages/ui/src/assets/motifs/transmutation-seal-star-paths.svg @@ -0,0 +1,2 @@ +SOLVE ET COAGULA ✦ DICE · LEATHER · PRINT ✦ PATIUN ✦ SOLVE ET COAGULA ✦ DICE · LEATHER · PRINT ✦ PATIUN ✦ ☉☽☿♀♂♃♄ + \ No newline at end of file diff --git a/packages/ui/src/assets/motifs/transmutation-seal.svg b/packages/ui/src/assets/motifs/transmutation-seal.svg new file mode 100644 index 0000000..8c7e5ff --- /dev/null +++ b/packages/ui/src/assets/motifs/transmutation-seal.svg @@ -0,0 +1,2 @@ +SOLVE ET COAGULA ✦ DICE · LEATHER · PRINT ✦ PATIUN ✦ SOLVE ET COAGULA ✦ DICE · LEATHER · PRINT ✦ PATIUN ✦ ☉☽☿♀♂♃♄ + \ No newline at end of file diff --git a/packages/ui/src/assets/motifs/tria-prima-array-seal.svg b/packages/ui/src/assets/motifs/tria-prima-array-seal.svg new file mode 100644 index 0000000..b324133 --- /dev/null +++ b/packages/ui/src/assets/motifs/tria-prima-array-seal.svg @@ -0,0 +1,2 @@ +SAL ✦ SULPHUR ✦ MERCURIUS ✦ PATIUN ✦ SAL ✦ SULPHUR ✦ MERCURIUS ✦ PATIUN ✦ 🜍☿🜔 + \ No newline at end of file diff --git a/packages/ui/src/assets/motifs/zodiac-seal.svg b/packages/ui/src/assets/motifs/zodiac-seal.svg new file mode 100644 index 0000000..871f1c8 --- /dev/null +++ b/packages/ui/src/assets/motifs/zodiac-seal.svg @@ -0,0 +1,2 @@ +QUOD EST SUPERIUS EST SICUT QUOD INFERIUS ✦ PATIUN ✦ QUOD EST SUPERIUS EST SICUT QUOD INFERIUS ✦ PATIUN ✦ ♈︎♉︎♊︎♋︎♌︎♍︎♎︎♏︎♐︎♑︎♒︎♓︎☉☽☿♀♂♃♄ + \ No newline at end of file diff --git a/packages/ui/src/assets/ornaments/card-ticks.svg b/packages/ui/src/assets/ornaments/card-ticks.svg new file mode 100644 index 0000000..f4cfb7e --- /dev/null +++ b/packages/ui/src/assets/ornaments/card-ticks.svg @@ -0,0 +1 @@ + diff --git a/packages/ui/src/components/Button.astro b/packages/ui/src/components/Button.astro new file mode 100644 index 0000000..7b146e0 --- /dev/null +++ b/packages/ui/src/components/Button.astro @@ -0,0 +1,37 @@ +--- +/** + * Rule I. One primary per section; a second action is secondary or a text + * link. A link when given `href`, otherwise a button. + */ +import type { HTMLAttributes } from "astro/types"; + +type Props = { + variant?: "primary" | "secondary" | "text"; + size?: "default" | "compact"; + href?: string; + type?: "button" | "submit" | "reset"; + disabled?: boolean; + /** Show the arrow used on "details" and "read more" actions. */ + arrow?: boolean; + class?: string; +} & Omit, "type">; + +const { variant = "primary", size = "default", href, type = "button", disabled, arrow, class: className, ...rest } = Astro.props; +const classes = [ + variant === "text" ? "gk-text" : ["gk-btn", variant === "primary" ? "gk-pri" : "gk-sec", size === "compact" && "gk-sm"], + className, +]; +--- +{ + href && !disabled ? ( + + + {arrow && } + + ) : ( + + ) +} diff --git a/packages/ui/src/components/Card.astro b/packages/ui/src/components/Card.astro new file mode 100644 index 0000000..aafcfa9 --- /dev/null +++ b/packages/ui/src/components/Card.astro @@ -0,0 +1,29 @@ +--- +/** + * Rule VI. A card. Linked cards turn their border gold on hover and show a + * tick scale along the top edge; the featured card keeps a gold border and + * gilt corners, one per page. + */ +import GiltCorners from "./GiltCorners.astro"; + +interface Props { + href?: string; + featured?: boolean; + as?: "div" | "article" | "li" | "section"; + class?: string; +} +const { href, featured, as: Tag = "article", class: className } = Astro.props; +--- +{ + href ? ( + + {featured && } + + + ) : ( + + {featured && } + + + ) +} diff --git a/packages/ui/src/components/Chip.astro b/packages/ui/src/components/Chip.astro new file mode 100644 index 0000000..4809988 --- /dev/null +++ b/packages/ui/src/components/Chip.astro @@ -0,0 +1,27 @@ +--- +/** + * Rule II. A square 6px chip is a label. With `href` it is a page link and + * leads with a violet star. Chips are never clickable unless they link. + */ +import Star from "./Star.astro"; + +interface Props { + href?: string; + /** A core skill or tag, given a teal edge. */ + core?: boolean; + class?: string; +} +const { href, core, class: className } = Astro.props; +--- +{ + href ? ( + + + + + ) : ( + + + + ) +} diff --git a/packages/ui/src/components/ConstellationDust.astro b/packages/ui/src/components/ConstellationDust.astro new file mode 100644 index 0000000..cc87226 --- /dev/null +++ b/packages/ui/src/components/ConstellationDust.astro @@ -0,0 +1,19 @@ +--- +/** Sparse, static specks behind a page; about one in five gold. */ +import { dust, seedOf } from "../lib/ornaments.ts"; + +interface Props { + count?: number; + seed?: string; +} +const { count = 48, seed = Astro.url.pathname } = Astro.props; +--- + + + diff --git a/packages/ui/src/components/Divider.astro b/packages/ui/src/components/Divider.astro new file mode 100644 index 0000000..bfb82ac --- /dev/null +++ b/packages/ui/src/components/Divider.astro @@ -0,0 +1,36 @@ +--- +/** + * The header divider, Greg's favorite: a gold line broken by a large gold + * center star, gold stars at the ends, and a star path riding the line on + * both sides. The footer uses the same divider. + */ +import { seedOf, starPath, starPoints } from "../lib/ornaments.ts"; + +interface Props { + seed?: string; + class?: string; +} +const { seed = Astro.url.pathname, class: className } = Astro.props; +const W = 1200; +const y = 20; +const s = seedOf(seed); +const left = starPath({ x1: 540, x2: 76, y, steps: 8, seed: s, openEnds: false, dot: 2, star: 4.5 }); +const right = starPath({ x1: 660, x2: 1124, y, steps: 8, seed: s + 1, openEnds: false, dot: 2, star: 4.5 }); +--- + diff --git a/packages/ui/src/components/EdgeSeals.astro b/packages/ui/src/components/EdgeSeals.astro new file mode 100644 index 0000000..0482078 --- /dev/null +++ b/packages/ui/src/components/EdgeSeals.astro @@ -0,0 +1,65 @@ +--- +/** + * One or two seals per page, cropped by the page edge at 5 to 8% opacity, on + * alternating sides. Never more than two, and a section with a photo gets no + * seal: place this in the page background, not in a photo section. + */ +import { pageSeals, seals, type PageFamily, type SealName } from "./seals.ts"; + +interface Props { + family?: PageFamily; + /** Override the family's seal, as a craft page does with its own. */ + seal?: SealName; + /** The second seal; defaults to the first. Pass false for just one. */ + second?: SealName | false; +} +const { family = "landing", seal, second } = Astro.props; +const first = seal ?? pageSeals[family]; +const other = second === false ? null : (second ?? first); +--- + + + diff --git a/packages/ui/src/components/Field.astro b/packages/ui/src/components/Field.astro new file mode 100644 index 0000000..67caac7 --- /dev/null +++ b/packages/ui/src/components/Field.astro @@ -0,0 +1,44 @@ +--- +/** + * Rule V. A labelled field: input, select or text area in the 10px box, with + * the filament drawn across its base on focus. + */ +import type { HTMLAttributes } from "astro/types"; + +type Props = { + label: string; + name: string; + as?: "input" | "select" | "textarea"; + hint?: string; + error?: string; + options?: (string | { value: string; label: string })[]; + /** Rows for a text area. */ + rows?: number; + class?: string; +} & HTMLAttributes<"input">; + +const { label, name, as = "input", hint, error, options = [], rows, class: className, id = `f-${name}`, ...rest } = Astro.props; +const describedBy = [hint && `${id}-hint`, error && `${id}-error`].filter(Boolean).join(" ") || undefined; +const common = { id, name, class: "gk-field", "aria-describedby": describedBy, "aria-invalid": error ? ("true" as const) : undefined }; +--- +
+ + {as === "select" ? ( + + ) : as === "textarea" ? ( + + For cards, paste your full card list, one per line. + {err.details && {err.details}} +
+
+ + + {err.notes && {err.notes}} +
+ + { + (lines.some((l) => l.data.terms?.length) || storeTerms.length > 0) && ( +
+ Order terms + {lines.map((l) => + l.data.terms?.length ? ( + + ) : null, + )} + {storeTerms.length > 0 && ( +
+ {lines.length > 1 &&

Every order

} + +
+ )} +
+ ) + } + + + {err.terms && {err.terms}} + + + + {turnstileKey &&
} + {turnstileKey && } + +
+ + {turnstileKey && Protected by Cloudflare Turnstile.} +
+ + + + + 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 ?
  • + ))} +
+ ) : ( +
    + {options.map((o) => ( +
  • +
  • + ))} +
+ ) +} + + 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) => ( + + )) + } +
+
+ + 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 ?? []; +--- + + + 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; +--- + + + 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 new file mode 100644 index 0000000..4ad6b07 --- /dev/null +++ b/services/site/src/lib/content.ts @@ -0,0 +1,114 @@ +/** + * The site's copy of the published notes. + * + * The site reads only the content service, with a reader token, and only + * published notes. It keeps every note in memory and refreshes in the + * background, so a saved note shows within a minute and a slow or restarting + * content service never takes a page down: the last good copy keeps serving. + */ +import { env } from "./env.ts"; +import type { NoteData, NoteType } from "@gk/schema"; +import { SiteIndex } from "./site.ts"; + +export interface Note { + path: string; + type: T; + /** The file name without `.md`: what wikilinks point at. */ + name: string; + slug: string; + title: string; + data: NoteData; + body: string; + acceptedAt: string; + /** + * What the content service's sync jobs fetched for this note (BoardGameGeek, + * Archidekt, cover art). Already merged under `data` by `withSynced`, so + * pages read `data`; kept here so a page can tell a synced value apart. + */ + synced?: Record; +} + +/** + * Lay the synced values under the note's own: a value set in the note always + * wins, and a note with nothing synced is returned as it came. + */ +export function withSynced(note: T): T { + const synced = note.synced; + if (!synced || !Object.keys(synced).length) return note; + const data: Record = { ...synced }; + for (const [k, v] of Object.entries(note.data as Record)) if (v !== undefined && v !== null) data[k] = v; + return { ...note, data: data as T["data"] }; +} + +export interface Redirect { + type: string; + fromSlug: string; + toPath: string; +} + +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 { + const res = await fetch(`${baseUrl()}${path}`, { + headers: { authorization: `Bearer ${token()}` }, + signal: AbortSignal.timeout(10_000), + }); + if (!res.ok) throw new Error(`Content service answered ${res.status} for ${path}`); + return (await res.json()) as T; +} + +async function loadAll(): Promise { + const notes: Note[] = []; + for (let offset = 0; ; offset += 1000) { + const published = includeDrafts() ? "" : "&published=true"; + const page = await api<(Note & { publish?: boolean })[]>(`/api/notes?full=true${published}&limit=1000&offset=${offset}`); + notes.push(...page.map(withSynced)); + if (page.length < 1000) break; + } + const redirects = await api("/api/redirects").catch(() => []); + return new SiteIndex(notes, redirects); +} + +let current: SiteIndex | null = null; +let loadedAt = 0; +let inflight: Promise | null = null; + +function refresh(): Promise { + inflight ??= loadAll() + .then((index) => { + current = index; + loadedAt = Date.now(); + return index; + }) + .finally(() => { + inflight = null; + }); + return inflight; +} + +/** The current index. Waits only on the very first load. */ +export async function site(): Promise { + if (!current) return refresh(); + if (Date.now() - loadedAt > refreshMs()) { + refresh().catch((err) => console.error("Content refresh failed; serving the last copy.", err)); + } + return current; +} + +/** Fetch a vault image from the content service, for the /media route. */ +export async function attachment(file: string): Promise { + return fetch(`${baseUrl()}/attachments/${encodeURIComponent(file)}`, { + headers: { authorization: `Bearer ${token()}` }, + signal: AbortSignal.timeout(15_000), + }); +} + +/** Use a fixed set of notes instead of the content service, for tests. */ +export function useIndex(index: SiteIndex) { + current = index; + loadedAt = Number.POSITIVE_INFINITY; +} diff --git a/services/site/src/lib/env.ts b/services/site/src/lib/env.ts new file mode 100644 index 0000000..29c2852 --- /dev/null +++ b/services/site/src/lib/env.ts @@ -0,0 +1,12 @@ +/** + * Server settings, read from process.env at run time only. Never read secrets + * through import.meta.env: Vite replaces it at build time and can copy every + * value, secrets included, into the built bundle. Set variables in the + * environment (Docker env_file, or the shell when running locally). + */ +export function env(key: string): string | undefined; +export function env(key: string, fallback: string): string; +export function env(key: string, fallback?: string): string | undefined { + const v = process.env[key]; + return v === undefined || v === "" ? fallback : v; +} diff --git a/services/site/src/lib/forms-server.ts b/services/site/src/lib/forms-server.ts new file mode 100644 index 0000000..65765f8 --- /dev/null +++ b/services/site/src/lib/forms-server.ts @@ -0,0 +1,155 @@ +/** + * What the form endpoints do outside the process: Turnstile, the content + * service (live store notes, saving the copy, recording delivery) and SMTP. + * Settings come from process.env through env(); see services/site/.env.example. + */ +import type { Note } from "./content.ts"; +import { env } from "./env.ts"; +import { DEFAULT_ATTACHMENT_MB, RateLimiter, type Row, type Upload } from "./forms.ts"; +import { SiteIndex } from "./site.ts"; +import { storeState, type StoreState } from "./store.ts"; + +const contentUrl = () => env("CONTENT_URL", "http://localhost:4310").replace(/\/$/, ""); + +export const maxAttachmentBytes = () => Math.round(Number(env("FORM_MAX_ATTACHMENT_MB", String(DEFAULT_ATTACHMENT_MB))) * 1024 * 1024); + +export const inquiryLimiter = new RateLimiter( + () => Number(env("CONTACT_RATE_LIMIT", "5")), + () => Number(env("CONTACT_RATE_WINDOW_MIN", "15")) * 60_000, +); +export const orderLimiter = new RateLimiter( + () => Number(env("ORDER_RATE_LIMIT", env("CONTACT_RATE_LIMIT", "5"))), + () => Number(env("ORDER_RATE_WINDOW_MIN", env("CONTACT_RATE_WINDOW_MIN", "15"))) * 60_000, +); + +/** The public site key, for the widget. */ +export const turnstileSiteKey = () => env("PUBLIC_TURNSTILE_SITE_KEY") ?? env("TURNSTILE_SITE_KEY"); + +/** + * Cloudflare Turnstile. Skipped when TURNSTILE_SECRET_KEY is unset (local + * development); when it is set, a missing token, a bad token or an error + * talking to Cloudflare all turn the post down. + */ +export async function verifyTurnstile(token: string, ip: string, fetcher: typeof fetch = fetch): Promise { + const secret = env("TURNSTILE_SECRET_KEY"); + if (!secret) return true; + if (!token) return false; + try { + const body = new URLSearchParams({ secret, response: token }); + if (ip && ip !== "unknown") body.set("remoteip", ip); + const res = await fetcher("https://challenges.cloudflare.com/turnstile/v0/siteverify", { + method: "POST", + body, + signal: AbortSignal.timeout(8000), + }); + const data = (await res.json()) as { success?: boolean }; + return data.success === true; + } catch (err) { + console.error("Turnstile verification failed; turning the post down.", err); + return false; + } +} + +/** + * The store's notes as they are right now, straight from the content + * service rather than the site's cached copy, so an order is checked against + * what Greg last saved. Throws when the service cannot answer. + */ +export async function liveStore(): Promise { + const get = async (type: string) => { + const res = await fetch(`${contentUrl()}/api/notes?type=${type}&full=true&published=true&limit=1000`, { + headers: { authorization: `Bearer ${env("CONTENT_TOKEN", "")}` }, + signal: AbortSignal.timeout(8000), + }); + if (!res.ok) throw new Error(`Content service answered ${res.status} for ${type}`); + return (await res.json()) as Note[]; + }; + const [settings, lines] = await Promise.all([get("store-settings"), get("product-line")]); + return storeState(new SiteIndex([...settings, ...lines])); +} + +export interface SubmissionInput { + kind: "inquiry" | "order"; + type: string; + name: string; + email: string; + fields: Row[]; + attachment?: Upload | null; +} + +const intake = async (path: string, body: unknown) => { + const token = env("CONTENT_INTAKE_TOKEN"); + if (!token) throw new Error("CONTENT_INTAKE_TOKEN is not set"); + const res = await fetch(`${contentUrl()}${path}`, { + method: "POST", + headers: { authorization: `Bearer ${token}`, "content-type": "application/json" }, + body: JSON.stringify(body), + signal: AbortSignal.timeout(20_000), + }); + const data = (await res.json().catch(() => null)) as Record | null; + if (!res.ok) throw new Error(`Content service answered ${res.status}: ${String(data?.error ?? "")}`); + return data; +}; + +/** Save the copy with the intake token. Returns the id and the receipt for reporting delivery. */ +export async function saveSubmission(s: SubmissionInput): Promise<{ id: number; receipt: string }> { + const attachment = s.attachment ? { name: s.attachment.name, data: Buffer.from(await s.attachment.arrayBuffer()).toString("base64") } : undefined; + const out = (await intake("/api/submissions", { ...s, attachment })) as { id: number; receipt: string }; + return { id: out.id, receipt: out.receipt }; +} + +export async function reportDelivery(id: number, receipt: string, status: "sent" | "skipped" | "failed", error?: string) { + await intake(`/api/submissions/${id}/delivery`, { receipt, status, ...(error ? { error } : {}) }); +} + +export interface Mail { + to: string; + replyTo: { name: string; address: string }; + subject: string; + text: string; + attachment?: Upload | null; +} + +/** + * Send through SMTP (nodemailer). "skipped" when SMTP_HOST is unset, so a + * site without mail still saves copies. Throws when sending fails. + */ +export async function sendMail(m: Mail): Promise<"sent" | "skipped"> { + const host = env("SMTP_HOST"); + if (!host) return "skipped"; + const nodemailer = (await import("nodemailer")).default; + const port = Number(env("SMTP_PORT", "587")); + const user = env("SMTP_USER"); + const transport = nodemailer.createTransport({ + host, + port, + secure: port === 465, + auth: user ? { user, pass: env("SMTP_PASS", "") } : undefined, + // Only for a relay on the same private network that presents a self-signed certificate. + ...(env("SMTP_ALLOW_SELFSIGNED") === "true" ? { tls: { rejectUnauthorized: false } } : {}), + connectionTimeout: 10_000, + greetingTimeout: 10_000, + socketTimeout: 20_000, + }); + await transport.sendMail({ + from: env("SMTP_FROM", "GregKilmer.com "), + to: m.to, + replyTo: m.replyTo, + subject: m.subject.replace(/[\r\n]+/g, " "), + text: m.text, + attachments: m.attachment ? [{ filename: m.attachment.name, content: Buffer.from(await m.attachment.arrayBuffer()) }] : undefined, + }); + return "sent"; +} + +export const contactTo = () => env("CONTACT_TO", "contact@gregkilmer.com"); +export const orderTo = () => env("ORDER_TO", "orders@gregkilmer.com"); + +/** Short-lived cookie settings for the confirmation and try-again cookies. */ +export const flashCookie = (url: URL) => ({ + path: "/", + httpOnly: true, + sameSite: "lax" as const, + secure: import.meta.env.PROD || url.protocol === "https:", + maxAge: 600, +}); diff --git a/services/site/src/lib/forms.ts b/services/site/src/lib/forms.ts new file mode 100644 index 0000000..acccafe --- /dev/null +++ b/services/site/src/lib/forms.ts @@ -0,0 +1,479 @@ +/** + * The inquiry and order forms' rules, as plain functions: what a valid post + * looks like, what an order may be accepted for, what the confirmation and + * the "try again" pages are handed, and how saving and emailing combine. + * Nothing here touches the network, so tests run it without Astro. + */ +import type { InquiryType, SentSummary } from "./work.ts"; +import { parseInquiryType } from "./work.ts"; +import { channelOf, hasCapacity, optionGroups, type Line, type StoreState } from "./store.ts"; + +export interface Row { + label: string; + value: string; +} + +/** What a form post gives: FormData, or anything shaped like it in tests. */ +export interface FormLike { + get(name: string): FormDataEntryValue | null; +} + +export type FieldErrors = Record; + +export type Invalid = { ok: false; code: ErrorCode; errors: FieldErrors }; + +export type ErrorCode = "invalid" | "bot" | "rate" | "closed" | "full" | "unavailable" | "failed"; + +/** What the visitor reads for each kind of failure. Nothing here leaks internals. */ +export const errorMessages: Record = { + invalid: "Some answers need another look. Nothing was sent yet.", + bot: "The bot check did not go through. Please try again.", + rate: "Too many requests from here in a short time. Please try again a little later.", + closed: "Orders for that are closed right now, so nothing was sent.", + full: "That line has no open slots right now, so nothing was sent.", + unavailable: "The store could not confirm availability just now. Please try again in a few minutes.", + failed: "Your request could not be sent just now. Please try again a little later.", +}; + +export const parseErrorCode = (raw: string | null | undefined): ErrorCode | undefined => + raw && raw in errorMessages ? (raw as ErrorCode) : undefined; + +const text = (form: FormLike, key: string): string => { + const v = form.get(key); + return typeof v === "string" ? v.replace(/\r\n/g, "\n").trim() : ""; +}; + +const EMAIL = /^[^\s@<>()[\]\\,;:"]+@[^\s@<>()[\]\\,;:"]+\.[^\s@<>()[\]\\,;:"]{2,}$/; +export const isEmail = (s: string) => s.length <= 200 && EMAIL.test(s); + +export const isHttpUrl = (s: string) => { + try { + const u = new URL(s); + return (u.protocol === "http:" || u.protocol === "https:") && !!u.hostname; + } catch { + return false; + } +}; + +const isDate = (s: string) => /^\d{4}-\d{2}-\d{2}$/.test(s) && !Number.isNaN(Date.parse(`${s}T00:00:00Z`)); + +/** The hidden field bots fill and people never see. */ +export const HONEYPOT = "leave_blank"; + +export const isBot = (form: FormLike) => text(form, HONEYPOT) !== ""; + +/** The Turnstile widget adds this field to the form it sits in. */ +export const TURNSTILE_FIELD = "cf-turnstile-response"; + +// ---------------------------------------------------------------- attachments + +/** Matches the content service's list (services/content/src/submissions.ts). */ +export const ATTACHMENT_EXTENSIONS = ["png", "jpg", "jpeg", "gif", "webp", "avif", "heic", "pdf", "txt", "csv", "dek", "dec"]; + +export const DEFAULT_ATTACHMENT_MB = 10; + +export interface Upload { + name: string; + size: number; + type: string; + arrayBuffer(): Promise; +} + +const isUpload = (v: unknown): v is Upload => + typeof v === "object" && v !== null && typeof (v as Upload).arrayBuffer === "function" && typeof (v as Upload).size === "number"; + +/** The attached file, or null for none; an error message when it cannot be taken. */ +export function checkAttachment(form: FormLike, key: string, maxBytes: number): { file: Upload | null; error?: string } { + const v = form.get(key); + if (!isUpload(v) || v.size === 0) return { file: null }; + const ext = /\.([A-Za-z0-9]{1,8})$/.exec(v.name)?.[1]?.toLowerCase(); + if (!ext || !ATTACHMENT_EXTENSIONS.includes(ext)) { + return { file: null, error: "Attach an image, a PDF, or a text, CSV or deck list file." }; + } + if (v.size > maxBytes) return { file: null, error: `Attachments can be up to ${Math.floor(maxBytes / 1024 / 1024)} MB.` }; + return { file: v }; +} + +// ---------------------------------------------------------------- inquiries + +export const choices = { + job_kind: ["Full-time", "Contract", "Not sure yet"], + freelance_need: ["A new site", "An app", "An integration", "Something else"], + patiun_item: ["Proxy cards", "Deck box", "Something else"], +} as const; + +export const timing = [ + { value: "no-rush", label: "No rush" }, + { value: "within-a-month", label: "Within a month" }, + { value: "date", label: "A specific date" }, +]; + +type Question = + | { key: string; label: string; kind: "text"; max: number } + | { key: string; label: string; kind: "url" } + | { key: string; label: string; kind: "choice"; options: readonly string[] } + | { key: string; label: string; kind: "timing"; dateKey: string }; + +/** Each type's own questions, in form order. Fields of other types are ignored. */ +export const questions: Record = { + job: [ + { key: "job_company", label: "Company", kind: "text", max: 160 }, + { key: "job_role", label: "Role", kind: "text", max: 160 }, + { key: "job_kind", label: "Kind of role", kind: "choice", options: choices.job_kind }, + { key: "job_link", label: "Posting", kind: "url" }, + ], + freelance: [ + { key: "freelance_need", label: "Needs", kind: "choice", options: choices.freelance_need }, + { key: "freelance_site", label: "Current site", kind: "url" }, + { key: "freelance_timing", label: "Timeline", kind: "timing", dateKey: "freelance_date" }, + ], + patiun: [ + { key: "patiun_item", label: "Item", kind: "choice", options: choices.patiun_item }, + { key: "patiun_quantity", label: "How many", kind: "text", max: 120 }, + { key: "patiun_timing", label: "Need it by", kind: "timing", dateKey: "patiun_date" }, + ], +}; + +export interface Inquiry { + type: InquiryType; + name: string; + email: string; + details: string; + /** The chosen type's answers, in form order, empty ones left out. */ + rows: Row[]; + attachment: Upload | null; +} + +const formatDay = (d: string) => new Date(`${d}T00:00:00Z`).toLocaleDateString("en-US", { month: "short", day: "numeric", year: "numeric", timeZone: "UTC" }); + +export function validateInquiry(form: FormLike, opts: { maxBytes: number }): { ok: true; value: Inquiry } | Invalid { + const errors: FieldErrors = {}; + const type = parseInquiryType(text(form, "type")); + if (!type) errors.type = "Pick what this is about."; + const name = text(form, "name"); + if (!name) errors.name = "Add your name."; + else if (name.length > 120) errors.name = "Keep your name under 120 characters."; + const email = text(form, "email"); + if (!isEmail(email)) errors.email = "Add an email address I can reply to."; + const details = text(form, "details"); + if (details.length < 10) errors.details = "Tell me a little more: a sentence or two is plenty."; + else if (details.length > 5000) errors.details = "Keep the details under 5,000 characters, or attach a file."; + + const rows: Row[] = []; + for (const q of type ? questions[type] : []) { + const v = text(form, q.key); + if (!v) continue; + if (q.kind === "text") { + if (v.length > q.max) errors[q.key] = `Keep this under ${q.max} characters.`; + else rows.push({ label: q.label, value: v }); + } else if (q.kind === "url") { + if (v.length > 500 || !isHttpUrl(v)) errors[q.key] = "Use a full link starting with https://."; + else rows.push({ label: q.label, value: v }); + } else if (q.kind === "choice") { + if (!q.options.includes(v)) errors[q.key] = "Pick one of the options."; + else rows.push({ label: q.label, value: v }); + } else { + const t = timing.find((x) => x.value === v); + if (!t) errors[q.key] = "Pick one of the options."; + else if (t.value === "date") { + const d = text(form, q.dateKey); + if (!isDate(d)) errors[q.dateKey] = "Pick the date."; + else rows.push({ label: q.label, value: formatDay(d) }); + } else rows.push({ label: q.label, value: t.label }); + } + } + + const att = checkAttachment(form, "attachment", opts.maxBytes); + if (att.error) errors.attachment = att.error; + + if (Object.keys(errors).length || !type) return { ok: false, code: "invalid", errors }; + return { ok: true, value: { type, name, email, details, rows, attachment: att.file } }; +} + +const inquiryTypeLabels: Record = { job: "A job or role", freelance: "Freelance web work", patiun: "Patiun custom order" }; + +/** Everything the saved copy and the email carry, in order. */ +export const inquiryFields = (q: Inquiry): Row[] => [...q.rows, { label: "Details", value: q.details }]; + +export function inquiryEmail(q: Inquiry, savedId: number | null): { subject: string; text: string } { + return { + subject: `Inquiry: ${inquiryTypeLabels[q.type]} from ${q.name}`, + text: [ + `New inquiry through gregkilmer.com`, + ``, + `Type: ${inquiryTypeLabels[q.type]}`, + `Name: ${q.name}`, + `Email: ${q.email}`, + ...q.rows.map((r) => `${r.label}: ${r.value}`), + ``, + `Details:`, + q.details, + ``, + q.attachment ? `Attached: ${q.attachment.name}` : "", + savedId ? `Saved in the admin inbox as #${savedId}.` : `Not saved in the admin inbox: the content service did not take it. This email is the only copy.`, + ].join("\n"), + }; +} + +/** The `gk-inquiry` cookie the sent page reads: what the visitor sent, briefly. */ +export function inquirySent(q: Inquiry): SentSummary { + const short = (v: string) => (v.length > 120 ? `${v.slice(0, 119)}…` : v); + return { + type: q.type, + name: q.name, + email: q.email, + rows: [...q.rows.map((r) => ({ label: r.label, value: short(r.value) })), ...(q.attachment ? [{ label: "Attachment", value: short(q.attachment.name) }] : [])].slice(0, 6), + }; +} + +// ---------------------------------------------------------------- orders + +export interface OrderRequest { + name: string; + email: string; + line: string; + quantity: string; + details: string; + notes: string; +} + +/** Check the fields that do not depend on the store. */ +export function validateOrderFields(form: FormLike): { ok: true; value: OrderRequest } | Invalid { + const errors: FieldErrors = {}; + const name = text(form, "name"); + if (!name) errors.name = "Add your name."; + else if (name.length > 120) errors.name = "Keep your name under 120 characters."; + const email = text(form, "email"); + if (!isEmail(email)) errors.email = "Add an email address I can reply to."; + const line = text(form, "line"); + if (!/^[a-z0-9-]{1,120}$/.test(line)) errors.line = "Pick what you would like."; + const quantity = text(form, "quantity"); + if (!quantity) errors.quantity = "Give a rough quantity."; + else if (quantity.length > 200) errors.quantity = "Keep this under 200 characters."; + const details = text(form, "details"); + if (details.length > 20_000) errors.details = "Keep this under 20,000 characters."; + const notes = text(form, "notes"); + if (notes.length > 5000) errors.notes = "Keep notes under 5,000 characters."; + if (text(form, "terms") !== "accepted") errors.terms = "Read and accept the order terms to send a request."; + if (Object.keys(errors).length) return { ok: false, code: "invalid", errors }; + return { ok: true, value: { name, email, line, quantity, details, notes } }; +} + +export interface CheckedOrder extends OrderRequest { + lineNote: Line; + /** The chosen options, as { label: group, value: option }. */ + options: Row[]; +} + +/** + * Whether the store takes this order now, against the live notes. An order + * is accepted only for a line sold here, in a category with ordersOpen true, + * with an open slot or stock, and only with options that exist and are in + * stock. Anything else is turned down, whatever the form said. + */ +export function checkOrder(req: OrderRequest, form: FormLike, st: StoreState): { ok: true; value: CheckedOrder } | Invalid { + const line = st.lines.find((l) => l.slug === req.line); + if (!line || channelOf(line) !== "here") return { ok: false, code: "invalid", errors: { line: "That is not something you can order here." } }; + if (!st.category(line.data.category).ordersOpen) return { ok: false, code: "closed", errors: {} }; + if (!hasCapacity(line)) return { ok: false, code: "full", errors: {} }; + const options: Row[] = []; + const errors: FieldErrors = {}; + for (const [group, opts] of optionGroups(line)) { + const key = `option.${line.slug}.${group}`; + const v = text(form, key); + if (!v) continue; + const o = opts.find((x) => x.name === v); + if (!o) errors[key] = `Pick one of the ${group.toLowerCase()} options.`; + else if (!o.inStock) errors[key] = `${o.name} is out of stock. Pick another.`; + else options.push({ label: group, value: o.name }); + } + if (Object.keys(errors).length) return { ok: false, code: "invalid", errors }; + return { ok: true, value: { ...req, lineNote: line, options } }; +} + +export const orderFields = (o: CheckedOrder): Row[] => [ + { label: "Line", value: o.lineNote.title }, + ...o.options, + { label: "Quantity", value: o.quantity }, + ...(o.details ? [{ label: "Details", value: o.details }] : []), + ...(o.notes ? [{ label: "Notes", value: o.notes }] : []), + { label: "Terms", value: "Accepted" }, +]; + +export function orderEmail(o: CheckedOrder, savedId: number | null): { subject: string; text: string } { + return { + subject: `Order request: ${o.lineNote.title} from ${o.name}`, + text: [ + `New order request through gregkilmer.com. No payment was taken.`, + ``, + `Name: ${o.name}`, + `Email: ${o.email}`, + ...orderFields(o).flatMap((r) => (r.value.includes("\n") ? [``, `${r.label}:`, r.value, ``] : [`${r.label}: ${r.value}`])), + ``, + savedId ? `Saved in the admin inbox as #${savedId}.` : `Not saved in the admin inbox: the content service did not take it. This email is the only copy.`, + ].join("\n"), + }; +} + +export interface OrderSent { + name: string; + email: string; + line: string; + lineSlug: string; + rows: Row[]; +} + +/** The `gk-order` cookie the order confirmation reads. */ +export function orderSent(o: CheckedOrder): OrderSent { + const short = (v: string) => (v.length > 120 ? `${v.slice(0, 119)}…` : v); + return { + name: o.name, + email: o.email, + line: o.lineNote.title, + lineSlug: o.lineNote.slug, + rows: [...o.options, { label: "Quantity", value: o.quantity }].map((r) => ({ label: r.label, value: short(r.value) })).slice(0, 6), + }; +} + +export function parseOrderSent(raw: string | undefined): OrderSent | 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) : ""); + const rows = Array.isArray(v.rows) + ? v.rows + .map((r) => ({ label: str((r as Row)?.label), value: str((r as Row)?.value) })) + .filter((r) => r.label && r.value) + .slice(0, 8) + : []; + return { name: str(v.name), email: str(v.email), line: str(v.line), lineSlug: str(v.lineSlug), rows }; + } catch { + return undefined; + } +} + +// ---------------------------------------------------------------- try again + +/** + * What goes back to the form when a post is turned down: the error, which + * fields to mark, and the visitor's answers. It travels as a short-lived + * cookie, so it has to fit: the longest answers are cut first. + */ +export interface Draft { + code: ErrorCode; + errors: FieldErrors; + values: Record; +} + +export const DRAFT_LIMIT = 3600; + +/** Every text value a form posted, files and the bot fields left out. */ +export function formValues(form: { entries(): IterableIterator<[string, FormDataEntryValue]> } | FormData): Record { + const out: Record = {}; + for (const [k, v] of (form as FormData).entries()) { + if (typeof v !== "string" || k === HONEYPOT || k === TURNSTILE_FIELD || k === "terms") continue; + if (k.length > 160) continue; + out[k] = v.slice(0, 20_000); + } + return out; +} + +export function encodeDraft(d: Draft, limit = DRAFT_LIMIT): string { + const values = { ...d.values }; + const enc = () => encodeURIComponent(JSON.stringify({ ...d, values })); + let out = enc(); + while (out.length > limit) { + const longest = Object.entries(values).sort((a, b) => b[1].length - a[1].length)[0]; + if (!longest || longest[1].length === 0) break; + const [k, v] = longest; + values[k] = v.length > 200 ? v.slice(0, Math.floor(v.length / 2)) : ""; + out = enc(); + } + return JSON.stringify({ ...d, values }); +} + +export function parseDraft(raw: string | undefined): Draft | undefined { + if (!raw) return undefined; + try { + const v = JSON.parse(raw.startsWith("%7B") || raw.startsWith("%7b") ? decodeURIComponent(raw) : raw) as Record; + const code = parseErrorCode(typeof v.code === "string" ? v.code : undefined); + if (!code) return undefined; + const strings = (x: unknown) => + Object.fromEntries(Object.entries(typeof x === "object" && x ? x : {}).filter((e): e is [string, string] => typeof e[1] === "string")); + return { code, errors: strings(v.errors), values: strings(v.values) }; + } catch { + return undefined; + } +} + +// ---------------------------------------------------------------- send + +export type EmailResult = "sent" | "skipped" | "failed"; + +/** + * Save the copy, then email, then record how the email went. The visitor + * hears it worked when either the saved copy or the email made it; when + * neither did, the form shows an error. + */ +export async function saveAndSend(deps: { + save(): Promise<{ id: number; receipt: string }>; + email(savedId: number | null): Promise<"sent" | "skipped">; + report(id: number, receipt: string, status: EmailResult, error?: string): Promise; + log?(message: string, err: unknown): void; +}): Promise<{ ok: boolean; savedId: number | null; email: EmailResult }> { + const log = deps.log ?? ((m, e) => console.error(m, e)); + let saved: { id: number; receipt: string } | null = null; + try { + saved = await deps.save(); + } catch (err) { + log("Saving the copy failed; trying the email anyway.", err); + } + let email: EmailResult; + let error: string | undefined; + try { + email = await deps.email(saved?.id ?? null); + } catch (err) { + email = "failed"; + error = err instanceof Error ? err.message.slice(0, 500) : String(err).slice(0, 500); + log("Sending the email failed.", err); + } + if (saved) { + await deps.report(saved.id, saved.receipt, email, error).catch((err: unknown) => log("Recording the email result failed.", err)); + } + return { ok: !!saved || email === "sent", savedId: saved?.id ?? null, email }; +} + +// ---------------------------------------------------------------- rate limit + +/** N posts per address per window, in memory. Enough for one site process. */ +export class RateLimiter { + private readonly hits = new Map(); + constructor( + private readonly limit: () => number, + private readonly windowMs: () => number, + private readonly now: () => number = Date.now, + ) {} + + /** Counts this post; true when it is over the limit. */ + hit(key: string): boolean { + const t = this.now(); + if (this.hits.size > 5000) for (const [k, e] of this.hits) if (t > e.reset) this.hits.delete(k); + const e = this.hits.get(key); + if (!e || t > e.reset) { + this.hits.set(key, { count: 1, reset: t + this.windowMs() }); + return false; + } + e.count += 1; + return e.count > this.limit(); + } +} + +/** The visitor's address: Cloudflare's CF-Connecting-IP, then the proxy's X-Forwarded-For, else the socket's. */ +export function clientIp(headers: Headers, fallback?: string): string { + // Cloudflare sets CF-Connecting-IP to the visitor's address and overwrites any copy a client sends. + const cf = headers.get("cf-connecting-ip"); + if (cf) return cf.trim(); + const fwd = headers.get("x-forwarded-for"); + if (fwd) return fwd.split(",")[0]!.trim(); + return headers.get("x-real-ip") ?? fallback ?? "unknown"; +} 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..91e5cd1 --- /dev/null +++ b/services/site/src/lib/library.ts @@ -0,0 +1,213 @@ +/** + * 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"; +import { formatDate } 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) + ); +} + +/** Progress PersonalReader syncs onto a linked item (see withSynced in content.ts). */ +interface ReaderSynced { + chapterProgress?: { chapter?: number; readAt?: string }; + bookProgress?: { percent?: number; readAt?: string }; +} + +/** "Chapter 42, read Oct 3, 2026" or "62% read", from PersonalReader; undefined when nothing is synced. */ +export function readerProgress(item: Item): string | undefined { + const d = item.data as ReaderSynced; + const c = d.chapterProgress; + const b = d.bookProgress; + const what = c?.chapter ? `Chapter ${c.chapter}` : b && typeof b.percent === "number" && b.percent > 0 ? `${b.percent}% read` : undefined; + if (!what) return undefined; + const at = (c?.chapter ? c.readAt : b?.readAt)?.slice(0, 10); + return at ? `${what}, read ${formatDate(at)}` : what; +} + +/** The row's details line. The note's own `progress` wins over the synced one. */ +export const itemDetails = (item: Item): (string | number)[] => { + const d = item.data; + return [d.creator, d.year, d.platform, d.progress || readerProgress(item), d.group].filter((x): x is string | number => x !== undefined && x !== ""); +}; + +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); +} + +/** Values BoardGameGeek syncs onto a game (see withSynced in content.ts). */ +interface GameSynced { + minPlayers?: number; + maxPlayers?: number; + playTime?: number; + minPlayTime?: number; + maxPlayTime?: number; +} + +const range = (lo?: number, hi?: number) => (lo && hi && lo !== hi ? `${lo}–${hi}` : String(lo || hi || "")); + +/** "2–4 players", "1 player" or undefined when BoardGameGeek has not been synced. */ +export function gamePlayers(g: Game): string | undefined { + const d = g.data as GameSynced; + const r = range(d.minPlayers, d.maxPlayers); + return r ? `${r} ${r === "1" ? "player" : "players"}` : undefined; +} + +/** "60–90 min", "45 min" or undefined. */ +export function gamePlayTime(g: Game): string | undefined { + const d = g.data as GameSynced; + const r = d.minPlayTime && d.maxPlayTime ? range(d.minPlayTime, d.maxPlayTime) : range(d.playTime); + return r ? `${r} min` : undefined; +} + +/** 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 new file mode 100644 index 0000000..b512d57 --- /dev/null +++ b/services/site/src/lib/markdown.ts @@ -0,0 +1,43 @@ +/** + * Note bodies as HTML. Raw HTML in a note is shown as text and only http, + * https, mailto and relative links survive, so nothing in a note can run on + * the site. Wikilinks to published notes become links; the rest plain text. + */ +import { Marked, type Tokens } from "marked"; + +const ESC: Record = { "&": "&", "<": "<", ">": ">", '"': """, "'": "'" }; +export const escapeHtml = (s: string) => s.replace(/[&<>"']/g, (c) => ESC[c]!); + +const safeUrl = (href: string) => { + const h = href.trim(); + return /^(https?:|mailto:|\/|#|\.\/|\.\.\/)/i.test(h) || !/^[a-z][a-z0-9+.-]*:/i.test(h) ? h : "#"; +}; + +const marked = new Marked({ + gfm: true, + renderer: { + html: ({ text }: Tokens.HTML | Tokens.Tag) => escapeHtml(text), + link({ href, title, tokens }: Tokens.Link) { + const t = title ? ` title="${escapeHtml(title)}"` : ""; + const external = /^https?:/i.test(href) ? ' rel="noreferrer"' : ""; + return `${this.parser.parseInline(tokens)}`; + }, + image: ({ href, text }: Tokens.Image) => + `${escapeHtml(text)}`, + }, +}); + +export function renderMarkdown( + body: string, + 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) => `![${alt ?? ""}](${opts.imageSrc(f.trim()) ?? ""})`) + .replace(/\[\[([^\]|#]+)(?:#[^\]|]*)?(?:\|([^\]]+))?\]\]/g, (_, n: string, label?: string) => { + const l = opts.link(n.trim()); + const text = label ?? l.title; + return l.href ? `[${text}](${l.href})` : text; + }); + return marked.parse(withLinks, { async: false }) as string; +} diff --git a/services/site/src/lib/page.ts b/services/site/src/lib/page.ts new file mode 100644 index 0000000..18f5239 --- /dev/null +++ b/services/site/src/lib/page.ts @@ -0,0 +1,17 @@ +/** Small helpers every page uses. */ +import { site } from "./content.ts"; + +export { site }; + +/** Group items by a key, keeping first-seen order. */ +export function groupBy(items: T[], key: (item: T) => string): [string, T[]][] { + const map = new Map(); + for (const item of items) { + const k = key(item); + map.set(k, [...(map.get(k) ?? []), item]); + } + return [...map.entries()]; +} + +/** "1 game" / "3 games". */ +export const plural = (n: number, one: string, many = `${one}s`) => `${n} ${n === 1 ? one : many}`; diff --git a/services/site/src/lib/resume-pdf.ts b/services/site/src/lib/resume-pdf.ts new file mode 100644 index 0000000..7e71561 --- /dev/null +++ b/services/site/src/lib/resume-pdf.ts @@ -0,0 +1,279 @@ +/** + * 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-Personal-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"; +import montMed from "../assets/pdf-fonts/Montserrat-Medium.ttf?inline"; +import montSemi from "../assets/pdf-fonts/Montserrat-SemiBold.ttf?inline"; +import { listOf, 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"; + +/** + * 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 { + x: number; + w: number; +} + +function renderBody(r: ResumeData): Promise { + const doc = new PDFDocument({ + size: PAGE.size, + margins: { top: PAGE.top, bottom: PAGE.bottom, left: PAGE.left, right: PAGE.right }, + 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 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) => { + 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(); + }; + + const section = (title: string, c: Col = full) => { + room(50); + doc.moveDown(0.4); + const y = doc.y; + 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, 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 }) + 3); + const y = doc.y; + 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 }); + doc.font("body").text(` ${text}`, { lineGap: 1 }); + } else { + doc.fillColor(INK).text(text, c.x + indent, y, { width: w, lineGap: 1 }); + } + 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 }); + }; + + // The letterhead carries the name, email, website and city; the resume adds + // the title, availability, profiles and date. + 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(11).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 updated = r.updated ? `Updated ${month(r.updated.slice(0, 7))}` : ""; + { + const y = doc.moveDown(0.15).y; + doc.font("body").fontSize(8).fillColor(MUTED); + if (updated) doc.text(updated, full.x + full.w - doc.widthOfString(updated), y, { lineBreak: false }); + const links = profiles.map((s) => s.url.replace(/^https?:\/\/(www\.)?/, "").replace(/\/$/, "")).join(" · "); + doc.text(links, full.x, y, { width: full.w - 120 }); + } + doc.moveDown(0.6); + + if (r.summary) { + doc.font("body").fontSize(9).fillColor(INK).text(r.summary.replace(/\s*\n\s*/g, " "), full.x, doc.y, { width: full.w, lineGap: 1.4 }); + } + + if (r.stats.length) { + doc.moveDown(0.6); + const cols = Math.min(r.stats.length, 4); + const colW = full.w / cols; + const top = doc.y; + let bottom = top; + r.stats.forEach((st, i) => { + const x = full.x + (i % cols) * colW; + const y = top + Math.floor(i / cols) * 36; + doc.font("title").fontSize(14).fillColor(GOLD).text(st.value, x, y, { width: colW - 8, lineBreak: false }); + doc.font("strong").fontSize(8).fillColor(INK).text(st.label, x, y + 16, { width: colW - 8, continued: !!st.detail }); + if (st.detail) doc.font("body").fillColor(MUTED).text(` ${st.detail}`); + bottom = Math.max(bottom, doc.y); + }); + 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.5); + titled(d.role, period(role), 10.5); + 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 }); + // 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; + }); + } + + 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).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 = 70; + const SKILL = 8; + 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(SKILL); + 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.6 })) + 2; + }, 24); + const entries = (r.education?.degrees.length ?? 0) + (r.education?.certifications?.length ?? 0); + doc.font("body"); + const rightH = + (entries ? 24 + entries * 26 : 0) + + (r.projects.length ? 24 : 0) + + r.projects.reduce((h, p) => h + doc.heightOfString(`${p.title} ${p.detail}`, { width: rightCol.w - 11, lineGap: 1 }) + 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(SKILL).fillColor(INK).text(label, leftCol.x, y, { width: labelW }); + const afterLabel = doc.y; + doc.font("body").fontSize(SKILL).fillColor(INK).text(items, leftCol.x + labelW, y, { width: leftCol.w - labelW, lineGap: 0.6 }); + doc.y = Math.max(doc.y, afterLabel) + 2; + } + leftEnd = doc.y; + } + + 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.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); + + // Page numbers sit just above the letterhead's footer rule, only when there is more than one page. + const range = doc.bufferedPageRange(); + if (range.count > 1) { + for (let i = range.start; i < range.start + range.count; i++) { + doc.switchToPage(i); + const bottom = doc.page.margins.bottom; + doc.page.margins.bottom = 0; + const label = `${i - range.start + 1} of ${range.count}`; + doc.font("body").fontSize(8).fillColor(MUTED); + doc.text(label, full.x + full.w - doc.widthOfString(label), doc.page.height - PAGE.bottom + 8, { lineBreak: false }); + doc.page.margins.bottom = bottom; + } + } + + doc.end(); + return done; +} + +/** The resume laid over the letterhead: each page draws the letterhead, then the resume on top, both as vectors. */ +export async function renderResumePdf(r: ResumeData): Promise { + const body = await PdfLib.load(await renderBody(r)); + const out = await PdfLib.create(); + out.setTitle(`${r.name} – Resume`, { showInWindowTitleBar: true }); + out.setAuthor(r.name); + out.setSubject(r.headline); + out.setKeywords(["resume"]); + out.setLanguage("en-US"); + out.setProducer("GregKilmer.com"); + out.setCreator("GregKilmer.com"); + const [letterhead] = await out.embedPdf(fromDataUrl(letterheadPdf), [0]); + const pages = await out.embedPdf(body, body.getPageIndices()); + for (const p of pages) { + const page = out.addPage([p.width, p.height]); + page.drawPage(letterhead!); + page.drawPage(p); + } + return Buffer.from(await out.save()); +} diff --git a/services/site/src/lib/resume.ts b/services/site/src/lib/resume.ts new file mode 100644 index 0000000..20a25f1 --- /dev/null +++ b/services/site/src/lib/resume.ts @@ -0,0 +1,60 @@ +/** 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; + 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 }[]; + /** 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)}`; + +/** "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"); + 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, + email: settings?.email, + stats: page?.data.stats ?? [], + 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 ?? [], + updated, + }; +} diff --git a/services/site/src/lib/site.ts b/services/site/src/lib/site.ts new file mode 100644 index 0000000..b40481d --- /dev/null +++ b/services/site/src/lib/site.ts @@ -0,0 +1,273 @@ +/** + * Everything the pages ask of the notes: lookups, addresses, links both ways, + * statuses and Markdown. Pure, so tests build one from plain notes. + */ +import { slugify, wikilinkTarget, wikilinksIn, type NoteType } from "@gk/schema"; +import type { Note, Redirect } from "./content.ts"; +import { renderMarkdown } from "./markdown.ts"; + +/** Fields that hold images, not links to notes. */ +const IMAGE_KEYS = new Set(["cover", "photos", "file", "images"]); + +/** Child types whose links also count for their parent (a make for its craft). */ +const ROLLUPS: Partial> = { make: "craft", deck: "game", campaign: "system" }; + +export class SiteIndex { + readonly notes: Note[]; + private readonly byNameMap = new Map(); + private readonly byTypeMap = new Map(); + private readonly outgoing = new Map(); + private readonly incoming = new Map(); + private readonly redirectMap = new Map(); + + constructor(notes: Note[], redirects: Redirect[] = []) { + this.notes = notes; + for (const n of notes) { + this.byNameMap.set(n.name.toLowerCase(), n); + const list = this.byTypeMap.get(n.type) ?? []; + list.push(n); + this.byTypeMap.set(n.type, list); + } + for (const n of notes) { + const names = [...new Set([...linksIn(n.data), ...wikilinksIn(n.body ?? "")].map((x) => x.toLowerCase()))]; + this.outgoing.set(n.path, names); + for (const name of names) { + const target = this.byNameMap.get(name); + if (!target || target.path === n.path) continue; + const list = this.incoming.get(target.path) ?? []; + list.push(n); + this.incoming.set(target.path, list); + } + } + const byPath = new Map(notes.map((n) => [n.path, n])); + for (const r of redirects) { + const to = byPath.get(r.toPath); + const href = to && this.href(to); + if (href) this.redirectMap.set(`${r.type}/${r.fromSlug}`, href); + } + } + + /** Every published note of a type. */ + all(type: T): Note[] { + return (this.byTypeMap.get(type) ?? []) as Note[]; + } + + /** The one note of a single-note type, like site settings. */ + one(type: T): Note | undefined { + return this.all(type)[0]; + } + + bySlug(type: T, slug: string): Note | undefined { + return this.all(type).find((n) => n.slug === slug); + } + + /** A note by the name a wikilink uses, or by a `[[wikilink]]` itself. */ + byName(nameOrLink: string | undefined): Note | undefined { + if (!nameOrLink) return undefined; + const name = wikilinkTarget(nameOrLink) ?? nameOrLink; + return this.byNameMap.get(name.trim().toLowerCase()); + } + + /** Where an old slug of a type now lives. */ + redirect(type: NoteType, slug: string): string | undefined { + return this.redirectMap.get(`${type}/${slug}`); + } + + /** The address that shows a note, or null for notes no page shows. */ + href(n: Note): string | null { + const d = n.data as Record; + switch (n.type) { + case "post": + return d.postType === "case study" ? `/work/${n.slug}` : `/blog/${n.slug}`; + case "library-item": + return `/library#${n.slug}`; + case "board-game": + return `/library/shelf#${n.slug}`; + case "craft": + return `/workshop/${n.slug}`; + case "make": { + const craft = this.byName(d.craft as string); + return craft ? `/workshop/${craft.slug}#${n.slug}` : `/workshop#${n.slug}`; + } + case "station": + case "tool": + return `/workshop#${n.slug}`; + case "tabletop-system": + case "campaign": + case "card-game": + case "deck": + return `/tabletop#${n.slug}`; + case "experience": + return `/resume#${n.slug}`; + case "status": + return `/now#${n.slug}`; + case "product-line": + return `/store/${n.slug}`; + case "page": + return (d.page as string) ?? null; + default: + return null; + } + } + + /** A wikilink as `{ title, href }`; href is null when the note is not published. */ + link(nameOrLink: string): { title: string; href: string | null; note?: Note } { + const note = this.byName(nameOrLink); + const fallback = (wikilinkTarget(nameOrLink) ?? nameOrLink).trim(); + return note ? { title: note.title, href: this.href(note), note } : { title: fallback, href: null }; + } + + /** + * Notes that link to this one, newest first. Links to a make count for its + * craft, to a deck for its game and to a campaign for its system. + */ + backlinks(n: Note, opts: { type?: NoteType } = {}): Note[] { + const seen = new Map(); + const add = (list: Note[] | undefined) => list?.forEach((b) => b.path !== n.path && seen.set(b.path, b)); + add(this.incoming.get(n.path)); + for (const [childType, field] of Object.entries(ROLLUPS)) { + for (const child of this.all(childType as NoteType)) { + const parent = this.byName((child.data as Record)[field] as string); + if (parent?.path === n.path) add(this.incoming.get(child.path)); + } + } + return [...seen.values()] + .filter((b) => !opts.type || b.type === opts.type) + .sort((a, b) => dateOf(b).localeCompare(dateOf(a))); + } + + /** The notes this one links to. */ + linksFrom(n: Note): Note[] { + return (this.outgoing.get(n.path) ?? []).map((name) => this.byNameMap.get(name)).filter((x): x is Note => !!x); + } + + /** Published blog posts, newest first. Case studies are listed on Work instead. */ + posts(opts: { topic?: string; includeCaseStudies?: boolean } = {}): Note<"post">[] { + return this.all("post") + .filter((p) => (opts.includeCaseStudies || p.data.postType !== "case study") && (!opts.topic || slugify(p.data.topic) === slugify(opts.topic))) + .sort((a, b) => b.data.date.localeCompare(a.data.date)); + } + + caseStudies(): Note<"post">[] { + return this.all("post") + .filter((p) => p.data.postType === "case study") + .sort((a, b) => b.data.date.localeCompare(a.data.date)); + } + + /** The status log: newest status per subject, pinned first, then newest. */ + statuses(): Note<"status">[] { + const latest = new Map>(); + const sorted = [...this.all("status")].sort((a, b) => b.data.date.localeCompare(a.data.date) || b.acceptedAt.localeCompare(a.acceptedAt)); + for (const s of sorted) { + const key = s.data.subject ? (this.byName(s.data.subject)?.path ?? s.data.subject.toLowerCase()) : s.path; + if (!latest.has(key)) latest.set(key, s); + } + return [...latest.values()].sort((a, b) => Number(!!b.data.pinned) - Number(!!a.data.pinned) || b.data.date.localeCompare(a.data.date)); + } + + /** The newest status about a note, for its Now chip. */ + statusFor(n: Note): Note<"status"> | undefined { + return this.statuses().find((s) => this.byName(s.data.subject)?.path === n.path); + } + + /** A status kind's label, icon and page. */ + kind(label: string): { label: string; icon: string; page?: string } { + const kinds = this.one("status-kinds")?.data.kinds ?? []; + const k = kinds.find((x) => x.label.toLowerCase() === label.toLowerCase()); + return { label, icon: brandIcon(k?.icon), page: k?.page }; + } + + /** Text for a page from its Page text note (lede, intro, quick facts). */ + pageText(path: string): Note<"page"> | undefined { + return this.all("page").find((p) => p.data.page === path); + } + + settings() { + return this.one("site-settings")?.data; + } + + /** A note's Markdown as safe HTML, wikilinks resolved, unpublished links as plain text. */ + render(markdown: string | undefined): string { + if (!markdown?.trim()) return ""; + return renderMarkdown(markdown, { link: (name) => this.link(name), imageSrc: mediaSrc }); + } +} + +/** Every `[[link]]` in a note's fields, skipping image fields. */ +function linksIn(value: unknown, key = ""): string[] { + if (IMAGE_KEYS.has(key)) return []; + if (typeof value === "string") { + const t = wikilinkTarget(value); + return t ? [t] : []; + } + if (Array.isArray(value)) return value.flatMap((v) => linksIn(v, key)); + if (value && typeof value === "object") return Object.entries(value).flatMap(([k, v]) => linksIn(v, k)); + return []; +} + +function dateOf(n: Note): string { + const d = n.data as Record; + return String(d.date ?? d.updated ?? d.created ?? n.acceptedAt ?? ""); +} + +/** An image field (`[[file.jpg]]` or an https URL) as a src. */ +export function mediaSrc(value: string | undefined): string | undefined { + if (!value) return undefined; + if (/^https:\/\//.test(value)) return value; + const file = wikilinkTarget(value) ?? value; + return `/media/${encodeURIComponent(file.trim())}`; +} + +/** 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", + "gamepad-variant": "gamepad", + "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", +}; + +/** 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". */ +export function formatDate(iso: string | undefined, opts: { month?: "short" | "long"; day?: boolean } = {}): string { + if (!iso) return ""; + const [y, m, d] = iso.split("-").map(Number); + const date = new Date(Date.UTC(y!, (m ?? 1) - 1, d ?? 1)); + return date.toLocaleDateString("en-US", { + timeZone: "UTC", + year: "numeric", + month: opts.month ?? "short", + ...(opts.day === false || !d ? {} : { day: "numeric" }), + }); +} diff --git a/services/site/src/lib/store.ts b/services/site/src/lib/store.ts new file mode 100644 index 0000000..d0e853b --- /dev/null +++ b/services/site/src/lib/store.ts @@ -0,0 +1,166 @@ +/** + * 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 }; +} + +export type Option = NonNullable[number] & { inStock: boolean }; + +/** Stock the content service syncs onto a line: Bambuddy filament, Homebox leather and paper. */ +type StockGroup = { group: string; options: { name: string; hex?: string; inStock: boolean }[] }; +interface StockSynced { + filamentStock?: StockGroup[]; + materialStock?: StockGroup[]; +} + +/** + * Option groups in note order, with their options. Synced stock sits under + * the note's own values: a note option keeps every value it sets, takes the + * synced `inStock` and `hex` only where it leaves them out (out of stock + * when the source does not have it, in stock when nothing is synced), and + * in-stock colors the note does not list follow its own. + */ +export function optionGroups(line: Line): [string, Option[]][] { + const d = line.data as Line["data"] & StockSynced; + const synced = new Map(); + for (const g of [...(d.filamentStock ?? []), ...(d.materialStock ?? [])]) synced.set(g.group.toLowerCase(), g.options); + const map = new Map(); + for (const o of line.data.options ?? []) { + const stock = synced.get(o.group.toLowerCase()); + const match = stock?.find((x) => x.name.toLowerCase() === o.name.toLowerCase() || (!!o.hex && x.hex?.toLowerCase() === o.hex.toLowerCase())); + const hex = o.hex ?? match?.hex; + map.set(o.group, [...(map.get(o.group) ?? []), { ...o, ...(hex ? { hex } : {}), inStock: o.inStock ?? (stock ? !!match?.inStock : true) }]); + } + for (const [group, opts] of map) { + const stock = synced.get(group.toLowerCase()) ?? []; + const listed = new Set(opts.map((o) => o.name.toLowerCase())); + const listedHex = new Set(opts.map((o) => o.hex?.toLowerCase()).filter(Boolean)); + for (const x of stock) { + if (!x.inStock || listed.has(x.name.toLowerCase()) || (x.hex && listedHex.has(x.hex.toLowerCase()))) continue; + opts.push({ group, name: x.name, ...(x.hex ? { hex: x.hex } : {}), inStock: true }); + listed.add(x.name.toLowerCase()); + } + } + 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..59994e0 --- /dev/null +++ b/services/site/src/lib/world.ts @@ -0,0 +1,120 @@ +/** + * 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}`; + +/** Values Archidekt syncs onto a deck (see withSynced in content.ts). */ +interface DeckSynced { + commanderArt?: string; + colors?: string[]; + commanders?: string[]; +} + +/** The deck's own cover, else its synced commander art. */ +export function deckArt(deck: Note<"deck">): string | undefined { + const d = deck.data as DeckSynced & { cover?: string }; + return mediaSrc(d.cover ?? d.commanderArt); +} + +const COLOR_NAMES: Record = { W: "White", U: "Blue", B: "Black", R: "Red", G: "Green" }; + +/** The deck's color identity as names, WUBRG order; empty when not synced. */ +export function deckColors(deck: Note<"deck">): string[] { + return ((deck.data as DeckSynced).colors ?? []).map((c) => COLOR_NAMES[c]).filter((c): c is string => !!c); +} + +/** The deck's commanders, when synced. */ +export const deckCommanders = (deck: Note<"deck">): string[] => (deck.data as DeckSynced).commanders ?? []; + +/** 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}

    +
    + + +
    +
    + + + + +
    + + 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 friends and fellow nerds +

    My world

    + +
    +
    +
    +
    +
    + + diff --git a/services/site/src/pages/api/inquiries.ts b/services/site/src/pages/api/inquiries.ts new file mode 100644 index 0000000..641d241 --- /dev/null +++ b/services/site/src/pages/api/inquiries.ts @@ -0,0 +1,76 @@ +/** + * POST /api/inquiries: the Inquiries form (multipart, works without script). + * Checks the post, the rate limit and Turnstile, saves a copy through the + * content service, emails Greg, and redirects (303) to /inquiries/sent with + * the gk-inquiry cookie. A post that is turned down goes back to /inquiries + * with the reason and the visitor's answers. + */ +import type { APIRoute } from "astro"; +import { + clientIp, + encodeDraft, + formValues, + inquiryEmail, + inquiryFields, + inquirySent, + isBot, + saveAndSend, + TURNSTILE_FIELD, + validateInquiry, + type ErrorCode, + type FieldErrors, +} from "../../lib/forms.ts"; +import { + contactTo, + flashCookie, + inquiryLimiter, + maxAttachmentBytes, + reportDelivery, + saveSubmission, + sendMail, + verifyTurnstile, +} from "../../lib/forms-server.ts"; + +export const prerender = false; + +export const POST: APIRoute = async ({ request, cookies, redirect, url, clientAddress }) => { + let form: FormData; + try { + form = await request.formData(); + } catch { + return redirect("/inquiries?error=invalid", 303); + } + const type = String(form.get("type") ?? ""); + const back = (code: ErrorCode, errors: FieldErrors = {}) => { + cookies.set("gk-inquiry-draft", encodeDraft({ code, errors, values: formValues(form) }), flashCookie(url)); + const q = new URLSearchParams({ error: code }); + if (/^[a-z]{1,20}$/.test(type)) q.set("type", type); + return redirect(`/inquiries?${q}`, 303); + }; + + // A bot filled the hidden field: answer as if it worked, keep nothing. + if (isBot(form)) return redirect("/inquiries/sent", 303); + + const checked = validateInquiry(form, { maxBytes: maxAttachmentBytes() }); + if (!checked.ok) return back(checked.code, checked.errors); + const q = checked.value; + + let ip = "unknown"; + try { + ip = clientIp(request.headers, clientAddress); + } catch { + ip = clientIp(request.headers); + } + if (inquiryLimiter.hit(ip)) return back("rate"); + if (!(await verifyTurnstile(String(form.get(TURNSTILE_FIELD) ?? ""), ip))) return back("bot"); + + const result = await saveAndSend({ + save: () => saveSubmission({ kind: "inquiry", type: q.type, name: q.name, email: q.email, fields: inquiryFields(q), attachment: q.attachment }), + email: (savedId) => sendMail({ to: contactTo(), replyTo: { name: q.name, address: q.email }, ...inquiryEmail(q, savedId), attachment: q.attachment }), + report: reportDelivery, + }); + if (!result.ok) return back("failed"); + + cookies.set("gk-inquiry", JSON.stringify(inquirySent(q)), flashCookie(url)); + return redirect("/inquiries/sent", 303); +}; diff --git a/services/site/src/pages/api/orders.ts b/services/site/src/pages/api/orders.ts new file mode 100644 index 0000000..9fc00e9 --- /dev/null +++ b/services/site/src/pages/api/orders.ts @@ -0,0 +1,80 @@ +/** + * POST /api/orders: the Store's order request form (works without script). + * Checks the post, the rate limit and Turnstile, then re-checks the line + * against the live store notes: an order is taken only when its category's + * ordersOpen is true and the line has an open slot or stock, whatever the + * post says. Then it saves a copy, emails Greg, and redirects (303) to + * /store/order/sent with the gk-order cookie. No payment is taken. + */ +import type { APIRoute } from "astro"; +import { + checkOrder, + clientIp, + encodeDraft, + formValues, + isBot, + orderEmail, + orderFields, + orderSent, + saveAndSend, + TURNSTILE_FIELD, + validateOrderFields, + type ErrorCode, + type FieldErrors, +} from "../../lib/forms.ts"; +import { flashCookie, liveStore, orderLimiter, orderTo, reportDelivery, saveSubmission, sendMail, verifyTurnstile } from "../../lib/forms-server.ts"; +import type { StoreState } from "../../lib/store.ts"; + +export const prerender = false; + +export const POST: APIRoute = async ({ request, cookies, redirect, url, clientAddress }) => { + let form: FormData; + try { + form = await request.formData(); + } catch { + return redirect("/store/order?error=invalid", 303); + } + const line = String(form.get("line") ?? ""); + const back = (code: ErrorCode, errors: FieldErrors = {}) => { + cookies.set("gk-order-draft", encodeDraft({ code, errors, values: formValues(form) }), flashCookie(url)); + const q = new URLSearchParams({ error: code }); + if (/^[a-z0-9-]{1,120}$/.test(line)) q.set("line", line); + return redirect(`/store/order?${q}`, 303); + }; + + if (isBot(form)) return redirect("/store/order/sent", 303); + + const fields = validateOrderFields(form); + if (!fields.ok) return back(fields.code, fields.errors); + + let ip = "unknown"; + try { + ip = clientIp(request.headers, clientAddress); + } catch { + ip = clientIp(request.headers); + } + if (orderLimiter.hit(ip)) return back("rate"); + if (!(await verifyTurnstile(String(form.get(TURNSTILE_FIELD) ?? ""), ip))) return back("bot"); + + // The gate: always the live notes, never the cached copy or the form. + let st: StoreState; + try { + st = await liveStore(); + } catch (err) { + console.error("Could not read the live store notes; turning the order down.", err); + return back("unavailable"); + } + const checked = checkOrder(fields.value, form, st); + if (!checked.ok) return back(checked.code, checked.errors); + const o = checked.value; + + const result = await saveAndSend({ + save: () => saveSubmission({ kind: "order", type: o.lineNote.slug, name: o.name, email: o.email, fields: orderFields(o) }), + email: (savedId) => sendMail({ to: orderTo(), replyTo: { name: o.name, address: o.email }, ...orderEmail(o, savedId) }), + report: reportDelivery, + }); + if (!result.ok) return back("failed"); + + cookies.set("gk-order", JSON.stringify(orderSent(o)), flashCookie(url)); + return redirect("/store/order/sent", 303); +}; 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) }))]} + /> +
    + + + + + { + months.length > 0 && ( + + ({ label: m.label, href: `/blog?month=${m.month}`, count: m.count, current: m.month === month }))} /> + + ) + } + RSS feed + +
    +
    + + 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 new file mode 100644 index 0000000..359bdbf --- /dev/null +++ b/services/site/src/pages/index.astro @@ -0,0 +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" }); +--- + + +
    +
    + { + eyebrow && ( + + + ) + } +

    {name}

    + {intro ?
    :

    [PLACEHOLDER]

    } +
    +
    + +
    +
    + +
    +
    + + +
    +
    For employers and clients
    +
    +

    Work with me

    + +
    + {role?.data.focus &&

    {role.data.focus}

    } +
    + +
    + + {coreStack.length > 0 && {coreStack.join(" · ")}} + {settings?.availability && {settings.availability}} +
    +
    + +
    + +
    + + + For friends and fellow nerds +
    +

    My world

    + +
    + +
    +
    +
    + + +
    + + { + 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..2f27ea2 --- /dev/null +++ b/services/site/src/pages/inquiries/index.astro @@ -0,0 +1,466 @@ +--- +import Button from "@gk/ui/components/Button.astro"; +import Field from "@gk/ui/components/Field.astro"; +import Notice from "@gk/ui/components/Notice.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 { choices, errorMessages, HONEYPOT, parseDraft, parseErrorCode, timing } from "../../lib/forms.ts"; +import { turnstileSiteKey } from "../../lib/forms-server.ts"; +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"); +// A post that was turned down comes back with its reason and the answers given. +const draft = parseDraft(Astro.cookies.get("gk-inquiry-draft")?.value); +if (draft) { + Astro.cookies.delete("gk-inquiry-draft", { path: "/" }); + Astro.response.headers.set("Cache-Control", "private, no-store"); +} +const errorCode = draft?.code ?? parseErrorCode(Astro.url.searchParams.get("error")); +const v = draft?.values ?? {}; +const err = draft?.errors ?? {}; +const selected = parseInquiryType(v.type ?? 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 ${s.settings()?.replyTime ?? "a few 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 turnstileKey = turnstileSiteKey(); +--- + + {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 " : ""} + + ))} + +
    • + ) + } +
    +
    + +
    +
    + {errorCode && ( + + {Object.keys(err).length > 0 ? "The marked answers are below." : ""} + + )} +
    + What's this about?{err.type && {err.type}} +
    + { + inquiryTypes.map((t) => ( + + )) + } +
    +
    + +
    + + +
    + +
    +
    + + +
    + + +
    + +
    + +
    + + +
    + +
    + +
    + +
    + + +
    + +
    + +
    + + + {err.details && {err.details}} +
    + + + {err.attachment && {err.attachment}} + {draft && !err.attachment && If you attached a file, add it again.} + + + + {turnstileKey &&
    } + {turnstileKey && } + +
    + + {turnstileKey && Protected by Cloudflare Turnstile.} +
    +
    +
    +
    +
    + + + + diff --git a/services/site/src/pages/inquiries/sent.astro b/services/site/src/pages/inquiries/sent.astro new file mode 100644 index 0000000..716faab --- /dev/null +++ b/services/site/src/pages/inquiries/sent.astro @@ -0,0 +1,231 @@ +--- +/** + * 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"; +import { site } from "../../lib/page.ts"; + +const replyTime = (await site().catch(() => null))?.settings()?.replyTime ?? "a few days"; +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 {replyTime}.

    +
      + { + steps.map((st, i) => ( +
    1. + {i + 1} + + {st.title} + {st.text} + +
    2. + )) + } +
    +
    + +
    + { + rows.length > 0 && ( +
    +

    + + What you sent +

    + +
    + {rows.map((r) => ( +
    +
    {r.label}
    +
    {r.value}
    +
    + ))} +
    +
    + ) + } + +
    +
    +
    + + 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 && } + + + {sections.map((sec) => )} + + + + + +
    + { + groups.map((g) => ( + + )) + } + +
    +
    +
    + + + + diff --git a/services/site/src/pages/library/shelf.astro b/services/site/src/pages/library/shelf.astro new file mode 100644 index 0000000..bcbbf19 --- /dev/null +++ b/services/site/src/pages/library/shelf.astro @@ -0,0 +1,370 @@ +--- +/** + * 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, play times and box art sync from BoardGameGeek (a cover set in the + * note wins); 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}`; +}; +--- + + + +
    + +
    +
    +

    On the table

    + +
    + { + table.length > 0 ? ( + + ) : ( +

    Nothing on the table right now.

    + ) + } +
    +
    + +
    + + + + + + { + styles.length > 0 && ( + + {styles.map((p) => )} + + ) + } + + + + + + + +
    +
    +

    Showing {visible.size} {visible.size === 1 ? "game" : "games"}

    +
    + + ({ label: o.label, href: sortHref(o.key), active: o.key === f.sort, value: o.key }))} /> +
    +
    +
      + {games.map((g) =>
    + +
    +
    +
    + + + + diff --git a/services/site/src/pages/media/[...file].ts b/services/site/src/pages/media/[...file].ts new file mode 100644 index 0000000..2d6f8cc --- /dev/null +++ b/services/site/src/pages/media/[...file].ts @@ -0,0 +1,16 @@ +/** Vault images, passed through from the content service with a day's cache. */ +import type { APIRoute } from "astro"; +import { attachment } from "../../lib/content.ts"; + +export const GET: APIRoute = async ({ params }) => { + const file = params.file ?? ""; + if (!file || file.includes("..")) return new Response("Not found", { status: 404 }); + const upstream = await attachment(file).catch(() => null); + if (!upstream?.ok || !upstream.body) return new Response("Not found", { status: 404 }); + return new Response(upstream.body, { + headers: { + "content-type": upstream.headers.get("content-type") ?? "application/octet-stream", + "cache-control": "public, max-age=86400", + }, + }); +}; 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)); +--- + + + + + +
    +
    + {intro &&
    } + { + groups.length ? ( + groups.map((g) => ( +
    + + + {g.title} + +
      + {g.items.map((v) => ( + + ))} +
    +
    + )) + ) : ( +

    Nothing on the status log yet.

    + ) + } + {!hasNext && } +
    + + +
    + + + + + diff --git a/services/site/src/pages/resume.astro b/services/site/src/pages/resume.astro new file mode 100644 index 0000000..786aa49 --- /dev/null +++ b/services/site/src/pages/resume.astro @@ -0,0 +1,653 @@ +--- +/** + * 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 { listOf, 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}

    } +
    + +
    + + { + 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 {listOf(role.data.previousRoles)} + ) : null} + {role.data.focus && {role.data.focus}} +
      + {role.data.bullets.map((b) => ( +
    • + + {b} +
    • + ))} +
    + {role.data.tech?.length ? ( +
    + {role.data.tech.map((t) => ( + {t} + ))} +
    + ) : null} +
    + )) + } +
    + { + r.earlier.length > 0 && ( +
    +

    Earlier

    +
      + {r.earlier.map((role) => ( +
    • +
      + {role.data.role} + {period(role)} +
      + {role.data.company} + {role.data.focus && {role.data.focus}} +
    • + ))} +
    +
    + ) + } + { + r.projects.length > 0 && ( +
    +

    Selected projects

    +
      + {r.projects.map((p) => ( +
    • + + + {p.link ? {p.title} : {p.title}} + · {p.detail} + +
    • + ))} +
    +
    + ) + } + {caseStudies.length > 0 && } +
    +
    +
    + + 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]; +--- + +
    + +
    + +
    + + +
    +

    {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 && } + {shop && } + +
    + {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 + + + + + {d.priceTiers.map((t) => ( + + + + + + ))} + +
    Prices for {line.title}
    {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 && } + +
    +
    + { + 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. + 1 + Send a request + Use the order form with your card list or deck box colors. No payment is taken through the site. +
    2. +
    3. + 2 + Confirm the details + I check the list and art, then confirm the total with you. +
    4. +
    5. + 3 + I make it + {turnarounds.map((t) => {t})} +
    6. +
    +
    + + { + 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..220942c --- /dev/null +++ b/services/site/src/pages/store/order.astro @@ -0,0 +1,197 @@ +--- +/** + * 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"; +import { errorMessages, parseDraft, parseErrorCode } from "../../lib/forms.ts"; +import { turnstileSiteKey } from "../../lib/forms-server.ts"; + +const s = await site(); +const st = storeState(s); +// A post that was turned down comes back with its reason and the answers given. +const draft = parseDraft(Astro.cookies.get("gk-order-draft")?.value); +if (draft) { + Astro.cookies.delete("gk-order-draft", { path: "/" }); + Astro.response.headers.set("Cache-Control", "private, no-store"); +} +const errorCode = draft?.code ?? parseErrorCode(Astro.url.searchParams.get("error")); +const turnstileKey = turnstileSiteKey(); +const selected = draft?.values.line ?? 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)}.`); +--- + + {turnstileKey && } +
    + +

    Order request

    +

    Send a request and I will confirm the details and the total with you. No payment is taken through the site.

    +
    + +
    +
    + {errorCode && ( +
    + + {draft && Object.keys(draft.errors).length > 0 ? "The marked answers are below." : ""} + +
    + )} + { + orderable.length > 0 ? ( + + ) : ( +
    + {st.anyOpen ? ( + Slots reopen as current orders finish. + ) : ( + {reopens.join(" ")} + )} +

    {st.settings?.capacityNote}

    +
    + + +
    +
    + ) + } +
    + + +
    +
    + + diff --git a/services/site/src/pages/store/order/sent.astro b/services/site/src/pages/store/order/sent.astro new file mode 100644 index 0000000..d0ee191 --- /dev/null +++ b/services/site/src/pages/store/order/sent.astro @@ -0,0 +1,204 @@ +--- +/** + * The confirmation after an order request. /api/orders sets the short-lived + * `gk-order` cookie (URL-encoded JSON: name, email, line, rows) before it + * redirects here; the page shows it once and clears it. Without the cookie + * the page still reads correctly. + */ +import Button from "@gk/ui/components/Button.astro"; +import PageShell from "@gk/ui/components/PageShell.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 Breadcrumb from "../../../components/store/Breadcrumb.astro"; +import { parseOrderSent } from "../../../lib/forms.ts"; +import { site } from "../../../lib/page.ts"; +import { storeState } from "../../../lib/store.ts"; + +const sent = parseOrderSent(Astro.cookies.get("gk-order")?.value); +if (sent) Astro.cookies.delete("gk-order", { path: "/" }); +Astro.response.headers.set("Cache-Control", "private, no-store"); + +const s = await site().catch(() => null); +const replyTime = s?.settings()?.replyTime ?? "a few days"; +const line = sent?.lineSlug && s ? storeState(s).lines.find((l) => l.slug === sent.lineSlug) : undefined; +const turnaround = line && s ? storeState(s).category(line.data.category).turnaround : undefined; +const rows = [ + ...(sent?.line ? [{ label: "Line", value: sent.line }] : []), + ...(sent?.rows ?? []), + ...(sent?.email ? [{ label: "Reply to", value: sent.email }] : []), +]; +const firstName = sent?.name?.split(/\s+/)[0]; +const steps = [ + { title: "I check the request", text: "The details, the options and my queue." }, + { title: "I confirm with you", text: "The total and the timing, by email. No payment is taken through the site." }, + { title: "I start once you say yes", text: turnaround ?? "Nothing is made until we agree." }, +]; +--- + + +
    + +

    Your request is in

    +

    Thanks{firstName ? `, ${firstName}` : ""}. I read every order request myself and reply within {replyTime}.

    +
    + +
    +
    +
      + { + steps.map((st, i) => ( +
    1. + {i + 1} + + {st.title} + {st.text} + +
    2. + )) + } +
    +
    + + +
    +
    + + { + rows.length > 0 && ( + + ) + } +
    +
    + + diff --git a/services/site/src/pages/tabletop.astro b/services/site/src/pages/tabletop.astro new file mode 100644 index 0000000..d100093 --- /dev/null +++ b/services/site/src/pages/tabletop.astro @@ -0,0 +1,454 @@ +--- +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, deckArt, deckColors, deckCommanders, 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 }))]} + /> + )} +
      + {systems.map((x) => { + const runs = campaigns.filter((c) => s.byName(c.data.system)?.path === x.path); + return ( +
    • +
      +

      {x.title}

      + {sentence(x.data.state)} +
      +

      {x.data.blurb}

      + {runs.length > 0 && ( +

      + {runs.map((c, i) => ( + <> + {i > 0 && " · "} + {c.title} + + ))} +

      + )} +
    • + ); + })} +
    + {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 ( +
    • + +
      + {[shortGame(game.title), d.data.format].filter(Boolean).join(" · ")} +
      +

      + {url ? ( + + {d.title} + + ) : ( + d.title + )} + {isFav(d) && } +

      + {deckOfMoment?.path === d.path && Playing now} +
      + {deckColors(d).length > 0 &&

      {deckColors(d).join(" · ")}

      } + {d.data.take &&

      {d.data.take}

      } + {url && ( + + {d.data.archidektId ? "On Archidekt" : "Deck list"} + + )} +
      +
    • + ); + })} +
    +
    + ) + } + + { + games.length > 0 && ( + +
      + {games.map((g) => { + const n = s.all("deck").filter((d) => s.byName(d.data.game)?.path === g.path).length; + return ( +
    • +
      +

      {g.title}

      + {g.data.stance} +
      + {g.data.blurb &&

      {g.data.blurb}

      } + {n > 0 &&

      {plural(n, "deck")} here

      } +
    • + ); + })} +
    +
    + ) + } + + + + + + + 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 && ( + + ) + } + + { + studies.length === 0 && ( +
    + They are being written up now. +
    + ) + } + + {featured && } + + { + rest.length > 0 && ( +
    +
      + {rest.map((p) => ( + + ))} +
    + +
    + ) + } + +
    +
    +
    +
    +

    Have something like this in mind?

    + +
    + Jobs and freelance web work. +
    +
    + + +
    +
    +
    +
    + + + + 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); +--- + + +
    Workshop / Craft
    +

    {craft.title}

    + {craft.data.blurb &&

    {craft.data.blurb}

    } +
    + {craft.data.status} + + {meta} + { + station && ( + <> + {" · "} + {station.href ? {station.title} : station.title} + {" station"} + + ) + } + +
    +
    + + {body &&
    } + + {nowItems.length > 0 && } + + { + tools.length > 0 && ( + +
      + {tools.map(({ title, note }) => ( +
    • + {note && {note.data.kind === "other" ? "Tool" : sentence(note.data.kind)}} +

      {note ? {title} : title}

      + {note?.data.blurb && {note.data.blurb}} +
    • + ))} +
    +
    + ) + } + + 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.title}

      + {m.data.blurb && {m.data.blurb}} + {(m.data.date || log) && ( + + {m.data.date && } + {log && Build log} + + )} +
      +
    • + ); + })} +
    + ) : ( +

    Nothing finished in this craft yet.

    + ) + } +
    + + { + logs.length > 0 && ( + +
      + {logs.map((p) => ( +
    • + +
      + + {p.data.topic} + + + {p.title} + {p.data.summary && {p.data.summary}} +
      +
    • + ))} +
    +
    + ) + } + + { + others.length > 0 && ( + + ) + } + + + + + + + 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 }) => ( +
    1. + + {station.data.order} +

      {station.title}

      +
      + {station.data.where} + {tools.length > 0 && ( +
        + {tools.map((t) => ( +
      • + + {t.title} +
      • + ))} +
      + )} + {station.data.goodFor && {station.data.goodFor}} +
    2. + ))} + {loose.length > 0 && ( +
    3. + + +

      Around the shop

      +
      +
        + {loose.map((t) => ( +
      • + + {t.title} +
      • + ))} +
      +
    4. + )} +
    +
    + ) + } + + + + + + { + 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.title} +

      + {m.data.blurb && {m.data.blurb}} + {log && ( + + Blog: {log.title} + + )} +
      +
    • + ); + })} +
    +
    + ) + } + + { + swords.length > 0 && ( + + ) + } + + + + + + + + diff --git a/services/site/test/forms.test.ts b/services/site/test/forms.test.ts new file mode 100644 index 0000000..c73f916 --- /dev/null +++ b/services/site/test/forms.test.ts @@ -0,0 +1,262 @@ +import { describe, expect, it, vi } from "vitest"; +import type { Note } from "../src/lib/content.ts"; +import { + checkOrder, + clientIp, + DRAFT_LIMIT, + encodeDraft, + formValues, + inquiryFields, + inquirySent, + isBot, + orderFields, + orderSent, + parseDraft, + parseOrderSent, + RateLimiter, + saveAndSend, + validateInquiry, + validateOrderFields, +} from "../src/lib/forms.ts"; +import { SiteIndex } from "../src/lib/site.ts"; +import { storeState } from "../src/lib/store.ts"; +import { parseSentSummary } from "../src/lib/work.ts"; + +const MB = 1024 * 1024; + +const form = (fields: Record) => { + const f = new FormData(); + for (const [k, v] of Object.entries(fields)) f.append(k, v); + return f; +}; + +const base = { name: "Jane Doe", email: "jane@example.com", details: "A senior role on the platform team." }; + +describe("inquiry validation", () => { + it("takes a job inquiry and only the job questions", () => { + const r = validateInquiry( + form({ ...base, type: "job", job_company: "Acme", job_kind: "Contract", job_link: "https://acme.example/jobs/1", patiun_quantity: "60", freelance_need: "An app" }), + { maxBytes: MB }, + ); + expect(r.ok).toBe(true); + if (!r.ok) return; + expect(r.value.rows).toEqual([ + { label: "Company", value: "Acme" }, + { label: "Kind of role", value: "Contract" }, + { label: "Posting", value: "https://acme.example/jobs/1" }, + ]); + expect(inquiryFields(r.value).at(-1)).toEqual({ label: "Details", value: base.details }); + }); + + it("marks what is wrong", () => { + const r = validateInquiry(form({ type: "nope", name: "", email: "x@", details: "hi", job_link: "javascript:alert(1)" }), { maxBytes: MB }); + expect(r.ok).toBe(false); + if (r.ok) return; + expect(Object.keys(r.errors).sort()).toEqual(["details", "email", "name", "type"]); + }); + + it("checks choices, links and dates for the chosen type", () => { + const bad = validateInquiry( + form({ ...base, type: "freelance", freelance_need: "World peace", freelance_site: "ftp://x", freelance_timing: "date", freelance_date: "soon" }), + { maxBytes: MB }, + ); + expect(bad.ok).toBe(false); + if (!bad.ok) expect(Object.keys(bad.errors).sort()).toEqual(["freelance_date", "freelance_need", "freelance_site"]); + const good = validateInquiry(form({ ...base, type: "patiun", patiun_item: "Deck box", patiun_timing: "date", patiun_date: "2026-12-01" }), { maxBytes: MB }); + expect(good.ok && good.value.rows).toEqual([ + { label: "Item", value: "Deck box" }, + { label: "Need it by", value: "Dec 1, 2026" }, + ]); + }); + + it("limits attachments by type and size", () => { + const ok = validateInquiry(form({ ...base, type: "patiun", attachment: new File(["1 Sol Ring"], "deck.dek") }), { maxBytes: MB }); + expect(ok.ok && ok.value.attachment?.name).toBe("deck.dek"); + const exe = validateInquiry(form({ ...base, type: "patiun", attachment: new File(["MZ"], "x.exe") }), { maxBytes: MB }); + expect(!exe.ok && exe.errors.attachment).toBeTruthy(); + const big = validateInquiry(form({ ...base, type: "patiun", attachment: new File([new Uint8Array(MB + 1)], "big.png") }), { maxBytes: MB }); + expect(!big.ok && big.errors.attachment).toContain("1 MB"); + const empty = validateInquiry(form({ ...base, type: "patiun", attachment: new File([], "") }), { maxBytes: MB }); + expect(empty.ok && empty.value.attachment).toBeNull(); + }); + + it("builds the sent cookie the sent page reads", () => { + const r = validateInquiry(form({ ...base, type: "patiun", patiun_quantity: "60 cards" }), { maxBytes: MB }); + if (!r.ok) throw new Error("should pass"); + const parsed = parseSentSummary(encodeURIComponent(JSON.stringify(inquirySent(r.value)))); + expect(parsed).toEqual({ type: "patiun", name: "Jane Doe", email: "jane@example.com", rows: [{ label: "How many", value: "60 cards" }] }); + }); + + it("spots the honeypot", () => { + expect(isBot(form({ leave_blank: "http://spam" }))).toBe(true); + expect(isBot(form({ leave_blank: "" }))).toBe(false); + }); +}); + +// Sample store notes, written for these tests. +const settings = (open: Record) => + ({ + path: "Store/_settings.md", + type: "store-settings", + name: "_settings", + slug: "settings", + title: "Store settings", + body: "", + acceptedAt: "2026-10-01", + data: { type: "store-settings", title: "Store settings", categories: Object.entries(open).map(([name, ordersOpen]) => ({ name, ordersOpen })) }, + }) as unknown as Note; + +const line = (slug: string, data: Record) => + ({ + path: `Store/${slug}.md`, + type: "product-line", + name: slug, + slug, + title: slug.replace(/-/g, " "), + body: "", + acceptedAt: "2026-10-01", + data: { type: "product-line", title: slug, category: "Tabletop", channel: "here", mode: "made to order", summary: "", priceTiers: [], ...data }, + }) as unknown as Note; + +const store = (open: Record, lines: Note[]) => storeState(new SiteIndex([settings(open), ...lines])); + +const cards = line("proxy-cards", { + slots: { open: 2, total: 5 }, + options: [ + { group: "Finish", name: "Matte", inStock: true }, + { group: "Finish", name: "Foil", inStock: false }, + ], +}); +const orderForm = (extra: Record = {}) => + form({ name: "Bo", email: "bo@example.com", line: "proxy-cards", quantity: "75 cards", terms: "accepted", ...extra }); + +const fieldsOf = (f: FormData) => { + const r = validateOrderFields(f); + if (!r.ok) throw new Error(JSON.stringify(r.errors)); + return r.value; +}; + +describe("order validation", () => { + it("needs the terms, a line, a quantity and contact details", () => { + const r = validateOrderFields(form({ line: "../x" })); + expect(!r.ok && Object.keys(r.errors).sort()).toEqual(["email", "line", "name", "quantity", "terms"]); + }); + + it("takes an order for an open line with room and an in-stock option", () => { + const f = orderForm({ "option.proxy-cards.Finish": "Matte", "option.other-line.Color": "Red" }); + const r = checkOrder(fieldsOf(f), f, store({ Tabletop: true }, [cards])); + expect(r.ok).toBe(true); + if (!r.ok) return; + expect(r.value.options).toEqual([{ label: "Finish", value: "Matte" }]); + expect(orderFields(r.value).map((x) => x.label)).toEqual(["Line", "Finish", "Quantity", "Terms"]); + expect(parseOrderSent(encodeURIComponent(JSON.stringify(orderSent(r.value))))?.rows).toEqual([ + { label: "Finish", value: "Matte" }, + { label: "Quantity", value: "75 cards" }, + ]); + }); + + it("turns down a closed category, whatever the post says", () => { + const f = orderForm(); + expect(checkOrder(fieldsOf(f), f, store({ Tabletop: false }, [cards]))).toMatchObject({ ok: false, code: "closed" }); + // No settings note at all counts as closed. + expect(checkOrder(fieldsOf(f), f, storeState(new SiteIndex([cards])))).toMatchObject({ ok: false, code: "closed" }); + }); + + it("turns down a line with no open slot or no stock", () => { + const f = orderForm(); + const full = line("proxy-cards", { slots: { open: 0, total: 5 } }); + expect(checkOrder(fieldsOf(f), f, store({ Tabletop: true }, [full]))).toMatchObject({ ok: false, code: "full" }); + const sold = line("proxy-cards", { mode: "in stock", quantity: 0 }); + expect(checkOrder(fieldsOf(f), f, store({ Tabletop: true }, [sold]))).toMatchObject({ ok: false, code: "full" }); + const stocked = line("proxy-cards", { mode: "in stock", quantity: 3 }); + expect(checkOrder(fieldsOf(f), f, store({ Tabletop: true }, [stocked])).ok).toBe(true); + }); + + it("turns down lines sold elsewhere or unknown, and out-of-stock options", () => { + const f = orderForm(); + const etsy = line("proxy-cards", { channel: "Patiun.com", slots: { open: 2, total: 5 } }); + expect(checkOrder(fieldsOf(f), f, store({ Tabletop: true }, [etsy]))).toMatchObject({ ok: false, code: "invalid" }); + expect(checkOrder(fieldsOf(f), f, store({ Tabletop: true }, []))).toMatchObject({ ok: false, code: "invalid" }); + const foil = orderForm({ "option.proxy-cards.Finish": "Foil" }); + expect(checkOrder(fieldsOf(foil), foil, store({ Tabletop: true }, [cards]))).toMatchObject({ ok: false, code: "invalid" }); + const made = orderForm({ "option.proxy-cards.Finish": "Gold leaf" }); + expect(checkOrder(fieldsOf(made), made, store({ Tabletop: true }, [cards]))).toMatchObject({ ok: false, code: "invalid" }); + }); +}); + +describe("try-again drafts", () => { + it("round-trips and fits in a cookie", () => { + const values = formValues(form({ name: "Jane", details: "x".repeat(20_000), leave_blank: "", "cf-turnstile-response": "tok", terms: "accepted" })); + expect(Object.keys(values)).toEqual(["name", "details"]); + const raw = encodeDraft({ code: "invalid", errors: { email: "Add an email" }, values }); + expect(encodeURIComponent(raw).length).toBeLessThanOrEqual(DRAFT_LIMIT); + const d = parseDraft(encodeURIComponent(raw)); + expect(d?.code).toBe("invalid"); + expect(d?.values.name).toBe("Jane"); + expect(d?.errors.email).toBe("Add an email"); + expect(parseDraft("garbage")).toBeUndefined(); + expect(parseDraft(JSON.stringify({ code: "nope" }))).toBeUndefined(); + }); +}); + +describe("saving and emailing", () => { + const quiet = { log: () => {} }; + + it("records a failed email and still succeeds when the copy was saved", async () => { + const report = vi.fn(async () => {}); + const r = await saveAndSend({ + ...quiet, + save: async () => ({ id: 7, receipt: "rcpt" }), + email: async () => { + throw new Error("ECONNREFUSED"); + }, + report, + }); + expect(r).toEqual({ ok: true, savedId: 7, email: "failed" }); + expect(report).toHaveBeenCalledWith(7, "rcpt", "failed", "ECONNREFUSED"); + }); + + it("still emails when saving fails, and fails only when both do", async () => { + const email = vi.fn(async (id: number | null) => (id === null ? ("sent" as const) : ("skipped" as const))); + const report = vi.fn(async () => {}); + const failSave = async (): Promise<{ id: number; receipt: string }> => { + throw new Error("down"); + }; + expect(await saveAndSend({ ...quiet, save: failSave, email, report })).toEqual({ ok: true, savedId: null, email: "sent" }); + expect(report).not.toHaveBeenCalled(); + const r = await saveAndSend({ + ...quiet, + save: failSave, + email: async () => { + throw new Error("smtp down"); + }, + report, + }); + expect(r.ok).toBe(false); + // Nothing configured for mail and nothing saved: not a success either. + expect((await saveAndSend({ ...quiet, save: failSave, email: async () => "skipped", report })).ok).toBe(false); + }); + + it("counts a skipped email as fine when the copy was saved", async () => { + const report = vi.fn(async () => {}); + const r = await saveAndSend({ ...quiet, save: async () => ({ id: 1, receipt: "r" }), email: async () => "skipped", report }); + expect(r.ok).toBe(true); + expect(report).toHaveBeenCalledWith(1, "r", "skipped", undefined); + }); +}); + +describe("rate limit and address", () => { + it("allows N posts per window per address", () => { + let t = 0; + const rl = new RateLimiter(() => 2, () => 1000, () => t); + expect([rl.hit("a"), rl.hit("a"), rl.hit("a"), rl.hit("b")]).toEqual([false, false, true, false]); + t = 1001; + expect(rl.hit("a")).toBe(false); + }); + + it("reads the forwarded address first", () => { + expect(clientIp(new Headers({ "x-forwarded-for": "203.0.113.9, 10.0.0.1" }), "10.0.0.2")).toBe("203.0.113.9"); + expect(clientIp(new Headers(), "10.0.0.2")).toBe("10.0.0.2"); + expect(clientIp(new Headers({ "cf-connecting-ip": "198.51.100.7", "x-forwarded-for": "1.2.3.4, 10.0.0.1" }))).toBe("198.51.100.7"); + }); +}); diff --git a/services/site/test/homelab-sync.test.ts b/services/site/test/homelab-sync.test.ts new file mode 100644 index 0000000..3945237 --- /dev/null +++ b/services/site/test/homelab-sync.test.ts @@ -0,0 +1,86 @@ +/** + * PersonalReader progress on Library rows and synced stock on Store options. + * Synced values are the shapes the content service's jobs store (see + * services/content/src/sync/jobs/); a note's own values always win, and a + * note with nothing synced renders as before. + */ +import { describe, expect, it } from "vitest"; +import { withSynced, type Note } from "../src/lib/content.ts"; +import { itemDetails, readerProgress, type Item } from "../src/lib/library.ts"; +import { optionGroups, type Line } from "../src/lib/store.ts"; + +const note = (type: string, data: Record, synced?: Record) => + withSynced({ path: `${type}/x.md`, type, name: "x", slug: "x", title: "X", body: "", acceptedAt: "", data: { type, ...data }, synced } as unknown as T); + +describe("Library progress from PersonalReader", () => { + const base = { medium: "manhwa", state: "now", creator: "Someone", year: 2020 }; + + it("adds the chapter line to the details", () => { + const i = note("library-item", base, { chapterProgress: { title: "X", status: "now", chapter: 142, readAt: "2026-10-03T05:00:00.000Z" } }); + expect(readerProgress(i)).toBe("Chapter 142, read Oct 3, 2026"); + expect(itemDetails(i)).toEqual(["Someone", 2020, "Chapter 142, read Oct 3, 2026"]); + const book = note("library-item", { ...base, medium: "light novel" }, { bookProgress: { title: "X", percent: 62 } }); + expect(readerProgress(book)).toBe("62% read"); + }); + + it("lets the note's own progress win, and shows the same line when nothing is synced", () => { + const own = note("library-item", { ...base, progress: "16/18 volumes" }, { chapterProgress: { chapter: 3 } }); + expect(itemDetails(own)).toEqual(["Someone", 2020, "16/18 volumes"]); + const bare = note("library-item", base); + expect(itemDetails(bare)).toEqual(["Someone", 2020]); + expect(readerProgress(note("library-item", base, { bookProgress: { percent: 0 } }))).toBeUndefined(); + }); +}); + +describe("Store options with synced stock", () => { + const lineData = (options: unknown[]) => ({ category: "Tabletop", mode: "made to order", channel: "here", summary: "", priceTiers: [], options }); + const stock = { + filamentStock: [ + { + group: "Filament", + options: [ + { name: "Jade White", hex: "#f5f5dc", inStock: true }, + { name: "Bambu Green", hex: "#00ae42", inStock: false }, + { name: "Charcoal", hex: "#2b2b2b", inStock: true }, + ], + }, + ], + }; + + it("is exactly the note's options when nothing is synced", () => { + const opts = [ + { group: "Filament", name: "Jade White", hex: "#ffffff", inStock: true }, + { group: "Finish", name: "Matte", inStock: false }, + ]; + expect(optionGroups(note("product-line", lineData(opts)))).toEqual([ + ["Filament", [opts[0]]], + ["Finish", [opts[1]]], + ]); + // Left out with nothing synced: in stock, as before the field was optional. + expect(optionGroups(note("product-line", lineData([{ group: "Finish", name: "Gloss" }])))[0]![1][0]!.inStock).toBe(true); + }); + + it("sits under the note: note values win, gaps take the synced value, unlisted in-stock colors follow", () => { + const l = note( + "product-line", + lineData([ + { group: "Filament", name: "Jade White" }, + { group: "Filament", name: "Bambu Green", inStock: true }, + { group: "Filament", name: "Galaxy Black" }, + { group: "Finish", name: "Matte", inStock: true }, + ]), + stock, + ); + const [[g, filament], [, finish]] = optionGroups(l) as [string, ReturnType[number][1]][]; + expect(g).toBe("Filament"); + expect(filament).toEqual([ + { group: "Filament", name: "Jade White", hex: "#f5f5dc", inStock: true }, + // The note says in stock: the note wins over the source. + { group: "Filament", name: "Bambu Green", hex: "#00ae42", inStock: true }, + // Not at the source: out of stock. + { group: "Filament", name: "Galaxy Black", inStock: false }, + { group: "Filament", name: "Charcoal", hex: "#2b2b2b", inStock: true }, + ]); + expect(finish).toEqual([{ group: "Finish", name: "Matte", inStock: true }]); + }); +}); diff --git a/services/site/test/synced.test.ts b/services/site/test/synced.test.ts new file mode 100644 index 0000000..0878866 --- /dev/null +++ b/services/site/test/synced.test.ts @@ -0,0 +1,38 @@ +import { describe, expect, it } from "vitest"; +import { withSynced, type Note } from "../src/lib/content.ts"; +import { gamePlayers, gamePlayTime, type Game } from "../src/lib/library.ts"; +import { deckArt, deckColors } from "../src/lib/world.ts"; + +const note = (type: string, data: Record, synced?: Record) => + ({ path: `${type}/x.md`, type, name: "x", slug: "x", title: "X", body: "", acceptedAt: "", data: { type, ...data }, synced }) as unknown as T; + +describe("synced values", () => { + it("sit under the note's own values: the note always wins", () => { + const n = withSynced(note("library-item", { cover: "[[mine.jpg]]", title: "X" }, { cover: "https://x/cover.jpg", year: 2006 })); + expect(n.data).toMatchObject({ cover: "[[mine.jpg]]", year: 2006 }); + const bare = withSynced(note("library-item", { title: "X" }, { cover: "https://x/cover.jpg" })); + expect((bare.data as { cover?: string }).cover).toBe("https://x/cover.jpg"); + }); + + it("leave a note with nothing synced exactly as it came", () => { + const n = note("board-game", { bggId: 1, blurb: "b" }, {}); + expect(withSynced(n)).toBe(n); + const none = note("board-game", { bggId: 1, blurb: "b" }); + expect(withSynced(none)).toBe(none); + expect([gamePlayers(none), gamePlayTime(none)]).toEqual([undefined, undefined]); + expect(deckArt(note("deck", { game: "[[Magic]]" }))).toBeUndefined(); + expect(deckColors(note("deck", { game: "[[Magic]]" }))).toEqual([]); + }); + + it("give the Shelf players and play time, and the decks art and colors", () => { + const g = withSynced(note("board-game", { bggId: 1, blurb: "b" }, { minPlayers: 1, maxPlayers: 5, playTime: 70, minPlayTime: 40, maxPlayTime: 70 })); + expect([gamePlayers(g), gamePlayTime(g)]).toEqual(["1–5 players", "40–70 min"]); + const solo = withSynced(note("board-game", { bggId: 1, blurb: "b" }, { minPlayers: 1, maxPlayers: 1, playTime: 30 })); + expect([gamePlayers(solo), gamePlayTime(solo)]).toEqual(["1 player", "30 min"]); + const d = withSynced(note>("deck", { game: "[[Magic]]" }, { commanderArt: "https://cards.scryfall.io/a.jpg", colors: ["W", "U", "G"] })); + expect(deckArt(d)).toBe("https://cards.scryfall.io/a.jpg"); + expect(deckColors(d)).toEqual(["White", "Blue", "Green"]); + const own = withSynced(note>("deck", { game: "[[Magic]]", cover: "[[deck.jpg]]" }, { commanderArt: "https://cards.scryfall.io/a.jpg" })); + expect(deckArt(own)).toBe("/media/deck.jpg"); + }); +}); diff --git a/services/site/tsconfig.json b/services/site/tsconfig.json new file mode 100644 index 0000000..4669a29 --- /dev/null +++ b/services/site/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "astro/tsconfigs/strict", + "include": ["src", ".astro/types.d.ts"], + "compilerOptions": { + "allowImportingTsExtensions": true, + "noUncheckedIndexedAccess": true + } +} 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..500f984 --- /dev/null +++ b/tools/migrate/src/build.ts @@ -0,0 +1,410 @@ +/** + * 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 }; + // 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: `${(answers.role as string | undefined) ?? 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, + ...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 }); + + // 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, + // 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, + 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..8d02627 --- /dev/null +++ b/tools/migrate/src/seed.ts @@ -0,0 +1,374 @@ +/** + * 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", + /** 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", + /** Greg, 2026-10-11. */ + replyTime: "2 days", + 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, 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: [ + "Software Architecture & System Design", + "Cloud & 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", "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, 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", + end: "2018-06", + earlier: true, + focus: "Ran the college's 4 Linux servers and automated 3 labs and the laptop carts with Ansible, then the lab help desk to 2018.", + bullets: [ + "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: "Senior Intern and Junior Developer (co-op)", + earlier: true, + focus: "Ran a team of 10 co-ops on smart remote, call center and inventory tools; built an NLP customer 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 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.", + ], + }, +}; + +/** 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 and 17 AI agents on a private network." }, + { title: "GregKilmer.com", detail: "This site, fed from an Obsidian vault by a rule-checked content service and MCP server." }, + ], + 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 */ +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.", + }, + ], + // 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 = { + // 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: "Docker containers", value: "20+" }, { 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..7aa695a --- /dev/null +++ b/tools/migrate/test/migrate.test.ts @@ -0,0 +1,70 @@ +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, with no slot open until Greg opens orders", () => { + const proxies = find("product-line", "Proxy MTG Cards")!.data as any; + 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 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) }); + }); + + 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/GregKilmer.com stack.md b/vault-kit/24.01_PersonalWebsite/GregKilmer.com stack.md new file mode 100644 index 0000000..295df81 --- /dev/null +++ b/vault-kit/24.01_PersonalWebsite/GregKilmer.com stack.md @@ -0,0 +1,36 @@ +--- +tags: [homelab, service] +--- + +# GregKilmer.com stack + +The new GregKilmer.com: a content service that reads this folder's `Site/` notes, a tailnet-only admin page, and the public site. Repo: `github.com/Patiun/com.gregkilmer.www` (mirrored to Gitea, deployed by Gitea Actions). Runbook: `docs/deploy.md` in the repo. + +## Containers + +| Container | Role | Network | Host port | +| --- | --- | --- | --- | +| `gk-content` | Content service, sync jobs and MCP server | homelab_network | none | +| `gk-admin` | Admin page | homelab_network | none | +| `gk-site` | Public site | homelab_network | none | + +- Compose: `/home/greg/homelab/gregkilmer/compose.yaml` (copied from the repo's `deploy/compose.yaml` on each deploy) +- Settings and secrets: `/home/greg/homelab/gregkilmer/{.env,content.env,admin.env,site.env}` (600) + +## Data and backups + +- `/home/greg/homelab/gregkilmer/data/content/content.db`: note history, review queue, saved inquiries and orders, sync cache. Label `backup.dump=sqlite` on `gk-content`. +- `/home/greg/homelab/gregkilmer/data/content/submissions/`: form attachments, covered by Kopia. +- The vault's `Site/` folder is mounted read-write into `gk-content`, which writes accepted API and MCP changes back as notes. + +## Access + +- `admin.gregkilmer.com` to `gk-admin:4320`: Tailnet only. +- `content.gregkilmer.com` to `gk-content:4310` (API and `/mcp`): Tailnet only. +- `beta.gregkilmer.com` to `gk-site:4321`: Tailnet only until cutover. After cutover, `gregkilmer.com` is public through Cloudflare. + +## Health + +- Healthchecks: the content service's full scan every 5 minutes, plus one check per sync job. +- A Discord webhook is sent whenever a change is held for review. +- Each container has a Docker health check on `/health` (site: `/`). 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..790f180 --- /dev/null +++ b/vault-kit/24.01_PersonalWebsite/Site/_templates/Experience.md @@ -0,0 +1,18 @@ +--- +type: "experience" +title: "{{title}}" +publish: false # Only notes set to true appear on the site. +created: "{{date:YYYY-MM-DD}}" # optional +company: +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 +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. +--- + + 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..1ddd20e --- /dev/null +++ b/vault-kit/24.01_PersonalWebsite/Site/_templates/Library item.md @@ -0,0 +1,19 @@ +--- +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. +kavitaSeriesId: # optional. Links a book or light novel to its series in Kavita, for 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..54d4e2a --- /dev/null +++ b/vitest.config.ts @@ -0,0 +1,5 @@ +import { defineConfig } from "vitest/config"; + +export default defineConfig({ + test: { include: ["packages/*/test/**/*.test.ts", "services/*/test/**/*.test.ts", "tools/*/test/**/*.test.ts"] }, +});