diff --git a/.gitea/workflows/deploy-web.yml b/.gitea/workflows/deploy-web.yml index c3ee9c0..c664077 100644 --- a/.gitea/workflows/deploy-web.yml +++ b/.gitea/workflows/deploy-web.yml @@ -8,6 +8,7 @@ on: paths: - 'apps/web/**' - 'packages/shared/**' + - 'packages/ui/**' - 'pnpm-lock.yaml' - 'pnpm-workspace.yaml' - 'package.json' diff --git a/.gitea/workflows/deploy.yml b/.gitea/workflows/deploy.yml index edd74e7..6d04077 100644 --- a/.gitea/workflows/deploy.yml +++ b/.gitea/workflows/deploy.yml @@ -1,21 +1,24 @@ name: Build & Deploy Landing -# Workflow pour la landing static (rubis.pro). -# L'app SaaS (apps/api + apps/web) a son propre workflow : deploy-app.yml. +# Workflow pour la landing Astro (rubis.pro). Migration depuis l'ancienne +# landing nginx statique : rendu SSR par Node 22 (Astro adapter standalone), +# pages statiques prerenderées + blog en SSR pur fetch via apps/api. on: push: branches: [main] paths: - - 'landing/**' - - 'Dockerfile' + - 'apps/landing/**' + - 'packages/ui/**' + - 'packages/shared/**' + - 'Dockerfile.landing' - 'k3s/namespace.yml' - - 'k3s/deployment.yml' - - 'k3s/service.yml' + - 'k3s/app/landing.yml' - '.gitea/workflows/deploy.yml' + - 'pnpm-lock.yaml' env: REGISTRY: git.arthurbarre.fr - IMAGE: ordinarthur/rubis + IMAGE: ordinarthur/rubis-landing NAMESPACE: rubis jobs: @@ -36,7 +39,7 @@ jobs: uses: docker/build-push-action@v5 with: context: . - file: Dockerfile + file: Dockerfile.landing push: true tags: | ${{ env.REGISTRY }}/${{ env.IMAGE }}:latest @@ -62,10 +65,9 @@ jobs: --docker-password=${{ secrets.REGISTRY_PASSWORD }} \ --dry-run=client -o yaml | kubectl apply -f - - kubectl apply -f k3s/deployment.yml - kubectl apply -f k3s/service.yml + kubectl apply -f k3s/app/landing.yml - kubectl -n $NAMESPACE set image deployment/rubis \ - rubis=$REGISTRY/$IMAGE:${{ github.sha }} + kubectl -n $NAMESPACE set image deployment/rubis-landing \ + landing=$REGISTRY/$IMAGE:${{ github.sha }} - kubectl -n $NAMESPACE rollout status deployment/rubis --timeout=120s + kubectl -n $NAMESPACE rollout status deployment/rubis-landing --timeout=180s diff --git a/CLAUDE.md b/CLAUDE.md index 830b1af..f8a02ed 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -75,6 +75,7 @@ Direct, concret, chaleureux, précis, empathique. *On parle comme un bon associ - Liste filtrable des factures - Détail facture avec timeline des relances - App mobile (web responsive) +- **Blog `rubis.pro/blog`** — SSR par `apps/landing` (Astro 6), contenu en DB (`posts`) servi par `apps/api` via `/api/v1/posts/*`, admin de validation côté `app.rubis.pro/admin/blog` (à venir PR3), génération hebdomadaire IA via cron (Sonnet 4.6) avec review humaine obligatoire. Détails dans `/docs/tech/architecture.md`. ### OUT (V2 ou plus tard) @@ -111,16 +112,16 @@ Voir `/docs/decisions.md` pour le log complet avec rationale. | Couche | Choix | Source | |---|---|---| -| Backend | **AdonisJS v7** (TS, MVC, Lucid ORM, auth & jobs intégrés) | ADR-014 | -| Frontend | **React + Vite** | ADR-014 | -| Routing client | **TanStack Router** | ADR-014 | -| State serveur | **TanStack Query** | ADR-014 | +| Backend (API) | **AdonisJS v7** (TS, MVC, Lucid ORM, auth & jobs intégrés) | ADR-014 | +| App SPA (`app.rubis.pro`) | **React 19 + Vite + TanStack Router/Query** | ADR-014 | +| Landing + blog (`rubis.pro`) | **Astro 6 SSR** (pages statiques prerenderées + blog en SSR) | — | +| Design system | **`@rubis/ui`** — Tailwind v4 tokens + composants TSX | — | | Base de données | **PostgreSQL** | ADR-014 | | Hosting | **Proxmox + K3s** (perso) | ADR-014 | | OCR provider | à benchmarker | ADR-020 (en attente) | | Email outbound | à benchmarker | ADR-021 (en attente) | -**Architecture** : monorepo (`apps/api` + `apps/web` + `packages/shared`), API REST AdonisJS Bearer-auth, SPA React/Vite séparé, PG et MinIO existants sur LXC Proxmox. Détails dans `/docs/tech/architecture.md`. +**Architecture** : monorepo Turborepo (`apps/api` AdonisJS, `apps/web` React SaaS, `apps/landing` Astro public, `packages/shared` types/schemas, `packages/ui` design system). API REST Bearer-auth, deux frontends qui consomment `@rubis/ui` pour un brand visuel unifié, PG et MinIO existants sur LXC Proxmox. Détails dans `/docs/tech/architecture.md`. **Décisions cadres** : ADR-014 (stack), ADR-015 (monorepo), ADR-016 (PG Proxmox existant), ADR-017 (Bearer tokens), ADR-018 (MinIO existant). @@ -133,10 +134,10 @@ Voir `/docs/decisions.md` pour le log complet avec rationale. | Fichier | Rôle | |---|---| | `/CLAUDE.md` (ce fichier) | Contexte top-level, toujours en tête | -| `/landing/index.html` | Landing page brand-applied, déployée (waitlist V1) | -| `/landing/favicon.{svg,ico,png}` | Set complet de favicons + apple-touch-icon | -| `/landing/site.webmanifest` | Manifest PWA (theme `#9F1239`, background `#FAF7F2`) | -| `/landing/assets/logo.png` | Logo Rubis original (généré, source pour les favicons) | +| `/apps/landing/` | Landing publique + blog (Astro 6 SSR) — déployée sur `rubis.pro` | +| `/apps/landing/public/favicon.{svg,ico,png}` | Set complet de favicons + apple-touch-icon | +| `/apps/landing/public/site.webmanifest` | Manifest PWA (theme `#9F1239`, background `#FAF7F2`) | +| `/packages/ui/` | Design system partagé (tokens Tailwind v4 + composants TSX) | | `/docs/produit.md` | Spec produit haut niveau (features, IN/OUT V1, pricing) | | `/docs/flow.md` | **Comportement produit deep-dive** : cycle de vie d'une facture, statuts + transitions, surfaces UI, mécanique de confirmation (check-in), mode démo, edge cases | | `/docs/marque.md` | Référence marque écrite (palette, typo, voix, do/don't) | @@ -153,9 +154,10 @@ Voir `/docs/decisions.md` pour le log complet avec rationale. ## Déploiement -- **Domaine principal** : https://rubis.pro (landing) + https://app.rubis.pro (SaaS V1) -- **Image landing** : `git.arthurbarre.fr/ordinarthur/rubis:latest` -- **Build landing** : `COPY landing/` → nginx servi sur port 80 +- **Domaine principal** : https://rubis.pro (landing + blog Astro) + https://app.rubis.pro (SaaS React) +- **Image landing** : `git.arthurbarre.fr/ordinarthur/rubis-landing:latest` (Astro Node SSR, port 4321) +- **Image API** : `git.arthurbarre.fr/ordinarthur/rubis-api:latest` (port 3333) +- **Image SPA** : `git.arthurbarre.fr/ordinarthur/rubis-web:latest` (nginx + proxy /api → rubis-api) - **Compat** : `rubis.arthurbarre.fr` / `app.rubis.arthurbarre.fr` redirigent en 301 vers `rubis.pro` / `app.rubis.pro` (config Traefik dans repo proxmox) - Voir `.claude/deploy-memory.md` pour la procédure complète. diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index e883a01..0000000 --- a/Dockerfile +++ /dev/null @@ -1,25 +0,0 @@ -FROM nginx:1.27-alpine - -COPY landing/ /usr/share/nginx/html/ - -RUN printf 'server {\n\ - listen 80;\n\ - server_name _;\n\ - root /usr/share/nginx/html;\n\ - index index.html;\n\ -\n\ - location / {\n\ - try_files $uri $uri/ /index.html;\n\ - }\n\ -\n\ - location ~* \\.(?:css|js|svg|png|jpg|jpeg|gif|ico|webp|woff2?)$ {\n\ - expires 7d;\n\ - add_header Cache-Control "public, max-age=604800, immutable";\n\ - }\n\ -\n\ - location = /site.webmanifest {\n\ - add_header Content-Type application/manifest+json;\n\ - }\n\ -}\n' > /etc/nginx/conf.d/default.conf - -EXPOSE 80 diff --git a/Dockerfile.landing b/Dockerfile.landing new file mode 100644 index 0000000..6e50c17 --- /dev/null +++ b/Dockerfile.landing @@ -0,0 +1,71 @@ +# syntax=docker/dockerfile:1.7 +# ============================================================================= +# Rubis — image landing (Astro 6, React 19, Node SSR standalone) +# Sert rubis.pro (landing + pages légales + blog SSR fetch via apps/api). +# ============================================================================= +# +# Astro adapter: @astrojs/node en mode "standalone" → bundle un mini-server +# Node dans dist/server/entry.mjs. Pas de nginx en frontal nécessaire au +# niveau du pod : Traefik (cluster) gère le TLS et le routing par hostname. +# ============================================================================= + +ARG NODE_VERSION=22.13.1 +ARG PNPM_VERSION=10.0.0 + +# ----------------------------------------------------------------------------- +# base — node + pnpm + tini +# ----------------------------------------------------------------------------- +FROM node:${NODE_VERSION}-alpine AS base +ARG PNPM_VERSION +RUN apk add --no-cache libc6-compat tini && \ + corepack enable && \ + corepack prepare pnpm@${PNPM_VERSION} --activate +WORKDIR /repo + +# ----------------------------------------------------------------------------- +# deps — install workspace (avec devDeps pour le build Astro) +# ----------------------------------------------------------------------------- +FROM base AS deps +COPY package.json pnpm-lock.yaml pnpm-workspace.yaml turbo.json tsconfig.base.json ./ +COPY apps/landing/package.json ./apps/landing/ +COPY packages/shared/package.json ./packages/shared/ +COPY packages/ui/package.json ./packages/ui/ +RUN --mount=type=cache,id=pnpm,target=/root/.local/share/pnpm/store \ + pnpm install --frozen-lockfile --filter @rubis/landing... + +# ----------------------------------------------------------------------------- +# build — astro build (output: server, adapter Node standalone) +# ----------------------------------------------------------------------------- +FROM deps AS build +COPY packages/shared ./packages/shared +COPY packages/ui ./packages/ui +COPY apps/landing ./apps/landing +RUN cd apps/landing && pnpm exec astro build + +# ----------------------------------------------------------------------------- +# runner — runtime minimal, user non-root +# ----------------------------------------------------------------------------- +FROM base AS runner +RUN addgroup -g 1001 -S nodejs && adduser -S astro -u 1001 + +ENV NODE_ENV=production \ + HOST=0.0.0.0 \ + PORT=4321 \ + LOG_LEVEL=info + +WORKDIR /app + +# Astro standalone bundle suffit (server + client static + node_modules +# nécessaires à l'entry sont déjà inclus dans dist/). +COPY --from=build --chown=astro:nodejs /repo/apps/landing/dist /app/dist + +USER astro + +EXPOSE 4321 + +# Healthcheck simple : / répond 200 (page d'accueil prerenderée). +HEALTHCHECK --interval=30s --timeout=5s --start-period=15s --retries=3 \ + CMD wget -qO- http://127.0.0.1:4321/ >/dev/null 2>&1 || exit 1 + +ENTRYPOINT ["/sbin/tini", "--"] +CMD ["node", "/app/dist/server/entry.mjs"] diff --git a/Dockerfile.web b/Dockerfile.web index 8567498..1b42369 100644 --- a/Dockerfile.web +++ b/Dockerfile.web @@ -42,6 +42,7 @@ WORKDIR /repo COPY package.json pnpm-lock.yaml pnpm-workspace.yaml turbo.json tsconfig.base.json ./ COPY apps/web/package.json ./apps/web/ COPY packages/shared/package.json ./packages/shared/ +COPY packages/ui/package.json ./packages/ui/ # Install : on prend tout le workspace pour que les workspace deps résolvent. # Le filter --include-deps évite de gaspiller en installant les deps de l'API. @@ -49,6 +50,7 @@ RUN --mount=type=cache,id=pnpm,target=/root/.local/share/pnpm/store \ pnpm install --frozen-lockfile COPY packages/shared ./packages/shared +COPY packages/ui ./packages/ui COPY apps/web ./apps/web # Re-déclare les ARG dans le stage où on les utilise (Docker scope). diff --git a/apps/api/app/controllers/blog_controller.ts b/apps/api/app/controllers/blog_controller.ts new file mode 100644 index 0000000..3a90c10 --- /dev/null +++ b/apps/api/app/controllers/blog_controller.ts @@ -0,0 +1,60 @@ +import type { HttpContext } from '@adonisjs/core/http' + +import Post from '#models/post' +import PostTransformer, { PostSummaryTransformer } from '#transformers/post_transformer' + +/** + * BlogController — endpoints JSON publics pour le blog. + * + * L'API ne sert plus de HTML : le rendu des pages blog est délégué à + * apps/landing (Astro SSR). Ce contrôleur expose uniquement les données. + * + * Routes (cf. start/routes.ts, sous /api/v1/posts) : + * GET /api/v1/posts → liste des articles publiés (summary) + * GET /api/v1/posts/:slug → article complet + articles liés + * + * Pas d'auth : le blog est public. Pas de pagination V1 (volume <100 articles + * sur les 12 prochains mois — pas de besoin). On la rajoutera si nécessaire. + * + * Convention sérialisation : on passe des plain objects à `response.json` pour + * matcher le pattern utilisé par clients_controller etc. (les autres contrôleurs + * du codebase n'utilisent pas `serialize()` pour les arrays). + */ +export default class BlogController { + /** + * GET /api/v1/posts — liste publique, articles publiés du plus récent au + * plus ancien, sans le contentHtml (payload léger pour la page liste). + */ + async index({ response }: HttpContext) { + const posts = await Post.query().withScopes((s) => s.published()) + return response.json({ + data: posts.map((p) => new PostSummaryTransformer(p).toObject()), + }) + } + + /** + * GET /api/v1/posts/:slug — article publié + 3 articles liés (intersection + * de tags). 404 si non trouvé ou pas publié. + */ + async show({ params, response }: HttpContext) { + const slug = String(params.slug ?? '') + const post = await Post.query() + .where('slug', slug) + .where('status', 'published') + .whereNotNull('publishedAt') + .first() + + if (!post) { + return response.status(404).json({ error: 'post_not_found' }) + } + + const related = await Post.query().withScopes((s) => s.relatedTo(post)).limit(3) + + return response.json({ + data: { + post: new PostTransformer(post).toObject(), + related: related.map((p) => new PostSummaryTransformer(p).toObject()), + }, + }) + } +} diff --git a/apps/api/app/models/post.ts b/apps/api/app/models/post.ts new file mode 100644 index 0000000..74287fd --- /dev/null +++ b/apps/api/app/models/post.ts @@ -0,0 +1,38 @@ +import { PostSchema } from '#database/schema' +import { column, scope } from '@adonisjs/lucid/orm' + +export type PostStatus = 'draft' | 'published' + +export default class Post extends PostSchema { + // Override : le générateur infère `any` pour les enums + arrays Postgres, + // on retype proprement. + @column() + declare status: PostStatus + + @column() + declare tags: string[] + + /** + * Articles publiés, du plus récent au plus ancien. + * Utilisé par toutes les surfaces publiques (index, RSS, sitemap, related). + */ + static published = scope((query) => { + query.where('status', 'published').whereNotNull('publishedAt').orderBy('publishedAt', 'desc') + }) + + /** + * Articles "liés" : intersection de tags non vide, hors article courant, + * triés par récence. Limit côté appelant. + */ + static relatedTo = scope((query, post: Post) => { + if (post.tags.length === 0) { + query.whereRaw('1 = 0') + return + } + query + .where('status', 'published') + .whereNot('id', post.id) + .whereRaw('tags && ?::text[]', [post.tags]) + .orderBy('publishedAt', 'desc') + }) +} diff --git a/apps/api/app/services/blog_renderer.ts b/apps/api/app/services/blog_renderer.ts new file mode 100644 index 0000000..ee0ff8f --- /dev/null +++ b/apps/api/app/services/blog_renderer.ts @@ -0,0 +1,103 @@ +/** + * blog_renderer — pipeline markdown → HTML pour les articles du blog. + * + * Appelé : + * - au seeder (3 articles fondateurs en DB) + * - depuis l'admin React au save (PR3) via un endpoint dédié + * - depuis le cron weekly_blog_generator (PR4) pour les drafts IA + * + * Le HTML rendu est cache dans `posts.content_html` pour éviter de re-parser + * le markdown à chaque hit page. Si tu changes ce module, prévois une + * migration de re-render des posts existants. + */ + +import { Marked, type Tokens } from 'marked' + +/** Mots/min retenus pour le calcul reading_time (moyenne lecteur fr web). */ +const WORDS_PER_MINUTE = 220 + +/** + * Renderer marked configuré pour le blog Rubis : + * - GFM (tables, autolinks, ~strikethrough~) + * - heading IDs auto pour ancres / future TOC + * - liens externes en target=_blank rel=noopener + * - br: false (un saut de ligne ne devient pas
, seul un \n\n crée un

) + */ +const marked = new Marked({ + gfm: true, + breaks: false, + pedantic: false, +}) + +marked.use({ + renderer: { + heading({ tokens, depth }: Tokens.Heading): string { + const text = this.parser.parseInline(tokens) + const id = slugify(stripTags(text)) + return `${text}\n` + }, + link({ href, title, tokens }: Tokens.Link): string { + const text = this.parser.parseInline(tokens) + const isExternal = /^https?:\/\//.test(href) && !href.startsWith('https://rubis.pro') + const titleAttr = title ? ` title="${escapeHtmlAttr(title)}"` : '' + const relAttr = isExternal ? ' rel="noopener noreferrer"' : '' + const targetAttr = isExternal ? ' target="_blank"' : '' + return `${text}` + }, + image({ href, title, text }: Tokens.Image): string { + // Lazy par défaut, dimensions à enrichir via image processing futur. + const altAttr = `alt="${escapeHtmlAttr(text || '')}"` + const titleAttr = title ? ` title="${escapeHtmlAttr(title)}"` : '' + return `` + }, + }, +}) + +export type RenderedPost = { + contentHtml: string + wordCount: number + readingTimeMinutes: number +} + +/** Markdown → HTML + métriques de lecture. */ +export function renderPost(contentMd: string): RenderedPost { + const contentHtml = marked.parse(contentMd, { async: false }) as string + const wordCount = countWords(contentMd) + const readingTimeMinutes = Math.max(1, Math.round(wordCount / WORDS_PER_MINUTE)) + return { contentHtml, wordCount, readingTimeMinutes } +} + +/** + * Slug ASCII kebab-case déterministe. + * "Comment relancer un client — sans rien casser" → "comment-relancer-un-client-sans-rien-casser" + */ +export function slugify(input: string): string { + return input + .normalize('NFD') + .replace(/[̀-ͯ]/g, '') // diacritiques + .toLowerCase() + .replace(/[^a-z0-9]+/g, '-') + .replace(/^-+|-+$/g, '') + .slice(0, 200) +} + +function countWords(md: string): number { + // On strippe code blocks + balisage MD avant de compter — sinon les ``` et ** + // gonflent artificiellement. + const text = md + .replace(/```[\s\S]*?```/g, ' ') + .replace(/`[^`]*`/g, ' ') + .replace(/!\[[^\]]*\]\([^)]*\)/g, ' ') + .replace(/\[([^\]]*)\]\([^)]*\)/g, '$1') + .replace(/[*_~#>]/g, ' ') + const matches = text.match(/\S+/g) + return matches ? matches.length : 0 +} + +function escapeHtmlAttr(s: string): string { + return s.replace(/"/g, '"').replace(//g, '>') +} + +function stripTags(s: string): string { + return s.replace(/<[^>]+>/g, '') +} diff --git a/apps/api/app/transformers/post_transformer.ts b/apps/api/app/transformers/post_transformer.ts new file mode 100644 index 0000000..72f2a90 --- /dev/null +++ b/apps/api/app/transformers/post_transformer.ts @@ -0,0 +1,53 @@ +import type Post from '#models/post' +import { BaseTransformer } from '@adonisjs/core/transformers' + +/** + * PostTransformer — sérialise un Post en JSON public pour l'app Astro qui + * rend les pages blog en SSR (cf. apps/landing/src/pages/blog/*.astro). + * + * On ne retourne JAMAIS contentMd (c'est la source d'édition) — uniquement + * contentHtml déjà rendu par blog_renderer côté API. Garde le payload léger + * et évite de re-parser markdown côté front. + */ +export default class PostTransformer extends BaseTransformer { + toObject() { + const p = this.resource + return { + id: p.id, + slug: p.slug, + title: p.title, + excerpt: p.excerpt, + contentHtml: p.contentHtml, + heroImageUrl: p.heroImageUrl, + heroImageAlt: p.heroImageAlt, + ogImageUrl: p.ogImageUrl, + canonicalUrl: p.canonicalUrl, + authorName: p.authorName, + tags: p.tags, + publishedAt: p.publishedAt?.toISO() ?? null, + updatedAt: p.updatedAt?.toISO() ?? null, + readingTimeMinutes: p.readingTimeMinutes, + wordCount: p.wordCount, + noindex: p.noindex, + } + } +} + +/** Variante minimaliste pour les listes / articles liés (pas de contentHtml). */ +export class PostSummaryTransformer extends BaseTransformer { + toObject() { + const p = this.resource + return { + id: p.id, + slug: p.slug, + title: p.title, + excerpt: p.excerpt, + heroImageUrl: p.heroImageUrl, + heroImageAlt: p.heroImageAlt, + authorName: p.authorName, + tags: p.tags, + publishedAt: p.publishedAt?.toISO() ?? null, + readingTimeMinutes: p.readingTimeMinutes, + } + } +} diff --git a/apps/api/commands/seed_blog.ts b/apps/api/commands/seed_blog.ts new file mode 100644 index 0000000..721b36d --- /dev/null +++ b/apps/api/commands/seed_blog.ts @@ -0,0 +1,77 @@ +import { BaseCommand, flags } from '@adonisjs/core/ace' +import type { CommandOptions } from '@adonisjs/core/types/ace' +import { DateTime } from 'luxon' + +import Post from '#models/post' +import { renderPost } from '#services/blog_renderer' +import { seedArticles } from '#database/seeders/blog_seed/index' + +/** + * Insère / met à jour les articles fondateurs du blog en DB. + * + * node ace seed:blog # idempotent, upsert par slug + * node ace seed:blog --reset # supprime tous les posts avant + * + * À lancer une fois en local et en prod après le déploiement de PR1 + * (avant la mise en route du routing Traefik en PR2). Les ré-exécutions + * sont sans effet de bord — utile si on retouche les MD source. + */ +export default class SeedBlog extends BaseCommand { + static commandName = 'seed:blog' + static description = 'Seed des 3 articles fondateurs du blog (idempotent par slug)' + + static options: CommandOptions = { + startApp: true, + } + + @flags.boolean({ + description: 'Supprime tous les posts existants avant le seed', + default: false, + }) + declare reset: boolean + + async run() { + if (this.reset) { + const deleted = await Post.query().delete() + this.logger.warning(`${deleted} posts supprimés (--reset).`) + } + + let created = 0 + let updated = 0 + + for (const draft of seedArticles) { + const { contentHtml, wordCount, readingTimeMinutes } = renderPost(draft.contentMd) + const publishedAt = DateTime.now().minus({ days: draft.publishedDaysAgo }).toUTC().startOf('minute') + + const existing = await Post.findBy('slug', draft.slug) + const payload = { + slug: draft.slug, + title: draft.title, + excerpt: draft.excerpt, + contentMd: draft.contentMd, + contentHtml, + authorName: draft.authorName, + tags: draft.tags, + status: 'published' as const, + publishedAt, + wordCount, + readingTimeMinutes, + aiGenerated: false, + noindex: false, + } + + if (existing) { + existing.merge(payload) + await existing.save() + updated += 1 + } else { + await Post.create(payload) + created += 1 + } + + this.logger.success(`✓ ${draft.slug} (${wordCount} mots, ${readingTimeMinutes} min)`) + } + + this.logger.info(`\nFait : ${created} créé(s), ${updated} mis à jour, ${seedArticles.length} total.`) + } +} diff --git a/apps/api/database/migrations/1778250000000_create_posts_table.ts b/apps/api/database/migrations/1778250000000_create_posts_table.ts new file mode 100644 index 0000000..35e421f --- /dev/null +++ b/apps/api/database/migrations/1778250000000_create_posts_table.ts @@ -0,0 +1,89 @@ +import { BaseSchema } from '@adonisjs/lucid/schema' + +/** + * posts — articles du blog rubis.pro/blog + * + * SSR par AdonisJS (cf. apps/api/app/controllers/blog_controller.ts), routé + * via Traefik Host(`rubis.pro`) && PathPrefix(`/blog`) → service rubis-api. + * Le même pod Adonis sert /api/v1/* (host app.rubis.pro) et /blog/* (host + * rubis.pro), distingués au niveau du routeur Adonis. + * + * Champs SEO : + * - slug : segment d'URL stable, unique + * - title : + og:title + JSON-LD headline (≤60 chars validé côté admin) + * - excerpt : meta description + og:description + card preview (≤160 chars) + * - content_md : markdown source (édité dans l'admin) + * - content_html : rendu cache au save pour éviter le coût marked à chaque hit + * - hero_image_url : visuel d'en-tête, sert aussi de og:image par défaut + * - og_image_url : OG dédié si on veut un crop différent (sinon = hero) + * - canonical_url : pour cross-posting éventuel (sinon défaut = rubis.pro/blog/{slug}) + * - tags : pour articles liés (recherche par tag commun) + filtres future + * - published_at : timestamp de publication (NULL tant que draft) + * - reading_time_minutes / word_count : calculés au save, affichés dans l'UI + * - ai_generated : flag pour distinguer les drafts générés par le cron weekly + * - noindex : permet de cacher un article aux crawlers (legal/test) + */ +export default class extends BaseSchema { + protected tableName = 'posts' + + async up() { + this.schema.createTable(this.tableName, (table) => { + table.uuid('id').primary().notNullable().defaultTo(this.raw('gen_random_uuid()')) + + // Clés SEO + table.string('slug', 200).notNullable().unique() + table.string('title', 200).notNullable() + table.string('excerpt', 280).notNullable() + + // Contenu + table.text('content_md').notNullable() + table.text('content_html').notNullable() + + // Visuels + table.string('hero_image_url', 500).nullable() + table.string('hero_image_alt', 250).nullable() + table.string('og_image_url', 500).nullable() + + // Métadonnées éditoriales + table.string('author_name', 100).notNullable().defaultTo('Arthur Barré') + table.specificType('tags', 'text[]').notNullable().defaultTo('{}') + + // État + publication + table + .enum('status', ['draft', 'published'], { + useNative: true, + enumName: 'post_status', + }) + .notNullable() + .defaultTo('draft') + table.timestamp('published_at', { useTz: true }).nullable() + + // Calculé au save + table.integer('reading_time_minutes').notNullable().defaultTo(0) + table.integer('word_count').notNullable().defaultTo(0) + + // SEO advanced + table.string('canonical_url', 500).nullable() + table.boolean('noindex').notNullable().defaultTo(false) + + // Pipeline IA (PR4) — pas de FK pour l'instant, table blog_topics arrive plus tard + table.boolean('ai_generated').notNullable().defaultTo(false) + table.uuid('ai_topic_id').nullable() + + table.timestamp('created_at', { useTz: true }).notNullable().defaultTo(this.now()) + table.timestamp('updated_at', { useTz: true }).nullable() + + // Index : la requête publique principale = WHERE status='published' ORDER BY published_at DESC. + // Partial index pour ne pas peser sur les drafts (qui ne seront jamais listés publiquement). + table.index(['status', 'published_at'], 'posts_published_idx') + // Recherche par tag : GIN sur le tableau text[] pour les requêtes "articles liés". + this.schema.raw('CREATE INDEX posts_tags_gin_idx ON posts USING GIN (tags)') + }) + } + + async down() { + this.schema.raw('DROP INDEX IF EXISTS posts_tags_gin_idx') + this.schema.dropTable(this.tableName) + this.schema.raw('DROP TYPE IF EXISTS post_status') + } +} diff --git a/apps/api/database/schema.ts b/apps/api/database/schema.ts index 9ecc9c0..b27e9c6 100644 --- a/apps/api/database/schema.ts +++ b/apps/api/database/schema.ts @@ -306,6 +306,53 @@ export class PlanSchema extends BaseModel { declare updatedAt: DateTime | null } +export class PostSchema extends BaseModel { + static $columns = ['aiGenerated', 'aiTopicId', 'authorName', 'canonicalUrl', 'contentHtml', 'contentMd', 'createdAt', 'excerpt', 'heroImageAlt', 'heroImageUrl', 'id', 'noindex', 'ogImageUrl', 'publishedAt', 'readingTimeMinutes', 'slug', 'status', 'tags', 'title', 'updatedAt', 'wordCount'] as const + $columns = PostSchema.$columns + @column() + declare aiGenerated: boolean + @column() + declare aiTopicId: string | null + @column() + declare authorName: string + @column() + declare canonicalUrl: string | null + @column() + declare contentHtml: string + @column() + declare contentMd: string + @column.dateTime({ autoCreate: true }) + declare createdAt: DateTime + @column() + declare excerpt: string + @column() + declare heroImageAlt: string | null + @column() + declare heroImageUrl: string | null + @column({ isPrimary: true }) + declare id: string + @column() + declare noindex: boolean + @column() + declare ogImageUrl: string | null + @column.dateTime() + declare publishedAt: DateTime | null + @column() + declare readingTimeMinutes: number + @column() + declare slug: string + @column() + declare status: any + @column() + declare tags: any + @column() + declare title: string + @column.dateTime({ autoCreate: true, autoUpdate: true }) + declare updatedAt: DateTime | null + @column() + declare wordCount: number +} + export class RefreshTokenSchema extends BaseModel { static $columns = ['createdAt', 'expiresAt', 'hashedToken', 'id', 'ipAddress', 'lastUsedAt', 'revokedAt', 'updatedAt', 'userAgent', 'userId'] as const $columns = RefreshTokenSchema.$columns diff --git a/apps/api/database/seeders/blog_seed/01_retards_paiement.ts b/apps/api/database/seeders/blog_seed/01_retards_paiement.ts new file mode 100644 index 0000000..0db3add --- /dev/null +++ b/apps/api/database/seeders/blog_seed/01_retards_paiement.ts @@ -0,0 +1,117 @@ +/** + * Article fondateur 1/3 — état du marché. + * Sert d'ancre SEO sur les requêtes "retards de paiement", "factures impayées + * France", "LME indemnités". Aussi few-shot pour le pipeline IA hebdo (PR4). + */ +export const article = { + slug: '25-pourcent-factures-retard-france', + title: '25 % des factures sont payées en retard en France — ce que ça vous coûte vraiment', + excerpt: + "Une facture sur quatre est payée en retard en France. Pour une TPE-PME, c'est rarement une question de mauvaise foi — c'est une question de coût et d'habitudes.", + authorName: 'Arthur Barré', + tags: ['trésorerie', 'retards de paiement', 'LME'], + publishedDaysAgo: 14, + contentMd: `Vous envoyez une facture le 5 du mois. Vous êtes payé le 22. Du **mois suivant**. Sur cinq factures, trois suivent ce schéma. Personne ne crie à la fraude, et pourtant : vos 8 000 € de chiffre d'affaires deviennent 8 000 € de trésorerie immobilisée pendant 47 jours. Cumulé sur l'année, ça représente l'équivalent d'un demi-salaire de salarié bloqué dans le circuit. + +C'est la réalité quotidienne d'une TPE-PME française sur quatre. + +## Le chiffre qui dérange : 25 % + +Selon l'Observatoire des délais de paiement de la Banque de France, environ **un quart des entreprises françaises sont payées en retard**, c'est-à-dire au-delà du délai contractuel négocié. Le retard moyen tourne autour de **12 à 14 jours** en 2024 — un chiffre stable depuis trois ans, malgré le renforcement des contrôles DGCCRF. + +Ramené à l'échelle de l'économie, ce sont **plusieurs dizaines de milliards d'euros de trésorerie** qui ne sont pas où ils devraient être. Pour les grandes entreprises, ce flottement est absorbable : leurs lignes de crédit l'épongent. Pour une TPE-PME, c'est une autre histoire. + +### Ce que ça représente concrètement pour vous + +Prenons un exemple chiffré simple. Vous facturez 200 000 € par an, avec un délai contractuel de 30 jours. Si vos clients paient en moyenne à 45 jours : + +- Trésorerie moyenne immobilisée par jour : **548 €** +- Trésorerie moyenne immobilisée à un instant T : **8 220 €** +- Si votre découvert vous coûte 6 % : **~493 € par an de coût direct** + +C'est peu ? Multiplié par les heures que vous passez à relancer (et qu'on chiffre plus bas), ce n'est plus du tout négligeable. + +## Le coût caché : votre temps + +Le coût financier direct du retard de paiement est mesurable. Le coût en temps, lui, est rarement mis sur la table — alors que c'est lui qui fait le plus mal en TPE. + +Pour une PME qui émet 50 factures par mois et gère ses relances "à la main" : + +- **Vérifier les factures impayées** chaque semaine : ~30 min +- **Relancer par email** chaque facture en retard (rédaction + envoi) : ~5 min × 8 = 40 min +- **Appeler les clients qui ne répondent pas** : ~15 min × 3 = 45 min +- **Mettre à jour le suivi** (Excel, comptable) : ~20 min + +Soit **environ 2 h 15 par semaine**, ou **9 heures par mois**. Un dirigeant qui se valorise à 60 €/h investit **6 480 € par an** dans des relances qu'il ne devrait pas avoir à faire. + +Et ce calcul est optimiste. Il ne compte pas la charge mentale (penser à relancer, hésiter sur la tonalité, vérifier ce qu'on a déjà envoyé), ni les nuits où on se réveille en se demandant si M. Dupont a bien payé. + +## Le cadre légal : la LME que personne n'ose brandir + +La **loi de modernisation de l'économie (LME)** de 2008 plafonne strictement les délais de paiement entre professionnels : + +- **60 jours** maximum à compter de la date d'émission de la facture, **OU** +- **45 jours fin de mois** si négocié dans les conditions générales de vente. + +Au-delà, l'entreprise débitrice est en infraction. Les sanctions sont lourdes : + +- **Amende administrative jusqu'à 2 millions d'euros** pour une personne morale +- **75 000 €** pour une personne physique +- Sanctions doublées en cas de récidive + +La DGCCRF a renforcé ses contrôles depuis 2023, et la liste des entreprises sanctionnées est désormais **publiée**, ce qui ajoute un risque réputationnel non négligeable. + +**Et pourtant** : combien de TPE-PME osent rappeler la LME à leur grand client retardataire ? Très peu. Parce que c'est l'arme nucléaire, et qu'on a peur de perdre le compte. + +## L'arme oubliée : les indemnités automatiques + +Bien plus utiles au quotidien, deux mécanismes prévus par l'**article L441-10 du Code de commerce** sont **automatiques** dès le premier jour de retard, sans qu'aucun rappel ou mise en demeure ne soit nécessaire : + +1. **Indemnité forfaitaire de 40 €** pour frais de recouvrement, par facture en retard (Décret n° 2012-1115). +2. **Pénalités de retard** au taux d'intérêt de la BCE majoré de 10 points (soit ~14 % en 2024). + +Concrètement : si votre client paie une facture de 5 000 € avec 20 jours de retard, vous êtes en droit de réclamer **40 € + ~38 € de pénalités = 78 €**. Et il vous doit ces 78 € — légalement, automatiquement. + +Le mentionner clairement sur vos factures et vos CGV est un puissant levier dissuasif. Pas besoin d'aller au tribunal, juste de rappeler le droit. + +## Pourquoi les retards persistent (ce n'est pas la mauvaise foi) + +Si la loi est si claire, pourquoi 25 % des factures sont-elles encore payées en retard ? + +**1. La culture du "fin de mois".** En France, beaucoup d'entreprises ont des cycles de paiement mensuels figés (le 25 ou le 30). Une facture émise le 16 attendra mécaniquement le mois suivant pour être réglée. + +**2. La file d'attente comptable.** Plus le client est gros, plus sa comptabilité est lente. Une facture sans bon de commande ou sans matricule attendra la régularisation, qui prendra trois semaines. + +**3. Le retard "stratégique".** Certains acheteurs jouent volontairement la montre pour optimiser leur propre trésorerie, pariant que vous ne réclamerez pas. + +**4. Le simple oubli.** Banal, mais dominant. Votre facture est noyée dans les 200 du mois. Personne ne pense à elle si vous ne le rappelez pas. + +Dans les quatre cas, **la solution est la même** : un rappel envoyé au bon moment, avec la bonne tonalité, débloque la situation. Sans drame, sans procédure. + +## Reprendre la main, sans devenir agressif + +Trois principes simples permettent à une TPE-PME de **diviser par deux** son délai moyen de paiement, sans abîmer aucune relation commerciale : + +### 1. Relancer dès J+3, systématiquement + +Une relance courte trois jours après l'échéance n'est pas perçue comme agressive — elle est perçue comme professionnelle. Elle évite le risque de "l'oubli" et signale au client que votre cycle de cash est suivi. + +### 2. Escalader la tonalité progressivement + +J+3 = ton chaleureux, "petit rappel". J+10 = ton plus ferme, factuel. J+20 = ton de pré-contentieux, mention des indemnités. Une seule relance "amicale" suivie d'un silence d'un mois ne fonctionne pas. La cadence et l'évolution du ton font tout. + +### 3. Toujours rappeler le contexte chiffré + +Numéro de facture, montant, date d'échéance, jours de retard. Pas d'ambiguïté possible, pas de "je vais vérifier" qui sert d'excuse pour gagner du temps. + +## En résumé + +Les retards de paiement ne sont ni une fatalité, ni une caractéristique culturelle française qu'il faudrait subir. Ce sont **des frictions de processus** qui se résolvent avec : + +- De la **systématisation** (relancer chaque facture, pas seulement les grosses) +- Du **timing** (commencer J+3, pas J+15) +- De la **constance** (cadencer plusieurs étapes, pas une seule) + +C'est exactement ce que Rubis automatise : vous définissez votre plan de relance une fois, et chaque facture suit son rythme sans que vous y pensiez. Vous récupérez en moyenne **5 heures par semaine** que vous remettez sur ce qui a vraiment de la valeur — votre métier. +`, +} diff --git a/apps/api/database/seeders/blog_seed/02_relancer_sans_casser.ts b/apps/api/database/seeders/blog_seed/02_relancer_sans_casser.ts new file mode 100644 index 0000000..f111325 --- /dev/null +++ b/apps/api/database/seeders/blog_seed/02_relancer_sans_casser.ts @@ -0,0 +1,113 @@ +/** + * Article fondateur 2/3 — playbook relation client. + * Cible les recherches "comment relancer", "relance facture client", + * "ton relance email". Maillage interne avec articles 1 et 3. + */ +export const article = { + slug: 'relancer-client-sans-casser-relation-commerciale', + title: 'Comment relancer un client sans casser la relation commerciale', + excerpt: + "Relancer une facture impayée n'est pas une agression — c'est de la rigueur. Voici la méthode pour réclamer son dû sans abîmer la relation, avec le bon timing et la bonne tonalité.", + authorName: 'Arthur Barré', + tags: ['relance', 'relation client', 'communication'], + publishedDaysAgo: 7, + contentMd: `> *La peur de perdre le client retient des milliers d'euros bloqués chaque mois en France. Pourtant, bien menée, une relance ne casse rien — elle structure.* + +C'est l'angoisse récurrente du dirigeant de TPE-PME : "Si je relance, je vais le braquer. Et c'est mon plus gros client." Alors la relance traîne. Une semaine. Trois. Six. La facture finit par être payée — souvent en grognant — ou disparaît dans les limbes du *"je n'ai jamais reçu"*. + +Cette peur est compréhensible. Elle est aussi presque toujours infondée. + +## Relancer, ce n'est pas agresser + +Un client professionnel qui paie en retard sait qu'il paie en retard. Dans 90 % des cas, il attend simplement votre rappel pour traiter votre facture parmi les dizaines qui passent dans son service comptable. La relance ne le surprend pas. Elle ne le vexe pas. Elle le **débloque**. + +Ce qui peut casser la relation, ce n'est pas la relance — c'est **la mauvaise relance** : + +- Une relance trop tardive qui arrive avec un ton accusateur (parce qu'on a accumulé la frustration). +- Une relance ambiguë où on s'excuse à moitié et on supplie à moitié. +- Une relance par téléphone à 17 h 59 un vendredi. +- Un copier-coller mal personnalisé avec le nom d'un autre client. + +Bien menée, la relance fait partie du dialogue commercial normal. Mal menée, elle devient une crise. + +## Trois dimensions à doser : timing, tonalité, canal + +### Le timing + +La règle d'or : **relancer tôt, et systématiquement**. + +- **J+3 après échéance** : premier rappel cordial. C'est tôt — exprès. À ce stade, personne ne se sent attaqué. Le ton est presque neutre : *"petit rappel, on n'a pas reçu votre règlement, peut-être un oubli ?"* +- **J+10** : deuxième relance, plus ferme et factuelle. On rappelle les éléments (numéro, montant, échéance), on demande explicitement une date de paiement. +- **J+20** : troisième relance, ton sec et professionnel. Mention possible des indemnités de retard prévues par la loi. +- **J+30 ou J+45** : mise en demeure. À ce stade, on sort du dialogue normal — c'est un acte juridique préalable à un éventuel contentieux. + +Plus on attend, plus la première relance devient difficile à formuler. À J+45 sans rien envoyer, vous êtes obligé de durcir le ton pour rattraper le retard, ce qui rend la relation crispée. À J+3, vous restez naturel. + +### La tonalité + +Quatre tons graduels, dans l'ordre : + +| Étape | Ton | Vocabulaire-clé | +|---|---|---| +| Rappel J+3 | Chaleureux, neutre | "petit rappel", "il s'agit peut-être d'un oubli", "n'hésitez pas si question" | +| Relance J+10 | Ferme, factuel | "je vous remercie de bien vouloir procéder", "merci de me confirmer une date" | +| Pré-contentieux J+20 | Sec, professionnel | "en l'absence de règlement", "indemnités forfaitaires de 40 €", "pénalités de retard" | +| Mise en demeure | Juridique | "mise en demeure", "à défaut sous 8 jours", "procédure contentieuse" | + +L'erreur la plus fréquente est de **mélanger les registres** : être amical au mauvais moment ou agressif trop tôt. La progression doit être visible et naturelle — votre client comprend que vous montez d'un cran, et il sait pourquoi. + +### Le canal + +- **Email** : le canal par défaut. Trace écrite, archivable, peu intrusif. **95 % des relances doivent passer par là.** +- **Téléphone** : à utiliser quand l'email reste sans réponse depuis deux relances, et qu'il faut "humaniser" la situation. Préparer un script court, noter les engagements pris, **toujours envoyer un email de confirmation** dans la foulée. +- **Courrier recommandé** : réservé à la mise en demeure. Coût ~6 €, mais c'est la valeur juridique qui compte. Sans AR signé, vous n'aurez rien à présenter en cas de litige. +- **SMS** : à éviter sauf relation vraiment proche. Trop intrusif, perçu comme agressif en B2B. + +## L'escalade en pratique + +Voici comment se déroule une cadence type sur une facture de 3 200 € émise le 1er janvier, échéance 31 janvier : + +- **3 février** — Email cordial. *"Bonjour M. Dupont, petit rappel concernant la facture F-2025-014 (3 200 €) échue le 31 janvier. Il s'agit peut-être d'un oubli — n'hésitez pas si vous avez besoin d'un duplicata. Bonne journée !"* +- **10 février** — Email ferme. *"Bonjour, sauf erreur, le règlement de la facture F-2025-014 ne nous est pas encore parvenu. Pourriez-vous m'indiquer une date de règlement ? Merci d'avance."* +- **20 février** — Email pré-contentieux. *"Bonjour, à ce jour la facture F-2025-014 reste impayée malgré nos relances. Sans règlement sous 7 jours, nous serons contraints d'appliquer les indemnités forfaitaires (40 €) et pénalités de retard prévues par l'article L441-10 du Code de commerce."* +- **5 mars** — Mise en demeure par recommandé AR. *Lettre formelle d'une page, mention "MISE EN DEMEURE", délai de 8 jours, mention de la procédure contentieuse en cas de non-règlement.* + +Dans 80 % des cas, le règlement arrive entre la deuxième et la troisième relance. Les 20 % restants sont les vrais litiges — et là, vous avez besoin de la **preuve écrite** que vous avez relancé proprement. + +## Les 5 erreurs qui cassent vraiment la relation + +**1. Relancer pour la première fois à J+45.** Vous avez accumulé l'agacement. Votre première relance est sèche. Le client se sent agressé d'un coup, alors qu'il n'avait jamais été prévenu. **C'est l'erreur n°1.** + +**2. S'excuser de relancer.** *"Désolé de vous embêter avec ça mais..."* — Vous n'avez pas à être désolé. Le client vous doit de l'argent. Une relance professionnelle n'a pas à se justifier. + +**3. Personnaliser émotionnellement.** *"Vous me mettez dans une situation difficile"* ou *"j'ai des fournisseurs à payer"*. Vrai, mais ce n'est pas l'affaire du client. Restez factuel : numéro, montant, échéance, retard. + +**4. Mélanger relance et autre sujet.** *"Au fait, sur le projet de mai, on pourrait faire X"* en bas du même email. Le sujet relance se dilue. **Faites des emails séparés.** + +**5. Téléphoner avant d'avoir une trace écrite.** Sans email préalable, le client peut nier. *"Personne ne m'a rien dit."* Toujours laisser une trace écrite avant de passer au téléphone. + +## Quand passer la main + +À J+45 sans réponse à la mise en demeure, **arrêtez de relancer vous-même**. Continuer ne sert plus à rien et vous expose juridiquement (acharnement, harcèlement caractérisé selon l'intensité). + +Trois options s'offrent à vous : + +- **Société de recouvrement amiable** : 5 à 15 % de commission sur le recouvré. Bonne option pour les factures supérieures à 1 500 €. +- **Injonction de payer** : procédure simplifiée au tribunal de commerce. Coût ~50 €, délai 1 à 3 mois, redoutablement efficace pour les créances incontestées. +- **Avocat** : si litige commercial réel (contestation du service rendu, etc.). Coût plus élevé, à mobiliser pour les factures supérieures à 5 000 €. + +Dans tous les cas, **votre dossier de relances proprement archivé** sera votre arme principale. Sans lui, le juge ou le médiateur n'a rien sur quoi s'appuyer. + +## En résumé + +Relancer ne casse pas la relation. **Ne pas relancer** la casse — parce que vous accumulez la frustration et que la première fois où vous ouvrez la bouche, c'est tendu. La méthode : + +- **Tôt** (J+3, pas J+30) +- **Cadencé** (3 à 4 étapes, pas une seule) +- **Tonalité progressive** (chaleureux → ferme → pré-contentieux → juridique) +- **Email d'abord, téléphone en renfort, recommandé pour la mise en demeure** +- **Toujours factuel** (numéro, montant, échéance, retard) + +Vos clients ne vous en voudront pas. Ceux qui s'en offusquent cherchaient probablement à ne pas payer. +`, +} diff --git a/apps/api/database/seeders/blog_seed/03_modeles_email.ts b/apps/api/database/seeders/blog_seed/03_modeles_email.ts new file mode 100644 index 0000000..a4cfe50 --- /dev/null +++ b/apps/api/database/seeders/blog_seed/03_modeles_email.ts @@ -0,0 +1,187 @@ +/** + * Article fondateur 3/3 — templates copiables. + * Ancré sur "modèle email relance", "template relance facture", + * "exemple email relance impayé". Le plus actionnable des trois — sera + * probablement le plus partagé / sauvegardé. + */ +export const article = { + slug: '5-modeles-email-relance-qui-marchent', + title: "5 modèles d'email de relance qui marchent vraiment", + excerpt: + "Cinq emails de relance prêts à copier-coller, calibrés pour les TPE-PME françaises. Du rappel cordial J+3 à la mise en demeure, avec les variables à personnaliser.", + authorName: 'Arthur Barré', + tags: ['templates', 'email', 'relance'], + publishedDaysAgo: 1, + contentMd: `Un email de relance qui fonctionne tient en trois critères : **objet clair, contexte chiffré, demande explicite**. Pas plus. Voici cinq templates calibrés pour les TPE-PME françaises, à copier-coller et à personnaliser. + +## Avant de copier : les 5 règles transverses + +Tous ces emails respectent la même grammaire : + +1. **L'objet contient le mot "facture" et le numéro.** *"Rappel facture F-2025-014"* — pas *"Petit message"* ni *"Bonjour"*. Les filtres anti-spam et les inbox encombrées passent dessus. +2. **Le numéro, le montant et l'échéance apparaissent dans les trois premières lignes.** Pas dans une PJ, pas dans un tableau. Texte brut visible immédiatement. +3. **La demande est explicite et fermée.** *"Pouvez-vous m'indiquer une date de règlement avant vendredi ?"* — pas *"Pourriez-vous me dire ce qu'il en est ?"* +4. **La signature inclut votre numéro de téléphone.** Si le client veut clarifier, il doit pouvoir vous appeler en un clic. +5. **Pas de pièce jointe au premier rappel.** Si le client a égaré la facture, c'est l'occasion qu'il vous le dise — vous renverrez à J+10. + +--- + +## Template 1 — J+3 : le rappel cordial + +**Quand l'envoyer** : 3 jours après la date d'échéance. Tonalité neutre, presque administrative. C'est un rappel, pas une réclamation. + +**Objet** : \`Rappel — facture {{numero}}\` + +**Corps** : + +> Bonjour {{prenom}}, +> +> Petit rappel concernant la facture **{{numero}}** d'un montant de **{{montant}} €**, échue le {{date_echeance}}. +> +> Sauf erreur de ma part, je n'ai pas trace de votre règlement. Il s'agit peut-être d'un simple oubli ou d'un délai côté comptabilité — n'hésitez pas si vous avez besoin d'un duplicata ou d'une précision. +> +> Bonne journée, +> +> {{signature}} + +**Ce qui le fait fonctionner** : + +- Le mot "rappel" dédramatise immédiatement. +- "Sauf erreur de ma part" laisse une porte de sortie au client (ce qui le détend). +- "Peut-être un oubli" attribue une cause neutre (pas une accusation). +- Aucune mention d'indemnités à ce stade — ce serait disproportionné. + +--- + +## Template 2 — J+10 : la relance ferme + +**Quand l'envoyer** : 10 jours après échéance, en l'absence de réponse au premier rappel. La tonalité monte d'un cran : factuel, professionnel, pas hostile. + +**Objet** : \`Facture {{numero}} — règlement en attente\` + +**Corps** : + +> Bonjour {{prenom}}, +> +> Je reviens vers vous concernant la facture **{{numero}}** de **{{montant}} €**, échue le {{date_echeance}} et toujours en attente de règlement à ce jour ({{jours_retard}} jours de retard). +> +> Pourriez-vous me confirmer une date de paiement ? Si la facture nécessite un duplicata ou un complément d'information, je vous l'envoie immédiatement. +> +> Je vous remercie d'avance pour votre retour. +> +> {{signature}} + +**Ce qui le fait fonctionner** : + +- Le nombre de jours de retard est mentionné explicitement. Aucun doute possible. +- "Confirmer une date" est une demande **fermée** — le client doit y répondre par une date, pas par un atermoiement. +- Maintien de la coopération : "je vous l'envoie immédiatement" si problème. +- Toujours pas de menace à ce stade — on garde un espace de dialogue. + +--- + +## Template 3 — J+20 : l'avant-mise-en-demeure + +**Quand l'envoyer** : 20 jours après échéance, après deux relances ignorées. C'est la dernière chance avant procédure formelle. Le ton bascule du commercial au juridique. + +**Objet** : \`Facture {{numero}} — relance avant pénalités\` + +**Corps** : + +> Bonjour {{prenom}}, +> +> Malgré mes précédents rappels, la facture **{{numero}}** ({{montant}} €) reste impayée à ce jour, soit {{jours_retard}} jours de retard. +> +> Sans règlement sous **7 jours**, je serai contraint d'appliquer : +> - L'indemnité forfaitaire de recouvrement de **40 €** prévue par le décret n°2012-1115 ; +> - Les pénalités de retard au taux BCE + 10 points (article L441-10 du Code de commerce). +> +> Je reste à votre disposition pour toute précision ou pour convenir d'un échéancier si nécessaire. +> +> Cordialement, +> +> {{signature}} + +**Ce qui le fait fonctionner** : + +- La référence légale précise (décret + article) signale que vous connaissez le cadre. Le client comprend que vous n'improvisez pas. +- L'ouverture sur un échéancier en fin d'email maintient la porte ouverte — souvent, le client paie partiellement, et c'est mieux que rien. +- Délai de 7 jours explicite : pas d'ambiguïté sur la suite. +- Le mot "mise en demeure" n'apparaît pas encore — on le réserve pour le recommandé. + +--- + +## Template 4 — La mise en demeure (recommandé AR) + +**Quand l'envoyer** : à l'issue du délai de 7 jours du template 3, sans règlement. **Toujours par courrier recommandé avec accusé de réception**, jamais par email seul. Sans AR signé, vous n'aurez aucune preuve juridique. + +**Objet du courrier** : \`MISE EN DEMEURE — facture {{numero}}\` + +**Corps** : + +> Madame, Monsieur {{nom}}, +> +> Par la présente, je vous **mets en demeure** de procéder au règlement de la facture **{{numero}}** d'un montant de **{{montant}} €**, échue le {{date_echeance}} et restée impayée malgré mes relances des {{date_relance_1}}, {{date_relance_2}} et {{date_relance_3}}. +> +> À défaut de règlement intégral sous **8 jours** à compter de la réception de la présente, je serai contraint d'engager toute procédure utile pour le recouvrement de ma créance, en ce compris : +> +> - Une procédure d'injonction de payer auprès du tribunal de commerce ; +> - L'application des indemnités forfaitaires (40 €) et pénalités de retard légales ; +> - Le recouvrement par voie d'huissier le cas échéant. +> +> Cette mise en demeure fait courir les intérêts de retard au taux légal majoré, conformément à l'article 1231-6 du Code civil. +> +> Je vous prie de croire, Madame, Monsieur, en l'expression de mes salutations distinguées. +> +> {{signature}} + +**Ce qui le fait fonctionner** : + +- Le terme "mise en demeure" est juridiquement contraignant : il fait courir les intérêts au taux majoré, et constitue le préalable obligatoire à toute procédure contentieuse. +- L'historique des relances est mentionné — vous prouvez votre patience. +- Les options évoquées (injonction, huissier) sont concrètes et chiffrables. Le client comprend que ce n'est plus du bluff. +- Le ton reste professionnel : pas d'insulte, pas de menace personnelle. + +--- + +## Template 5 — Le merci post-paiement + +**Quand l'envoyer** : dans les 24 h suivant la réception du paiement. C'est le template que tout le monde oublie — alors que c'est celui qui **répare la relation**. + +**Objet** : \`Bien reçu — facture {{numero}}\` + +**Corps** : + +> Bonjour {{prenom}}, +> +> Je vous confirme la bonne réception du règlement de la facture **{{numero}}**. +> +> Merci, et bonne fin de semaine. +> +> {{signature}} + +**Ce qui le fait fonctionner** : + +- Court, sans surenchère ni rappel des relances passées. On tourne la page. +- Confirmation explicite : le client n'a pas à se demander si vous avez bien encaissé. +- Le retour à un ton chaleureux signale que la relation reprend son cours normal. + +C'est aussi le bon moment pour glisser, le mois suivant, *"d'ailleurs, on a sorti une nouvelle prestation X qui pourrait vous intéresser..."* — la relation est de nouveau alignée. + +--- + +## Variables à automatiser + +Si vous gérez plus de 5 factures par mois, copier-coller manuellement ces templates devient vite fastidieux. Les variables à personnaliser dans chaque envoi : + +- \`{{prenom}}\`, \`{{nom}}\` — interlocuteur côté client +- \`{{numero}}\` — numéro de facture +- \`{{montant}}\` — montant TTC en euros +- \`{{date_echeance}}\` — date d'échéance contractuelle +- \`{{jours_retard}}\` — calcul automatique vs. aujourd'hui +- \`{{date_relance_1}}\`, \`{{date_relance_2}}\` — historique des relances précédentes +- \`{{signature}}\` — bloc-signature avec téléphone + +C'est exactement ce que Rubis automatise. Vous configurez la cadence et les templates une seule fois ; le système les envoie au bon moment avec les bonnes variables, et vous récupérez en moyenne 5 heures de travail par semaine. +`, +} diff --git a/apps/api/database/seeders/blog_seed/index.ts b/apps/api/database/seeders/blog_seed/index.ts new file mode 100644 index 0000000..cb678a3 --- /dev/null +++ b/apps/api/database/seeders/blog_seed/index.ts @@ -0,0 +1,7 @@ +import { article as article1 } from './01_retards_paiement.js' +import { article as article2 } from './02_relancer_sans_casser.js' +import { article as article3 } from './03_modeles_email.js' + +export type SeedArticle = typeof article1 + +export const seedArticles: SeedArticle[] = [article1, article2, article3] diff --git a/apps/api/package.json b/apps/api/package.json index c0aaad3..5c32d9b 100644 --- a/apps/api/package.json +++ b/apps/api/package.json @@ -86,6 +86,7 @@ "bullmq": "^5.76.5", "ioredis": "^5.10.1", "luxon": "^3.7.2", + "marked": "^15.0.12", "pg": "^8.20.0", "react": "^19.2.5", "reflect-metadata": "^0.2.2", diff --git a/apps/api/start/routes.ts b/apps/api/start/routes.ts index 4fba853..387507b 100644 --- a/apps/api/start/routes.ts +++ b/apps/api/start/routes.ts @@ -13,6 +13,13 @@ import { middleware } from '#start/kernel' import router from '@adonisjs/core/services/router' import { controllers } from '#generated/controllers' +/** + * Blog public — endpoints JSON consommés par apps/landing (Astro SSR). + * Pas auth, pas de paginiation V1 (volume cible <100 articles). + */ +const BlogController = () => import('#controllers/blog_controller') + + router .group(() => { /** @@ -35,6 +42,19 @@ router throw new Error(`Sentry test from rubis-api — ${new Date().toISOString()}`) }) } + + /** + * Blog — endpoints JSON publics consommés par apps/landing en SSR. + * Cf. apps/landing/src/pages/blog/*.astro pour la consommation et + * apps/api/app/controllers/blog_controller.ts pour l'implémentation. + */ + router + .group(() => { + router.get('', [BlogController, 'index']).as('index') + router.get(':slug', [BlogController, 'show']).as('show') + }) + .prefix('posts') + .as('posts') }) .prefix('/api/v1') diff --git a/apps/api/start/sentry.ts b/apps/api/start/sentry.ts index ee25acf..b4e33c7 100644 --- a/apps/api/start/sentry.ts +++ b/apps/api/start/sentry.ts @@ -8,20 +8,27 @@ | d'Adonis n'est pas encore chargé à ce stade. | | Si SENTRY_DSN_API n'est pas défini (dev local typique), Sentry est -| simplement no-op — aucun appel réseau. +| simplement no-op — aucun appel réseau, et `@sentry/profiling-node` +| n'est PAS importé (l'import eager déclenche le chargement de la +| binary native CPU profiler, qui n'est pas dispo pour toutes les +| versions Node — ex. Node 25 sur darwin-arm64). | | Cf. ADR-024 — choix Sentry SaaS, free tier, 2 projects | (rubis-api / rubis-web). */ import * as Sentry from '@sentry/node' -import { nodeProfilingIntegration } from '@sentry/profiling-node' const dsn = process.env.SENTRY_DSN_API const environment = process.env.NODE_ENV ?? 'development' const release = process.env.APP_VERSION ?? 'dev' if (dsn) { + // Import dynamique : on ne charge le binding C++ que quand on en a + // vraiment besoin (prod avec DSN). En dev, l'absence de binaire pour + // l'ABI Node courant n'est pas un problème. + const { nodeProfilingIntegration } = await import('@sentry/profiling-node') + Sentry.init({ dsn, environment, diff --git a/apps/landing/.env.development b/apps/landing/.env.development new file mode 100644 index 0000000..e48ae1a --- /dev/null +++ b/apps/landing/.env.development @@ -0,0 +1 @@ +API_URL=http://localhost:3333 diff --git a/apps/landing/.env.example b/apps/landing/.env.example new file mode 100644 index 0000000..bce9521 --- /dev/null +++ b/apps/landing/.env.example @@ -0,0 +1,4 @@ +# URL de base de apps/api — utilisée en SSR pour fetcher /api/v1/posts. +# En dev local : http://localhost:3333 +# En prod (K3s ConfigMap) : https://app.rubis.pro +API_URL=http://localhost:3333 diff --git a/apps/landing/.gitignore b/apps/landing/.gitignore new file mode 100644 index 0000000..481d754 --- /dev/null +++ b/apps/landing/.gitignore @@ -0,0 +1,15 @@ +# Build & cache Astro +dist/ +.astro/ + +# Node +node_modules/ + +# Env +.env +.env.local +.env.*.local + +# Logs / OS +*.log +.DS_Store diff --git a/apps/landing/astro.config.mjs b/apps/landing/astro.config.mjs new file mode 100644 index 0000000..cc95d9e --- /dev/null +++ b/apps/landing/astro.config.mjs @@ -0,0 +1,36 @@ +// @ts-check +import { defineConfig } from "astro/config"; +import react from "@astrojs/react"; +import node from "@astrojs/node"; +import tailwindcss from "@tailwindcss/vite"; + +/** + * Astro 6 — landing publique + blog rubis.pro. + * + * Stratégie de rendu (cf. /docs/tech/architecture.md §3) : + * - `output: "server"` → SSR par défaut, on opt-out par page avec + * `export const prerender = true;` pour les pages statiques. + * - Landing + pages légales : `prerender = true` (HTML figé au build, ultra-rapide). + * - Blog (/blog, /blog/:slug) : SSR pur, fetch Adonis API à la requête, cache + * HTTP côté Traefik / nginx pour absorber les pics — publish admin = immédiat. + * + * Adapter Node standalone : produit un serveur autonome dans `dist/server/`, + * démarré par `node ./dist/server/entry.mjs` dans le container K3s. + */ +export default defineConfig({ + site: "https://rubis.pro", + output: "server", + adapter: node({ + mode: "standalone", + }), + integrations: [ + react(), + ], + vite: { + plugins: [tailwindcss()], + }, + server: { + host: "0.0.0.0", + port: 5174, + }, +}); diff --git a/apps/landing/package.json b/apps/landing/package.json new file mode 100644 index 0000000..1f408ca --- /dev/null +++ b/apps/landing/package.json @@ -0,0 +1,35 @@ +{ + "name": "@rubis/landing", + "private": true, + "version": "0.1.0", + "type": "module", + "description": "Landing publique rubis.pro + blog (Astro 6 SSR/SSG hybride, React 19 + @rubis/ui).", + "scripts": { + "dev": "astro dev --port 5174", + "build": "astro build", + "preview": "astro preview --port 5174", + "start": "node ./dist/server/entry.mjs", + "lint": "eslint .", + "typecheck": "astro check" + }, + "dependencies": { + "@astrojs/check": "^0.9.4", + "@astrojs/node": "^10.1.0", + "@astrojs/react": "^5.0.4", + "@fontsource-variable/bricolage-grotesque": "^5.2.5", + "@fontsource-variable/inter": "^5.2.5", + "@rubis/shared": "workspace:*", + "@rubis/ui": "workspace:*", + "@tailwindcss/vite": "^4.1.0", + "astro": "^6.3.1", + "lucide-react": "^0.475.0", + "react": "^19.2.5", + "react-dom": "^19.2.5", + "tailwindcss": "^4.1.0" + }, + "devDependencies": { + "@types/react": "^19.2.14", + "@types/react-dom": "^19.2.3", + "typescript": "~6.0.2" + } +} diff --git a/landing/apple-touch-icon.png b/apps/landing/public/apple-touch-icon.png similarity index 100% rename from landing/apple-touch-icon.png rename to apps/landing/public/apple-touch-icon.png diff --git a/landing/favicon-96x96.png b/apps/landing/public/favicon-96x96.png similarity index 100% rename from landing/favicon-96x96.png rename to apps/landing/public/favicon-96x96.png diff --git a/landing/favicon.ico b/apps/landing/public/favicon.ico similarity index 100% rename from landing/favicon.ico rename to apps/landing/public/favicon.ico diff --git a/landing/favicon.svg b/apps/landing/public/favicon.svg similarity index 100% rename from landing/favicon.svg rename to apps/landing/public/favicon.svg diff --git a/landing/site.webmanifest b/apps/landing/public/site.webmanifest similarity index 100% rename from landing/site.webmanifest rename to apps/landing/public/site.webmanifest diff --git a/landing/web-app-manifest-192x192.png b/apps/landing/public/web-app-manifest-192x192.png similarity index 100% rename from landing/web-app-manifest-192x192.png rename to apps/landing/public/web-app-manifest-192x192.png diff --git a/landing/web-app-manifest-512x512.png b/apps/landing/public/web-app-manifest-512x512.png similarity index 100% rename from landing/web-app-manifest-512x512.png rename to apps/landing/public/web-app-manifest-512x512.png diff --git a/apps/landing/src/components/SiteFooter.tsx b/apps/landing/src/components/SiteFooter.tsx new file mode 100644 index 0000000..4a82c3d --- /dev/null +++ b/apps/landing/src/components/SiteFooter.tsx @@ -0,0 +1,50 @@ +import { Brand } from "@rubis/ui"; + +const CURRENT_YEAR = new Date().getFullYear(); + +/** + * Footer public commun à toutes les pages rubis.pro/*. + * Liens légaux + tagline. Pas de réseaux sociaux V1. + */ +export function SiteFooter() { + return ( + <footer className="border-t border-line bg-cream-2 mt-24"> + <div className="max-w-[1180px] mx-auto px-5 sm:px-8 py-12"> + <div className="flex flex-col md:flex-row md:items-center md:justify-between gap-6"> + <div className="flex flex-col gap-3"> + <Brand withSuffix gemSize={24} /> + <p className="text-[13px] text-ink-3 max-w-md"> + Le SaaS de relance de factures impayées pour TPE-PME françaises. + Fait à Paris, avec du temps libéré. + </p> + </div> + + <nav aria-label="Liens utiles" className="flex flex-wrap gap-x-6 gap-y-2 text-[13px]"> + <a href="/blog" className="text-ink-2 hover:text-rubis transition-colors"> + Blog + </a> + <a href="/mentions-legales" className="text-ink-2 hover:text-rubis transition-colors"> + Mentions légales + </a> + <a href="/confidentialite" className="text-ink-2 hover:text-rubis transition-colors"> + Confidentialité + </a> + <a href="/cgv" className="text-ink-2 hover:text-rubis transition-colors"> + CGV + </a> + <a + href="mailto:contact@rubis.pro" + className="text-ink-2 hover:text-rubis transition-colors" + > + contact@rubis.pro + </a> + </nav> + </div> + + <div className="mt-8 pt-6 border-t border-line text-[12px] text-ink-3"> + © {CURRENT_YEAR} Rubis sur l'ongle. Tous droits réservés. + </div> + </div> + </footer> + ); +} diff --git a/apps/landing/src/components/SiteHeader.tsx b/apps/landing/src/components/SiteHeader.tsx new file mode 100644 index 0000000..a7fd11a --- /dev/null +++ b/apps/landing/src/components/SiteHeader.tsx @@ -0,0 +1,56 @@ +import { Brand, Button, cn } from "@rubis/ui"; + +const APP_URL = "https://app.rubis.pro"; + +type SiteHeaderProps = { + /** Si true, fond opaque + bordure (utile sur les pages blog où on n'a pas de hero). Sinon transparent + sticky-blur. */ + solid?: boolean; + className?: string; +}; + +/** + * Header public commun à toutes les pages rubis.pro/* : + * - lockup brand → / + * - liens nav (Tarifs, Blog) + * - CTA "Essai gratuit 30 j" → app.rubis.pro + * + * Sticky avec backdrop-blur quand le header est posé sur un hero (transparent), + * solid+bordure sur les pages secondaires (blog, légal). + */ +export function SiteHeader({ solid = false, className }: SiteHeaderProps) { + return ( + <header + className={cn( + "sticky top-0 z-40 w-full", + solid + ? "border-b border-line bg-cream/92 backdrop-blur-md" + : "bg-cream/80 backdrop-blur-md", + className, + )} + > + <div className="max-w-[1180px] mx-auto px-5 sm:px-8 h-[68px] flex items-center justify-between gap-6"> + <a href="/" className="flex items-center hover:no-underline"> + <Brand withSuffix gemSize={26} /> + </a> + + <nav aria-label="Navigation principale" className="flex items-center gap-1.5 sm:gap-3"> + <a + href="/#pricing" + className="hidden sm:inline-flex h-10 items-center px-3 text-[14px] font-medium text-ink-2 hover:text-rubis transition-colors" + > + Tarifs + </a> + <a + href="/blog" + className="hidden sm:inline-flex h-10 items-center px-3 text-[14px] font-medium text-ink-2 hover:text-rubis transition-colors" + > + Blog + </a> + <Button asChild size="sm"> + <a href={APP_URL}>Essai gratuit 30 j</a> + </Button> + </nav> + </div> + </header> + ); +} diff --git a/apps/landing/src/components/blog/PostCard.tsx b/apps/landing/src/components/blog/PostCard.tsx new file mode 100644 index 0000000..1bcf8fd --- /dev/null +++ b/apps/landing/src/components/blog/PostCard.tsx @@ -0,0 +1,61 @@ +import { Gem, cn } from "@rubis/ui"; + +import type { PostSummary } from "../../lib/api"; + +const DATE_FMT = new Intl.DateTimeFormat("fr-FR", { + year: "numeric", + month: "long", + day: "numeric", +}); + +export function PostCard({ + post, + className, +}: { + post: PostSummary; + className?: string; +}) { + const dateLabel = post.publishedAt ? DATE_FMT.format(new Date(post.publishedAt)) : null; + + return ( + <a + href={`/blog/${post.slug}`} + className={cn( + "group flex flex-col bg-white border border-line rounded-card overflow-hidden", + "transition-all duration-150", + "hover:no-underline hover:-translate-y-0.5 hover:border-rubis-light hover:shadow-card", + className, + )} + > + <div className="aspect-[16/9] flex items-center justify-center bg-gradient-to-br from-rubis-glow to-cream-2"> + {post.heroImageUrl ? ( + <img + src={post.heroImageUrl} + alt={post.heroImageAlt ?? post.title} + loading="lazy" + className="size-full object-cover" + /> + ) : ( + <Gem size={56} className="opacity-40" /> + )} + </div> + <div className="p-6 flex-1 flex flex-col"> + <div className="flex items-center gap-2 text-[11.5px] uppercase tracking-[0.08em] font-semibold text-ink-3 mb-3"> + {dateLabel && ( + <time dateTime={post.publishedAt ?? undefined}>{dateLabel}</time> + )} + {dateLabel && <span aria-hidden className="size-[3px] rounded-full bg-ink-3" />} + <span>{post.readingTimeMinutes} min de lecture</span> + </div> + <h2 className="font-display font-bold text-ink text-[20px] tracking-[-0.018em] leading-[1.2]"> + {post.title} + </h2> + <p className="mt-3 flex-1 text-[14.5px] leading-relaxed text-ink-2">{post.excerpt}</p> + <span className="mt-4 inline-flex items-center gap-1.5 font-display font-semibold text-[14px] text-rubis"> + Lire l'article + <span className="transition-transform group-hover:translate-x-0.5">→</span> + </span> + </div> + </a> + ); +} diff --git a/apps/landing/src/components/sections/FAQ.tsx b/apps/landing/src/components/sections/FAQ.tsx new file mode 100644 index 0000000..65b820b --- /dev/null +++ b/apps/landing/src/components/sections/FAQ.tsx @@ -0,0 +1,138 @@ +import { Eyebrow } from "@rubis/ui"; + +const FAQS: Array<{ q: string; a: React.ReactNode }> = [ + { + q: "Et si mon client paie hors plateforme — comment Rubis le sait ?", + a: ( + <> + Avant chaque relance, Rubis vous envoie un email rapide :{" "} + <i>« Avez-vous été payé pour la facture F-2024-0042 ? »</i> avec deux boutons. Vous + cliquez "Oui" en 3 secondes, le plan s'arrête. Vous cliquez "Non" (ou ne répondez pas), + la relance part comme prévu. Vous configurez la cadence et le timing de ces + vérifications dans vos plans. + </> + ), + }, + { + q: "Mes factures et données restent-elles privées ?", + a: ( + <> + Évidemment. Hébergement français, conforme RGPD. Vos PDF sont stockés chiffrés. Aucune + donnée n'est partagée avec des tiers. Vous pouvez exporter ou supprimer vos données à + tout moment. + </> + ), + }, + { + q: "Puis-je personnaliser le contenu des emails ?", + a: ( + <> + Oui, dès le plan Pro. Tous les emails sont des templates avec variables ( + <code className="bg-cream-2 px-1.5 py-0.5 rounded text-[13px]">{"{{prenom_client}}"}</code>,{" "} + <code className="bg-cream-2 px-1.5 py-0.5 rounded text-[13px]">{"{{numero}}"}</code>,{" "} + <code className="bg-cream-2 px-1.5 py-0.5 rounded text-[13px]">{"{{montant}}"}</code>…). + Vous pouvez réécrire chaque étape, ajuster le ton, ajouter votre signature email et + votre logo. + </> + ), + }, + { + q: "Mes clients verront-ils que j'utilise Rubis ?", + a: ( + <> + Pas vraiment. En plan <b>Pro</b>, vos clients voient <b>votre nom</b> en grand comme + expéditeur, et quand ils cliquent « Répondre », leur message revient directement sur{" "} + <b>votre email</b>. Aucun pied de page ne mentionne Rubis. Suffisant pour 95 % des + freelances et TPE. + <br /> + <br /> + En plan <b>Business</b>, on va plus loin : vos emails partent vraiment depuis{" "} + <b>votre propre adresse</b> ( + <code className="bg-cream-2 px-1.5 py-0.5 rounded text-[13px]"> + compta@votre-entreprise.fr + </code> + ). Personne ne devine que vous utilisez un outil, et vos relances atterrissent{" "} + <b>mieux en boîte principale</b> plutôt qu'en spam ou en promotions — gain typique de 10 + à 15 % sur le taux d'ouverture. + </> + ), + }, + { + q: "Et si je veux relancer manuellement, sans plan ?", + a: ( + <> + Toujours possible. Sur n'importe quelle facture, vous avez un bouton "Relancer + maintenant" qui envoie un email immédiat. Pratique quand vous venez d'avoir le client au + téléphone et qu'il vous a demandé un récapitulatif. + </> + ), + }, + { + q: "Et la mise en demeure, elle part toute seule ?", + a: ( + <> + Non. Jamais. C'est une décision produit forte : la mise en demeure a des conséquences + légales et relationnelles importantes. Rubis prépare le brouillon à l'étape prévue de + votre plan, vous notifie, et c'est <b>vous</b> qui cliquez "Envoyer" sur une modale de + confirmation. Vous gardez la main sur le moment où le ton change vraiment. + </> + ), + }, + { + q: "Combien de temps pour démarrer ?", + a: ( + <> + Inscription en 30 secondes. Configuration de votre signature email et de votre première + facture en 5 minutes. La première relance peut partir dans la foulée. Si vous avez un + plan par défaut bien configuré, créer une nouvelle facture en relance prend{" "} + <b>2 clics</b>. + </> + ), + }, + { + q: 'Pourquoi cette histoire de "rubis" ?', + a: ( + <> + Parce que les chiffres comptables (DSO, taux de recouvrement, AR aging) ne réveillent + personne le matin. Le temps gagné, oui. <b>1 rubis = 10 minutes libérées</b> = 1 relance + que vous n'avez pas eu à écrire. À la fin du mois, vous voyez "124 rubis ≈ 24 h 48". + C'est concret. Et c'est plus fun que de regarder un graphique de courbes. + </> + ), + }, +]; + +export function FAQ() { + return ( + <section id="faq" className="bg-cream-2 border-y border-line"> + <div className="max-w-[760px] mx-auto px-5 sm:px-8 py-20 lg:py-24"> + <div className="text-center mb-12"> + <Eyebrow>Questions fréquentes</Eyebrow> + <h2 className="mt-4 font-display font-bold text-ink leading-[1.1] tracking-[-0.025em] text-[34px] sm:text-[44px]"> + Vous vous demandez sûrement… + </h2> + </div> + + <div className="space-y-2"> + {FAQS.map(({ q, a }, i) => ( + <details + key={i} + className="group bg-white border border-line rounded-card overflow-hidden hover:border-ink-3 transition-colors" + > + <summary className="px-6 py-5 cursor-pointer list-none flex items-start justify-between gap-4 font-display font-semibold text-[16.5px] text-ink tracking-[-0.012em] leading-snug select-none [&::-webkit-details-marker]:hidden"> + {q} + <span + aria-hidden + className="flex-shrink-0 size-7 rounded-full bg-cream-2 border border-line flex items-center justify-center text-ink-2 group-open:rotate-45 transition-transform" + > + + + </span> + </summary> + <div className="px-6 pb-6 -mt-1 text-[15.5px] leading-relaxed text-ink-2">{a}</div> + </details> + ))} + </div> + </div> + </section> + ); +} diff --git a/apps/landing/src/components/sections/FinalCTA.tsx b/apps/landing/src/components/sections/FinalCTA.tsx new file mode 100644 index 0000000..7d4b9da --- /dev/null +++ b/apps/landing/src/components/sections/FinalCTA.tsx @@ -0,0 +1,27 @@ +import { Button } from "@rubis/ui"; + +const APP_URL = "https://app.rubis.pro"; + +export function FinalCTA() { + return ( + <section id="lancer"> + <div className="max-w-[820px] mx-auto px-5 sm:px-8 py-24 lg:py-28 text-center"> + <h2 className="font-display font-bold text-ink leading-[1.1] tracking-[-0.025em] text-[36px] sm:text-[48px]"> + Récupérez vos premières heures dès aujourd'hui. + </h2> + <p className="mt-5 text-[17.5px] text-ink-2 leading-relaxed max-w-[580px] mx-auto"> + 30 jours gratuits, puis le plan Free continue avec 5 factures actives. Pas de carte + demandée pour démarrer. + </p> + <div className="mt-8 flex justify-center"> + <Button asChild size="lg"> + <a href={APP_URL}>Lancer Rubis →</a> + </Button> + </div> + <p className="mt-5 text-[13px] text-ink-3"> + Inscription en 30 secondes. Annulation 1-clic à tout moment. + </p> + </div> + </section> + ); +} diff --git a/apps/landing/src/components/sections/Footnotes.tsx b/apps/landing/src/components/sections/Footnotes.tsx new file mode 100644 index 0000000..2ccc8e0 --- /dev/null +++ b/apps/landing/src/components/sections/Footnotes.tsx @@ -0,0 +1,14 @@ +export function Footnotes() { + return ( + <aside className="border-t border-line bg-cream"> + <div className="max-w-[820px] mx-auto px-5 sm:px-8 py-8"> + <p className="text-[13.5px] text-ink-3 leading-relaxed"> + <span className="text-rubis font-semibold mr-1.5">*</span> + <b className="text-ink-2">OCR</b> — pour <i>Optical Character Recognition</i>. La + reconnaissance automatique du texte sur un PDF ou une photo. La machine lit votre + facture par-dessus votre épaule, en somme. + </p> + </div> + </aside> + ); +} diff --git a/apps/landing/src/components/sections/Gamification.tsx b/apps/landing/src/components/sections/Gamification.tsx new file mode 100644 index 0000000..c696e44 --- /dev/null +++ b/apps/landing/src/components/sections/Gamification.tsx @@ -0,0 +1,46 @@ +import { Eyebrow, Gem } from "@rubis/ui"; + +export function Gamification() { + return ( + <section> + <div className="max-w-[1180px] mx-auto px-5 sm:px-8 py-20 lg:py-24"> + <div className="bg-gradient-to-br from-rubis to-rubis-deep text-cream rounded-card p-8 sm:p-14 text-center shadow-card"> + <div className="inline-flex"> + <Eyebrow tone="ink" className="!text-rubis-glow"> + La devise du temps gagné + </Eyebrow> + </div> + <h2 className="mt-5 font-display font-bold leading-[1.1] tracking-[-0.025em] text-[34px] sm:text-[44px] lg:text-[52px]"> + 1 rubis = 10 minutes de votre vie. + </h2> + <p className="mt-5 max-w-[680px] mx-auto text-[17px] text-cream/85 leading-relaxed"> + À chaque relance que Rubis envoie à votre place, vous gagnez un rubis. À la fin du + mois, vous voyez exactement combien d'heures vous avez récupérées. Pas un graphique + de DSO. Pas un PDF abscons. Du temps. Concret. + </p> + + <div className="mt-10 flex flex-col items-center gap-3"> + <div className="flex items-baseline gap-3"> + <Gem size={56} className="!text-cream" /> + <span className="font-display font-extrabold text-[88px] sm:text-[120px] leading-none tracking-[-0.04em]"> + 124 + </span> + <span className="font-display font-medium text-[28px] sm:text-[36px] tracking-[-0.02em] text-cream/80"> + rubis + </span> + </div> + <span className="text-[16px] text-cream/85"> + ≈ <b className="text-cream">24 h 48</b> de votre mois + </span> + </div> + + <p className="mt-10 max-w-[460px] mx-auto text-[14px] text-cream/70"> + Et oui, on garde un classement amical. Les meilleurs utilisateurs libèrent{" "} + <b className="text-cream">30 heures par mois</b>. Plus de quoi prendre un long + week-end. Toutes les 4 semaines. + </p> + </div> + </div> + </section> + ); +} diff --git a/apps/landing/src/components/sections/Hero.tsx b/apps/landing/src/components/sections/Hero.tsx new file mode 100644 index 0000000..a47ccfd --- /dev/null +++ b/apps/landing/src/components/sections/Hero.tsx @@ -0,0 +1,162 @@ +import { Button, Eyebrow, Gem, cn } from "@rubis/ui"; +import { Check } from "lucide-react"; + +const APP_URL = "https://app.rubis.pro"; + +/** + * Hero principal de la landing. + * Mirror direct de l'ancienne section .hero du landing/index.html — même + * messages, même hiérarchie typo, même mock card (124 rubis + KPIs + + * activité). On reprend les composants @rubis/ui là où ça a du sens. + */ +export function Hero() { + return ( + <section className="relative overflow-hidden"> + {/* Halo rubis discret en haut-droite */} + <div + aria-hidden + className="pointer-events-none absolute -top-40 -right-40 size-[480px] rounded-full" + style={{ + background: + "radial-gradient(circle, var(--color-rubis-glow) 0%, transparent 65%)", + }} + /> + + <div className="max-w-[1180px] mx-auto px-5 sm:px-8 pt-16 pb-20 lg:pt-24 lg:pb-28 grid lg:grid-cols-[1.1fr_1fr] gap-12 lg:gap-16 items-center relative"> + {/* ============ Texte ============ */} + <div> + <Eyebrow>L'outil de relance pour TPE-PME françaises</Eyebrow> + + <h1 className="mt-5 font-display font-extrabold text-ink leading-[1.05] tracking-[-0.03em] text-[44px] sm:text-[56px] lg:text-[64px] max-w-[680px]"> + Vos factures relancées <em>toutes seules</em> pendant que vous travaillez. + </h1> + + <p className="mt-6 text-[18px] sm:text-[19px] leading-relaxed text-ink-2 max-w-[580px] md:text-justify hyphens-auto"> + L'app de relance de factures impayées pensée pour les TPE-PME françaises. + Glissez-déposez vos factures, choisissez un plan de relance, oubliez-les. + Rubis envoie, suit, relance — et vous récupérez en moyenne{" "} + <b className="text-ink">5 heures par semaine</b>. + </p> + + <div className="mt-8 flex flex-wrap items-center gap-3"> + <Button asChild size="lg"> + <a href={APP_URL}>Lancer Rubis →</a> + </Button> + <Button asChild variant="secondary" size="lg"> + <a href="#pricing">Voir les tarifs</a> + </Button> + </div> + + <div className="mt-7 flex flex-wrap items-center gap-x-3 gap-y-2 text-[13px] text-ink-3"> + <span className="inline-flex items-center gap-1.5"> + <Check size={14} className="text-rubis" aria-hidden /> + 30 jours gratuits puis Free 5 factures + </span> + <span aria-hidden className="size-[3px] rounded-full bg-ink-3" /> + <span>Hébergement souverain</span> + <span aria-hidden className="size-[3px] rounded-full bg-ink-3" /> + <span>Made in France 🇫🇷</span> + </div> + </div> + + {/* ============ Mock card ============ */} + {/* + Wrapper avec largeur cappée + relatif : le badge flottant se + positionne par rapport à la carte, pas à la grille parente. + Centrée mobile/tablet, alignée à droite en lg. + */} + <div className="relative w-full max-w-[480px] mx-auto lg:ml-auto lg:mr-0"> + <div + className={cn( + "bg-white border border-line rounded-card shadow-card", + "p-6 sm:p-7 lg:p-8", + )} + > + {/* Hero rubis */} + <div className="flex items-center gap-4 pb-5 border-b border-line"> + <Gem size={56} glow /> + <div> + <div className="font-display font-bold text-[32px] tracking-[-0.022em] leading-none text-ink"> + 124 rubis + </div> + <div className="mt-1.5 text-[14px] text-ink-2"> + ≈ <b className="text-ink">24 h 48</b> que vous n'avez pas passées à relancer. + </div> + </div> + </div> + + {/* KPIs */} + <div className="grid grid-cols-2 gap-5 mt-5"> + <div> + <div className="text-[10.5px] uppercase tracking-[0.06em] font-semibold text-ink-3"> + Encaissé + </div> + <div className="mt-1.5 font-display font-bold text-[22px] tracking-[-0.015em] text-ink tabular-nums"> + 14 320 € + </div> + <div className="mt-1 text-[11.5px] text-rubis font-medium"> + + 2 800 € vs avril + </div> + </div> + <div> + <div className="text-[10.5px] uppercase tracking-[0.06em] font-semibold text-ink-3"> + DSO + </div> + <div className="mt-1.5 font-display font-bold text-[22px] tracking-[-0.015em] text-ink tabular-nums"> + 38 j + </div> + <div className="mt-1 text-[11.5px] text-rubis font-medium"> + ↘ −6 j depuis Rubis + </div> + </div> + </div> + + {/* Activity */} + <div className="mt-5 pt-4 border-t border-dashed border-line"> + <div className="text-[11px] uppercase tracking-[0.08em] font-semibold text-ink-3 mb-2.5"> + Aujourd'hui + </div> + <ul className="space-y-2 text-[13px]"> + <li className="flex items-baseline justify-between gap-3"> + <span className="text-ink-2"> + 📤 Relance envoyée à <b className="text-ink">Atelier Durand</b> + </span> + <time className="text-ink-3 tabular-nums text-[11.5px]">11:14</time> + </li> + <li className="flex items-baseline justify-between gap-3"> + <span className="text-ink-2"> + ✓ Facture <b className="text-ink">F-2024-0035</b> encaissée + </span> + <time className="text-ink-3 tabular-nums text-[11.5px]">10:02</time> + </li> + <li className="flex items-baseline justify-between gap-3"> + <span className="text-ink-2">📥 3 factures importées et OCRisées</span> + <time className="text-ink-3 tabular-nums text-[11.5px]">09:48</time> + </li> + </ul> + </div> + </div> + + {/* Badge flottant — relatif au wrapper carte (max-w 480) */} + <div className="absolute -bottom-3 left-4 sm:left-6 bg-ink text-cream rounded-full px-4 py-2 text-[12.5px] font-semibold flex items-center gap-1.5 shadow-card"> + <svg + width="14" + height="14" + viewBox="0 0 24 24" + fill="none" + stroke="currentColor" + strokeWidth="2" + strokeLinecap="round" + strokeLinejoin="round" + aria-hidden + > + <circle cx="12" cy="12" r="10" /> + <polyline points="12 6 12 12 16 14" /> + </svg> + ~3 minutes le matin + </div> + </div> + </div> + </section> + ); +} diff --git a/apps/landing/src/components/sections/HowItWorks.tsx b/apps/landing/src/components/sections/HowItWorks.tsx new file mode 100644 index 0000000..50b5a6d --- /dev/null +++ b/apps/landing/src/components/sections/HowItWorks.tsx @@ -0,0 +1,220 @@ +import { Eyebrow, cn } from "@rubis/ui"; + +export function HowItWorks() { + return ( + <section id="how" className="bg-cream-2 border-y border-line"> + <div className="max-w-[1180px] mx-auto px-5 sm:px-8 py-20 lg:py-28"> + <div className="text-center max-w-[640px] mx-auto mb-16"> + <Eyebrow>Comment ça marche</Eyebrow> + <h2 className="mt-4 font-display font-bold text-ink leading-[1.1] tracking-[-0.025em] text-[34px] sm:text-[44px]"> + Trois étapes. C'est tout. + </h2> + <p className="mt-4 text-[17px] text-ink-2 leading-relaxed"> + Vraiment. Parfois deux, si votre plan par défaut est bien réglé. + </p> + </div> + + <Step + num="01" + title="Vous importez vos factures." + body="PDF, photo prise depuis votre téléphone à la caisse, scan reçu par mail — peu importe. L'OCR* lit, extrait montant, client, échéance, RIB. Vous vérifiez. Vingt secondes par facture, montre en main." + > + <DropzoneWidget /> + </Step> + + <Step + flip + num="02" + title="Vous choisissez un plan de relance." + body={ + <> + <p> + Nous en avons décliné plusieurs — par exemple, un standard B2B (J+3, J+10, J+20), + adapter le ton en fonction de l'échéance et de l'historique du client. + </p> + <p className="mt-3">Et bien sûr, vous pouvez aussi créer les vôtres sur mesure.</p> + </> + } + > + <CalendarWidget /> + </Step> + + <Step + num="03" + title="Et puis c'est tout." + body={ + <> + <p> + Sérieusement. Pendant que vous travaillez, Rubis envoie les emails au moment prévu, + suit qui a ouvert, qui n'a pas répondu, et avant chaque relance vous demande + discrètement par email : « Cette facture a-t-elle été réglée ? ». Vous répondez en + deux secondes. La machine fait le reste. + </p> + <div className="mt-5 inline-flex items-center gap-2 px-4 py-2.5 bg-rubis-glow border border-rubis/15 rounded-default text-[14px] text-rubis-deep font-medium"> + <span aria-hidden className="size-[7px] bg-rubis rotate-45" /> + La récompense : votre compteur de rubis grimpe. Tranquillement. Comme une bonne + nouvelle régulière. + </div> + </> + } + > + <AssistantWidget /> + </Step> + </div> + </section> + ); +} + +type StepProps = { + num: string; + title: string; + body: React.ReactNode; + flip?: boolean; + children: React.ReactNode; +}; + +function Step({ num, title, body, flip = false, children }: StepProps) { + return ( + <div + className={cn( + // Mobile + tablet : 1 colonne, prose pleine largeur (juste le padding du container). + // Desktop : 2 colonnes avec prose cappée par la cellule de grille. + "grid lg:grid-cols-2 gap-8 lg:gap-16 items-center py-10 md:py-14", + "border-b border-dashed border-line last:border-b-0", + flip && "lg:[&>*:first-child]:order-2", + )} + > + <div className="flex flex-col items-center lg:items-start gap-5 md:gap-6"> + <div className="inline-flex items-center font-display font-bold text-[12px] tracking-[0.16em] uppercase text-rubis bg-rubis-glow border border-rubis/15 px-3 py-1.5 rounded-full"> + Étape {num} + </div> + {children} + </div> + <div className="w-full lg:max-w-[520px]"> + <h3 className="font-display font-bold text-ink text-[26px] sm:text-[30px] lg:text-[32px] tracking-[-0.022em] leading-[1.15]"> + {title} + </h3> + <div className="mt-4 md:mt-5 text-[16.5px] md:text-[17px] leading-relaxed text-ink-2 md:text-justify hyphens-auto"> + {body} + </div> + </div> + </div> + ); +} + +/* ============== Widget 01 — Dropzone ============== */ +function DropzoneWidget() { + return ( + <div className="relative w-full max-w-[420px] aspect-[5/4] bg-white border border-dashed border-rubis/40 rounded-card p-6 flex flex-col items-center justify-center gap-3 shadow-soft"> + <div className="flex items-center gap-2" aria-hidden> + {[0, 1, 2].map((i) => ( + <span + key={i} + className="size-3 bg-rubis rotate-45 opacity-60" + style={{ opacity: 1 - i * 0.25 }} + /> + ))} + </div> + <div className="text-rubis text-2xl font-bold" aria-hidden>↓</div> + <div className="w-full max-w-[260px] bg-cream-2 border border-line rounded-default p-4 space-y-1.5"> + <div className="h-1.5 bg-ink-3/30 rounded w-2/3" /> + <div className="h-1.5 bg-ink-3/30 rounded w-1/2" /> + <div className="h-1.5 bg-ink-3/30 rounded w-full" /> + <div className="h-1.5 bg-ink-3/30 rounded w-2/3" /> + <div className="font-display font-bold text-ink text-[18px] tabular-nums mt-3 pt-3 border-t border-line"> + 1 240,00 € + </div> + </div> + <div className="text-[12px] text-ink-3 font-mono">facture-2024-0042.pdf</div> + </div> + ); +} + +/* ============== Widget 02 — Calendrier ============== */ +function CalendarWidget() { + const days = Array.from({ length: 35 }, (_, i) => i - 3); // 4 jours vides, puis 1-31 + const marked = new Set([8, 15, 25]); + return ( + <div className="w-full max-w-[420px] bg-white border border-line rounded-card p-5 shadow-soft"> + <div className="flex items-center justify-between mb-4"> + <div className="font-display font-bold text-ink">Mai 2026</div> + <div className="flex items-center gap-1 text-ink-3 text-[14px]"> + <span className="size-7 inline-flex items-center justify-center rounded border border-line cursor-pointer hover:bg-cream-2"> + ‹ + </span> + <span className="size-7 inline-flex items-center justify-center rounded border border-line cursor-pointer hover:bg-cream-2"> + › + </span> + </div> + </div> + + <div className="grid grid-cols-7 gap-1.5 text-center text-[12px]"> + {["L", "M", "M", "J", "V", "S", "D"].map((d, i) => ( + <div key={i} className="text-ink-3 font-semibold uppercase tracking-wider pb-2"> + {d} + </div> + ))} + {days.map((d, i) => { + if (d < 1) return <div key={i} className="text-ink-3/30 py-1.5">·</div>; + const isMarked = marked.has(d); + return ( + <div + key={i} + className={cn( + "py-1.5 rounded text-[12.5px] tabular-nums", + isMarked + ? "bg-rubis text-white font-semibold" + : "text-ink hover:bg-cream-2", + )} + > + {d} + </div> + ); + })} + </div> + + <div className="flex items-center justify-between mt-4 pt-3 border-t border-line text-[11.5px]"> + <span className="inline-flex items-center gap-1.5 text-ink-2"> + <span aria-hidden className="size-2 rounded-full bg-rubis" /> + Relances programmées + </span> + <span className="text-ink-3">3 étapes · plan B2B</span> + </div> + </div> + ); +} + +/* ============== Widget 03 — Assistant (mini illustration) ============== */ +function AssistantWidget() { + return ( + <div className="w-full max-w-[420px] aspect-[5/4] bg-white border border-line rounded-card p-6 flex flex-col items-center justify-center gap-4 shadow-soft"> + <div className="flex items-center gap-3"> + <div className="size-14 rounded-full bg-rubis text-white flex items-center justify-center font-display font-bold text-[20px]"> + ◆ + </div> + <div className="bg-cream-2 border border-line rounded-default px-4 py-3 text-[13.5px] text-ink-2 max-w-[220px] relative"> + Cette facture a-t-elle été réglée ? + <div + aria-hidden + className="absolute left-[-7px] top-1/2 -translate-y-1/2 size-3 rotate-45 bg-cream-2 border-l border-b border-line" + /> + </div> + </div> + <div className="flex gap-2"> + <button + type="button" + className="px-4 py-2 bg-rubis text-white rounded-default text-[13px] font-semibold shadow-rubis" + > + ✓ Oui + </button> + <button + type="button" + className="px-4 py-2 bg-white border border-line text-ink rounded-default text-[13px] font-medium" + > + Pas encore + </button> + </div> + <div className="text-[12px] text-ink-3 italic">2 secondes, et la machine s'occupe du reste.</div> + </div> + ); +} diff --git a/apps/landing/src/components/sections/Legal.tsx b/apps/landing/src/components/sections/Legal.tsx new file mode 100644 index 0000000..726c8d5 --- /dev/null +++ b/apps/landing/src/components/sections/Legal.tsx @@ -0,0 +1,74 @@ +import { Eyebrow } from "@rubis/ui"; + +const SANCTIONED = [ + ["Fnac Darty", "3,9 M€"], + ["Cdiscount", "2,1 M€"], + ["Sanofi", "1,65 M€"], + ["LCL", "1,5 M€"], +]; + +export function Legal() { + return ( + <section className="bg-cream-2 border-y border-line"> + <div className="max-w-[1180px] mx-auto px-5 sm:px-8 py-20 lg:py-24 grid lg:grid-cols-[1.3fr_1fr] gap-12 lg:gap-16 items-start"> + <div> + <Eyebrow>Vous êtes dans votre droit</Eyebrow> + <h2 className="mt-4 font-display font-bold text-ink leading-[1.12] tracking-[-0.025em] text-[32px] sm:text-[42px]"> + La loi est de votre côté. On vous évite juste de la brandir. + </h2> + + <div className="mt-6 space-y-5 text-[17px] leading-relaxed text-ink-2"> + <p> + La{" "} + <span className="inline-flex items-center px-2 py-0.5 bg-rubis-glow border border-rubis/15 rounded text-rubis-deep font-semibold text-[14px] tracking-tight"> + loi LME + </span>{" "} + plafonne les délais de paiement entre entreprises à <b className="text-ink">60 jours</b>{" "} + (ou 45 jours fin de mois). Les sanctions peuvent atteindre{" "} + <b className="text-ink">2 millions d'euros</b>. En 2025, le Sénat a voté à + l'unanimité un durcissement supplémentaire des règles. + </p> + <p> + Mais vous n'avez pas envie d'envoyer un commissaire de justice à votre meilleur + client. Rubis fait le boulot intermédiaire — relances pro, courtoises, espacées + dans le temps. Le ton monte progressivement, jamais d'un coup. Et vous gardez le + contrôle total : la mise en demeure, c'est <b className="text-ink">vous</b> qui + l'envoyez, sur validation manuelle. + </p> + </div> + </div> + + {/* Clipping presse-style */} + <aside className="bg-white border border-line rounded-card p-7 shadow-soft"> + <div className="flex items-center justify-between text-[11px] uppercase tracking-[0.14em] font-semibold text-ink-3 pb-3 mb-4 border-b border-line"> + <span>Sanctions DGCCRF</span> + <span>2025</span> + </div> + + <div className="font-display font-extrabold text-rubis text-[56px] sm:text-[68px] leading-none tracking-[-0.035em]"> + 47 M€ + </div> + <p className="mt-2 text-[15px] text-ink-2 leading-snug"> + de pénalités prononcées contre les mauvais payeurs français l'an dernier. + </p> + + <ul className="mt-5 divide-y divide-line"> + {SANCTIONED.map(([name, amount]) => ( + <li key={name} className="flex items-center justify-between py-2.5 text-[14.5px]"> + <b className="text-ink font-semibold">{name}</b> + <span className="text-rubis font-display font-bold tabular-nums">{amount}</span> + </li> + ))} + <li className="py-2.5 text-[13px] italic text-ink-3"> + … et 405 autres entreprises contrôlées + </li> + </ul> + + <p className="mt-4 pt-4 border-t border-line text-[11.5px] text-ink-3"> + Source · DGCCRF, bilan annuel 2025 · economie.gouv.fr + </p> + </aside> + </div> + </section> + ); +} diff --git a/apps/landing/src/components/sections/Pricing.tsx b/apps/landing/src/components/sections/Pricing.tsx new file mode 100644 index 0000000..716098f --- /dev/null +++ b/apps/landing/src/components/sections/Pricing.tsx @@ -0,0 +1,164 @@ +import { Button, Eyebrow } from "@rubis/ui"; +import { Check } from "lucide-react"; + +const APP_URL = "https://app.rubis.pro"; + +const FEATURES = [ + ["Factures illimitées.", "Que vous en émettiez 10 ou 500 par mois, c'est le même prix."], + ["OCR illimité.", "Drag & drop, photo mobile, batch de 20 d'un coup."], + [ + "Relances signées de votre nom.", + "Vos clients voient votre nom et répondent directement à votre email. Aucune mention de Rubis.", + ], + ["Plans personnalisables", "avec variables et tonalités sur-mesure."], + ["Stats détaillées", "+ export CSV pour vos comptables."], + ["Support prioritaire.", "Réponse sous 4 h ouvrées, par un humain en France."], + ["App mobile et desktop,", "hébergement français."], +] as const; + +export function Pricing() { + return ( + <section id="pricing"> + <div className="max-w-[1180px] mx-auto px-5 sm:px-8 py-20 lg:py-28"> + <div className="text-center max-w-[640px] mx-auto mb-14"> + <Eyebrow>Tarifs</Eyebrow> + <h2 className="mt-4 font-display font-bold text-ink leading-[1.1] tracking-[-0.025em] text-[34px] sm:text-[44px]"> + Moins cher qu'une heure de votre temps mensuel. + </h2> + <p className="mt-4 text-[17px] text-ink-2 leading-relaxed"> + On va droit au but. Un plan principal qu'on recommande à 99 % d'entre vous, et deux + options autour. C'est tout. + </p> + </div> + + {/* Pro plan — anchor */} + <div className="bg-white border border-line rounded-card shadow-card grid lg:grid-cols-[1.05fr_1fr] gap-0 overflow-hidden"> + <div className="p-8 lg:p-12 border-b lg:border-b-0 lg:border-r border-line"> + <span className="inline-flex items-center text-[11px] uppercase tracking-[0.16em] font-semibold text-rubis bg-rubis-glow border border-rubis/15 px-3 py-1.5 rounded-full"> + Le plan qu'on recommande + </span> + <h3 className="mt-5 font-display font-bold text-ink text-[32px] sm:text-[40px] tracking-[-0.025em] leading-tight"> + Le plan <em>Pro</em>. + </h3> + <div className="mt-6 flex items-baseline gap-3"> + <span className="font-display font-extrabold text-rubis text-[68px] sm:text-[88px] tracking-[-0.04em] leading-none tabular-nums"> + 19 € + </span> + <span className="font-sans text-[14px] text-ink-3 leading-tight"> + par mois + <br /> + hors taxes + </span> + </div> + <p className="mt-6 text-[16.5px] leading-relaxed text-ink-2 max-w-md"> + Pour ce prix, vous avez Rubis dans son <b className="text-ink">intégralité</b>. + Factures et OCR illimités, plans de relance personnalisés, statistiques détaillées, + support prioritaire. Aucun palier caché, aucun surcoût à l'usage. + </p> + <div className="mt-7 flex flex-wrap items-center gap-x-4 gap-y-2"> + <Button asChild size="lg"> + <a href={APP_URL}>Commencer l'essai 30 jours →</a> + </Button> + <span className="text-[13px] text-ink-3"> + Sans engagement, annulable à tout moment + </span> + </div> + </div> + + <div className="p-8 lg:p-12 bg-cream-2"> + <div className="font-display font-bold text-[14px] uppercase tracking-[0.08em] text-ink mb-5"> + Ce qui est inclus + </div> + <ul className="space-y-3.5"> + {FEATURES.map(([head, tail]) => ( + <li key={head} className="flex gap-3 text-[15px] leading-snug text-ink-2"> + <Check + size={18} + strokeWidth={2.5} + className="text-rubis flex-shrink-0 mt-0.5" + aria-hidden + /> + <span> + <b className="text-ink">{head}</b> {tail} + </span> + </li> + ))} + </ul> + </div> + </div> + + {/* Asides */} + <div className="text-center my-12 text-[14px] uppercase tracking-[0.2em] font-semibold text-ink-3"> + — ou — + </div> + + <div className="grid md:grid-cols-2 gap-5 lg:gap-7"> + <PricingAside + name="Plan Free" + price="0 €" + qualifier="Pour tester ou démarrer en freelance" + > + Le plan Free fait tourner Rubis sur <b className="text-ink">5 factures actives</b> en + permanence. Gratuit, pour de bon. Notre façon de prouver que la promesse tient. + </PricingAside> + <PricingAside + name="Plan Business" + price="49 €" + qualifier="Pour les équipes & les pros exigeants" + > + Tout du Pro, plus : <b className="text-ink">jusqu'à 5 collaborateurs</b> dans la boîte, + chacun avec son accès. Vos relances partent de{" "} + <b className="text-ink">votre vraie adresse pro</b> ( + <code className="bg-cream-2 px-1.5 py-0.5 rounded text-[12.5px]"> + compta@votre-entreprise.fr + </code> + ), pas d'une adresse Rubis — vos clients ne se rendent compte de rien et vos emails + arrivent mieux en boîte principale. + </PricingAside> + </div> + + <p className="mt-12 text-center text-[14px] text-ink-3 max-w-[560px] mx-auto"> + Pas de palier caché. Pas de surcoût à l'usage. Annulation en un clic, sans question + posée. + </p> + </div> + </section> + ); +} + +function PricingAside({ + name, + price, + qualifier, + children, +}: { + name: string; + price: string; + qualifier: string; + children: React.ReactNode; +}) { + return ( + <a + href={APP_URL} + className="group block bg-white border border-line rounded-card p-7 shadow-soft hover:border-rubis/40 hover:shadow-card transition-all hover:no-underline" + > + <div className="flex items-baseline justify-between gap-3 pb-4 border-b border-line"> + <span className="font-display font-bold text-ink text-[20px] tracking-[-0.018em]"> + {name} + </span> + <span className="font-display font-extrabold text-ink text-[24px] tabular-nums"> + {price} + <span className="font-sans font-normal text-[13px] text-ink-3 ml-1">/mois</span> + </span> + </div> + <div className="mt-3 text-[12.5px] uppercase tracking-[0.1em] font-semibold text-ink-3"> + {qualifier} + </div> + <p className="mt-3 text-[15px] leading-relaxed text-ink-2">{children}</p> + <span className="mt-5 inline-flex items-center gap-1.5 text-[14px] font-display font-semibold text-rubis"> + Démarrer {name.replace("Plan ", "")} maintenant + <span className="transition-transform group-hover:translate-x-0.5">→</span> + </span> + </a> + ); +} diff --git a/apps/landing/src/components/sections/Promise.tsx b/apps/landing/src/components/sections/Promise.tsx new file mode 100644 index 0000000..34fbfc5 --- /dev/null +++ b/apps/landing/src/components/sections/Promise.tsx @@ -0,0 +1,58 @@ +import { Eyebrow } from "@rubis/ui"; + +export function Promise() { + return ( + <section> + <div className="max-w-[1180px] mx-auto px-5 sm:px-8 py-20 lg:py-28"> + <div className="text-center max-w-[820px] mx-auto"> + <Eyebrow>Notre conviction</Eyebrow> + <blockquote className="mt-6 font-display font-bold text-ink leading-[1.05] tracking-[-0.03em] text-[40px] sm:text-[56px] lg:text-[64px]"> + Votre temps vaut <em>plus que ça</em>. + </blockquote> + </div> + + <div className="mt-14 grid lg:grid-cols-[1.4fr_1fr] gap-10 lg:gap-16 items-start max-w-[1080px] mx-auto"> + <div className="space-y-5 md:text-justify hyphens-auto"> + <p className="text-[17.5px] leading-relaxed text-ink-2"> + Vous n'avez pas créé votre boîte pour passer vos lundis soirs à rédiger des + relances polies. Pendant que vous écrivez "je me permets un petit rappel + concernant…", vous ne facturez pas, vous ne vendez pas, vous ne créez pas. + </p> + <p className="text-[17.5px] leading-relaxed text-ink-2"> + Les PME qui automatisent leurs relances passent de{" "} + <b className="text-ink">8 heures par semaine</b> à{" "} + <b className="text-ink">moins de 3</b>. Soit 5 heures de votre vie récupérées. + Toutes les semaines. Pour toujours. + </p> + </div> + + <div className="bg-cream-2 border border-line rounded-card p-7"> + <h4 className="font-display font-bold text-[15px] text-ink uppercase tracking-[0.06em]"> + Votre temps en chiffres + </h4> + <dl className="mt-4 divide-y divide-line"> + {[ + ["Heures perdues / semaine", "5 h"], + ["Sur un mois", "~ 21 h"], + ["À 50 €/h facturés", "1 050 €"], + ].map(([label, val]) => ( + <div key={label} className="flex items-center justify-between py-3 text-[14px]"> + <dt className="text-ink-2">{label}</dt> + <dd className="font-display font-bold text-ink tabular-nums">{val}</dd> + </div> + ))} + <div className="flex items-center justify-between pt-4 mt-1 border-t-2 border-ink"> + <dt className="text-ink font-semibold text-[14px]"> + Coût annuel d'une relance manuelle + </dt> + <dd className="font-display font-extrabold text-rubis text-[20px] tabular-nums"> + 12 600 € + </dd> + </div> + </dl> + </div> + </div> + </div> + </section> + ); +} diff --git a/apps/landing/src/components/sections/Stats.tsx b/apps/landing/src/components/sections/Stats.tsx new file mode 100644 index 0000000..074c946 --- /dev/null +++ b/apps/landing/src/components/sections/Stats.tsx @@ -0,0 +1,55 @@ +import { Eyebrow } from "@rubis/ui"; + +const STATS = [ + { + num: "44 j", + desc: "Le retard moyen de paiement pour une facture émise par une TPE en France.", + source: "Source : Altares · Observatoire des délais de paiement, 2024", + }, + { + num: "15 Md€", + desc: "De trésorerie bloquée chez les PME françaises à cause des retards de paiement.", + source: "Source : Banque de France · Rapport annuel 2024", + }, + { + num: "−26 %", + desc: "De chances d'être payé si vous attendez plus de 30 jours pour relancer.", + source: "Source : AFDCC · Étude crédit management", + }, +]; + +export function Stats() { + return ( + <section className="bg-cream-2 border-y border-line"> + <div className="max-w-[1180px] mx-auto px-5 sm:px-8 py-20 lg:py-24"> + <div className="text-center max-w-[640px] mx-auto mb-12"> + <Eyebrow>L'état des paiements en France</Eyebrow> + <h2 className="mt-4 font-display font-bold text-ink leading-[1.1] tracking-[-0.025em] text-[34px] sm:text-[44px]"> + Trois chiffres qui devraient vous fâcher. + </h2> + <p className="mt-4 text-[17px] text-ink-2 leading-relaxed"> + Si vous lisez ça, vous avez probablement une facture impayée à l'heure où on parle. + Vous n'êtes pas un cas isolé. + </p> + </div> + + <div className="grid sm:grid-cols-3 gap-5 lg:gap-7"> + {STATS.map((s) => ( + <div + key={s.num} + className="bg-white border border-line rounded-card p-7 lg:p-8 shadow-soft" + > + <div className="font-display font-extrabold text-rubis text-[44px] sm:text-[52px] tracking-[-0.03em] leading-none"> + {s.num} + </div> + <p className="mt-4 text-[15.5px] text-ink leading-relaxed">{s.desc}</p> + <p className="mt-4 pt-4 border-t border-line text-[12px] text-ink-3 italic"> + {s.source} + </p> + </div> + ))} + </div> + </div> + </section> + ); +} diff --git a/apps/landing/src/layouts/Layout.astro b/apps/landing/src/layouts/Layout.astro new file mode 100644 index 0000000..35c008a --- /dev/null +++ b/apps/landing/src/layouts/Layout.astro @@ -0,0 +1,115 @@ +--- +/** + * Layout commun à toutes les pages rubis.pro/*. + * + * Reçoit en props les meta SEO essentiels. Importe le CSS racine (qui inline + * les tokens + base @rubis/ui), monte SiteHeader + SiteFooter, et expose un + * <slot /> pour le contenu de la page. + * + * Convention : zéro JS hydraté côté public sauf nécessité explicite — le + * SiteHeader + SiteFooter sont rendus côté serveur uniquement (pas de + * client:load) pour rester sur le bundle minimal. + */ +import "../styles/app.css"; +import { SiteHeader } from "../components/SiteHeader"; +import { SiteFooter } from "../components/SiteFooter"; + +const SITE_URL = "https://rubis.pro"; + +interface Props { + title: string; + description: string; + /** Path absolu de la page courante (ex. "/blog/foo"). Default = location actuelle. */ + pathname?: string; + /** OG image absolue (1200×630 idéal). */ + ogImage?: string; + /** OG type. Default "website". Mettre "article" sur les pages de blog. */ + ogType?: "website" | "article"; + /** Si true → noindex (legal/test). */ + noindex?: boolean; + /** Header solide (bordure + fond) plutôt que transparent (par défaut). */ + solidHeader?: boolean; + /** JSON-LD structured data — passé en string déjà sérialisé OU en object. */ + jsonLd?: object | object[]; +} + +const { + title, + description, + pathname, + ogImage, + ogType = "website", + noindex = false, + solidHeader = false, + jsonLd, +} = Astro.props; + +const fullTitle = title.includes("Rubis") ? title : `${title} — Rubis sur l'ongle`; +const url = `${SITE_URL}${pathname ?? Astro.url.pathname}`; +const robots = noindex ? "noindex,nofollow" : "index,follow,max-image-preview:large"; +const jsonLdArray = jsonLd ? (Array.isArray(jsonLd) ? jsonLd : [jsonLd]) : []; +--- + +<!doctype html> +<html lang="fr"> + <head> + <meta charset="utf-8" /> + <meta name="viewport" content="width=device-width, initial-scale=1" /> + <title>{fullTitle} + + + + + + + + + + {/* Open Graph */} + + + + + + + { + ogImage && ( + <> + + + + + + ) + } + + {/* Twitter Card */} + + + + {ogImage && } + + {/* Favicons */} + + + + + + {/* RSS auto-discovery */} + + + {/* JSON-LD structured data */} + { + jsonLdArray.map((data) => ( + - - - - - - -

- - -
-
-
-
L'outil de relance pour TPE-PME françaises
-

Vos factures relancées toutes seules pendant que vous travaillez.

-

L'app de relance de factures impayées pensée pour les TPE-PME françaises. Glissez-déposez vos - factures, choisissez un plan de relance, oubliez-les. Rubis envoie, suit, relance — et vous récupérez en - moyenne - 5 heures par semaine. -

- -
- - - 30 jours gratuits puis Free 5 factures - - Hébergement souverain - - Made in France 🇫🇷 -
-
-
-
-
- - - - - - -
-
124 rubis
-
≈ 24 h 48 que vous n'avez pas passées à relancer.
-
-
-
-
-
Encaissé
-
14 320 €
-
+ 2 800 € vs avril
-
-
-
DSO
-
38 j
-
↘ −6 j depuis Rubis
-
-
-
-
Aujourd'hui
-
    -
  • 📤 Relance envoyée à Atelier Durand
  • -
  • ✓ Facture F-2024-0035 encaissée
  • -
  • 📥 3 factures importées et OCRisées
  • -
-
-
-
- - - - - ~3 minutes le matin -
-
-
-
- - -
-
-
-
L'état des paiements en France
-

Trois chiffres qui devraient vous fâcher.

-

Si vous lisez ça, vous avez probablement une facture impayée à l'heure où on parle. Vous n'êtes pas un cas - isolé.

-
-
-
-
44 j
-
Le retard moyen de paiement pour une facture émise par une TPE en France.
-
Source : Altares · Observatoire des délais de paiement, 2024
-
-
-
15 Md€
-
De trésorerie bloquée chez les PME françaises à cause des retards de paiement.
-
Source : Banque de France · Rapport annuel 2024
-
-
-
−26 %
-
De chances d'être payé si vous attendez plus de 30 jours pour relancer.
-
Source : AFDCC · Étude crédit management
-
-
-
-
- - -
-
-
Notre conviction
-
Votre temps vaut plus que ça.
-
-
-

Vous n'avez pas créé votre boîte pour passer vos lundis soirs à rédiger des relances polies. Pendant que - vous écrivez "je me permets un petit rappel concernant…", vous ne facturez pas, vous ne vendez pas, vous ne - créez pas.

-

Les PME qui automatisent leurs relances passent de 8 heures par semaine à moins de 3. Soit 5 - heures de votre vie récupérées. Toutes les semaines. Pour toujours.

-
-
-

Votre temps en chiffres

-
Heures perdues / semaine5 h
-
Sur un mois~ 21 h
-
À 50 €/h facturés1 050 €
-
Coût annuel d'une relance manuelle12 600 - €
-
-
-
-
- - -
-
-
-
Comment ça marche
-

Trois étapes. C'est tout.

-

Vraiment. Parfois deux, si votre plan par défaut est bien réglé.

-
- -
-
-
Étape 01
-
- -
-
-
-
-
-
-
1 240,00 €
-
-
facture-2024-0042.pdf
-
-
-
-

Vous importez vos factures.

-

PDF, photo prise depuis votre téléphone à la caisse, scan reçu par mail — peu importe. L'OCR* lit, extrait - montant, client, échéance, RIB. Vous vérifiez. Vingt secondes par facture, montre en main.

-
-
- -
-
-
Étape 02
-
-
-
Mai 2026
-
- - -
-
-
-
L
-
M
-
M
-
J
-
V
-
S
-
D
-
·
-
·
-
·
-
·
-
1
-
2
-
3
-
4
-
5
-
6
-
7
-
8
-
9
-
10
-
11
-
12
-
13
-
14
-
15
-
16
-
17
-
18
-
19
-
20
-
21
-
22
-
23
-
24
-
25
-
26
-
27
-
28
-
29
-
30
-
31
-
-
- Relances programmées - 3 étapes · plan B2B -
-
-
-
-

Vous choisissez un plan de relance.

-

Nous en avons décliné plusieurs — par exemple, un standard B2B (J+3, J+10, J+20), adapter le ton en - fonction - de l'échéance et de l'historique du client. -

-

- Et bien sûr, vous pouvez aussi créer les vôtres sur mesure. -

-
-
- -
-
-
Étape 03
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
-
-

Et puis c'est tout.

-

Sérieusement. Pendant que vous travaillez, Rubis envoie les emails au moment prévu, suit qui a ouvert, qui - n'a pas répondu, et avant chaque relance vous demande discrètement par email : « Cette facture a-t-elle été - réglé ? ». Vous répondez en deux secondes. La machine fait le reste.

-
La récompense : votre compteur de rubis grimpe. Tranquillement. Comme une bonne nouvelle - régulière.
-
-
-
-
- - -
-
-
-
La devise du temps gagné
-

1 rubis = 10 minutes de votre vie.

-

À chaque relance que Rubis envoie à votre place, vous gagnez un rubis. À la fin du mois, vous - voyez exactement combien d'heures vous avez récupérées. Pas un graphique de DSO. Pas un PDF abscons. Du temps. - Concret.

-
- 124rubis - 24 h 48 de votre mois -
-

Et oui, on garde un - classement amical. Les meilleurs utilisateurs libèrent 30 heures par mois. Plus de quoi prendre un long - week-end. Toutes les 4 semaines.

-
-
-
- - - - - -
-
-
-
Tarifs
-

Moins cher qu'une heure de votre temps mensuel.

-

On va droit au but. Un plan principal qu'on recommande à 99 % d'entre vous, et deux options autour. C'est - tout.

-
- -
-
- Le plan qu'on recommande -
Le plan Pro.
-
- 19 € - par mois
hors taxes
-
-

Pour ce prix, vous avez Rubis dans son intégralité. Factures et OCR illimités, plans - de relance personnalisés, statistiques détaillées, support prioritaire. Aucun palier caché, aucun surcoût à - l'usage.

-
- Commencer l'essai 30 jours → - Sans engagement, annulable à tout moment -
-
-
-
Ce qui est inclus
-
    -
  • - - - - Factures illimitées. Que vous en émettiez 10 ou 500 par mois, c'est le même prix. -
  • -
  • - - - - OCR illimité. Drag & drop, photo mobile, batch de 20 d'un coup. -
  • -
  • - - - - Relances signées de votre nom. Vos clients voient votre nom et répondent directement à votre - email. Aucune mention de Rubis. -
  • -
  • - - - - Plans personnalisables avec variables et tonalités sur-mesure. -
  • -
  • - - - - Stats détaillées + export CSV pour vos comptables. -
  • -
  • - - - - Support prioritaire. Réponse sous 4 h ouvrées, par un humain en France. -
  • -
  • - - - - App mobile et desktop, hébergement français. -
  • -
-
-
- -
— ou —
- - - -

Pas de palier caché. Pas de surcoût à l'usage. Annulation en un clic, sans question - posée.

-
-
- - -
-
-
-
Questions fréquentes
-

Vous vous demandez sûrement…

-
-
- Et si mon client paie hors plateforme — comment Rubis le sait ? -
Avant chaque relance, Rubis vous envoie un email rapide : « Avez-vous été payé pour la - facture F-2024-0042 ? » avec deux boutons. Vous cliquez "Oui" en 3 secondes, le plan s'arrête. Vous - cliquez "Non" (ou ne répondez pas), la relance part comme prévu. Vous configurez la cadence et le timing de - ces vérifications dans vos plans.
-
-
- Mes factures et données restent-elles privées ? -
Évidemment. Hébergement français, conforme RGPD. Vos PDF sont stockés chiffrés. Aucune - donnée n'est partagée avec des tiers. Vous pouvez exporter ou supprimer vos données à tout moment.
-
-
- Puis-je personnaliser le contenu des emails ? -
Oui, dès le plan Pro. Tous les emails sont des templates avec variables - ({{prenom_client}}, {{numero}}, {{montant}}…). Vous pouvez réécrire - chaque étape, ajuster le ton, ajouter votre signature email et votre logo.
-
-
- Mes clients verront-ils que j'utilise Rubis ? -
Pas vraiment. En plan Pro, vos clients voient votre nom en grand comme - expéditeur, et quand ils cliquent « Répondre », leur message revient directement sur votre email. Aucun - pied de page ne mentionne Rubis. Suffisant pour 95 % des freelances et TPE.

- En plan Business, on va plus loin : vos emails partent vraiment depuis votre propre adresse - (compta@votre-entreprise.fr). Personne ne devine que vous utilisez un outil, et vos relances - atterrissent mieux en boîte principale plutôt qu'en spam ou en promotions — gain typique de 10 à 15 % - sur le taux d'ouverture.
-
-
- Et si je veux relancer manuellement, sans plan ? -
Toujours possible. Sur n'importe quelle facture, vous avez un bouton "Relancer maintenant" - qui envoie un email immédiat. Pratique quand vous venez d'avoir le client au téléphone et qu'il vous a demandé - un récapitulatif.
-
-
- Et la mise en demeure, elle part toute seule ? -
Non. Jamais. C'est une décision produit forte : la mise en demeure a des conséquences - légales et relationnelles importantes. Rubis prépare le brouillon à l'étape prévue de votre plan, vous - notifie, et c'est vous qui cliquez "Envoyer" sur une modale de confirmation. Vous gardez la main sur le - moment où le ton change vraiment.
-
-
- Combien de temps pour démarrer ? -
Inscription en 30 secondes. Configuration de votre signature email et de votre première - facture en 5 minutes. La première relance peut partir dans la foulée. Si vous avez un plan par défaut bien - configuré, créer une nouvelle facture en relance prend 2 clics.
-
-
- Pourquoi cette histoire de "rubis" ? -
Parce que les chiffres comptables (DSO, taux de recouvrement, AR aging) ne réveillent - personne le matin. Le temps gagné, oui. 1 rubis = 10 minutes libérées = 1 relance que vous n'avez pas - eu à écrire. À la fin du mois, vous voyez "124 rubis ≈ 24 h 48". C'est concret. Et c'est plus fun que de - regarder un graphique de courbes.
-
-
-
- - -
-
-

Récupérez vos premières heures dès aujourd'hui.

-

30 jours gratuits, puis le plan Free continue avec 5 factures actives. Pas de carte demandée pour démarrer.

- -

Inscription en 30 secondes. Annulation 1-clic à tout moment.

-
-
- - - - - - - - - - - - \ No newline at end of file diff --git a/landing/mentions-legales.html b/landing/mentions-legales.html deleted file mode 100644 index fb1b746..0000000 --- a/landing/mentions-legales.html +++ /dev/null @@ -1,170 +0,0 @@ - - - - - - - - - - - - - Mentions légales · Rubis sur l'ongle - - - - - -
-
-

Informations légales

-

Mentions légales

-

- Conformément aux articles 6-III et 19 de la Loi n° 2004-575 du 21 juin 2004 pour la - confiance dans l'économie numérique (LCEN), voici les informations relatives à - l'éditeur, à l'hébergeur et aux conditions d'utilisation du site. -

-

Dernière mise à jour : 7 mai 2026

- - - -

1. Éditeur du site

-

- Le site rubis.pro (ainsi que l'application - app.rubis.pro) est édité par : -

- - -

2. Hébergement

-

- Hébergeur : OVH SAS
- Adresse : 2 rue Kellermann, 59100 Roubaix, France. -

-

- L'ensemble de l'infrastructure et des données utilisateurs est situé en France. -

- -

3. Directeur de la publication

-

- Arthur Barré, en sa qualité d'éditeur, est responsable de la - publication du contenu du site et de l'application Rubis sur l'ongle. -

- -

4. Contact

-
-

- Email général : - contact@rubis.pro -

-
-

- Pour les demandes liées aux données personnelles ou pour exercer vos droits RGPD, - consultez notre politique de confidentialité. -

- -

5. Propriété intellectuelle

-

- L'ensemble des contenus présents sur le site et l'application — y compris les textes, - la marque "Rubis sur l'ongle", le logo (◆), les illustrations, la palette graphique, - le code source des templates et de l'application — sont la propriété exclusive - d'Arthur Barré ou font l'objet d'une autorisation d'utilisation. -

-

- Toute reproduction, représentation, modification, publication ou adaptation totale - ou partielle des éléments du site, quel que soit le moyen ou le procédé utilisé, est - interdite sans autorisation écrite préalable. -

- -

6. Limitation de responsabilité

-

- L'éditeur s'efforce d'assurer au mieux l'exactitude et la mise à jour des informations - présentées. Cependant, il ne peut garantir l'absence d'erreurs ou d'omissions et - décline toute responsabilité quant à l'usage qui pourrait être fait du contenu. -

-

- Concernant l'application Rubis, l'éditeur met à disposition un outil d'assistance à - la relance automatisée. L'utilisateur reste seul responsable du - contenu des relances envoyées à ses clients, du respect de la législation applicable - (notamment la LME pour les délais de paiement) et de la qualification éventuelle - d'une mise en demeure. -

- -

7. Liens externes

-

- Le site peut contenir des liens vers des sites tiers (notamment les pages - réglementaires de Stripe, Resend ou la CNIL). L'éditeur n'exerce aucun contrôle - sur ces sites et décline toute responsabilité quant à leur contenu ou aux pratiques - de confidentialité qui leur sont propres. -

- -

8. Cookies

-

- Le site landing rubis.pro ne dépose aucun cookie de mesure - d'audience ni de traçage publicitaire. L'application - app.rubis.pro utilise uniquement des cookies strictement - nécessaires au fonctionnement (session d'authentification, refresh tokens). - Les détails sont décrits dans la - politique de confidentialité. -

- -

9. Droit applicable et juridiction

-

- Les présentes mentions légales sont régies par le droit français. En cas de litige - ou de désaccord, et après tentative de recherche d'une solution amiable, compétence - est attribuée aux tribunaux français compétents, conformément aux règles de - procédure en vigueur. -

-
-
- - - - diff --git a/packages/ui/package.json b/packages/ui/package.json new file mode 100644 index 0000000..c81dd35 --- /dev/null +++ b/packages/ui/package.json @@ -0,0 +1,35 @@ +{ + "name": "@rubis/ui", + "version": "0.1.0", + "private": true, + "type": "module", + "description": "Design system Rubis — composants React + tokens Tailwind partagés entre apps/web, apps/landing et tout futur surface", + "exports": { + ".": { + "types": "./src/index.ts", + "default": "./src/index.ts" + }, + "./styles/tokens.css": "./src/styles/tokens.css", + "./styles/base.css": "./src/styles/base.css" + }, + "scripts": { + "lint": "eslint src", + "typecheck": "tsc --noEmit" + }, + "peerDependencies": { + "lucide-react": ">=0.475.0", + "react": "^19.0.0", + "react-dom": "^19.0.0" + }, + "dependencies": { + "@radix-ui/react-slot": "^1.1.2", + "class-variance-authority": "^0.7.1", + "clsx": "^2.1.1", + "tailwind-merge": "^3.0.1" + }, + "devDependencies": { + "@types/react": "^19.2.14", + "@types/react-dom": "^19.2.3", + "typescript": "~6.0.2" + } +} diff --git a/apps/web/src/components/brand/Brand.tsx b/packages/ui/src/components/Brand.tsx similarity index 96% rename from apps/web/src/components/brand/Brand.tsx rename to packages/ui/src/components/Brand.tsx index e360a35..ae338c7 100644 --- a/apps/web/src/components/brand/Brand.tsx +++ b/packages/ui/src/components/Brand.tsx @@ -1,5 +1,5 @@ -import { cn } from "@/lib/utils"; -import { Gem } from "./Gem"; +import { cn } from "../lib/cn.js"; +import { Gem } from "./Gem.js"; /** * Lockup horizontal : ◆ + "Rubis" (+ optionnel "sur l'ongle" en suffixe italique muted). diff --git a/apps/web/src/components/ui/Button.tsx b/packages/ui/src/components/Button.tsx similarity index 98% rename from apps/web/src/components/ui/Button.tsx rename to packages/ui/src/components/Button.tsx index d4ad61d..2271774 100644 --- a/apps/web/src/components/ui/Button.tsx +++ b/packages/ui/src/components/Button.tsx @@ -1,7 +1,8 @@ import { forwardRef } from "react"; import { Slot } from "@radix-ui/react-slot"; import { cva, type VariantProps } from "class-variance-authority"; -import { cn } from "@/lib/utils"; + +import { cn } from "../lib/cn.js"; /** * Bouton — primitive maison. diff --git a/apps/web/src/components/ui/Card.tsx b/packages/ui/src/components/Card.tsx similarity index 97% rename from apps/web/src/components/ui/Card.tsx rename to packages/ui/src/components/Card.tsx index b826f6f..9efc267 100644 --- a/apps/web/src/components/ui/Card.tsx +++ b/packages/ui/src/components/Card.tsx @@ -1,6 +1,7 @@ import { forwardRef } from "react"; import { cva, type VariantProps } from "class-variance-authority"; -import { cn } from "@/lib/utils"; + +import { cn } from "../lib/cn.js"; /** * Card — surface standardisée. diff --git a/apps/web/src/components/ui/Chip.tsx b/packages/ui/src/components/Chip.tsx similarity index 97% rename from apps/web/src/components/ui/Chip.tsx rename to packages/ui/src/components/Chip.tsx index 937543b..d0ea69e 100644 --- a/apps/web/src/components/ui/Chip.tsx +++ b/packages/ui/src/components/Chip.tsx @@ -1,6 +1,7 @@ import { forwardRef } from "react"; import { Check } from "lucide-react"; -import { cn } from "@/lib/utils"; + +import { cn } from "../lib/cn.js"; /** * Chip — pastille sélectionnable. Utilisée pour : diff --git a/apps/web/src/components/ui/EmptyState.tsx b/packages/ui/src/components/EmptyState.tsx similarity index 94% rename from apps/web/src/components/ui/EmptyState.tsx rename to packages/ui/src/components/EmptyState.tsx index 43de8dd..ae0dd15 100644 --- a/apps/web/src/components/ui/EmptyState.tsx +++ b/packages/ui/src/components/EmptyState.tsx @@ -1,5 +1,5 @@ -import { Card } from "./Card"; -import { cn } from "@/lib/utils"; +import { cn } from "../lib/cn.js"; +import { Card } from "./Card.js"; /** * EmptyState — message centré pour pages vides ou écrans en attente. diff --git a/apps/web/src/components/ui/Eyebrow.tsx b/packages/ui/src/components/Eyebrow.tsx similarity index 96% rename from apps/web/src/components/ui/Eyebrow.tsx rename to packages/ui/src/components/Eyebrow.tsx index 487aee6..09c0806 100644 --- a/apps/web/src/components/ui/Eyebrow.tsx +++ b/packages/ui/src/components/Eyebrow.tsx @@ -1,4 +1,4 @@ -import { cn } from "@/lib/utils"; +import { cn } from "../lib/cn.js"; /** * Eyebrow — petit label majuscule rubis avec ◆ géométrique en préfixe. diff --git a/apps/web/src/components/brand/Gem.tsx b/packages/ui/src/components/Gem.tsx similarity index 98% rename from apps/web/src/components/brand/Gem.tsx rename to packages/ui/src/components/Gem.tsx index 34a310d..b1f5632 100644 --- a/apps/web/src/components/brand/Gem.tsx +++ b/packages/ui/src/components/Gem.tsx @@ -1,4 +1,4 @@ -import { cn } from "@/lib/utils"; +import { cn } from "../lib/cn.js"; /** * Le ◆ — gem facetté, signature de la marque. diff --git a/packages/ui/src/index.ts b/packages/ui/src/index.ts new file mode 100644 index 0000000..edae7af --- /dev/null +++ b/packages/ui/src/index.ts @@ -0,0 +1,19 @@ +/** + * @rubis/ui — design system partagé. + * + * Convention : exports nommés uniquement, pas de default export. + * Un consommateur fait `import { Button, Card } from "@rubis/ui"`. + * + * Tokens CSS et base layer importables séparément : + * import "@rubis/ui/styles/tokens.css"; + * import "@rubis/ui/styles/base.css"; + */ +export { cn } from "./lib/cn.js"; + +export { Brand } from "./components/Brand.js"; +export { Button, buttonVariants, type ButtonProps } from "./components/Button.js"; +export { Card, type CardProps } from "./components/Card.js"; +export { Chip, type ChipProps } from "./components/Chip.js"; +export { EmptyState } from "./components/EmptyState.js"; +export { Eyebrow } from "./components/Eyebrow.js"; +export { Gem } from "./components/Gem.js"; diff --git a/packages/ui/src/lib/cn.ts b/packages/ui/src/lib/cn.ts new file mode 100644 index 0000000..c5b4395 --- /dev/null +++ b/packages/ui/src/lib/cn.ts @@ -0,0 +1,10 @@ +import { clsx, type ClassValue } from "clsx"; +import { twMerge } from "tailwind-merge"; + +/** + * Combine et résout les conflits de classes Tailwind. + * Utilisation : `
` + */ +export function cn(...inputs: ClassValue[]): string { + return twMerge(clsx(inputs)); +} diff --git a/packages/ui/src/styles/base.css b/packages/ui/src/styles/base.css new file mode 100644 index 0000000..7d6d61e --- /dev/null +++ b/packages/ui/src/styles/base.css @@ -0,0 +1,103 @@ +/** + * Base layer Rubis — règles globales partagées (body styles, focus rings, + * sélection, utilities transverses comme `eyebrow`). + * + * À importer APRÈS `tokens.css` et `tailwindcss`. L'app consommatrice est + * libre de surcharger. + * + * NOTE : les @fontsource imports JS sont gérés côté app consommatrice + * (apps/web → import direct depuis main.tsx, apps/landing → idem). Ce + * fichier suppose que les polices sont déjà chargées. + */ +@layer base { + html { + -webkit-text-size-adjust: 100%; + text-rendering: optimizeLegibility; + } + + body { + background: var(--color-cream); + color: var(--color-ink); + font-family: var(--font-sans); + font-feature-settings: "ss01", "cv11"; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + line-height: 1.55; + } + + ::selection { + background: var(--color-rubis); + color: white; + } + + fieldset { + border: 0; + padding: 0; + margin: 0; + } + + button:not(:disabled), + [role="button"]:not([aria-disabled="true"]), + summary, + label[for], + a[href], + select:not(:disabled), + [type="checkbox"]:not(:disabled), + [type="radio"]:not(:disabled), + [type="submit"]:not(:disabled), + [type="button"]:not(:disabled), + [type="reset"]:not(:disabled) { + cursor: pointer; + } + + button:disabled, + [role="button"][aria-disabled="true"] { + cursor: not-allowed; + } + + .tabular-nums { + font-variant-numeric: tabular-nums; + } + + em { + font-style: italic; + color: var(--color-rubis); + } +} + +@utility eyebrow { + font-family: var(--font-sans); + font-size: 11px; + font-weight: 600; + letter-spacing: 0.14em; + color: var(--color-rubis); + text-transform: uppercase; + display: inline-flex; + align-items: center; + gap: 8px; + line-height: 1.4; +} + +@utility eyebrow-mark { + width: 7px; + height: 7px; + background: currentColor; + display: inline-block; + transform: rotate(45deg); +} + +@utility shadow-rubis { + box-shadow: var(--shadow-rubis); +} + +@utility shadow-rubis-hover { + box-shadow: var(--shadow-rubis-hover); +} + +@utility shadow-card { + box-shadow: var(--shadow-card); +} + +@utility shadow-soft { + box-shadow: var(--shadow-soft); +} diff --git a/packages/ui/src/styles/tokens.css b/packages/ui/src/styles/tokens.css new file mode 100644 index 0000000..195ad9b --- /dev/null +++ b/packages/ui/src/styles/tokens.css @@ -0,0 +1,42 @@ +/** + * Design tokens Rubis — source unique de vérité. + * + * Importé par : + * - apps/web/src/styles/app.css + * - apps/landing/... (à venir) + * + * Tailwind v4 lit les `@theme` et génère automatiquement les classes + * dérivées (text-rubis, bg-cream, font-display, rounded-card, etc.). + * + * Ne PAS dupliquer ces tokens ailleurs : si tu ajoutes une couleur ou + * une font, c'est ici que ça se passe. + */ +@theme { + --color-rubis: #9f1239; + --color-rubis-deep: #771328; + --color-rubis-light: #c9415c; + --color-rubis-glow: #fbe4ea; + + --color-cream: #faf7f2; + --color-cream-2: #f5efe7; + --color-line: #e8e0d6; + --color-ink: #1a1410; + --color-ink-2: #4f4640; + --color-ink-3: #8a7f76; + + --font-display: "Bricolage Grotesque Variable", "Bricolage Grotesque", -apple-system, + BlinkMacSystemFont, "Segoe UI", sans-serif; + --font-sans: "Inter Variable", "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", + sans-serif; + + --radius-sharp: 4px; + --radius-default: 6px; + --radius-soft: 10px; + --radius-card: 14px; + + --shadow-rubis: 0 2px 8px rgba(159, 18, 57, 0.25); + --shadow-rubis-hover: 0 6px 16px rgba(159, 18, 57, 0.35); + --shadow-card: + 0 16px 40px -16px rgba(26, 20, 16, 0.18), 0 4px 8px -2px rgba(26, 20, 16, 0.06); + --shadow-soft: 0 4px 16px rgba(26, 20, 16, 0.04); +} diff --git a/packages/ui/tsconfig.json b/packages/ui/tsconfig.json new file mode 100644 index 0000000..3a279e1 --- /dev/null +++ b/packages/ui/tsconfig.json @@ -0,0 +1,13 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "outDir": "./dist", + "rootDir": "./src", + "noEmit": true, + "jsx": "react-jsx", + "lib": ["ES2022", "DOM", "DOM.Iterable"], + "types": ["react", "react-dom"] + }, + "include": ["src"], + "exclude": ["node_modules", "dist"] +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 56f092a..3e8460d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -110,12 +110,18 @@ importers: luxon: specifier: ^3.7.2 version: 3.7.2 + marked: + specifier: ^15.0.12 + version: 15.0.12 pg: specifier: ^8.20.0 version: 8.20.0 react: specifier: ^19.2.5 version: 19.2.5 + react-dom: + specifier: ^19.2.5 + version: 19.2.5(react@19.2.5) reflect-metadata: specifier: ^0.2.2 version: 0.2.2 @@ -159,6 +165,9 @@ importers: '@types/react': specifier: ^19.2.14 version: 19.2.14 + '@types/react-dom': + specifier: ^19.2.3 + version: 19.2.3(@types/react@19.2.14) eslint: specifier: ^10.2.0 version: 10.3.0(jiti@2.7.0) @@ -181,6 +190,58 @@ importers: specifier: ^4.1.1 version: 4.1.1 + apps/landing: + dependencies: + '@astrojs/check': + specifier: ^0.9.4 + version: 0.9.9(prettier@3.8.3)(typescript@6.0.3) + '@astrojs/node': + specifier: ^10.1.0 + version: 10.1.0(astro@6.3.1(@types/node@25.6.0)(ioredis@5.10.1)(jiti@2.7.0)(lightningcss@1.32.0)(rollup@4.60.3)(tsx@4.21.0)(yaml@2.8.4)) + '@astrojs/react': + specifier: ^5.0.4 + version: 5.0.4(@types/node@25.6.0)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(jiti@2.7.0)(lightningcss@1.32.0)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(tsx@4.21.0)(yaml@2.8.4) + '@fontsource-variable/bricolage-grotesque': + specifier: ^5.2.5 + version: 5.2.10 + '@fontsource-variable/inter': + specifier: ^5.2.5 + version: 5.2.8 + '@rubis/shared': + specifier: workspace:* + version: link:../../packages/shared + '@rubis/ui': + specifier: workspace:* + version: link:../../packages/ui + '@tailwindcss/vite': + specifier: ^4.1.0 + version: 4.2.4(vite@7.3.2(@types/node@25.6.0)(jiti@2.7.0)(lightningcss@1.32.0)(tsx@4.21.0)(yaml@2.8.4)) + astro: + specifier: ^6.3.1 + version: 6.3.1(@types/node@25.6.0)(ioredis@5.10.1)(jiti@2.7.0)(lightningcss@1.32.0)(rollup@4.60.3)(tsx@4.21.0)(yaml@2.8.4) + lucide-react: + specifier: ^0.475.0 + version: 0.475.0(react@19.2.5) + react: + specifier: ^19.2.5 + version: 19.2.5 + react-dom: + specifier: ^19.2.5 + version: 19.2.5(react@19.2.5) + tailwindcss: + specifier: ^4.1.0 + version: 4.2.4 + devDependencies: + '@types/react': + specifier: ^19.2.14 + version: 19.2.14 + '@types/react-dom': + specifier: ^19.2.3 + version: 19.2.3(@types/react@19.2.14) + typescript: + specifier: ~6.0.2 + version: 6.0.3 + apps/web: dependencies: '@fontsource-variable/bricolage-grotesque': @@ -210,6 +271,9 @@ importers: '@rubis/shared': specifier: workspace:* version: link:../../packages/shared + '@rubis/ui': + specifier: workspace:* + version: link:../../packages/ui '@sentry/react': specifier: ^10.52.0 version: 10.52.0(react@19.2.5) @@ -330,7 +394,7 @@ importers: version: 8.0.10(@types/node@24.12.2)(esbuild@0.27.7)(jiti@2.7.0)(tsx@4.21.0)(yaml@2.8.4) vitest: specifier: ^3.0.5 - version: 3.2.4(@types/node@24.12.2)(jiti@2.7.0)(jsdom@26.1.0)(lightningcss@1.32.0)(msw@2.14.3(@types/node@24.12.2)(typescript@6.0.3))(tsx@4.21.0)(yaml@2.8.4) + version: 3.2.4(@types/debug@4.1.13)(@types/node@24.12.2)(jiti@2.7.0)(jsdom@26.1.0)(lightningcss@1.32.0)(msw@2.14.3(@types/node@24.12.2)(typescript@6.0.3))(tsx@4.21.0)(yaml@2.8.4) packages/shared: dependencies: @@ -342,6 +406,40 @@ importers: specifier: ^5.7.3 version: 5.9.3 + packages/ui: + dependencies: + '@radix-ui/react-slot': + specifier: ^1.1.2 + version: 1.2.4(@types/react@19.2.14)(react@19.2.5) + class-variance-authority: + specifier: ^0.7.1 + version: 0.7.1 + clsx: + specifier: ^2.1.1 + version: 2.1.1 + lucide-react: + specifier: '>=0.475.0' + version: 0.475.0(react@19.2.5) + react: + specifier: ^19.0.0 + version: 19.2.5 + react-dom: + specifier: ^19.0.0 + version: 19.2.5(react@19.2.5) + tailwind-merge: + specifier: ^3.0.1 + version: 3.5.0 + devDependencies: + '@types/react': + specifier: ^19.2.14 + version: 19.2.14 + '@types/react-dom': + specifier: ^19.2.3 + version: 19.2.3(@types/react@19.2.14) + typescript: + specifier: ~6.0.2 + version: 6.0.3 + packages: '@adobe/css-tools@4.4.4': @@ -783,6 +881,61 @@ packages: resolution: {integrity: sha512-+YEv9ElJi9azr8AYII79NxYXQRJsrUy1kUqZfxZfvPM7rhs3174mzB+qEE9Pl3sVKAJS5cevyT4lgLNV0AZK6A==} engines: {node: '>= 10'} + '@astrojs/check@0.9.9': + resolution: {integrity: sha512-A5UW8uIuErLWEoRQvzgXpO1gTjUFtK8r7nU2Z7GewAMxUb7bPvpk11qaKKgxqXlHJWlAvaaxy+Xg28A6bmQ1Tg==} + hasBin: true + peerDependencies: + typescript: ^5.0.0 || ^6.0.0 + + '@astrojs/compiler@2.13.1': + resolution: {integrity: sha512-f3FN83d2G/v32ipNClRKgYv30onQlMZX1vCeZMjPsMMPl1mDpmbl0+N5BYo4S/ofzqJyS5hvwacEo0CCVDn/Qg==} + + '@astrojs/compiler@4.0.0': + resolution: {integrity: sha512-eouss7G8ygdZqHuke033VMcVw5HTZUu+PXd/h06DGDUg/jt5btPYPqh66ENWw/mU78rBrf/oeC4oqoBwMtDMNA==} + + '@astrojs/internal-helpers@0.9.0': + resolution: {integrity: sha512-GdYkzR26re8izmyYlBqf4z2s7zNngmWLFuxw0UKiPNqHraZGS6GKWIwSHgS22RDlu2ePFJ8bzmpBcUszut/SDg==} + + '@astrojs/language-server@2.16.8': + resolution: {integrity: sha512-yg1pZF6hs9FaKr2fgXMOGbW7pDLgFexFjuhWilPAc8VybTU+WSnbfbhYaUL1exm6dAK4sM3aKXGcfVwss+HXbg==} + hasBin: true + peerDependencies: + prettier: ^3.0.0 + prettier-plugin-astro: '>=0.11.0' + peerDependenciesMeta: + prettier: + optional: true + prettier-plugin-astro: + optional: true + + '@astrojs/markdown-remark@7.1.1': + resolution: {integrity: sha512-C6e9BnLGlbdv6bV8MYGeHpHxsUHrCrB4OuRLqi5LI7oiBVcBcqfUN06zpwFQdHgV48QCCrMmLpyqBr7VqC+swA==} + + '@astrojs/node@10.1.0': + resolution: {integrity: sha512-4/2oqUTQ71UQ8+xX249T4l/d0/YkC5ssOVl4R2yQO7Wg4mOnvsq9Z9iaTkWAyElg3lqZq7XRNCEXCmDNiYcW1A==} + peerDependencies: + astro: ^6.3.0 + + '@astrojs/prism@4.0.1': + resolution: {integrity: sha512-nksZQVjlferuWzhPsBpQ1JE5XuKAf1id1/9Hj4a9KG4+ofrlzxUUwX4YGQF/SuDiuiGKEnzopGOt38F3AnVWsQ==} + engines: {node: '>=22.12.0'} + + '@astrojs/react@5.0.4': + resolution: {integrity: sha512-yDNE4VnKOzCjH9dCBi7pT4F6kpI3M9TkS+uxnCB0sGIS6t5vKonOY+Hs/UUnSajJGT5jeBRfpI9IQp+r/n1fBA==} + 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 + + '@astrojs/telemetry@3.3.2': + resolution: {integrity: sha512-j8DNruA8ors99Al39RYZPJK4DC1bKkoNm93mAMuBhY9TCNC4R8n1q7ovFnJ5qhGh5Lsh7pa1gpQVpYpsJPeTHQ==} + engines: {node: 18.20.8 || ^20.3.0 || >=22.0.0} + + '@astrojs/yaml2ts@0.2.3': + resolution: {integrity: sha512-PJzRmgQzUxI2uwpdX2lXSHtP4G8ocp24/t+bZyf5Fy0SZLSF9f9KXZoMlFM/XCGue+B0nH/2IZ7FpBYQATBsCg==} + '@aws-crypto/crc32@5.2.0': resolution: {integrity: sha512-nLbCWqQNgUiwwtFsen1AdzAtvuLRsQS8rYgMuxCrdKf9kOssamGLuPwyTY9wyYblNr9+1XM8v6zoDTPPSIeANg==} engines: {node: '>=16.0.0'} @@ -1021,6 +1174,18 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 + '@babel/plugin-transform-react-jsx-self@7.27.1': + resolution: {integrity: sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-react-jsx-source@7.27.1': + resolution: {integrity: sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + '@babel/runtime@7.29.2': resolution: {integrity: sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==} engines: {node: '>=6.9.0'} @@ -1044,6 +1209,10 @@ packages: resolution: {integrity: sha512-wGGOE7ywA4W6KAVoVC7s1P4ULzFLIQA/JvthGAa41EA0CaH7kGGawkBB5t5tvWopgBNMhOpIg3uxvULxqf2rQw==} engines: {node: '>=20.6'} + '@capsizecss/unpack@4.0.0': + resolution: {integrity: sha512-VERIM64vtTP1C4mxQ5thVT9fK0apjPFobqybMtA1UdUujWka24ERHbRHFGmpbbhp73MhV+KSsHQH9C6uOTdEQA==} + engines: {node: '>=18'} + '@chevrotain/cst-dts-gen@11.2.0': resolution: {integrity: sha512-ssJFvn/UXhQQeICw3SR/fZPmYVj+JM2mP+Lx7bZ51cOeHaMWOKp3AUMuyM3QR82aFFXTfcAp67P5GpPjGmbZWQ==} @@ -1059,6 +1228,14 @@ packages: '@chevrotain/utils@11.2.0': resolution: {integrity: sha512-+7whECg4yNWHottjvr2To2BRxL4XJVjIyyv5J4+bJ0iMOVU8j/8n1qPDLZS/90W/BObDR8VNL46lFbzY/Hosmw==} + '@clack/core@1.3.0': + resolution: {integrity: sha512-xJPHpAmEQUBrXSLx0gF+q5K/IyihXpsHZcha+jB+tyahsKRK3Dxo4D0coZDewHo12NhiuzC3dTtMPbm53GEAAA==} + engines: {node: '>= 20.12.0'} + + '@clack/prompts@1.3.0': + resolution: {integrity: sha512-GgcWwRCs/xPtaqlMy8qRhPnZf9vlWcWZNHAitnVQ3yk7JmSralSiq5q07yaffYE8SogtDm7zFeKccx1QNVARpw==} + engines: {node: '>= 20.12.0'} + '@colors/colors@1.5.0': resolution: {integrity: sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==} engines: {node: '>=0.1.90'} @@ -1091,6 +1268,27 @@ packages: resolution: {integrity: sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==} engines: {node: '>=18'} + '@emmetio/abbreviation@2.3.3': + resolution: {integrity: sha512-mgv58UrU3rh4YgbE/TzgLQwJ3pFsHHhCLqY20aJq+9comytTXUDNGG/SMtSeMJdkpxgXSXunBGLD8Boka3JyVA==} + + '@emmetio/css-abbreviation@2.1.8': + resolution: {integrity: sha512-s9yjhJ6saOO/uk1V74eifykk2CBYi01STTK3WlXWGOepyKa23ymJ053+DNQjpFcy1ingpaO7AxCcwLvHFY9tuw==} + + '@emmetio/css-parser@0.4.1': + resolution: {integrity: sha512-2bC6m0MV/voF4CTZiAbG5MWKbq5EBmDPKu9Sb7s7nVcEzNQlrZP6mFFFlIaISM8X6514H9shWMme1fCm8cWAfQ==} + + '@emmetio/html-matcher@1.3.0': + resolution: {integrity: sha512-NTbsvppE5eVyBMuyGfVu2CRrLvo7J4YHb6t9sBFLyY03WYhXET37qA4zOYUjBWFCRHO7pS1B9khERtY0f5JXPQ==} + + '@emmetio/scanner@1.0.4': + resolution: {integrity: sha512-IqRuJtQff7YHHBk4G8YZ45uB9BaAGcwQeVzgj/zj8/UdOhtQpEIupUhSk8dys6spFIWVZVeK20CzGEnqR5SbqA==} + + '@emmetio/stream-reader-utils@0.1.0': + resolution: {integrity: sha512-ZsZ2I9Vzso3Ho/pjZFsmmZ++FWeEd/txqybHTm4OgaZzdS8V9V/YYWQwg5TC38Z7uLWUV1vavpLLbjJtKubR1A==} + + '@emmetio/stream-reader@2.2.0': + resolution: {integrity: sha512-fXVXEyFA5Yv3M3n8sUGT7+fvecGrZP4k6FnWWMSZVQf69kAq0LLpaBQLGcPR30m3zMmKYhECP4k/ZkzvhEW5kw==} + '@emnapi/core@1.10.0': resolution: {integrity: sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==} @@ -1373,6 +1571,143 @@ packages: resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==} engines: {node: '>=18.18'} + '@img/colour@1.1.0': + resolution: {integrity: sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==} + engines: {node: '>=18'} + + '@img/sharp-darwin-arm64@0.34.5': + resolution: {integrity: sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [darwin] + + '@img/sharp-darwin-x64@0.34.5': + resolution: {integrity: sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [darwin] + + '@img/sharp-libvips-darwin-arm64@1.2.4': + resolution: {integrity: sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==} + cpu: [arm64] + os: [darwin] + + '@img/sharp-libvips-darwin-x64@1.2.4': + resolution: {integrity: sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==} + cpu: [x64] + os: [darwin] + + '@img/sharp-libvips-linux-arm64@1.2.4': + resolution: {integrity: sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==} + cpu: [arm64] + os: [linux] + + '@img/sharp-libvips-linux-arm@1.2.4': + resolution: {integrity: sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==} + cpu: [arm] + os: [linux] + + '@img/sharp-libvips-linux-ppc64@1.2.4': + resolution: {integrity: sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==} + cpu: [ppc64] + os: [linux] + + '@img/sharp-libvips-linux-riscv64@1.2.4': + resolution: {integrity: sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==} + cpu: [riscv64] + os: [linux] + + '@img/sharp-libvips-linux-s390x@1.2.4': + resolution: {integrity: sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==} + cpu: [s390x] + os: [linux] + + '@img/sharp-libvips-linux-x64@1.2.4': + resolution: {integrity: sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==} + cpu: [x64] + os: [linux] + + '@img/sharp-libvips-linuxmusl-arm64@1.2.4': + resolution: {integrity: sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==} + cpu: [arm64] + os: [linux] + + '@img/sharp-libvips-linuxmusl-x64@1.2.4': + resolution: {integrity: sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==} + cpu: [x64] + os: [linux] + + '@img/sharp-linux-arm64@0.34.5': + resolution: {integrity: sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [linux] + + '@img/sharp-linux-arm@0.34.5': + resolution: {integrity: sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm] + os: [linux] + + '@img/sharp-linux-ppc64@0.34.5': + resolution: {integrity: sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [ppc64] + os: [linux] + + '@img/sharp-linux-riscv64@0.34.5': + resolution: {integrity: sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [riscv64] + os: [linux] + + '@img/sharp-linux-s390x@0.34.5': + resolution: {integrity: sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [s390x] + os: [linux] + + '@img/sharp-linux-x64@0.34.5': + resolution: {integrity: sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [linux] + + '@img/sharp-linuxmusl-arm64@0.34.5': + resolution: {integrity: sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [linux] + + '@img/sharp-linuxmusl-x64@0.34.5': + resolution: {integrity: sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [linux] + + '@img/sharp-wasm32@0.34.5': + resolution: {integrity: sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [wasm32] + + '@img/sharp-win32-arm64@0.34.5': + resolution: {integrity: sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [win32] + + '@img/sharp-win32-ia32@0.34.5': + resolution: {integrity: sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [ia32] + os: [win32] + + '@img/sharp-win32-x64@0.34.5': + resolution: {integrity: sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [win32] + '@inquirer/ansi@2.0.5': resolution: {integrity: sha512-doc2sWgJpbFQ64UflSVd17ibMGDuxO1yKgOgLMwavzESnXjFWJqUeG8saYosqKpHp4kWiM5x1nXvEjbpx90gzw==} engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} @@ -1738,6 +2073,9 @@ packages: peerDependencies: '@opentelemetry/api': ^1.1.0 + '@oslojs/encoding@1.1.0': + resolution: {integrity: sha512-70wQhgYmndg4GCPxPPxPGevRKqTIJ2Nh4OkiMWmDAVYsTQ+Ta7Sq+rPevXyXGdzr30/qZBnyOalCszoMxlyldQ==} + '@oxc-project/types@0.127.0': resolution: {integrity: sha512-aIYXQBo4lCbO4z0R3FHeucQHpF46l2LbMdxRvqvuRuW2OxdnSkcng5B8+K12spgLDj93rtN3+J2Vac/TIO+ciQ==} @@ -2565,9 +2903,21 @@ packages: '@rolldown/pluginutils@1.0.0-rc.17': resolution: {integrity: sha512-n8iosDOt6Ig1UhJ2AYqoIhHWh/isz0xpicHTzpKBeotdVsTEcxsSA/i3EVM7gQAj0rU27OLAxCjzlj15IWY7bg==} + '@rolldown/pluginutils@1.0.0-rc.3': + resolution: {integrity: sha512-eybk3TjzzzV97Dlj5c+XrBFW57eTNhzod66y9HrBlzJ6NsCrWCp/2kaPS3K9wJmurBC0Tdw4yPjXKZqlznim3Q==} + '@rolldown/pluginutils@1.0.0-rc.7': resolution: {integrity: sha512-qujRfC8sFVInYSPPMLQByRh7zhwkGFS4+tyMQ83srV1qrxL4g8E2tyxVVyxd0+8QeBM1mIk9KbWxkegRr76XzA==} + '@rollup/pluginutils@5.3.0': + resolution: {integrity: sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q==} + engines: {node: '>=14.0.0'} + peerDependencies: + rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0 + peerDependenciesMeta: + rollup: + optional: true + '@rollup/rollup-android-arm-eabi@4.60.3': resolution: {integrity: sha512-x35CNW/ANXG3hE/EZpRU8MXX1JDN86hBb2wMGAtltkz7pc6cxgjpy1OMMfDosOQ+2hWqIkag/fGok1Yady9nGw==} cpu: [arm] @@ -2848,6 +3198,37 @@ packages: resolution: {integrity: sha512-sSQzOhN8xvo/R70vqgyonnC/fwXpJ1kbkJ0g81Xy/OR+N89+v5tPN4VlKTAq3T2c5yAPE39XCbdgeEnI4kbWGg==} engines: {node: '>= 18'} + '@shikijs/core@4.0.2': + resolution: {integrity: sha512-hxT0YF4ExEqB8G/qFdtJvpmHXBYJ2lWW7qTHDarVkIudPFE6iCIrqdgWxGn5s+ppkGXI0aEGlibI0PAyzP3zlw==} + engines: {node: '>=20'} + + '@shikijs/engine-javascript@4.0.2': + resolution: {integrity: sha512-7PW0Nm49DcoUIQEXlJhNNBHyoGMjalRETTCcjMqEaMoJRLljy1Bi/EGV3/qLBgLKQejdspiiYuHGQW6dX94Nag==} + engines: {node: '>=20'} + + '@shikijs/engine-oniguruma@4.0.2': + resolution: {integrity: sha512-UpCB9Y2sUKlS9z8juFSKz7ZtysmeXCgnRF0dlhXBkmQnek7lAToPte8DkxmEYGNTMii72zU/lyXiCB6StuZeJg==} + engines: {node: '>=20'} + + '@shikijs/langs@4.0.2': + resolution: {integrity: sha512-KaXby5dvoeuZzN0rYQiPMjFoUrz4hgwIE+D6Du9owcHcl6/g16/yT5BQxSW5cGt2MZBz6Hl0YuRqf12omRfUUg==} + engines: {node: '>=20'} + + '@shikijs/primitive@4.0.2': + resolution: {integrity: sha512-M6UMPrSa3fN5ayeJwFVl9qWofl273wtK1VG8ySDZ1mQBfhCpdd8nEx7nPZ/tk7k+TYcpqBZzj/AnwxT9lO+HJw==} + engines: {node: '>=20'} + + '@shikijs/themes@4.0.2': + resolution: {integrity: sha512-mjCafwt8lJJaVSsQvNVrJumbnnj1RI8jbUKrPKgE6E3OvQKxnuRoBaYC51H4IGHePsGN/QtALglWBU7DoKDFnA==} + engines: {node: '>=20'} + + '@shikijs/types@4.0.2': + resolution: {integrity: sha512-qzbeRooUTPnLE+sHD/Z8DStmaDgnbbc/pMrU203950aRqjX/6AFHeDYT+j00y2lPdz0ywJKx7o/7qnqTivtlXg==} + engines: {node: '>=20'} + + '@shikijs/vscode-textmate@10.0.2': + resolution: {integrity: sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==} + '@sinclair/typebox@0.34.49': resolution: {integrity: sha512-brySQQs7Jtn0joV8Xh9ZV/hZb9Ozb0pmazDIASBkYKCjXrXU3mpcFahmK/z4YDhGkQvP9mWJbVyahdtU5wQA+A==} @@ -3480,6 +3861,18 @@ packages: '@types/aria-query@5.0.4': resolution: {integrity: sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==} + '@types/babel__core@7.20.5': + resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} + + '@types/babel__generator@7.27.0': + resolution: {integrity: sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==} + + '@types/babel__template@7.4.4': + resolution: {integrity: sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==} + + '@types/babel__traverse@7.28.0': + resolution: {integrity: sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==} + '@types/chai@5.2.3': resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==} @@ -3516,6 +3909,9 @@ packages: '@types/d3-timer@3.0.2': resolution: {integrity: sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==} + '@types/debug@4.1.13': + resolution: {integrity: sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw==} + '@types/deep-eql@4.0.2': resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==} @@ -3525,6 +3921,9 @@ packages: '@types/estree@1.0.8': resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} + '@types/hast@3.0.4': + resolution: {integrity: sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==} + '@types/he@1.2.3': resolution: {integrity: sha512-q67/qwlxblDzEDvzHhVkwc1gzVWxaNxeyHUBF4xElrvjL11O+Ytze+1fGpBHlr/H9myiBUaUXNnNPmBHxxfAcA==} @@ -3534,12 +3933,21 @@ packages: '@types/luxon@3.7.1': resolution: {integrity: sha512-H3iskjFIAn5SlJU7OuxUmTEpebK6TKB8rxZShDslBMZJ5u9S//KM1sbdAisiSrqwLQncVjnpi2OK2J51h+4lsg==} + '@types/mdast@4.0.4': + resolution: {integrity: sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==} + '@types/methods@1.1.4': resolution: {integrity: sha512-ymXWVrDiCxTBE3+RIrrP533E70eA+9qu7zdWoHuOmGujkYtzf4HQF96b8nwHLqhuf4ykX61IGRIB38CC6/sImQ==} + '@types/ms@2.1.0': + resolution: {integrity: sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==} + '@types/mysql@2.15.27': resolution: {integrity: sha512-YfWiV16IY0OeBfBCk8+hXKmdTKrKlwKN1MNKAPBu5JYxLwBEZl7QzeEpGnlZb3VMGJrrGmB84gXiH+ofs/TezA==} + '@types/nlcst@2.0.3': + resolution: {integrity: sha512-vSYNSDe6Ix3q+6Z7ri9lyWqgGhJTmzRjZRqyq15N0Z/1/UnVsno9G/N40NBijoYx2seFDIl0+B2mgAb9mezUCA==} + '@types/node@22.19.17': resolution: {integrity: sha512-wGdMcf+vPYM6jikpS/qhg6WiqSV/OhG+jeeHT/KlVqxYfD40iYJf9/AE1uQxVWFvU7MipKRkRv8NSHiCGgPr8Q==} @@ -3587,6 +3995,9 @@ packages: '@types/tedious@4.0.14': resolution: {integrity: sha512-KHPsfX/FoVbUGbyYvk1q9MMQHLPeRZhRJZdO45Q4YjvFkv4hMNghCWTvy7rdKessBsmtz4euWCWAB6/tVpI1Iw==} + '@types/unist@3.0.3': + resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==} + '@types/use-sync-external-store@0.0.6': resolution: {integrity: sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==} @@ -3652,6 +4063,9 @@ packages: resolution: {integrity: sha512-NwjLUnGy8/Zfx23fl50tRC8rYaYnM52xNRYFAXvmiil9yh1+K6aRVQMnzW6gQB/1DLgWt977lYQn7C+wtgXZiA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@ungap/structured-clone@1.3.1': + resolution: {integrity: sha512-mUFwbeTqrVgDQxFveS+df2yfap6iuP20NAKAsBt5jDEoOTDew+zwLAOilHCeQJOVSvmgCX4ogqIrA0mnyr08yQ==} + '@vinejs/compiler@4.1.3': resolution: {integrity: sha512-UyH7Zn8dkTMLeU+PF2WjCnWkFb2qYaOxAcvp/uXW0njtKNcJOnVJaPsnWYwqewkTcHN47yvOdzosj3kj3RAP5w==} engines: {node: '>=18.0.0'} @@ -3660,6 +4074,12 @@ packages: resolution: {integrity: sha512-cfnNXjs9+f+22d3Eb8koyg2qlCbaT394XBlL4AeEr1WM+NH1omFcXwe1zrkW4hHwPDSfUd4lFUdNO6E+YnTeWQ==} engines: {node: '>=18.16.0'} + '@vitejs/plugin-react@5.2.0': + resolution: {integrity: sha512-YmKkfhOAi3wsB1PhJq5Scj3GXMn3WvtQ/JC0xoopuHoXSdmtdStOpFrYaT1kie2YgFBcIe64ROzMYRjCrYOdYw==} + 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 + '@vitejs/plugin-react@6.0.1': resolution: {integrity: sha512-l9X/E3cDb+xY3SWzlG1MOGt2usfEHGMNIaegaUGFsLkb3RCn/k8/TOXBcab+OndDI4TBtktT8/9BwwW8Vi9KUQ==} engines: {node: ^20.19.0 || >=22.12.0} @@ -3702,6 +4122,32 @@ packages: '@vitest/utils@3.2.4': resolution: {integrity: sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==} + '@volar/kit@2.4.28': + resolution: {integrity: sha512-cKX4vK9dtZvDRaAzeoUdaAJEew6IdxHNCRrdp5Kvcl6zZOqb6jTOfk3kXkIkG3T7oTFXguEMt5+9ptyqYR84Pg==} + peerDependencies: + typescript: '*' + + '@volar/language-core@2.4.28': + resolution: {integrity: sha512-w4qhIJ8ZSitgLAkVay6AbcnC7gP3glYM3fYwKV3srj8m494E3xtrCv6E+bWviiK/8hs6e6t1ij1s2Endql7vzQ==} + + '@volar/language-server@2.4.28': + resolution: {integrity: sha512-NqcLnE5gERKuS4PUFwlhMxf6vqYo7hXtbMFbViXcbVkbZ905AIVWhnSo0ZNBC2V127H1/2zP7RvVOVnyITFfBw==} + + '@volar/language-service@2.4.28': + resolution: {integrity: sha512-Rh/wYCZJrI5vCwMk9xyw/Z+MsWxlJY1rmMZPsxUoJKfzIRjS/NF1NmnuEcrMbEVGja00aVpCsInJfixQTMdvLw==} + + '@volar/source-map@2.4.28': + resolution: {integrity: sha512-yX2BDBqJkRXfKw8my8VarTyjv48QwxdJtvRgUpNE5erCsgEUdI2DsLbpa+rOQVAJYshY99szEcRDmyHbF10ggQ==} + + '@volar/typescript@2.4.28': + resolution: {integrity: sha512-Ja6yvWrbis2QtN4ClAKreeUZPVYMARDYZl9LMEv1iQ1QdepB6wn0jTRxA9MftYmYa4DQ4k/DaSZpFPUfxl8giw==} + + '@vscode/emmet-helper@2.11.0': + resolution: {integrity: sha512-QLxjQR3imPZPQltfbWRnHU6JecWTF1QSWhx3GAKQpslx7y3Dp6sIIXhKjiUJ/BR9FX8PVthjr9PD6pNwOJfAzw==} + + '@vscode/l10n@0.0.18': + resolution: {integrity: sha512-KYSIHVmslkaCDyw013pphY+d7x1qV8IZupYfeIfzNA+nsaWHbn5uPuQRvdRFsa9zFzGeudPuoGoZ1Op4jrJXIQ==} + abs-svg-path@0.1.1: resolution: {integrity: sha512-d8XPSGjfyzlXC3Xx891DJRyZfqk5JU0BJrDQcsWomFIV1/BIzPW5HDH5iDdWpqWaav0YVIEzT1RHTwWr0FFshA==} @@ -3731,9 +4177,20 @@ packages: resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==} engines: {node: '>= 14'} + ajv-draft-04@1.0.0: + resolution: {integrity: sha512-mv00Te6nmYbRp5DCwclxtt7yV/joXJPGS7nM+97GdxvuttCOfgI3K4U25zboyeX0O+myI8ERluxQe5wljMmVIw==} + peerDependencies: + ajv: ^8.5.0 + peerDependenciesMeta: + ajv: + optional: true + ajv@6.15.0: resolution: {integrity: sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==} + ajv@8.20.0: + resolution: {integrity: sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==} + ansi-colors@4.1.3: resolution: {integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==} engines: {node: '>=6'} @@ -3784,6 +4241,9 @@ packages: resolution: {integrity: sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==} engines: {node: '>= 0.4'} + array-iterate@2.0.1: + resolution: {integrity: sha512-I1jXZMjAgCMmxT4qxXfPXa6SthSoE8h6gkSI9BGGNv8mP8G/v0blc+qFnZu6K42vTOiuME596QaLO0TP3Lk0xg==} + asap@2.0.6: resolution: {integrity: sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==} @@ -3791,6 +4251,11 @@ packages: resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} engines: {node: '>=12'} + astro@6.3.1: + resolution: {integrity: sha512-atz6dmkE3Gu24bDgb7g2RE/BYnKqPYIHd6hTUM1UXvu/i7qNZOKLAqEHvgYpv9PQVcgWsXpk4/OOXZ0E/FzvSQ==} + engines: {node: '>=22.12.0', npm: '>=9.6.5', pnpm: '>=7.1.0'} + hasBin: true + async-retry@1.3.3: resolution: {integrity: sha512-wfr/jstw9xNi/0teMHrRW7dsz3Lt5ARhYNZ2ewpadnhaIp5mbALhOAP+EAdsC7t4Z6wqsDVv9+W6gm1Dk9mEyw==} @@ -3801,9 +4266,16 @@ packages: resolution: {integrity: sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==} engines: {node: '>=8.0.0'} + axobject-query@4.1.0: + resolution: {integrity: sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==} + engines: {node: '>= 0.4'} + babel-dead-code-elimination@1.0.12: resolution: {integrity: sha512-GERT7L2TiYcYDtYk1IpD+ASAYXjKbLTDPhBtYj7X1NuRMDTMtAx9kyBenub1Ev41lo91OHCKdmP+egTDmfQ7Ig==} + bail@2.0.2: + resolution: {integrity: sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==} + balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} @@ -3844,6 +4316,9 @@ packages: bl@4.1.0: resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==} + boolbase@1.0.0: + resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==} + bowser@2.14.1: resolution: {integrity: sha512-tzPjzCxygAKWFOJP011oxFHs57HzIhOEracIgAePE4pqB3LikALKnSzUyU4MGs9/iCEUuHlAJTjTc5M+u7YEGg==} @@ -3915,6 +4390,9 @@ packages: resolution: {integrity: sha512-wljhAjDDIv/hM2FzgJnYQg90AWmZMNtESCjTeLH680qTzdo0nErlCxOmgzgX4ZsZAtIvqHyD87ES8QyriXB+BQ==} engines: {node: '>=18'} + ccount@2.0.1: + resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==} + chai@5.3.3: resolution: {integrity: sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==} engines: {node: '>=18'} @@ -3934,6 +4412,15 @@ packages: change-case@5.4.4: resolution: {integrity: sha512-HRQyTk2/YPEkt9TnUPbOpr64Uw3KOicFWPVBb+xiHvd6eBx/qPr9xqfBFDT8P2vWsvvz4jbEkfDe71W3VyNu2w==} + character-entities-html4@2.1.0: + resolution: {integrity: sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==} + + character-entities-legacy@3.0.0: + resolution: {integrity: sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==} + + character-entities@2.0.2: + resolution: {integrity: sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==} + check-disk-space@3.4.0: resolution: {integrity: sha512-drVkSqfwA+TvuEhFipiR1OC9boEGZL5RrWvVsOthdcvQNXyCCuKkEiTOTXZ7qxSf/GLwq4GvzfrQD/Wz325hgw==} engines: {node: '>=16'} @@ -3949,6 +4436,10 @@ packages: resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} engines: {node: '>= 8.10.0'} + chokidar@4.0.3: + resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==} + engines: {node: '>= 14.16.0'} + chokidar@5.0.0: resolution: {integrity: sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==} engines: {node: '>= 20.19.0'} @@ -4038,14 +4529,25 @@ packages: resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} engines: {node: '>= 0.8'} + comma-separated-tokens@2.0.3: + resolution: {integrity: sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==} + commander@10.0.1: resolution: {integrity: sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==} engines: {node: '>=14'} + commander@11.1.0: + resolution: {integrity: sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==} + engines: {node: '>=16'} + commander@13.1.0: resolution: {integrity: sha512-/rFeCpNJQbhSZjGVwO9RFV3xPqbnERS8MmIQzCtD/zl6gpJuV/bMLuN92oG3F7d8oDEHHRrujSXNUr8fpjntKw==} engines: {node: '>=18'} + common-ancestor-path@2.0.0: + resolution: {integrity: sha512-dnN3ibLeoRf2HNC+OlCiNc5d2zxbLJXOtiZUudNFSXZrNSydxcCsSpRzXwfu7BBWCIfHPw+xTayeBvJCP/D8Ng==} + engines: {node: '>= 18'} + common-path-prefix@3.0.0: resolution: {integrity: sha512-QE33hToZseCH3jS0qN96O/bSh3kaw/h+Tq7ngyY9eWDUnTlTNUyqfqvCXioLe5Na5jFsL78ra/wuBU4iuEgd4w==} @@ -4066,6 +4568,9 @@ packages: convert-source-map@2.0.0: resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + cookie-es@1.2.3: + resolution: {integrity: sha512-lXVyvUvrNXblMqzIRrxHb57UUVmqsSWlxqt3XIjCkUP0wDAf6uicO6KMbEgYrMNtEvWgWHwe42CKxPu9MYAnWw==} + cookie-es@2.0.1: resolution: {integrity: sha512-aVf4A4hI2w70LnF7GG+7xDQUkliwiXWXFvTjkip4+b64ygDQ2sJPRSKFDHbxn8o0xu9QzPkMuuiWIXyFSE2slA==} @@ -4090,13 +4595,35 @@ packages: resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} engines: {node: '>= 8'} + crossws@0.3.5: + resolution: {integrity: sha512-ojKiDvcmByhwa8YYqbQI/hg7MEU0NC03+pSdEq4ZUnZR9xXpwk7E43SMNGkn+JxJGPFtNvQ48+vV2p+P1ml5PA==} + csrf@3.1.0: resolution: {integrity: sha512-uTqEnCvWRk042asU6JtapDTcJeeailFy4ydOQS28bj1hcLnYRiqi8SsD2jS412AY1I/4qdOwWZun774iqywf9w==} engines: {node: '>= 0.8'} + css-select@5.2.2: + resolution: {integrity: sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==} + + css-tree@2.2.1: + resolution: {integrity: sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA==} + engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0, npm: '>=7.0.0'} + + css-tree@3.2.1: + resolution: {integrity: sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA==} + engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0} + + css-what@6.2.2: + resolution: {integrity: sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==} + engines: {node: '>= 6'} + css.escape@1.5.1: resolution: {integrity: sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==} + csso@5.0.5: + resolution: {integrity: sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ==} + engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0, npm: '>=7.0.0'} + cssstyle@4.6.0: resolution: {integrity: sha512-2z+rWdzbbSZv6/rhtvzvqeZQHrBaqgogqt85sqFNbabZOuFbCVFb8kPeEtZjiKkbrm395irpNKiYeFeLiQnFPg==} engines: {node: '>=18'} @@ -4185,6 +4712,9 @@ packages: decimal.js@10.6.0: resolution: {integrity: sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==} + decode-named-character-reference@1.3.0: + resolution: {integrity: sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q==} + decompress-response@6.0.0: resolution: {integrity: sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==} engines: {node: '>=10'} @@ -4224,6 +4754,9 @@ packages: resolution: {integrity: sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==} engines: {node: '>=12'} + defu@6.1.7: + resolution: {integrity: sha512-7z22QmUWiQ/2d0KkdYmANbRUVABpZ9SNYyH5vx6PZ+nE5bcC0l7uFvEfHlyld/HcGBFTL536ClDt3DEcSlEJAQ==} + delayed-stream@1.0.0: resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} engines: {node: '>=0.4.0'} @@ -4240,6 +4773,9 @@ packages: resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} engines: {node: '>=6'} + destr@2.0.5: + resolution: {integrity: sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==} + destroy@1.2.0: resolution: {integrity: sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==} engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} @@ -4251,6 +4787,12 @@ packages: detect-node-es@1.1.0: resolution: {integrity: sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==} + devalue@5.8.0: + resolution: {integrity: sha512-2zA9pFEsnp7vWBZbXF5JAgAq0fsUIt/1XPbRiAmRV3lp/2C3upzH+sADiyy66aFCihoLEsrQHxNM5w1gIDfsBg==} + + devlop@1.1.0: + resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==} + dezalgo@1.0.4: resolution: {integrity: sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig==} @@ -4287,6 +4829,10 @@ packages: resolution: {integrity: sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==} engines: {node: '>=12'} + dset@3.1.4: + resolution: {integrity: sha512-2QF/g9/zTaPDc3BjNcVTGoBbXBgYfMTTceLaYcFJ/W9kggFUkhxD/hMEeuLKbugyef9SqAx8cpgwlIP/jinUTA==} + engines: {node: '>=4'} + dunder-proto@1.0.1: resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} engines: {node: '>= 0.4'} @@ -4304,6 +4850,9 @@ packages: resolution: {integrity: sha512-sFz64DCRjirhwHLxofFqxYQm6DCp6o0Ix7jwKQvuCHPn4GMRZNuBZyLPu9Ccmk/QSCAMZt6FOUqA8JZCQvA9fw==} engines: {node: '>=14.16'} + emmet@2.4.11: + resolution: {integrity: sha512-23QPJB3moh/U9sT4rQzGgeyyGIrcM+GH5uVYg2C6wZIxAIJq7Ng3QLT79tl8FUwDXhyq9SusfknOrofAKqvgyQ==} + emoji-regex-xs@1.0.0: resolution: {integrity: sha512-LRlerrMYoIDrT6jgpeZ2YYl/L8EulRTt5hQcYjy5AInh7HWXKimpqx68aknBFpGL2+/IcogTcaydJEgaTmOpDg==} @@ -4354,6 +4903,9 @@ packages: es-module-lexer@1.7.0: resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==} + es-module-lexer@2.1.0: + resolution: {integrity: sha512-n27zTYMjYu1aj4MjCWzSP7G9r75utsaoc8m61weK+W8JMBGGQybd43GstCXZ3WNmSFtGT9wi59qQTW6mhTR5LQ==} + es-object-atoms@1.1.1: resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} engines: {node: '>= 0.4'} @@ -4385,6 +4937,10 @@ packages: resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} engines: {node: '>=10'} + escape-string-regexp@5.0.0: + resolution: {integrity: sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==} + engines: {node: '>=12'} + eslint-config-prettier@10.1.8: resolution: {integrity: sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==} hasBin: true @@ -4486,6 +5042,9 @@ packages: resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} engines: {node: '>=4.0'} + estree-walker@2.0.2: + resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} + estree-walker@3.0.3: resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} @@ -4520,6 +5079,9 @@ packages: resolution: {integrity: sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==} engines: {node: '>=12.0.0'} + extend@3.0.2: + resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} + fast-copy@4.0.3: resolution: {integrity: sha512-58apWr0GUiDFM8+3afrO6eYwJBn9ZAhDOzG3L+/9llab/haCARS2UIfffmOurYLwbgDRs8n0rfr6qAAPEAuAQw==} @@ -4548,6 +5110,9 @@ packages: fast-string-width@3.0.2: resolution: {integrity: sha512-gX8LrtNEI5hq8DVUfRQMbr5lpaS4nMIWV+7XEbXk2b8kiQIizgnlr12B4dA3ZEx3308ze0O4Q1R+cHts8kyUJg==} + fast-uri@3.1.2: + resolution: {integrity: sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ==} + fast-wrap-ansi@0.2.0: resolution: {integrity: sha512-rLV8JHxTyhVmFYhBJuMujcrHqOT2cnO5Zxj37qROj23CP39GXubJRBUFF0z8KFK77Uc0SukZUf7JZhsVEQ6n8w==} @@ -4634,9 +5199,16 @@ packages: '@google-cloud/storage': optional: true + fontace@0.4.1: + resolution: {integrity: sha512-lDMvbAzSnHmbYMTEld5qdtvNH2/pWpICOqpean9IgC7vUbUJc3k+k5Dokp85CegamqQpFbXf0rAVkbzpyTA8aw==} + fontkit@2.0.4: resolution: {integrity: sha512-syetQadaUEDNdxdugga9CpEYVaQIxOwk7GlwZWWZ19//qW4zE5bknOKeMBDYAASwnpaSHKJITRLMF9m1fp3s6g==} + fontkitten@1.0.3: + resolution: {integrity: sha512-Wp1zXWPVUPBmfoa3Cqc9ctaKuzKAV6uLstRqlR56kSjplf5uAce+qeyYym7F+PHbGTk+tCEdkCW6RD7DX/gBZw==} + engines: {node: '>=20'} + form-data@4.0.5: resolution: {integrity: sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==} engines: {node: '>= 6'} @@ -4714,12 +5286,19 @@ packages: get-tsconfig@4.14.0: resolution: {integrity: sha512-yTb+8DXzDREzgvYmh6s9vHsSVCHeC0G3PI5bEXNBHtmshPnO+S5O7qgLEOn0I5QvMy6kpZN8K1NKGyilLb93wA==} + get-tsconfig@5.0.0-beta.4: + resolution: {integrity: sha512-7nF7C9fIPFEMHgEMEfgIlO9wDdZ8CyHw27rWciFZfHvHDReIiPhsYuzPRXsfvBCqFy1l8RRyyWV7QLM+ZhUJsQ==} + engines: {node: '>=20.20.0'} + getopts@2.3.0: resolution: {integrity: sha512-5eDf9fuSXwxBL6q5HX+dhDj+dslFGWzU5thZ9kNKUkcPtaPdatmUFKwHFrLb/uf/WpA4BHET+AX3Scl56cAjpA==} github-from-package@0.0.0: resolution: {integrity: sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==} + github-slugger@2.0.0: + resolution: {integrity: sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw==} + glob-parent@5.1.2: resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} engines: {node: '>= 6'} @@ -4760,6 +5339,9 @@ packages: resolution: {integrity: sha512-5bJ+nf/UCpAjHM8i06fl7eLyVC9iuNAjm9qzkiu2ZGhM0VscSvS6WDPfAwkdkBuoXGM9FJSbKl6wylMwP9Ktig==} engines: {node: ^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0} + h3@1.15.11: + resolution: {integrity: sha512-L3THSe2MPeBwgIZVSH5zLdBBU90TOxarvhK9d04IDY2AmVS8j2Jz2LIWtwsGOU3lu2I5jCN7FNvVfY2+XyF+mg==} + has-flag@4.0.0: resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} engines: {node: '>=8'} @@ -4776,6 +5358,36 @@ packages: resolution: {integrity: sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==} engines: {node: '>= 0.4'} + hast-util-from-html@2.0.3: + resolution: {integrity: sha512-CUSRHXyKjzHov8yKsQjGOElXy/3EKpyX56ELnkHH34vDVw1N1XSQ1ZcAvTyAPtGqLTuKP/uxM+aLkSPqF/EtMw==} + + hast-util-from-parse5@8.0.3: + resolution: {integrity: sha512-3kxEVkEKt0zvcZ3hCRYI8rqrgwtlIOFMWkbclACvjlDw8Li9S2hk/d51OI0nr/gIpdMHNepwgOKqZ/sy0Clpyg==} + + hast-util-is-element@3.0.0: + resolution: {integrity: sha512-Val9mnv2IWpLbNPqc/pUem+a7Ipj2aHacCwgNfTiK0vJKl0LF+4Ba4+v1oPHFpf3bLYmreq0/l3Gud9S5OH42g==} + + hast-util-parse-selector@4.0.0: + resolution: {integrity: sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A==} + + hast-util-raw@9.1.0: + resolution: {integrity: sha512-Y8/SBAHkZGoNkpzqqfCldijcuUKh7/su31kEBp67cFY09Wy0mTRgtsLYsiIxMJxlu0f6AA5SUTbDR8K0rxnbUw==} + + hast-util-to-html@9.0.5: + resolution: {integrity: sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw==} + + hast-util-to-parse5@8.0.1: + resolution: {integrity: sha512-MlWT6Pjt4CG9lFCjiz4BH7l9wmrMkfkJYCxFwKQic8+RTZgWPuWxwAfjJElsXkex7DJjfSJsQIt931ilUgmwdA==} + + hast-util-to-text@4.0.2: + resolution: {integrity: sha512-KK6y/BN8lbaq654j7JgBydev7wuNMcID54lkRav1P0CaE1e47P72AWWPiGKXTJU271ooYzcvTAn/Zt0REnvc7A==} + + hast-util-whitespace@3.0.0: + resolution: {integrity: sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==} + + hastscript@9.0.1: + resolution: {integrity: sha512-g7df9rMFX/SPi34tyGCyUBREQoKkapwdY/T04Qn9TDWfHhAYt4/I0gMVirzK5wEzeUqIjEB+LXC/ypb7Aqno5w==} + he@1.2.0: resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==} hasBin: true @@ -4809,13 +5421,22 @@ packages: resolution: {integrity: sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==} engines: {node: '>=18'} + html-escaper@3.0.3: + resolution: {integrity: sha512-RuMffC89BOWQoY0WKGpIhn5gX3iI54O6nRA0yC124NYVtzjmFWBIiFd8M0x+ZdX0P9R4lADg1mgP8C7PxGOWuQ==} + html-to-text@9.0.5: resolution: {integrity: sha512-qY60FjREgVZL03vJU6IfMV4GDjGBIoOyvuFdpBDIX9yTlDw0TjxVBQp+P8NvpdIXNJvfWBTNul7fsAQJq2FNpg==} engines: {node: '>=14'} + html-void-elements@3.0.0: + resolution: {integrity: sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==} + htmlparser2@8.0.2: resolution: {integrity: sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==} + http-cache-semantics@4.2.0: + resolution: {integrity: sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==} + http-errors@2.0.1: resolution: {integrity: sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==} engines: {node: '>= 0.8'} @@ -4966,6 +5587,9 @@ packages: resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} engines: {node: '>= 0.10'} + iron-webcrypto@1.2.1: + resolution: {integrity: sha512-feOM6FaSr6rEABp/eDfVseKyTMDt+KGpeB35SkVn9Tyn0CqvVsY3EwI0v5i8nMHyJnzCIQf7nsy3p41TPkJZhg==} + is-binary-path@2.1.0: resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} engines: {node: '>=8'} @@ -4983,6 +5607,11 @@ packages: engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} hasBin: true + is-docker@4.0.0: + resolution: {integrity: sha512-LHE+wROyG/Y/0ZnbktRCoTix2c1RhgWaZraMZ8o1Q7zCh0VSrICJQO5oqIIISrcSBtrXv0o233w1IYwsWCjTzA==} + engines: {node: '>=20'} + hasBin: true + is-extglob@2.1.1: resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} engines: {node: '>=0.10.0'} @@ -5100,6 +5729,9 @@ packages: json-schema-traverse@0.4.1: resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} + json-schema-traverse@1.0.0: + resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} + json-stable-stringify-without-jsonify@1.0.1: resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} @@ -5108,6 +5740,12 @@ packages: engines: {node: '>=6'} hasBin: true + jsonc-parser@2.3.1: + resolution: {integrity: sha512-H8jvkz1O50L3dMZCsLqiuB2tA7muqbSg1AtGEkN0leAqGjsUzDJir3Zwr02BhqdcITPg3ei3mZ+HjMocAknhhg==} + + jsonc-parser@3.3.1: + resolution: {integrity: sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==} + jsonschema@1.5.0: resolution: {integrity: sha512-K+A9hhqbn0f3pJX17Q/7H6yQfD/5OXgdrR5UE12gMXCiN9D5Xq2o5mddV2QEcX/bjla99ASsAAQUyMCCRWAEhw==} @@ -5312,6 +5950,9 @@ packages: resolution: {integrity: sha512-iLs7dGSyjZiUgvrUvuD3FndAxVJk+TywBkkkwUSm9HdYoskJalWg5qVsEiXeufPvRVPbCUmNQewg798rx+sPXg==} engines: {node: '>=20'} + longest-streak@3.1.0: + resolution: {integrity: sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==} + loose-envify@1.4.0: resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} hasBin: true @@ -5345,6 +5986,12 @@ packages: magic-string@0.30.21: resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + magicast@0.5.2: + resolution: {integrity: sha512-E3ZJh4J3S9KfwdjZhe2afj6R9lGIN5Pher1pF39UGrXRqq/VDaGVIGN13BjHd2u8B61hArAGOnso7nBOouW3TQ==} + + markdown-table@3.0.4: + resolution: {integrity: sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==} + marked@15.0.12: resolution: {integrity: sha512-8dD6FusOQSrpv9Z1rdNMdlSgQOIP880DHqnohobOmYLElGEqAL/JvxvuxZO16r4HtjTlfPRDC1hbvxC9dPN2nA==} engines: {node: '>= 18'} @@ -5354,6 +6001,51 @@ packages: resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} engines: {node: '>= 0.4'} + mdast-util-definitions@6.0.0: + resolution: {integrity: sha512-scTllyX6pnYNZH/AIp/0ePz6s4cZtARxImwoPJ7kS42n+MnVsI4XbnG6d4ibehRIldYMWM2LD7ImQblVhUejVQ==} + + mdast-util-find-and-replace@3.0.2: + resolution: {integrity: sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg==} + + mdast-util-from-markdown@2.0.3: + resolution: {integrity: sha512-W4mAWTvSlKvf8L6J+VN9yLSqQ9AOAAvHuoDAmPkz4dHf553m5gVj2ejadHJhoJmcmxEnOv6Pa8XJhpxE93kb8Q==} + + mdast-util-gfm-autolink-literal@2.0.1: + resolution: {integrity: sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ==} + + mdast-util-gfm-footnote@2.1.0: + resolution: {integrity: sha512-sqpDWlsHn7Ac9GNZQMeUzPQSMzR6Wv0WKRNvQRg0KqHh02fpTz69Qc1QSseNX29bhz1ROIyNyxExfawVKTm1GQ==} + + mdast-util-gfm-strikethrough@2.0.0: + resolution: {integrity: sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg==} + + mdast-util-gfm-table@2.0.0: + resolution: {integrity: sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg==} + + mdast-util-gfm-task-list-item@2.0.0: + resolution: {integrity: sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ==} + + mdast-util-gfm@3.1.0: + resolution: {integrity: sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ==} + + mdast-util-phrasing@4.1.0: + resolution: {integrity: sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==} + + mdast-util-to-hast@13.2.1: + resolution: {integrity: sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA==} + + mdast-util-to-markdown@2.1.2: + resolution: {integrity: sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==} + + mdast-util-to-string@4.0.0: + resolution: {integrity: sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==} + + mdn-data@2.0.28: + resolution: {integrity: sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g==} + + mdn-data@2.27.1: + resolution: {integrity: sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==} + media-engine@1.0.3: resolution: {integrity: sha512-aa5tG6sDoK+k70B9iEX1NeyfT8ObCKhNDs6lJVpwF6r8vhUfuKMslIcirq6HIUYuuUYLefcEQOn9bSBOvawtwg==} @@ -5372,6 +6064,90 @@ packages: resolution: {integrity: sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==} engines: {node: '>= 0.6'} + micromark-core-commonmark@2.0.3: + resolution: {integrity: sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==} + + micromark-extension-gfm-autolink-literal@2.1.0: + resolution: {integrity: sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==} + + micromark-extension-gfm-footnote@2.1.0: + resolution: {integrity: sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw==} + + micromark-extension-gfm-strikethrough@2.1.0: + resolution: {integrity: sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw==} + + micromark-extension-gfm-table@2.1.1: + resolution: {integrity: sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg==} + + micromark-extension-gfm-tagfilter@2.0.0: + resolution: {integrity: sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg==} + + micromark-extension-gfm-task-list-item@2.1.0: + resolution: {integrity: sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw==} + + micromark-extension-gfm@3.0.0: + resolution: {integrity: sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w==} + + micromark-factory-destination@2.0.1: + resolution: {integrity: sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==} + + micromark-factory-label@2.0.1: + resolution: {integrity: sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==} + + micromark-factory-space@2.0.1: + resolution: {integrity: sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==} + + micromark-factory-title@2.0.1: + resolution: {integrity: sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==} + + micromark-factory-whitespace@2.0.1: + resolution: {integrity: sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==} + + micromark-util-character@2.1.1: + resolution: {integrity: sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==} + + micromark-util-chunked@2.0.1: + resolution: {integrity: sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==} + + micromark-util-classify-character@2.0.1: + resolution: {integrity: sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==} + + micromark-util-combine-extensions@2.0.1: + resolution: {integrity: sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==} + + micromark-util-decode-numeric-character-reference@2.0.2: + resolution: {integrity: sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==} + + micromark-util-decode-string@2.0.1: + resolution: {integrity: sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==} + + micromark-util-encode@2.0.1: + resolution: {integrity: sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==} + + micromark-util-html-tag-name@2.0.1: + resolution: {integrity: sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==} + + micromark-util-normalize-identifier@2.0.1: + resolution: {integrity: sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==} + + micromark-util-resolve-all@2.0.1: + resolution: {integrity: sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==} + + micromark-util-sanitize-uri@2.0.1: + resolution: {integrity: sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==} + + micromark-util-subtokenize@2.1.0: + resolution: {integrity: sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==} + + micromark-util-symbol@2.0.1: + resolution: {integrity: sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==} + + micromark-util-types@2.0.2: + resolution: {integrity: sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==} + + micromark@4.0.2: + resolution: {integrity: sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==} + micromatch@4.0.8: resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} engines: {node: '>=8.6'} @@ -5433,6 +6209,10 @@ packages: module-details-from-path@1.0.4: resolution: {integrity: sha512-EGWKgxALGMgzvxYF1UyGTy0HXX/2vHLkw6+NvDKW2jypWbHpjQuj4UMcqQWXHERJhVGKikolT06G3bcKe4fi7w==} + mrmime@2.0.1: + resolution: {integrity: sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==} + engines: {node: '>=10'} + ms@2.1.2: resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} @@ -5456,6 +6236,9 @@ packages: typescript: optional: true + muggle-string@0.4.1: + resolution: {integrity: sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==} + mute-stream@3.0.0: resolution: {integrity: sha512-dkEJPVvun4FryqBmZ5KhDo0K9iDXAwn08tMLDinNdRBNPcYEDiWYysLcc6k3mjTMlbP9KyylvRpd4wFtwrT9rw==} engines: {node: ^20.17.0 || >=22.9.0} @@ -5471,6 +6254,13 @@ packages: natural-compare@1.4.0: resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + neotraverse@0.6.18: + resolution: {integrity: sha512-Z4SmBUweYa09+o6pG+eASabEpP6QkQ70yHj351pQoEXIs8uHbaU2DWVmzBANKgflPa47A50PtB2+NgRpQvr7vA==} + engines: {node: '>= 10'} + + nlcst-to-string@4.0.0: + resolution: {integrity: sha512-YKLBCcUYKAg0FNlOBT6aI91qFmSiFKiluk655WzPF+DDMA02qIyy8uiRqI8QXtcFpEvll12LpL5MXqEmAZ+dcA==} + node-abi@3.91.0: resolution: {integrity: sha512-B+S7X/GS3Un6wMICtnsNjQD7oSpVBQrZftHE6GZ1Fe9/k3XOOoqbM5DZZ0GO4x3YiSCQfrM28yj1ppplwgIsfg==} engines: {node: '>=10'} @@ -5478,6 +6268,9 @@ packages: node-abort-controller@3.1.1: resolution: {integrity: sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ==} + node-fetch-native@1.6.7: + resolution: {integrity: sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q==} + node-fetch@2.7.0: resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==} engines: {node: 4.x || >=6.0.0} @@ -5491,6 +6284,9 @@ packages: resolution: {integrity: sha512-s+w+rBWnpTMwSFbaE0UXsRlg7hU4FjekKU4eyAih5T8nJuNZT1nNsskXpxmeqSK9UzkBl6UgRlnKc8hz8IEqOw==} hasBin: true + node-mock-http@1.0.4: + resolution: {integrity: sha512-8DY+kFsDkNXy1sJglUfuODx1/opAGJGyrTuFqEoN90oRc2Vk0ZbD4K2qmKXBBEhZQzdKHIVfEJpDU8Ak2NJEvQ==} + node-releases@2.0.38: resolution: {integrity: sha512-3qT/88Y3FbH/Kx4szpQQ4HzUbVrHPKTLVpVocKiLfoYvw9XSGOX2FmD2d6DrXbVYyAQTF2HeF6My8jmzx7/CRw==} @@ -5521,6 +6317,9 @@ packages: resolution: {integrity: sha512-9qny7Z9DsQU8Ou39ERsPU4OZQlSTP47ShQzuKZ6PRXpYLtIFgl/DEBYEXKlvcEa+9tHVcK8CF81Y2V72qaZhWA==} engines: {node: '>=18'} + nth-check@2.1.1: + resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==} + nwsapi@2.2.23: resolution: {integrity: sha512-7wfH4sLbt4M0gCDzGE6vzQBo0bfTKjU7Sfpqy/7gs1qBfYz2vEJH6vXcBKpO3+6Yu1telwd0t9HpyOoLEQQbIQ==} @@ -5535,6 +6334,15 @@ packages: object-to-formdata@4.5.1: resolution: {integrity: sha512-QiM9D0NiU5jV6J6tjE1g7b4Z2tcUnKs1OPUi4iMb2zH+7jwlcUrASghgkFk9GtzqNNq8rTQJtT8AzjBAvLoNMw==} + obug@2.1.1: + resolution: {integrity: sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==} + + ofetch@1.5.1: + resolution: {integrity: sha512-2W4oUZlVaqAPAil6FUg/difl6YhqhUR7x2eZY4bQCko22UXg3hptq9KLQdqFClV+Wu85UX7hNtdGTngi/1BxcA==} + + ohash@2.0.11: + resolution: {integrity: sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==} + on-exit-leak-free@2.1.2: resolution: {integrity: sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==} engines: {node: '>=14.0.0'} @@ -5554,6 +6362,12 @@ packages: resolution: {integrity: sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==} engines: {node: '>=18'} + oniguruma-parser@0.12.2: + resolution: {integrity: sha512-6HVa5oIrgMC6aA6WF6XyyqbhRPJrKR02L20+2+zpDtO5QAzGHAUGw5TKQvwi5vctNnRHkJYmjAhRVQF2EKdTQw==} + + oniguruma-to-es@4.3.6: + resolution: {integrity: sha512-csuQ9x3Yr0cEIs/Zgx/OEt9iBw9vqIunAPQkx19R/fiMq2oGVTgcMqO/V3Ybqefr1TBvosI6jU539ksaBULJyA==} + open@11.0.0: resolution: {integrity: sha512-smsWv2LzFjP03xmvFoJ331ss6h+jixfA4UUV/Bsiyuu4YJPfN+FIQGOIiv4w9/+MoHkfkJ22UIaQWRVFRfH6Vw==} engines: {node: '>=20'} @@ -5569,10 +6383,22 @@ packages: resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} engines: {node: '>=10'} + p-limit@7.3.0: + resolution: {integrity: sha512-7cIXg/Z0M5WZRblrsOla88S4wAK+zOQQWeBYfV3qJuJXMr+LnbYjaadrFaS0JILfEDPVqHyKnZ1Z/1d6J9VVUw==} + engines: {node: '>=20'} + p-locate@5.0.0: resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} engines: {node: '>=10'} + p-queue@9.2.0: + resolution: {integrity: sha512-dWgLE8AH0HjQ9fe74pUkKkvzzYT18Inp4zra3lKHnnwqGvcfcUBrvF2EAVX+envufDNBOzpPq/IBUONDbI7+3g==} + engines: {node: '>=20'} + + p-timeout@7.0.1: + resolution: {integrity: sha512-AxTM2wDGORHGEkPCt8yqxOTMgpfbEHqF51f/5fJCmwFC3C/zNcGT63SymH2ttOAaiIws2zVg4+izQCjrakcwHg==} + engines: {node: '>=20'} + package-manager-detector@1.6.0: resolution: {integrity: sha512-61A5ThoTiDG/C8s8UMZwSorAGwMJ0ERVGj2OjoW5pAalsNOg15+iQiPzrLJ4jhZ1HJzmC2PIHT2oEiH3R5fzNA==} @@ -5594,6 +6420,9 @@ packages: resolution: {integrity: sha512-ybiGyvspI+fAoRQbIPRddCcSTV9/LsJbf0e/S85VLowVGzRmokfneg2kwVW/KU5rOXrPSbF1qAKPMgNTqqROQQ==} engines: {node: '>=18'} + parse-latin@7.0.0: + resolution: {integrity: sha512-mhHgobPPua5kZ98EF4HWiH167JWBfl4pvAIXXdbaVohtK7a6YBOy56kvhCqduqyo/f3yrHFWmqmiMg/BkBkYYQ==} + parse-ms@4.0.0: resolution: {integrity: sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw==} engines: {node: '>=18'} @@ -5687,6 +6516,9 @@ packages: pgpass@1.0.5: resolution: {integrity: sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==} + piccolore@0.1.3: + resolution: {integrity: sha512-o8bTeDWjE086iwKrROaDf31K0qC/BENdm15/uH9usSC/uZjJOKb2YGiVHfLY4GhwsERiPI1jmwI2XrA7ACOxVw==} + picocolors@1.1.1: resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} @@ -5807,6 +6639,9 @@ packages: prop-types@15.8.1: resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} + property-information@7.1.0: + resolution: {integrity: sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==} + proxy-addr@2.0.7: resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} engines: {node: '>= 0.10'} @@ -5834,6 +6669,9 @@ packages: quick-format-unescaped@4.0.4: resolution: {integrity: sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==} + radix3@1.1.2: + resolution: {integrity: sha512-b484I/7b8rDEdSDKckSSBA8knMpcdsXudlE/LNL639wFoHKwLbEkQFZHWEYwDC0wa0FKUcCY+GAF73Z7wxNVFA==} + random-bytes@1.0.0: resolution: {integrity: sha512-iv7LhNVO047HzYR3InF6pUcUsPQiHTM1Qal51DcGSuZFBil1aBBWG5eHPNek7bvILMaYJ/8RU1e8w1AMdHmLQQ==} engines: {node: '>= 0.8'} @@ -5879,6 +6717,10 @@ packages: redux: optional: true + react-refresh@0.18.0: + resolution: {integrity: sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw==} + engines: {node: '>=0.10.0'} + react-remove-scroll-bar@2.3.8: resolution: {integrity: sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==} engines: {node: '>=10'} @@ -5929,6 +6771,10 @@ packages: resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} engines: {node: '>=8.10.0'} + readdirp@4.1.2: + resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==} + engines: {node: '>= 14.18.0'} + readdirp@5.0.0: resolution: {integrity: sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==} engines: {node: '>= 20.19.0'} @@ -5972,6 +6818,15 @@ packages: reflect-metadata@0.2.2: resolution: {integrity: sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==} + regex-recursion@6.0.2: + resolution: {integrity: sha512-0YCaSCq2VRIebiaUviZNs0cBz1kg5kVS2UKUfNIx8YVs1cN3AV7NTctO5FOKBA+UT2BPJIWZauYHPqJODG50cg==} + + regex-utilities@2.3.0: + resolution: {integrity: sha512-8VhliFJAWRaUiVvREIiW2NXXTmHs4vMNnSzuJVhscgmGav3g9VDxLrQndI3dZZVVdp0ZO/5v0xmX516/7M9cng==} + + regex@6.1.0: + resolution: {integrity: sha512-6VwtthbV4o/7+OaAF9I5L5V3llLEsoPyq9P1JVXkedTP33c7MfCG0/5NOPcSJn0TzXcG9YUrR0gQSWioew3LDg==} + regexp-tree@0.1.27: resolution: {integrity: sha512-iETxpjK6YoRWJG5o6hXLwvjYAoW+FEZn9os0PD/b6AP6xQwsa/Y7lCVgIixBbUPMfhu+i2LtdeAqVTgGlQarfA==} hasBin: true @@ -5980,6 +6835,40 @@ packages: resolution: {integrity: sha512-dLsljMd9sqwRkby8zhO1gSg3PnJIBFid8f4CQj/sXx+7cKx+E7u0PKhZ+U4wmhx7EfmtvnA318oVaIkAB1lRJw==} hasBin: true + rehype-parse@9.0.1: + resolution: {integrity: sha512-ksCzCD0Fgfh7trPDxr2rSylbwq9iYDkSn8TCDmEJ49ljEUBxDVCzCHv7QNzZOfODanX4+bWQ4WZqLCRWYLfhag==} + + rehype-raw@7.0.0: + resolution: {integrity: sha512-/aE8hCfKlQeA8LmyeyQvQF3eBiLRGNlfBJEvWH7ivp9sBqs7TNqBL5X3v157rM4IFETqDnIOO+z5M/biZbo9Ww==} + + rehype-stringify@10.0.1: + resolution: {integrity: sha512-k9ecfXHmIPuFVI61B9DeLPN0qFHfawM6RsuX48hoqlaKSF61RskNjSm1lI8PhBEM0MRdLxVVm4WmTqJQccH9mA==} + + rehype@13.0.2: + resolution: {integrity: sha512-j31mdaRFrwFRUIlxGeuPXXKWQxet52RBQRvCmzl5eCefn/KGbomK5GMHNMsOJf55fgo3qw5tST5neDuarDYR2A==} + + remark-gfm@4.0.1: + resolution: {integrity: sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg==} + + remark-parse@11.0.0: + resolution: {integrity: sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==} + + remark-rehype@11.1.2: + resolution: {integrity: sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw==} + + remark-smartypants@3.0.2: + resolution: {integrity: sha512-ILTWeOriIluwEvPjv67v7Blgrcx+LZOkAUVtKI3putuhlZm84FnqDORNXPPm+HY3NdZOMhyDwZ1E+eZB/Df5dA==} + engines: {node: '>=16.0.0'} + + remark-stringify@11.0.0: + resolution: {integrity: sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==} + + request-light@0.5.8: + resolution: {integrity: sha512-3Zjgh+8b5fhRJBQZoy+zbVKpAQGLyka0MPgW3zruTF4dFFJ8Fqcfu9YsAvi/rvdcaTeWG3MkbZv4WKxAn/84Lg==} + + request-light@0.7.0: + resolution: {integrity: sha512-lMbBMrDoxgsyO+yB3sDcrDuX85yYt7sS8BfQd11jtbW/z5ZWgLZRcEGLsLoYw7I0WSUGQBs8CC8ScIxkTX1+6Q==} + require-directory@2.1.1: resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} engines: {node: '>=0.10.0'} @@ -6018,6 +6907,18 @@ packages: restructure@3.0.2: resolution: {integrity: sha512-gSfoiOEA0VPE6Tukkrr7I0RBdE0s7H1eFCDBk05l1KIQT1UIKNc5JZy6jdyW6eYH3aR3g5b3PuL77rq0hvwtAw==} + retext-latin@4.0.0: + resolution: {integrity: sha512-hv9woG7Fy0M9IlRQloq/N6atV82NxLGveq+3H2WOi79dtIYWN8OaxogDm77f8YnVXJL2VD3bbqowu5E3EMhBYA==} + + retext-smartypants@6.2.0: + resolution: {integrity: sha512-kk0jOU7+zGv//kfjXEBjdIryL1Acl4i9XNkHxtM7Tm5lFiCog576fjNC9hjoR7LTKQ0DsPWy09JummSsH1uqfQ==} + + retext-stringify@4.0.0: + resolution: {integrity: sha512-rtfN/0o8kL1e+78+uxPTqu1Klt0yPzKuQ2BfWwwfgIUSayyzxpM1PJzkKt4V8803uB9qSy32MvI7Xep9khTpiA==} + + retext@9.0.0: + resolution: {integrity: sha512-sbMDcpHCNjvlheSgMfEcVrZko3cDzdbe1x/e7G66dFp0Ff7Mldvi2uv6JkJQzdRcvLYE8CA8Oe8siQx8ZOgTcA==} + retry@0.13.1: resolution: {integrity: sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==} engines: {node: '>= 4'} @@ -6068,6 +6969,10 @@ packages: safer-buffer@2.1.2: resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + sax@1.6.0: + resolution: {integrity: sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA==} + engines: {node: '>=11.0.0'} + saxes@6.0.0: resolution: {integrity: sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==} engines: {node: '>=v12.22.7'} @@ -6111,6 +7016,9 @@ packages: resolution: {integrity: sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==} engines: {node: '>= 18'} + server-destroy@1.0.1: + resolution: {integrity: sha512-rb+9B5YBIEzYcD6x2VKidaa+cqYBJQKnU4oe4E3ANwRRN56yk/ua1YCJT1n21NTS8w6CcOclAKNP3PhdCXKYtQ==} + set-cookie-parser@2.7.2: resolution: {integrity: sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==} @@ -6120,6 +7028,10 @@ packages: setprototypeof@1.2.0: resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} + sharp@0.34.5: + resolution: {integrity: sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + shebang-command@2.0.0: resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} engines: {node: '>=8'} @@ -6128,6 +7040,10 @@ packages: resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} engines: {node: '>=8'} + shiki@4.0.2: + resolution: {integrity: sha512-eAVKTMedR5ckPo4xne/PjYQYrU3qx78gtJZ+sHlXEg5IHhhoQhMfZVzetTYuaJS0L2Ef3AcCRzCHV8T0WI6nIQ==} + engines: {node: '>=20'} + side-channel-list@1.0.1: resolution: {integrity: sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==} engines: {node: '>= 0.4'} @@ -6157,6 +7073,9 @@ packages: simple-get@4.0.1: resolution: {integrity: sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==} + sisteransi@1.0.5: + resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==} + slash@5.1.0: resolution: {integrity: sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==} engines: {node: '>=14.16'} @@ -6180,6 +7099,10 @@ packages: resolution: {integrity: sha512-vZ7rfeehZui7wQs438JXBckYLkIIdfHOXsaVEUMyS5fHo1483l1bMdo0EDSWYclY0yZKFOipDy4KHuKs6ssvdg==} engines: {node: '>=8.0.0'} + smol-toml@1.6.1: + resolution: {integrity: sha512-dWUG8F5sIIARXih1DTaQAX4SsiTXhInKf1buxdY9DIg4ZYPZK5nGM1VRIYmEbDbsHt7USo99xSLFu5Q1IqTmsg==} + engines: {node: '>= 18'} + sonic-boom@4.2.1: resolution: {integrity: sha512-w6AxtubXa2wTXAUsZMMWERrsIRAdrK0Sc+FUytWvYAhBJLyuI4llrMIC1DtlNSdI99EI86KZum2MMq3EAZlF9Q==} @@ -6193,6 +7116,9 @@ packages: resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} engines: {node: '>=0.10.0'} + space-separated-tokens@2.0.2: + resolution: {integrity: sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==} + spdx-correct@3.2.0: resolution: {integrity: sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==} @@ -6248,6 +7174,9 @@ packages: string_decoder@1.3.0: resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} + stringify-entities@4.0.4: + resolution: {integrity: sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==} + strip-ansi@6.0.1: resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} engines: {node: '>=8'} @@ -6322,6 +7251,11 @@ packages: svg-arc-to-cubic-bezier@3.2.0: resolution: {integrity: sha512-djbJ/vZKZO+gPoSDThGNpKDO+o+bAeA4XQKovvkNCqnIS2t+S4qnLAGQhyyrulhCFRl1WWzAp0wUDV8PpTVU3g==} + svgo@4.0.1: + resolution: {integrity: sha512-XDpWUOPC6FEibaLzjfe0ucaV0YrOjYotGJO1WpF0Zd+n6ZGEQUsSugaoLq9QkEZtAfQIxT42UChcssDVPP3+/w==} + engines: {node: '>=16'} + hasBin: true + symbol-tree@3.2.4: resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==} @@ -6382,6 +7316,10 @@ packages: tinybench@2.9.0: resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} + tinyclip@0.1.12: + resolution: {integrity: sha512-Ae3OVUqifDw0wBriIBS7yVaW44Dp6eSHQcyq4Igc7eN2TJH/2YsicswaW+J/OuMvhpDPOKEgpAZCjkb4hpoyeA==} + engines: {node: ^16.14.0 || >= 17.3.0} + tinyexec@0.3.2: resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==} @@ -6450,6 +7388,12 @@ packages: resolution: {integrity: sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==} engines: {node: '>=18'} + trim-lines@3.0.1: + resolution: {integrity: sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==} + + trough@2.2.0: + resolution: {integrity: sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==} + ts-api-utils@2.5.0: resolution: {integrity: sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==} engines: {node: '>=18.12'} @@ -6494,6 +7438,12 @@ packages: resolution: {integrity: sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==} engines: {node: '>= 0.6'} + typesafe-path@0.2.2: + resolution: {integrity: sha512-OJabfkAg1WLZSqJAJ0Z6Sdt3utnbzr/jh+NAHoyWHJe8CMSy79Gm085094M9nvTPy22KzTVn5Zq5mbapCI/hPA==} + + typescript-auto-import-cache@0.3.6: + resolution: {integrity: sha512-RpuHXrknHdVdK7wv/8ug3Fr0WNsNi5l5aB8MYYuXhq2UH5lnEB1htJ1smhtD5VeCsGr2p8mUDtd83LCQDFVgjQ==} + typescript-eslint@8.59.2: resolution: {integrity: sha512-pJw051uomb3ZeCzGTpRb8RbEqB5Y4WWet8gl/GcTlU35BSx0PVdZ86/bqkQCyKKuraVQEK7r6kBHQXF+fBhkoQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -6511,6 +7461,9 @@ packages: engines: {node: '>=14.17'} hasBin: true + ufo@1.6.4: + resolution: {integrity: sha512-JFNbkD1Svwe0KvGi8GOeLcP4kAWQ609twvCdcHxq1oSL8svv39ZuSvajcD8B+5D0eL4+s1Is2D/O6KN3qcTeRA==} + uglify-js@3.19.3: resolution: {integrity: sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==} engines: {node: '>=0.8.0'} @@ -6524,6 +7477,12 @@ packages: resolution: {integrity: sha512-rvKSBiC5zqCCiDZ9kAOszZcDvdAHwwIKJG33Ykj43OKcWsnmcBRL09YTU4nOeHZ8Y2a7l1MgTd08SBe9A8Qj6A==} engines: {node: '>=18'} + ultrahtml@1.6.0: + resolution: {integrity: sha512-R9fBn90VTJrqqLDwyMph+HGne8eqY1iPfYhPzZrvKpIfwkWZbcYlfpsb8B9dTvBfpy1/hqAD7Wi8EKfP9e8zdw==} + + uncrypto@0.1.3: + resolution: {integrity: sha512-Ql87qFHB3s/De2ClA9e0gsnS6zXG27SkTiSJwjCc9MebbfapQfuPzumMIUMi38ezPZVNFcHI9sUIepeQfw8J8Q==} + undici-types@6.21.0: resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} @@ -6547,6 +7506,39 @@ packages: resolution: {integrity: sha512-wH590V9VNgYH9g3lH9wWjTrUoKsjLF6sGLjhR4sH1LWpLmCOH0Zf7PukhDA8BiS7KHe4oPNkcTHqYkj7SOGUOw==} engines: {node: '>=20'} + unified@11.0.5: + resolution: {integrity: sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==} + + unifont@0.7.4: + resolution: {integrity: sha512-oHeis4/xl42HUIeHuNZRGEvxj5AaIKR+bHPNegRq5LV1gdc3jundpONbjglKpihmJf+dswygdMJn3eftGIMemg==} + + unist-util-find-after@5.0.0: + resolution: {integrity: sha512-amQa0Ep2m6hE2g72AugUItjbuM8X8cGQnFoHk0pGfrFeT9GZhzN5SW8nRsiGKK7Aif4CrACPENkA6P/Lw6fHGQ==} + + unist-util-is@6.0.1: + resolution: {integrity: sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==} + + unist-util-modify-children@4.0.0: + resolution: {integrity: sha512-+tdN5fGNddvsQdIzUF3Xx82CU9sMM+fA0dLgR9vOmT0oPT2jH+P1nd5lSqfCfXAw+93NhcXNY2qqvTUtE4cQkw==} + + unist-util-position@5.0.0: + resolution: {integrity: sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==} + + unist-util-remove-position@5.0.0: + resolution: {integrity: sha512-Hp5Kh3wLxv0PHj9m2yZhhLt58KzPtEYKQQ4yxfYFEO7EvHwzyDYnduhHnY1mDxoqr7VUwVuHXk9RXKIiYS1N8Q==} + + unist-util-stringify-position@4.0.0: + resolution: {integrity: sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==} + + unist-util-visit-children@3.0.0: + resolution: {integrity: sha512-RgmdTfSBOg04sdPcpTSD1jzoNBjt9a80/ZCzp5cI9n1qPzLZWF9YdvWGN2zmTumP1HWhXKdUWexjy/Wy/lJ7tA==} + + unist-util-visit-parents@6.0.2: + resolution: {integrity: sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==} + + unist-util-visit@5.1.0: + resolution: {integrity: sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg==} + unpipe@1.0.0: resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} engines: {node: '>= 0.8'} @@ -6555,6 +7547,68 @@ packages: resolution: {integrity: sha512-0Mqk3AT2TZCXWKdcoaufeXNukv2mTrEZExeXlHIOZXdqYoHHr4n51pymnwV8x2BOVxwXbK2HLlI7usrqMpycdg==} engines: {node: ^20.19.0 || >=22.12.0} + unstorage@1.17.5: + resolution: {integrity: sha512-0i3iqvRfx29hkNntHyQvJTpf5W9dQ9ZadSoRU8+xVlhVtT7jAX57fazYO9EHvcRCfBCyi5YRya7XCDOsbTgkPg==} + 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 + until-async@3.0.2: resolution: {integrity: sha512-IiSk4HlzAMqTUseHHe3VhIGyuFmN90zMTpD3Z3y8jeQbzLIq500MVM7Jq2vUAnTKAFPJrqwkzr6PoTcPhGcOiw==} @@ -6606,6 +7660,15 @@ packages: resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} engines: {node: '>= 0.8'} + vfile-location@5.0.3: + resolution: {integrity: sha512-5yXvWDEgqeiYiBe1lbxYF7UMAIm/IcopxMHrMQDq3nvKcjPKIhZklUKL+AE7J7uApI4kwe2snsK+eI6UTj9EHg==} + + vfile-message@4.0.3: + resolution: {integrity: sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==} + + vfile@6.0.3: + resolution: {integrity: sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==} + victory-vendor@37.3.6: resolution: {integrity: sha512-SbPDPdDBYp+5MJHhBCAyI7wKM3d5ivekigc2Dk2s7pgbZ9wIgIBYGVw4zGHBml/qTFbexrofXW6Gu4noGxrOwQ==} @@ -6701,6 +7764,14 @@ packages: yaml: optional: true + vitefu@1.1.3: + resolution: {integrity: sha512-ub4okH7Z5KLjb6hDyjqrGXqWtWvoYdU3IGm/NorpgHncKoLTCfRIbvlhBm7r0YstIaQRYlp4yEbFqDcKSzXSSg==} + peerDependencies: + vite: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 + peerDependenciesMeta: + vite: + optional: true + vitest@3.2.4: resolution: {integrity: sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==} engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} @@ -6729,10 +7800,105 @@ packages: jsdom: optional: true + volar-service-css@0.0.70: + resolution: {integrity: sha512-K1qyOvBpE3rzdAv3e4/6Rv5yizrYPy5R/ne3IWCAzLBuMO4qBMV3kSqWzj6KUVe6S0AnN6wxF7cRkiaKfYMYJw==} + peerDependencies: + '@volar/language-service': ~2.4.0 + peerDependenciesMeta: + '@volar/language-service': + optional: true + + volar-service-emmet@0.0.70: + resolution: {integrity: sha512-xi5bC4m/VyE3zy/n2CXspKeDZs3qA41tHLTw275/7dNWM/RqE2z3BnDICQybHIVp/6G1iOQj5c1qXMgQC08TNg==} + peerDependencies: + '@volar/language-service': ~2.4.0 + peerDependenciesMeta: + '@volar/language-service': + optional: true + + volar-service-html@0.0.70: + resolution: {integrity: sha512-eR6vCgMdmYAo4n+gcT7DSyBQbwB8S3HZZvSagTf0sxNaD4WppMCFfpqWnkrlGStPKMZvMiejRRVmqsX9dYcTvQ==} + peerDependencies: + '@volar/language-service': ~2.4.0 + peerDependenciesMeta: + '@volar/language-service': + optional: true + + volar-service-prettier@0.0.70: + resolution: {integrity: sha512-Z6BCFSpGVCd8BPAsZ785Kce1BGlWd5ODqmqZGVuB14MJvrR4+CYz6cDy4F+igmE1gMifqfvMhdgT8Aud4M5ngg==} + peerDependencies: + '@volar/language-service': ~2.4.0 + prettier: ^2.2 || ^3.0 + peerDependenciesMeta: + '@volar/language-service': + optional: true + prettier: + optional: true + + volar-service-typescript-twoslash-queries@0.0.70: + resolution: {integrity: sha512-IdD13Z9N2Bu8EM6CM0fDV1E69olEYGHDU25X51YXmq8Y0CmJ2LNj6gOiBJgpS5JGUqFzECVhMNBW7R0sPdRTMQ==} + peerDependencies: + '@volar/language-service': ~2.4.0 + peerDependenciesMeta: + '@volar/language-service': + optional: true + + volar-service-typescript@0.0.70: + resolution: {integrity: sha512-l46Bx4cokkUedTd74ojO5H/zqHZJ8SUuyZ0IB8JN4jfRqUM3bQFBHoOwlZCyZmOeO0A3RQNkMnFclxO4c++gsg==} + peerDependencies: + '@volar/language-service': ~2.4.0 + peerDependenciesMeta: + '@volar/language-service': + optional: true + + volar-service-yaml@0.0.70: + resolution: {integrity: sha512-0c8bXDBeoATF9F6iPIlOuYTuZAC4c+yi0siQo920u7eiBJk8oQmUmg9cDUbR4+Gl++bvGP4plj3fErbJuPqdcQ==} + peerDependencies: + '@volar/language-service': ~2.4.0 + peerDependenciesMeta: + '@volar/language-service': + optional: true + + vscode-css-languageservice@6.3.10: + resolution: {integrity: sha512-eq5N9Er3fC4vA9zd9EFhyBG90wtCCuXgRSpAndaOgXMh1Wgep5lBgRIeDgjZBW9pa+332yC9+49cZMW8jcL3MA==} + + vscode-html-languageservice@5.6.2: + resolution: {integrity: sha512-ulCrSnFnfQ16YzvwnYUgEbUEl/ZG7u2eV27YhvLObSHKkb8fw1Z9cgsnUwjTEeDIdJDoTDTDpxuhQwoenoLNMg==} + + vscode-json-languageservice@4.1.8: + resolution: {integrity: sha512-0vSpg6Xd9hfV+eZAaYN63xVVMOTmJ4GgHxXnkLCh+9RsQBkWKIghzLhW2B9ebfG+LQQg8uLtsQ2aUKjTgE+QOg==} + engines: {npm: '>=7.0.0'} + + vscode-jsonrpc@8.2.0: + resolution: {integrity: sha512-C+r0eKJUIfiDIfwJhria30+TYWPtuHJXHtI7J0YlOmKAo7ogxP20T0zxB7HZQIFhIyvoBPwWskjxrvAtfjyZfA==} + engines: {node: '>=14.0.0'} + + vscode-languageserver-protocol@3.17.5: + resolution: {integrity: sha512-mb1bvRJN8SVznADSGWM9u/b07H7Ecg0I3OgXDuLdn307rl/J3A9YD6/eYOssqhecL27hK1IPZAsaqh00i/Jljg==} + + vscode-languageserver-textdocument@1.0.12: + resolution: {integrity: sha512-cxWNPesCnQCcMPeenjKKsOCKQZ/L6Tv19DTRIGuLWe32lyzWhihGVJ/rcckZXJxfdKCFvRLS3fpBIsV/ZGX4zA==} + + vscode-languageserver-types@3.17.5: + resolution: {integrity: sha512-Ld1VelNuX9pdF39h2Hgaeb5hEZM2Z3jUrrMgWQAu82jMtZp7p3vJT3BzToKtZI7NgQssZje5o0zryOrhQvzQAg==} + + vscode-languageserver@9.0.1: + resolution: {integrity: sha512-woByF3PDpkHFUreUa7Hos7+pUWdeWMXRd26+ZX2A8cFx6v/JPTtd4/uN0/jB6XQHYaOlHbio03NTHCqrgG5n7g==} + hasBin: true + + vscode-nls@5.2.0: + resolution: {integrity: sha512-RAaHx7B14ZU04EU31pT+rKz2/zSl7xMsfIZuo8pd+KZO6PXtQmpevpq3vxvWNcrGbdmhM/rr5Uw5Mz+NBfhVng==} + + vscode-uri@3.1.0: + resolution: {integrity: sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==} + w3c-xmlserializer@5.0.0: resolution: {integrity: sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==} engines: {node: '>=18'} + web-namespaces@2.0.1: + resolution: {integrity: sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==} + webidl-conversions@3.0.1: resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} @@ -6759,6 +7925,10 @@ packages: whatwg-url@5.0.0: resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} + which-pm-runs@1.1.0: + resolution: {integrity: sha512-n1brCuqClxfFfq/Rb0ICg9giSZqCS+pLtccdag6C2HyufBrh3fBOiy9nb6ggRMvWOVH5GrdJskj5iGTZNxd7SA==} + engines: {node: '>=4'} + which@2.0.2: resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} engines: {node: '>= 8'} @@ -6815,6 +7985,9 @@ packages: resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==} engines: {node: '>=0.4'} + xxhash-wasm@1.1.0: + resolution: {integrity: sha512-147y/6YNh+tlp6nd/2pWq38i9h6mz/EuQ6njIrmW8D1BS5nCqs0P6DG+m6zTGnNz5I+uhZ0SHxBs9BsPrwcKDA==} + y18n@5.0.8: resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} engines: {node: '>=10'} @@ -6822,6 +7995,15 @@ packages: yallist@3.1.1: resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} + yaml-language-server@1.20.0: + resolution: {integrity: sha512-qhjK/bzSRZ6HtTvgeFvjNPJGWdZ0+x5NREV/9XZWFjIGezew2b4r5JPy66IfOhd5OA7KeFwk1JfmEbnTvev0cA==} + hasBin: true + + yaml@2.7.1: + resolution: {integrity: sha512-10ULxpnOCQXxJvBgxsn9ptjq6uviG/htZKk9veJGhlqn3w/DxQ631zFF+nlQXLwmImeS5amR2dl2U8sg6U9jsQ==} + engines: {node: '>= 14'} + hasBin: true + yaml@2.8.4: resolution: {integrity: sha512-ml/JPOj9fOQK8RNnWojA67GbZ0ApXAUlN2UQclwv2eVgTgn7O9gg9o7paZWKMp4g0H3nTLtS9LVzhkpOFIKzog==} engines: {node: '>= 14.6'} @@ -6843,6 +8025,10 @@ packages: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} + yocto-queue@1.2.2: + resolution: {integrity: sha512-4LCcse/U2MHZ63HAJVE+v71o7yOdIe4cZ70Wpf8D/IyjDKYQLV5GD46B+hSTjJsvV5PztjvHoU580EftxjDZFQ==} + engines: {node: '>=12.20'} + yoctocolors@2.1.2: resolution: {integrity: sha512-CzhO+pFNo8ajLM2d2IW/R93ipy99LWjtwblvC1RsoSUMZgyLbYFr221TnSNT7GjGdYui6P459mw9JH/g/zW2ug==} engines: {node: '>=18'} @@ -6865,6 +8051,12 @@ packages: zod@3.25.76: resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==} + zod@4.4.3: + resolution: {integrity: sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==} + + zwitch@2.0.4: + resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==} + snapshots: '@adobe/css-tools@4.4.4': {} @@ -7277,6 +8469,126 @@ snapshots: '@ast-grep/napi-win32-ia32-msvc': 0.42.1 '@ast-grep/napi-win32-x64-msvc': 0.42.1 + '@astrojs/check@0.9.9(prettier@3.8.3)(typescript@6.0.3)': + dependencies: + '@astrojs/language-server': 2.16.8(prettier@3.8.3)(typescript@6.0.3) + chokidar: 4.0.3 + kleur: 4.1.5 + typescript: 6.0.3 + yargs: 17.7.2 + transitivePeerDependencies: + - prettier + - prettier-plugin-astro + + '@astrojs/compiler@2.13.1': {} + + '@astrojs/compiler@4.0.0': {} + + '@astrojs/internal-helpers@0.9.0': + dependencies: + picomatch: 4.0.4 + + '@astrojs/language-server@2.16.8(prettier@3.8.3)(typescript@6.0.3)': + dependencies: + '@astrojs/compiler': 2.13.1 + '@astrojs/yaml2ts': 0.2.3 + '@jridgewell/sourcemap-codec': 1.5.5 + '@volar/kit': 2.4.28(typescript@6.0.3) + '@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.70(@volar/language-service@2.4.28) + volar-service-emmet: 0.0.70(@volar/language-service@2.4.28) + volar-service-html: 0.0.70(@volar/language-service@2.4.28) + volar-service-prettier: 0.0.70(@volar/language-service@2.4.28)(prettier@3.8.3) + volar-service-typescript: 0.0.70(@volar/language-service@2.4.28) + volar-service-typescript-twoslash-queries: 0.0.70(@volar/language-service@2.4.28) + volar-service-yaml: 0.0.70(@volar/language-service@2.4.28) + vscode-html-languageservice: 5.6.2 + vscode-uri: 3.1.0 + optionalDependencies: + prettier: 3.8.3 + transitivePeerDependencies: + - typescript + + '@astrojs/markdown-remark@7.1.1': + dependencies: + '@astrojs/internal-helpers': 0.9.0 + '@astrojs/prism': 4.0.1 + github-slugger: 2.0.0 + hast-util-from-html: 2.0.3 + hast-util-to-text: 4.0.2 + js-yaml: 4.1.1 + mdast-util-definitions: 6.0.0 + rehype-raw: 7.0.0 + rehype-stringify: 10.0.1 + remark-gfm: 4.0.1 + remark-parse: 11.0.0 + remark-rehype: 11.1.2 + remark-smartypants: 3.0.2 + retext-smartypants: 6.2.0 + shiki: 4.0.2 + smol-toml: 1.6.1 + 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 + transitivePeerDependencies: + - supports-color + + '@astrojs/node@10.1.0(astro@6.3.1(@types/node@25.6.0)(ioredis@5.10.1)(jiti@2.7.0)(lightningcss@1.32.0)(rollup@4.60.3)(tsx@4.21.0)(yaml@2.8.4))': + dependencies: + '@astrojs/internal-helpers': 0.9.0 + astro: 6.3.1(@types/node@25.6.0)(ioredis@5.10.1)(jiti@2.7.0)(lightningcss@1.32.0)(rollup@4.60.3)(tsx@4.21.0)(yaml@2.8.4) + send: 1.2.1 + server-destroy: 1.0.1 + transitivePeerDependencies: + - supports-color + + '@astrojs/prism@4.0.1': + dependencies: + prismjs: 1.30.0 + + '@astrojs/react@5.0.4(@types/node@25.6.0)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(jiti@2.7.0)(lightningcss@1.32.0)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(tsx@4.21.0)(yaml@2.8.4)': + dependencies: + '@astrojs/internal-helpers': 0.9.0 + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) + '@vitejs/plugin-react': 5.2.0(vite@7.3.2(@types/node@25.6.0)(jiti@2.7.0)(lightningcss@1.32.0)(tsx@4.21.0)(yaml@2.8.4)) + devalue: 5.8.0 + react: 19.2.5 + react-dom: 19.2.5(react@19.2.5) + ultrahtml: 1.6.0 + vite: 7.3.2(@types/node@25.6.0)(jiti@2.7.0)(lightningcss@1.32.0)(tsx@4.21.0)(yaml@2.8.4) + transitivePeerDependencies: + - '@types/node' + - jiti + - less + - lightningcss + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + - tsx + - yaml + + '@astrojs/telemetry@3.3.2': + dependencies: + ci-info: 4.4.0 + dset: 3.1.4 + is-docker: 4.0.0 + is-wsl: 3.1.1 + which-pm-runs: 1.1.0 + + '@astrojs/yaml2ts@0.2.3': + dependencies: + yaml: 2.8.4 + '@aws-crypto/crc32@5.2.0': dependencies: '@aws-crypto/util': 5.2.0 @@ -7831,6 +9143,16 @@ snapshots: '@babel/core': 7.29.0 '@babel/helper-plugin-utils': 7.28.6 + '@babel/plugin-transform-react-jsx-self@7.27.1(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-react-jsx-source@7.27.1(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/runtime@7.29.2': {} '@babel/template@7.28.6': @@ -7862,6 +9184,10 @@ snapshots: dependencies: '@poppinss/utils': 7.0.1 + '@capsizecss/unpack@4.0.0': + dependencies: + fontkitten: 1.0.3 + '@chevrotain/cst-dts-gen@11.2.0': dependencies: '@chevrotain/gast': 11.2.0 @@ -7879,6 +9205,18 @@ snapshots: '@chevrotain/utils@11.2.0': {} + '@clack/core@1.3.0': + dependencies: + fast-wrap-ansi: 0.2.0 + sisteransi: 1.0.5 + + '@clack/prompts@1.3.0': + dependencies: + '@clack/core': 1.3.0 + fast-string-width: 3.0.2 + fast-wrap-ansi: 0.2.0 + sisteransi: 1.0.5 + '@colors/colors@1.5.0': optional: true @@ -7902,6 +9240,29 @@ snapshots: '@csstools/css-tokenizer@3.0.4': {} + '@emmetio/abbreviation@2.3.3': + dependencies: + '@emmetio/scanner': 1.0.4 + + '@emmetio/css-abbreviation@2.1.8': + dependencies: + '@emmetio/scanner': 1.0.4 + + '@emmetio/css-parser@0.4.1': + dependencies: + '@emmetio/stream-reader': 2.2.0 + '@emmetio/stream-reader-utils': 0.1.0 + + '@emmetio/html-matcher@1.3.0': + dependencies: + '@emmetio/scanner': 1.0.4 + + '@emmetio/scanner@1.0.4': {} + + '@emmetio/stream-reader-utils@0.1.0': {} + + '@emmetio/stream-reader@2.2.0': {} + '@emnapi/core@1.10.0': dependencies: '@emnapi/wasi-threads': 1.2.1 @@ -8123,6 +9484,103 @@ snapshots: '@humanwhocodes/retry@0.4.3': {} + '@img/colour@1.1.0': + optional: true + + '@img/sharp-darwin-arm64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-darwin-arm64': 1.2.4 + optional: true + + '@img/sharp-darwin-x64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-darwin-x64': 1.2.4 + optional: true + + '@img/sharp-libvips-darwin-arm64@1.2.4': + optional: true + + '@img/sharp-libvips-darwin-x64@1.2.4': + optional: true + + '@img/sharp-libvips-linux-arm64@1.2.4': + optional: true + + '@img/sharp-libvips-linux-arm@1.2.4': + optional: true + + '@img/sharp-libvips-linux-ppc64@1.2.4': + optional: true + + '@img/sharp-libvips-linux-riscv64@1.2.4': + optional: true + + '@img/sharp-libvips-linux-s390x@1.2.4': + optional: true + + '@img/sharp-libvips-linux-x64@1.2.4': + optional: true + + '@img/sharp-libvips-linuxmusl-arm64@1.2.4': + optional: true + + '@img/sharp-libvips-linuxmusl-x64@1.2.4': + optional: true + + '@img/sharp-linux-arm64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-arm64': 1.2.4 + optional: true + + '@img/sharp-linux-arm@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-arm': 1.2.4 + optional: true + + '@img/sharp-linux-ppc64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-ppc64': 1.2.4 + optional: true + + '@img/sharp-linux-riscv64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-riscv64': 1.2.4 + optional: true + + '@img/sharp-linux-s390x@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-s390x': 1.2.4 + optional: true + + '@img/sharp-linux-x64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-x64': 1.2.4 + optional: true + + '@img/sharp-linuxmusl-arm64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linuxmusl-arm64': 1.2.4 + optional: true + + '@img/sharp-linuxmusl-x64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linuxmusl-x64': 1.2.4 + optional: true + + '@img/sharp-wasm32@0.34.5': + dependencies: + '@emnapi/runtime': 1.10.0 + optional: true + + '@img/sharp-win32-arm64@0.34.5': + optional: true + + '@img/sharp-win32-ia32@0.34.5': + optional: true + + '@img/sharp-win32-x64@0.34.5': + optional: true + '@inquirer/ansi@2.0.5': {} '@inquirer/confirm@6.0.12(@types/node@24.12.2)': @@ -8528,6 +9986,8 @@ snapshots: '@opentelemetry/api': 1.9.1 '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.1) + '@oslojs/encoding@1.1.0': {} + '@oxc-project/types@0.127.0': {} '@paralleldrive/cuid2@2.3.1': @@ -9294,8 +10754,18 @@ snapshots: '@rolldown/pluginutils@1.0.0-rc.17': {} + '@rolldown/pluginutils@1.0.0-rc.3': {} + '@rolldown/pluginutils@1.0.0-rc.7': {} + '@rollup/pluginutils@5.3.0(rollup@4.60.3)': + dependencies: + '@types/estree': 1.0.8 + estree-walker: 2.0.2 + picomatch: 4.0.4 + optionalDependencies: + rollup: 4.60.3 + '@rollup/rollup-android-arm-eabi@4.60.3': optional: true @@ -9559,6 +11029,46 @@ snapshots: - rollup - supports-color + '@shikijs/core@4.0.2': + dependencies: + '@shikijs/primitive': 4.0.2 + '@shikijs/types': 4.0.2 + '@shikijs/vscode-textmate': 10.0.2 + '@types/hast': 3.0.4 + hast-util-to-html: 9.0.5 + + '@shikijs/engine-javascript@4.0.2': + dependencies: + '@shikijs/types': 4.0.2 + '@shikijs/vscode-textmate': 10.0.2 + oniguruma-to-es: 4.3.6 + + '@shikijs/engine-oniguruma@4.0.2': + dependencies: + '@shikijs/types': 4.0.2 + '@shikijs/vscode-textmate': 10.0.2 + + '@shikijs/langs@4.0.2': + dependencies: + '@shikijs/types': 4.0.2 + + '@shikijs/primitive@4.0.2': + dependencies: + '@shikijs/types': 4.0.2 + '@shikijs/vscode-textmate': 10.0.2 + '@types/hast': 3.0.4 + + '@shikijs/themes@4.0.2': + dependencies: + '@shikijs/types': 4.0.2 + + '@shikijs/types@4.0.2': + dependencies: + '@shikijs/vscode-textmate': 10.0.2 + '@types/hast': 3.0.4 + + '@shikijs/vscode-textmate@10.0.2': {} + '@sinclair/typebox@0.34.49': {} '@sindresorhus/is@7.2.0': {} @@ -10042,6 +11552,13 @@ snapshots: '@tailwindcss/oxide-win32-arm64-msvc': 4.2.4 '@tailwindcss/oxide-win32-x64-msvc': 4.2.4 + '@tailwindcss/vite@4.2.4(vite@7.3.2(@types/node@25.6.0)(jiti@2.7.0)(lightningcss@1.32.0)(tsx@4.21.0)(yaml@2.8.4))': + dependencies: + '@tailwindcss/node': 4.2.4 + '@tailwindcss/oxide': 4.2.4 + tailwindcss: 4.2.4 + vite: 7.3.2(@types/node@25.6.0)(jiti@2.7.0)(lightningcss@1.32.0)(tsx@4.21.0)(yaml@2.8.4) + '@tailwindcss/vite@4.2.4(vite@8.0.10(@types/node@24.12.2)(esbuild@0.27.7)(jiti@2.7.0)(tsx@4.21.0)(yaml@2.8.4))': dependencies: '@tailwindcss/node': 4.2.4 @@ -10272,6 +11789,27 @@ snapshots: '@types/aria-query@5.0.4': {} + '@types/babel__core@7.20.5': + dependencies: + '@babel/parser': 7.29.3 + '@babel/types': 7.29.0 + '@types/babel__generator': 7.27.0 + '@types/babel__template': 7.4.4 + '@types/babel__traverse': 7.28.0 + + '@types/babel__generator@7.27.0': + dependencies: + '@babel/types': 7.29.0 + + '@types/babel__template@7.4.4': + dependencies: + '@babel/parser': 7.29.3 + '@babel/types': 7.29.0 + + '@types/babel__traverse@7.28.0': + dependencies: + '@babel/types': 7.29.0 + '@types/chai@5.2.3': dependencies: '@types/deep-eql': 4.0.2 @@ -10307,24 +11845,42 @@ snapshots: '@types/d3-timer@3.0.2': {} + '@types/debug@4.1.13': + dependencies: + '@types/ms': 2.1.0 + '@types/deep-eql@4.0.2': {} '@types/esrecurse@4.3.1': {} '@types/estree@1.0.8': {} + '@types/hast@3.0.4': + dependencies: + '@types/unist': 3.0.3 + '@types/he@1.2.3': {} '@types/json-schema@7.0.15': {} '@types/luxon@3.7.1': {} + '@types/mdast@4.0.4': + dependencies: + '@types/unist': 3.0.3 + '@types/methods@1.1.4': {} + '@types/ms@2.1.0': {} + '@types/mysql@2.15.27': dependencies: '@types/node': 25.6.0 + '@types/nlcst@2.0.3': + dependencies: + '@types/unist': 3.0.3 + '@types/node@22.19.17': dependencies: undici-types: 6.21.0 @@ -10386,6 +11942,8 @@ snapshots: dependencies: '@types/node': 25.6.0 + '@types/unist@3.0.3': {} + '@types/use-sync-external-store@0.0.6': {} '@types/validator@13.15.10': {} @@ -10481,6 +12039,8 @@ snapshots: '@typescript-eslint/types': 8.59.2 eslint-visitor-keys: 5.0.1 + '@ungap/structured-clone@1.3.1': {} + '@vinejs/compiler@4.1.3': {} '@vinejs/vine@4.4.0': @@ -10496,6 +12056,18 @@ snapshots: normalize-url: 8.1.1 validator: 13.15.35 + '@vitejs/plugin-react@5.2.0(vite@7.3.2(@types/node@25.6.0)(jiti@2.7.0)(lightningcss@1.32.0)(tsx@4.21.0)(yaml@2.8.4))': + dependencies: + '@babel/core': 7.29.0 + '@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-react-jsx-source': 7.27.1(@babel/core@7.29.0) + '@rolldown/pluginutils': 1.0.0-rc.3 + '@types/babel__core': 7.20.5 + react-refresh: 0.18.0 + vite: 7.3.2(@types/node@25.6.0)(jiti@2.7.0)(lightningcss@1.32.0)(tsx@4.21.0)(yaml@2.8.4) + transitivePeerDependencies: + - supports-color + '@vitejs/plugin-react@6.0.1(vite@8.0.10(@types/node@24.12.2)(esbuild@0.27.7)(jiti@2.7.0)(tsx@4.21.0)(yaml@2.8.4))': dependencies: '@rolldown/pluginutils': 1.0.0-rc.7 @@ -10544,6 +12116,56 @@ snapshots: loupe: 3.2.1 tinyrainbow: 2.0.0 + '@volar/kit@2.4.28(typescript@6.0.3)': + dependencies: + '@volar/language-service': 2.4.28 + '@volar/typescript': 2.4.28 + typesafe-path: 0.2.2 + typescript: 6.0.3 + vscode-languageserver-textdocument: 1.0.12 + vscode-uri: 3.1.0 + + '@volar/language-core@2.4.28': + dependencies: + '@volar/source-map': 2.4.28 + + '@volar/language-server@2.4.28': + 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.12 + vscode-uri: 3.1.0 + + '@volar/language-service@2.4.28': + dependencies: + '@volar/language-core': 2.4.28 + vscode-languageserver-protocol: 3.17.5 + vscode-languageserver-textdocument: 1.0.12 + vscode-uri: 3.1.0 + + '@volar/source-map@2.4.28': {} + + '@volar/typescript@2.4.28': + dependencies: + '@volar/language-core': 2.4.28 + path-browserify: 1.0.1 + vscode-uri: 3.1.0 + + '@vscode/emmet-helper@2.11.0': + dependencies: + emmet: 2.4.11 + jsonc-parser: 2.3.1 + vscode-languageserver-textdocument: 1.0.12 + vscode-languageserver-types: 3.17.5 + vscode-uri: 3.1.0 + + '@vscode/l10n@0.0.18': {} + abs-svg-path@0.1.1: {} abstract-logging@2.0.1: {} @@ -10566,6 +12188,10 @@ snapshots: agent-base@7.1.4: {} + ajv-draft-04@1.0.0(ajv@8.20.0): + optionalDependencies: + ajv: 8.20.0 + ajv@6.15.0: dependencies: fast-deep-equal: 3.1.3 @@ -10573,6 +12199,13 @@ snapshots: json-schema-traverse: 0.4.1 uri-js: 4.4.1 + ajv@8.20.0: + dependencies: + fast-deep-equal: 3.1.3 + fast-uri: 3.1.2 + json-schema-traverse: 1.0.0 + require-from-string: 2.0.2 + ansi-colors@4.1.3: {} ansi-escapes@7.3.0: @@ -10610,10 +12243,105 @@ snapshots: aria-query@5.3.2: {} + array-iterate@2.0.1: {} + asap@2.0.6: {} assertion-error@2.0.1: {} + astro@6.3.1(@types/node@25.6.0)(ioredis@5.10.1)(jiti@2.7.0)(lightningcss@1.32.0)(rollup@4.60.3)(tsx@4.21.0)(yaml@2.8.4): + dependencies: + '@astrojs/compiler': 4.0.0 + '@astrojs/internal-helpers': 0.9.0 + '@astrojs/markdown-remark': 7.1.1 + '@astrojs/telemetry': 3.3.2 + '@capsizecss/unpack': 4.0.0 + '@clack/prompts': 1.3.0 + '@oslojs/encoding': 1.1.0 + '@rollup/pluginutils': 5.3.0(rollup@4.60.3) + 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: 1.1.1 + devalue: 5.8.0 + diff: 8.0.4 + dset: 3.1.4 + es-module-lexer: 2.1.0 + esbuild: 0.27.7 + 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.1.1 + jsonc-parser: 3.3.1 + magic-string: 0.30.21 + magicast: 0.5.2 + mrmime: 2.0.1 + neotraverse: 0.6.18 + obug: 2.1.1 + p-limit: 7.3.0 + p-queue: 9.2.0 + package-manager-detector: 1.6.0 + piccolore: 0.1.3 + picomatch: 4.0.4 + rehype: 13.0.2 + semver: 7.7.4 + shiki: 4.0.2 + smol-toml: 1.6.1 + svgo: 4.0.1 + tinyclip: 0.1.12 + tinyexec: 1.1.2 + tinyglobby: 0.2.16 + ultrahtml: 1.6.0 + unifont: 0.7.4 + unist-util-visit: 5.1.0 + unstorage: 1.17.5(ioredis@5.10.1) + vfile: 6.0.3 + vite: 7.3.2(@types/node@25.6.0)(jiti@2.7.0)(lightningcss@1.32.0)(tsx@4.21.0)(yaml@2.8.4) + vitefu: 1.1.3(vite@7.3.2(@types/node@25.6.0)(jiti@2.7.0)(lightningcss@1.32.0)(tsx@4.21.0)(yaml@2.8.4)) + xxhash-wasm: 1.1.0 + yargs-parser: 22.0.0 + zod: 4.4.3 + optionalDependencies: + sharp: 0.34.5 + transitivePeerDependencies: + - '@azure/app-configuration' + - '@azure/cosmos' + - '@azure/data-tables' + - '@azure/identity' + - '@azure/keyvault-secrets' + - '@azure/storage-blob' + - '@capacitor/preferences' + - '@deno/kv' + - '@netlify/blobs' + - '@planetscale/database' + - '@types/node' + - '@upstash/redis' + - '@vercel/blob' + - '@vercel/functions' + - '@vercel/kv' + - aws4fetch + - db0 + - idb-keyval + - ioredis + - jiti + - less + - lightningcss + - rollup + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + - tsx + - uploadthing + - yaml + async-retry@1.3.3: dependencies: retry: 0.13.1 @@ -10622,6 +12350,8 @@ snapshots: atomic-sleep@1.0.0: {} + axobject-query@4.1.0: {} + babel-dead-code-elimination@1.0.12: dependencies: '@babel/core': 7.29.0 @@ -10631,6 +12361,8 @@ snapshots: transitivePeerDependencies: - supports-color + bail@2.0.2: {} + balanced-match@1.0.2: {} balanced-match@4.0.4: {} @@ -10666,6 +12398,8 @@ snapshots: inherits: 2.0.4 readable-stream: 3.6.2 + boolbase@1.0.0: {} + bowser@2.14.1: {} brace-expansion@1.1.14: @@ -10741,6 +12475,8 @@ snapshots: case-anything@3.1.2: {} + ccount@2.0.1: {} + chai@5.3.3: dependencies: assertion-error: 2.0.1 @@ -10760,6 +12496,12 @@ snapshots: change-case@5.4.4: {} + character-entities-html4@2.1.0: {} + + character-entities-legacy@3.0.0: {} + + character-entities@2.0.2: {} + check-disk-space@3.4.0: {} check-error@2.1.3: {} @@ -10785,6 +12527,10 @@ snapshots: optionalDependencies: fsevents: 2.3.3 + chokidar@4.0.3: + dependencies: + readdirp: 4.1.2 + chokidar@5.0.0: dependencies: readdirp: 5.0.0 @@ -10861,10 +12607,16 @@ snapshots: dependencies: delayed-stream: 1.0.0 + comma-separated-tokens@2.0.3: {} + commander@10.0.1: {} + commander@11.1.0: {} + commander@13.1.0: {} + common-ancestor-path@2.0.0: {} + common-path-prefix@3.0.0: {} component-emitter@1.3.1: {} @@ -10877,6 +12629,8 @@ snapshots: convert-source-map@2.0.0: {} + cookie-es@1.2.3: {} + cookie-es@2.0.1: {} cookie-es@3.1.1: {} @@ -10899,14 +12653,42 @@ snapshots: shebang-command: 2.0.0 which: 2.0.2 + crossws@0.3.5: + dependencies: + uncrypto: 0.1.3 + csrf@3.1.0: dependencies: rndm: 1.2.0 tsscmp: 1.0.6 uid-safe: 2.1.5 + css-select@5.2.2: + dependencies: + boolbase: 1.0.0 + css-what: 6.2.2 + domhandler: 5.0.3 + domutils: 3.2.2 + nth-check: 2.1.1 + + css-tree@2.2.1: + dependencies: + mdn-data: 2.0.28 + source-map-js: 1.2.1 + + css-tree@3.2.1: + dependencies: + mdn-data: 2.27.1 + source-map-js: 1.2.1 + + css-what@6.2.2: {} + css.escape@1.5.1: {} + csso@5.0.5: + dependencies: + css-tree: 2.2.1 + cssstyle@4.6.0: dependencies: '@asamuzakjp/css-color': 3.2.0 @@ -10975,6 +12757,10 @@ snapshots: decimal.js@10.6.0: {} + decode-named-character-reference@1.3.0: + dependencies: + character-entities: 2.0.2 + decompress-response@6.0.0: dependencies: mimic-response: 3.1.0 @@ -10998,6 +12784,8 @@ snapshots: define-lazy-prop@3.0.0: {} + defu@6.1.7: {} + delayed-stream@1.0.0: {} denque@2.1.0: {} @@ -11006,12 +12794,20 @@ snapshots: dequal@2.0.3: {} + destr@2.0.5: {} + destroy@1.2.0: {} detect-libc@2.1.2: {} detect-node-es@1.1.0: {} + devalue@5.8.0: {} + + devlop@1.1.0: + dependencies: + dequal: 2.0.3 + dezalgo@1.0.4: dependencies: asap: 2.0.6 @@ -11047,6 +12843,8 @@ snapshots: dotenv@16.6.1: {} + dset@3.1.4: {} + dunder-proto@1.0.1: dependencies: call-bind-apply-helpers: 1.0.2 @@ -11063,6 +12861,11 @@ snapshots: emittery@1.2.1: {} + emmet@2.4.11: + dependencies: + '@emmetio/abbreviation': 2.3.3 + '@emmetio/css-abbreviation': 2.1.8 + emoji-regex-xs@1.0.0: {} emoji-regex@10.6.0: {} @@ -11099,6 +12902,8 @@ snapshots: es-module-lexer@1.7.0: {} + es-module-lexer@2.1.0: {} + es-object-atoms@1.1.1: dependencies: es-errors: 1.3.0 @@ -11149,6 +12954,8 @@ snapshots: escape-string-regexp@4.0.0: {} + escape-string-regexp@5.0.0: {} + eslint-config-prettier@10.1.8(eslint@10.3.0(jiti@2.7.0)): dependencies: eslint: 10.3.0(jiti@2.7.0) @@ -11317,6 +13124,8 @@ snapshots: estraverse@5.3.0: {} + estree-walker@2.0.2: {} + estree-walker@3.0.3: dependencies: '@types/estree': 1.0.8 @@ -11360,6 +13169,8 @@ snapshots: expect-type@1.3.0: {} + extend@3.0.2: {} + fast-copy@4.0.3: {} fast-deep-equal@3.1.3: {} @@ -11386,6 +13197,8 @@ snapshots: dependencies: fast-string-truncated-width: 3.0.3 + fast-uri@3.1.2: {} + fast-wrap-ansi@0.2.0: dependencies: fast-string-width: 3.0.2 @@ -11467,6 +13280,10 @@ snapshots: '@aws-sdk/client-s3': 3.1043.0 '@aws-sdk/s3-request-presigner': 3.1043.0 + fontace@0.4.1: + dependencies: + fontkitten: 1.0.3 + fontkit@2.0.4: dependencies: '@swc/helpers': 0.5.21 @@ -11479,6 +13296,10 @@ snapshots: unicode-properties: 1.4.1 unicode-trie: 2.0.0 + fontkitten@1.0.3: + dependencies: + tiny-inflate: 1.0.3 + form-data@4.0.5: dependencies: asynckit: 0.4.0 @@ -11549,10 +13370,16 @@ snapshots: dependencies: resolve-pkg-maps: 1.0.0 + get-tsconfig@5.0.0-beta.4: + dependencies: + resolve-pkg-maps: 1.0.0 + getopts@2.3.0: {} github-from-package@0.0.0: {} + github-slugger@2.0.0: {} + glob-parent@5.1.2: dependencies: is-glob: 4.0.3 @@ -11583,6 +13410,18 @@ snapshots: graphql@16.13.2: {} + h3@1.15.11: + dependencies: + cookie-es: 1.2.3 + crossws: 0.3.5 + defu: 6.1.7 + destr: 2.0.5 + iron-webcrypto: 1.2.1 + node-mock-http: 1.0.4 + radix3: 1.1.2 + ufo: 1.6.4 + uncrypto: 0.1.3 + has-flag@4.0.0: {} has-symbols@1.1.0: {} @@ -11595,6 +13434,93 @@ snapshots: dependencies: function-bind: 1.1.2 + hast-util-from-html@2.0.3: + dependencies: + '@types/hast': 3.0.4 + devlop: 1.1.0 + hast-util-from-parse5: 8.0.3 + parse5: 7.3.0 + vfile: 6.0.3 + vfile-message: 4.0.3 + + hast-util-from-parse5@8.0.3: + dependencies: + '@types/hast': 3.0.4 + '@types/unist': 3.0.3 + devlop: 1.1.0 + hastscript: 9.0.1 + property-information: 7.1.0 + vfile: 6.0.3 + vfile-location: 5.0.3 + web-namespaces: 2.0.1 + + hast-util-is-element@3.0.0: + dependencies: + '@types/hast': 3.0.4 + + hast-util-parse-selector@4.0.0: + dependencies: + '@types/hast': 3.0.4 + + hast-util-raw@9.1.0: + dependencies: + '@types/hast': 3.0.4 + '@types/unist': 3.0.3 + '@ungap/structured-clone': 1.3.1 + hast-util-from-parse5: 8.0.3 + hast-util-to-parse5: 8.0.1 + html-void-elements: 3.0.0 + mdast-util-to-hast: 13.2.1 + parse5: 7.3.0 + unist-util-position: 5.0.0 + unist-util-visit: 5.1.0 + vfile: 6.0.3 + web-namespaces: 2.0.1 + zwitch: 2.0.4 + + hast-util-to-html@9.0.5: + dependencies: + '@types/hast': 3.0.4 + '@types/unist': 3.0.3 + ccount: 2.0.1 + comma-separated-tokens: 2.0.3 + hast-util-whitespace: 3.0.0 + html-void-elements: 3.0.0 + mdast-util-to-hast: 13.2.1 + property-information: 7.1.0 + space-separated-tokens: 2.0.2 + stringify-entities: 4.0.4 + zwitch: 2.0.4 + + hast-util-to-parse5@8.0.1: + dependencies: + '@types/hast': 3.0.4 + comma-separated-tokens: 2.0.3 + devlop: 1.1.0 + property-information: 7.1.0 + space-separated-tokens: 2.0.2 + web-namespaces: 2.0.1 + zwitch: 2.0.4 + + hast-util-to-text@4.0.2: + dependencies: + '@types/hast': 3.0.4 + '@types/unist': 3.0.3 + hast-util-is-element: 3.0.0 + unist-util-find-after: 5.0.0 + + hast-util-whitespace@3.0.0: + dependencies: + '@types/hast': 3.0.4 + + hastscript@9.0.1: + dependencies: + '@types/hast': 3.0.4 + comma-separated-tokens: 2.0.3 + hast-util-parse-selector: 4.0.0 + property-information: 7.1.0 + space-separated-tokens: 2.0.2 + he@1.2.0: {} headers-polyfill@5.0.1: @@ -11632,6 +13558,8 @@ snapshots: dependencies: whatwg-encoding: 3.1.1 + html-escaper@3.0.3: {} + html-to-text@9.0.5: dependencies: '@selderee/plugin-htmlparser2': 0.11.0 @@ -11640,6 +13568,8 @@ snapshots: htmlparser2: 8.0.2 selderee: 0.11.0 + html-void-elements@3.0.0: {} + htmlparser2@8.0.2: dependencies: domelementtype: 2.3.0 @@ -11647,6 +13577,8 @@ snapshots: domutils: 3.2.2 entities: 4.5.0 + http-cache-semantics@4.2.0: {} + http-errors@2.0.1: dependencies: depd: 2.0.0 @@ -11766,6 +13698,8 @@ snapshots: ipaddr.js@1.9.1: {} + iron-webcrypto@1.2.1: {} + is-binary-path@2.1.0: dependencies: binary-extensions: 2.3.0 @@ -11780,6 +13714,8 @@ snapshots: is-docker@3.0.0: {} + is-docker@4.0.0: {} + is-extglob@2.1.1: {} is-fullwidth-code-point@3.0.0: {} @@ -11882,10 +13818,16 @@ snapshots: json-schema-traverse@0.4.1: {} + json-schema-traverse@1.0.0: {} + json-stable-stringify-without-jsonify@1.0.1: {} json5@2.2.3: {} + jsonc-parser@2.3.1: {} + + jsonc-parser@3.3.1: {} + jsonschema@1.5.0: {} junk@4.0.1: {} @@ -12074,6 +14016,8 @@ snapshots: strip-ansi: 7.2.0 wrap-ansi: 10.0.0 + longest-streak@3.1.0: {} + loose-envify@1.4.0: dependencies: js-tokens: 4.0.0 @@ -12100,10 +14044,142 @@ snapshots: dependencies: '@jridgewell/sourcemap-codec': 1.5.5 + magicast@0.5.2: + dependencies: + '@babel/parser': 7.29.3 + '@babel/types': 7.29.0 + source-map-js: 1.2.1 + + markdown-table@3.0.4: {} + marked@15.0.12: {} math-intrinsics@1.1.0: {} + mdast-util-definitions@6.0.0: + dependencies: + '@types/mdast': 4.0.4 + '@types/unist': 3.0.3 + unist-util-visit: 5.1.0 + + mdast-util-find-and-replace@3.0.2: + dependencies: + '@types/mdast': 4.0.4 + escape-string-regexp: 5.0.0 + unist-util-is: 6.0.1 + unist-util-visit-parents: 6.0.2 + + mdast-util-from-markdown@2.0.3: + dependencies: + '@types/mdast': 4.0.4 + '@types/unist': 3.0.3 + decode-named-character-reference: 1.3.0 + devlop: 1.1.0 + mdast-util-to-string: 4.0.0 + micromark: 4.0.2 + micromark-util-decode-numeric-character-reference: 2.0.2 + micromark-util-decode-string: 2.0.1 + micromark-util-normalize-identifier: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + unist-util-stringify-position: 4.0.0 + transitivePeerDependencies: + - supports-color + + mdast-util-gfm-autolink-literal@2.0.1: + dependencies: + '@types/mdast': 4.0.4 + ccount: 2.0.1 + devlop: 1.1.0 + mdast-util-find-and-replace: 3.0.2 + micromark-util-character: 2.1.1 + + mdast-util-gfm-footnote@2.1.0: + dependencies: + '@types/mdast': 4.0.4 + devlop: 1.1.0 + mdast-util-from-markdown: 2.0.3 + mdast-util-to-markdown: 2.1.2 + micromark-util-normalize-identifier: 2.0.1 + transitivePeerDependencies: + - supports-color + + mdast-util-gfm-strikethrough@2.0.0: + dependencies: + '@types/mdast': 4.0.4 + mdast-util-from-markdown: 2.0.3 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-gfm-table@2.0.0: + dependencies: + '@types/mdast': 4.0.4 + devlop: 1.1.0 + markdown-table: 3.0.4 + mdast-util-from-markdown: 2.0.3 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-gfm-task-list-item@2.0.0: + dependencies: + '@types/mdast': 4.0.4 + devlop: 1.1.0 + mdast-util-from-markdown: 2.0.3 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-gfm@3.1.0: + dependencies: + mdast-util-from-markdown: 2.0.3 + mdast-util-gfm-autolink-literal: 2.0.1 + mdast-util-gfm-footnote: 2.1.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.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-phrasing@4.1.0: + dependencies: + '@types/mdast': 4.0.4 + unist-util-is: 6.0.1 + + mdast-util-to-hast@13.2.1: + dependencies: + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 + '@ungap/structured-clone': 1.3.1 + devlop: 1.1.0 + micromark-util-sanitize-uri: 2.0.1 + trim-lines: 3.0.1 + unist-util-position: 5.0.0 + unist-util-visit: 5.1.0 + vfile: 6.0.3 + + mdast-util-to-markdown@2.1.2: + dependencies: + '@types/mdast': 4.0.4 + '@types/unist': 3.0.3 + longest-streak: 3.1.0 + mdast-util-phrasing: 4.1.0 + mdast-util-to-string: 4.0.0 + micromark-util-classify-character: 2.0.1 + micromark-util-decode-string: 2.0.1 + unist-util-visit: 5.1.0 + zwitch: 2.0.4 + + mdast-util-to-string@4.0.0: + dependencies: + '@types/mdast': 4.0.4 + + mdn-data@2.0.28: {} + + mdn-data@2.27.1: {} + media-engine@1.0.3: {} media-typer@1.1.0: {} @@ -12114,6 +14190,197 @@ snapshots: methods@1.1.2: {} + micromark-core-commonmark@2.0.3: + dependencies: + decode-named-character-reference: 1.3.0 + devlop: 1.1.0 + micromark-factory-destination: 2.0.1 + micromark-factory-label: 2.0.1 + micromark-factory-space: 2.0.1 + micromark-factory-title: 2.0.1 + micromark-factory-whitespace: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-chunked: 2.0.1 + micromark-util-classify-character: 2.0.1 + micromark-util-html-tag-name: 2.0.1 + micromark-util-normalize-identifier: 2.0.1 + micromark-util-resolve-all: 2.0.1 + micromark-util-subtokenize: 2.1.0 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-gfm-autolink-literal@2.1.0: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-sanitize-uri: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-gfm-footnote@2.1.0: + dependencies: + devlop: 1.1.0 + micromark-core-commonmark: 2.0.3 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-normalize-identifier: 2.0.1 + micromark-util-sanitize-uri: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-gfm-strikethrough@2.1.0: + dependencies: + devlop: 1.1.0 + micromark-util-chunked: 2.0.1 + micromark-util-classify-character: 2.0.1 + micromark-util-resolve-all: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-gfm-table@2.1.1: + dependencies: + devlop: 1.1.0 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-gfm-tagfilter@2.0.0: + dependencies: + micromark-util-types: 2.0.2 + + micromark-extension-gfm-task-list-item@2.1.0: + dependencies: + devlop: 1.1.0 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-gfm@3.0.0: + dependencies: + micromark-extension-gfm-autolink-literal: 2.1.0 + micromark-extension-gfm-footnote: 2.1.0 + micromark-extension-gfm-strikethrough: 2.1.0 + micromark-extension-gfm-table: 2.1.1 + micromark-extension-gfm-tagfilter: 2.0.0 + micromark-extension-gfm-task-list-item: 2.1.0 + micromark-util-combine-extensions: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-factory-destination@2.0.1: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-factory-label@2.0.1: + dependencies: + devlop: 1.1.0 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-factory-space@2.0.1: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-types: 2.0.2 + + micromark-factory-title@2.0.1: + dependencies: + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-factory-whitespace@2.0.1: + dependencies: + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-util-character@2.1.1: + dependencies: + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-util-chunked@2.0.1: + dependencies: + micromark-util-symbol: 2.0.1 + + micromark-util-classify-character@2.0.1: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-util-combine-extensions@2.0.1: + dependencies: + micromark-util-chunked: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-util-decode-numeric-character-reference@2.0.2: + dependencies: + micromark-util-symbol: 2.0.1 + + micromark-util-decode-string@2.0.1: + dependencies: + decode-named-character-reference: 1.3.0 + micromark-util-character: 2.1.1 + micromark-util-decode-numeric-character-reference: 2.0.2 + micromark-util-symbol: 2.0.1 + + micromark-util-encode@2.0.1: {} + + micromark-util-html-tag-name@2.0.1: {} + + micromark-util-normalize-identifier@2.0.1: + dependencies: + micromark-util-symbol: 2.0.1 + + micromark-util-resolve-all@2.0.1: + dependencies: + micromark-util-types: 2.0.2 + + micromark-util-sanitize-uri@2.0.1: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-encode: 2.0.1 + micromark-util-symbol: 2.0.1 + + micromark-util-subtokenize@2.1.0: + dependencies: + devlop: 1.1.0 + micromark-util-chunked: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-util-symbol@2.0.1: {} + + micromark-util-types@2.0.2: {} + + micromark@4.0.2: + dependencies: + '@types/debug': 4.1.13 + debug: 4.4.3 + decode-named-character-reference: 1.3.0 + devlop: 1.1.0 + micromark-core-commonmark: 2.0.3 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-chunked: 2.0.1 + micromark-util-combine-extensions: 2.0.1 + micromark-util-decode-numeric-character-reference: 2.0.2 + micromark-util-encode: 2.0.1 + micromark-util-normalize-identifier: 2.0.1 + micromark-util-resolve-all: 2.0.1 + micromark-util-sanitize-uri: 2.0.1 + micromark-util-subtokenize: 2.1.0 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + transitivePeerDependencies: + - supports-color + micromatch@4.0.8: dependencies: braces: 3.0.3 @@ -12157,6 +14424,8 @@ snapshots: module-details-from-path@1.0.4: {} + mrmime@2.0.1: {} + ms@2.1.2: {} ms@2.1.3: {} @@ -12202,6 +14471,8 @@ snapshots: transitivePeerDependencies: - '@types/node' + muggle-string@0.4.1: {} + mute-stream@3.0.0: {} nanoid@3.3.12: {} @@ -12210,12 +14481,20 @@ snapshots: natural-compare@1.4.0: {} + neotraverse@0.6.18: {} + + nlcst-to-string@4.0.0: + dependencies: + '@types/nlcst': 2.0.3 + node-abi@3.91.0: dependencies: semver: 7.7.4 node-abort-controller@3.1.1: {} + node-fetch-native@1.6.7: {} + node-fetch@2.7.0: dependencies: whatwg-url: 5.0.0 @@ -12225,6 +14504,8 @@ snapshots: detect-libc: 2.1.2 optional: true + node-mock-http@1.0.4: {} + node-releases@2.0.38: {} nodemailer@8.0.7: {} @@ -12252,6 +14533,10 @@ snapshots: path-key: 4.0.0 unicorn-magic: 0.3.0 + nth-check@2.1.1: + dependencies: + boolbase: 1.0.0 + nwsapi@2.2.23: {} object-assign@4.1.1: {} @@ -12260,6 +14545,16 @@ snapshots: object-to-formdata@4.5.1: {} + obug@2.1.1: {} + + ofetch@1.5.1: + dependencies: + destr: 2.0.5 + node-fetch-native: 1.6.7 + ufo: 1.6.4 + + ohash@2.0.11: {} + on-exit-leak-free@2.1.2: {} on-finished@2.4.1: @@ -12278,6 +14573,14 @@ snapshots: dependencies: mimic-function: 5.0.1 + oniguruma-parser@0.12.2: {} + + oniguruma-to-es@4.3.6: + dependencies: + oniguruma-parser: 0.12.2 + regex: 6.1.0 + regex-recursion: 6.0.2 + open@11.0.0: dependencies: default-browser: 5.5.0 @@ -12302,10 +14605,21 @@ snapshots: dependencies: yocto-queue: 0.1.0 + p-limit@7.3.0: + dependencies: + yocto-queue: 1.2.2 + p-locate@5.0.0: dependencies: p-limit: 3.1.0 + p-queue@9.2.0: + dependencies: + eventemitter3: 5.0.4 + p-timeout: 7.0.1 + + p-timeout@7.0.1: {} + package-manager-detector@1.6.0: {} pako@0.2.9: {} @@ -12327,6 +14641,15 @@ snapshots: index-to-position: 1.2.0 type-fest: 4.41.0 + parse-latin@7.0.0: + dependencies: + '@types/nlcst': 2.0.3 + '@types/unist': 3.0.3 + nlcst-to-string: 4.0.0 + unist-util-modify-children: 4.0.0 + unist-util-visit-children: 3.0.0 + vfile: 6.0.3 + parse-ms@4.0.0: {} parse-svg-path@0.1.2: {} @@ -12404,6 +14727,8 @@ snapshots: dependencies: split2: 4.2.0 + piccolore@0.1.3: {} + picocolors@1.1.1: {} picomatch@2.3.2: {} @@ -12538,6 +14863,8 @@ snapshots: object-assign: 4.1.1 react-is: 16.13.1 + property-information@7.1.0: {} + proxy-addr@2.0.7: dependencies: forwarded: 0.2.0 @@ -12564,6 +14891,8 @@ snapshots: quick-format-unescaped@4.0.4: {} + radix3@1.1.2: {} + random-bytes@1.0.0: {} range-parser@1.2.1: {} @@ -12604,6 +14933,8 @@ snapshots: '@types/react': 19.2.14 redux: 5.0.1 + react-refresh@0.18.0: {} + react-remove-scroll-bar@2.3.8(@types/react@19.2.14)(react@19.2.5): dependencies: react: 19.2.5 @@ -12657,6 +14988,8 @@ snapshots: dependencies: picomatch: 2.3.2 + readdirp@4.1.2: {} + readdirp@5.0.0: {} real-require@0.2.0: {} @@ -12704,12 +15037,92 @@ snapshots: reflect-metadata@0.2.2: {} + regex-recursion@6.0.2: + dependencies: + regex-utilities: 2.3.0 + + regex-utilities@2.3.0: {} + + regex@6.1.0: + dependencies: + regex-utilities: 2.3.0 + regexp-tree@0.1.27: {} regjsparser@0.13.1: dependencies: jsesc: 3.1.0 + rehype-parse@9.0.1: + dependencies: + '@types/hast': 3.0.4 + hast-util-from-html: 2.0.3 + unified: 11.0.5 + + rehype-raw@7.0.0: + dependencies: + '@types/hast': 3.0.4 + hast-util-raw: 9.1.0 + vfile: 6.0.3 + + rehype-stringify@10.0.1: + dependencies: + '@types/hast': 3.0.4 + hast-util-to-html: 9.0.5 + unified: 11.0.5 + + rehype@13.0.2: + dependencies: + '@types/hast': 3.0.4 + rehype-parse: 9.0.1 + rehype-stringify: 10.0.1 + unified: 11.0.5 + + remark-gfm@4.0.1: + dependencies: + '@types/mdast': 4.0.4 + mdast-util-gfm: 3.1.0 + micromark-extension-gfm: 3.0.0 + remark-parse: 11.0.0 + remark-stringify: 11.0.0 + unified: 11.0.5 + transitivePeerDependencies: + - supports-color + + remark-parse@11.0.0: + dependencies: + '@types/mdast': 4.0.4 + mdast-util-from-markdown: 2.0.3 + micromark-util-types: 2.0.2 + unified: 11.0.5 + transitivePeerDependencies: + - supports-color + + remark-rehype@11.1.2: + dependencies: + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 + mdast-util-to-hast: 13.2.1 + unified: 11.0.5 + vfile: 6.0.3 + + remark-smartypants@3.0.2: + dependencies: + retext: 9.0.0 + retext-smartypants: 6.2.0 + unified: 11.0.5 + unist-util-visit: 5.1.0 + + remark-stringify@11.0.0: + dependencies: + '@types/mdast': 4.0.4 + mdast-util-to-markdown: 2.1.2 + unified: 11.0.5 + + request-light@0.5.8: {} + + request-light@0.7.0: {} + require-directory@2.1.1: {} require-from-string@2.0.2: {} @@ -12743,6 +15156,31 @@ snapshots: restructure@3.0.2: {} + retext-latin@4.0.0: + dependencies: + '@types/nlcst': 2.0.3 + parse-latin: 7.0.0 + unified: 11.0.5 + + retext-smartypants@6.2.0: + dependencies: + '@types/nlcst': 2.0.3 + nlcst-to-string: 4.0.0 + unist-util-visit: 5.1.0 + + retext-stringify@4.0.0: + dependencies: + '@types/nlcst': 2.0.3 + nlcst-to-string: 4.0.0 + unified: 11.0.5 + + retext@9.0.0: + dependencies: + '@types/nlcst': 2.0.3 + retext-latin: 4.0.0 + retext-stringify: 4.0.0 + unified: 11.0.5 + retry@0.13.1: {} rettime@0.11.11: {} @@ -12821,6 +15259,8 @@ snapshots: safer-buffer@2.1.2: {} + sax@1.6.0: {} + saxes@6.0.0: dependencies: xmlchars: 2.2.0 @@ -12870,18 +15310,63 @@ snapshots: transitivePeerDependencies: - supports-color + server-destroy@1.0.1: {} + set-cookie-parser@2.7.2: {} set-cookie-parser@3.1.0: {} setprototypeof@1.2.0: {} + sharp@0.34.5: + dependencies: + '@img/colour': 1.1.0 + detect-libc: 2.1.2 + semver: 7.7.4 + optionalDependencies: + '@img/sharp-darwin-arm64': 0.34.5 + '@img/sharp-darwin-x64': 0.34.5 + '@img/sharp-libvips-darwin-arm64': 1.2.4 + '@img/sharp-libvips-darwin-x64': 1.2.4 + '@img/sharp-libvips-linux-arm': 1.2.4 + '@img/sharp-libvips-linux-arm64': 1.2.4 + '@img/sharp-libvips-linux-ppc64': 1.2.4 + '@img/sharp-libvips-linux-riscv64': 1.2.4 + '@img/sharp-libvips-linux-s390x': 1.2.4 + '@img/sharp-libvips-linux-x64': 1.2.4 + '@img/sharp-libvips-linuxmusl-arm64': 1.2.4 + '@img/sharp-libvips-linuxmusl-x64': 1.2.4 + '@img/sharp-linux-arm': 0.34.5 + '@img/sharp-linux-arm64': 0.34.5 + '@img/sharp-linux-ppc64': 0.34.5 + '@img/sharp-linux-riscv64': 0.34.5 + '@img/sharp-linux-s390x': 0.34.5 + '@img/sharp-linux-x64': 0.34.5 + '@img/sharp-linuxmusl-arm64': 0.34.5 + '@img/sharp-linuxmusl-x64': 0.34.5 + '@img/sharp-wasm32': 0.34.5 + '@img/sharp-win32-arm64': 0.34.5 + '@img/sharp-win32-ia32': 0.34.5 + '@img/sharp-win32-x64': 0.34.5 + optional: true + shebang-command@2.0.0: dependencies: shebang-regex: 3.0.0 shebang-regex@3.0.0: {} + shiki@4.0.2: + dependencies: + '@shikijs/core': 4.0.2 + '@shikijs/engine-javascript': 4.0.2 + '@shikijs/engine-oniguruma': 4.0.2 + '@shikijs/langs': 4.0.2 + '@shikijs/themes': 4.0.2 + '@shikijs/types': 4.0.2 + '@shikijs/vscode-textmate': 10.0.2 + '@types/hast': 3.0.4 + side-channel-list@1.0.1: dependencies: es-errors: 1.3.0 @@ -12922,6 +15407,8 @@ snapshots: once: 1.4.0 simple-concat: 1.0.1 + sisteransi@1.0.5: {} + slash@5.1.0: {} slashes@3.0.12: {} @@ -12943,6 +15430,8 @@ snapshots: slugify@1.6.9: {} + smol-toml@1.6.1: {} + sonic-boom@4.2.1: dependencies: atomic-sleep: 1.0.0 @@ -12954,6 +15443,8 @@ snapshots: source-map-js@1.2.1: {} + space-separated-tokens@2.0.2: {} + spdx-correct@3.2.0: dependencies: spdx-expression-parse: 3.0.1 @@ -13005,6 +15496,11 @@ snapshots: dependencies: safe-buffer: 5.2.1 + stringify-entities@4.0.4: + dependencies: + character-entities-html4: 2.1.0 + character-entities-legacy: 3.0.0 + strip-ansi@6.0.1: dependencies: ansi-regex: 5.0.1 @@ -13067,6 +15563,16 @@ snapshots: svg-arc-to-cubic-bezier@3.2.0: {} + svgo@4.0.1: + dependencies: + commander: 11.1.0 + css-select: 5.2.2 + css-tree: 3.2.1 + css-what: 6.2.2 + csso: 5.0.5 + picocolors: 1.1.1 + sax: 1.6.0 + symbol-tree@3.2.4: {} synckit@0.11.12: @@ -13116,6 +15622,8 @@ snapshots: tinybench@2.9.0: {} + tinyclip@0.1.12: {} + tinyexec@0.3.2: {} tinyexec@1.1.2: {} @@ -13171,6 +15679,10 @@ snapshots: dependencies: punycode: 2.3.1 + trim-lines@3.0.1: {} + + trough@2.2.0: {} + ts-api-utils@2.5.0(typescript@6.0.3): dependencies: typescript: 6.0.3 @@ -13220,6 +15732,12 @@ snapshots: media-typer: 1.1.0 mime-types: 3.0.2 + typesafe-path@0.2.2: {} + + typescript-auto-import-cache@0.3.6: + dependencies: + semver: 7.7.4 + typescript-eslint@8.59.2(eslint@10.3.0(jiti@2.7.0))(typescript@6.0.3): dependencies: '@typescript-eslint/eslint-plugin': 8.59.2(@typescript-eslint/parser@8.59.2(eslint@10.3.0(jiti@2.7.0))(typescript@6.0.3))(eslint@10.3.0(jiti@2.7.0))(typescript@6.0.3) @@ -13235,6 +15753,8 @@ snapshots: typescript@6.0.3: {} + ufo@1.6.4: {} + uglify-js@3.19.3: {} uid-safe@2.1.5: @@ -13243,6 +15763,10 @@ snapshots: uint8array-extras@1.5.0: {} + ultrahtml@1.6.0: {} + + uncrypto@0.1.3: {} + undici-types@6.21.0: {} undici-types@7.16.0: {} @@ -13263,6 +15787,64 @@ snapshots: unicorn-magic@0.4.0: {} + unified@11.0.5: + dependencies: + '@types/unist': 3.0.3 + bail: 2.0.2 + devlop: 1.1.0 + extend: 3.0.2 + is-plain-obj: 4.1.0 + trough: 2.2.0 + vfile: 6.0.3 + + unifont@0.7.4: + dependencies: + css-tree: 3.2.1 + ofetch: 1.5.1 + ohash: 2.0.11 + + unist-util-find-after@5.0.0: + dependencies: + '@types/unist': 3.0.3 + unist-util-is: 6.0.1 + + unist-util-is@6.0.1: + dependencies: + '@types/unist': 3.0.3 + + unist-util-modify-children@4.0.0: + dependencies: + '@types/unist': 3.0.3 + array-iterate: 2.0.1 + + unist-util-position@5.0.0: + dependencies: + '@types/unist': 3.0.3 + + unist-util-remove-position@5.0.0: + dependencies: + '@types/unist': 3.0.3 + unist-util-visit: 5.1.0 + + unist-util-stringify-position@4.0.0: + dependencies: + '@types/unist': 3.0.3 + + unist-util-visit-children@3.0.0: + dependencies: + '@types/unist': 3.0.3 + + unist-util-visit-parents@6.0.2: + dependencies: + '@types/unist': 3.0.3 + unist-util-is: 6.0.1 + + unist-util-visit@5.1.0: + dependencies: + '@types/unist': 3.0.3 + unist-util-is: 6.0.1 + unist-util-visit-parents: 6.0.2 + unpipe@1.0.0: {} unplugin@3.0.0: @@ -13271,6 +15853,19 @@ snapshots: picomatch: 4.0.4 webpack-virtual-modules: 0.6.2 + unstorage@1.17.5(ioredis@5.10.1): + dependencies: + anymatch: 3.1.3 + chokidar: 5.0.0 + destr: 2.0.5 + h3: 1.15.11 + lru-cache: 11.3.6 + node-fetch-native: 1.6.7 + ofetch: 1.5.1 + ufo: 1.6.4 + optionalDependencies: + ioredis: 5.10.1 + until-async@3.0.2: {} update-browserslist-db@1.2.3(browserslist@4.28.2): @@ -13313,6 +15908,21 @@ snapshots: vary@1.1.2: {} + vfile-location@5.0.3: + dependencies: + '@types/unist': 3.0.3 + vfile: 6.0.3 + + vfile-message@4.0.3: + dependencies: + '@types/unist': 3.0.3 + unist-util-stringify-position: 4.0.0 + + vfile@6.0.3: + dependencies: + '@types/unist': 3.0.3 + vfile-message: 4.0.3 + victory-vendor@37.3.6: dependencies: '@types/d3-array': 3.2.2 @@ -13373,6 +15983,22 @@ snapshots: tsx: 4.21.0 yaml: 2.8.4 + vite@7.3.2(@types/node@25.6.0)(jiti@2.7.0)(lightningcss@1.32.0)(tsx@4.21.0)(yaml@2.8.4): + dependencies: + esbuild: 0.27.7 + fdir: 6.5.0(picomatch@4.0.4) + picomatch: 4.0.4 + postcss: 8.5.14 + rollup: 4.60.3 + tinyglobby: 0.2.16 + optionalDependencies: + '@types/node': 25.6.0 + fsevents: 2.3.3 + jiti: 2.7.0 + lightningcss: 1.32.0 + tsx: 4.21.0 + yaml: 2.8.4 + vite@8.0.10(@types/node@24.12.2)(esbuild@0.27.7)(jiti@2.7.0)(tsx@4.21.0)(yaml@2.8.4): dependencies: lightningcss: 1.32.0 @@ -13388,7 +16014,11 @@ snapshots: tsx: 4.21.0 yaml: 2.8.4 - vitest@3.2.4(@types/node@24.12.2)(jiti@2.7.0)(jsdom@26.1.0)(lightningcss@1.32.0)(msw@2.14.3(@types/node@24.12.2)(typescript@6.0.3))(tsx@4.21.0)(yaml@2.8.4): + vitefu@1.1.3(vite@7.3.2(@types/node@25.6.0)(jiti@2.7.0)(lightningcss@1.32.0)(tsx@4.21.0)(yaml@2.8.4)): + optionalDependencies: + vite: 7.3.2(@types/node@25.6.0)(jiti@2.7.0)(lightningcss@1.32.0)(tsx@4.21.0)(yaml@2.8.4) + + vitest@3.2.4(@types/debug@4.1.13)(@types/node@24.12.2)(jiti@2.7.0)(jsdom@26.1.0)(lightningcss@1.32.0)(msw@2.14.3(@types/node@24.12.2)(typescript@6.0.3))(tsx@4.21.0)(yaml@2.8.4): dependencies: '@types/chai': 5.2.3 '@vitest/expect': 3.2.4 @@ -13414,6 +16044,7 @@ snapshots: vite-node: 3.2.4(@types/node@24.12.2)(jiti@2.7.0)(lightningcss@1.32.0)(tsx@4.21.0)(yaml@2.8.4) why-is-node-running: 2.3.0 optionalDependencies: + '@types/debug': 4.1.13 '@types/node': 24.12.2 jsdom: 26.1.0 transitivePeerDependencies: @@ -13430,10 +16061,109 @@ snapshots: - tsx - yaml + volar-service-css@0.0.70(@volar/language-service@2.4.28): + dependencies: + vscode-css-languageservice: 6.3.10 + vscode-languageserver-textdocument: 1.0.12 + vscode-uri: 3.1.0 + optionalDependencies: + '@volar/language-service': 2.4.28 + + volar-service-emmet@0.0.70(@volar/language-service@2.4.28): + dependencies: + '@emmetio/css-parser': 0.4.1 + '@emmetio/html-matcher': 1.3.0 + '@vscode/emmet-helper': 2.11.0 + vscode-uri: 3.1.0 + optionalDependencies: + '@volar/language-service': 2.4.28 + + volar-service-html@0.0.70(@volar/language-service@2.4.28): + dependencies: + vscode-html-languageservice: 5.6.2 + vscode-languageserver-textdocument: 1.0.12 + vscode-uri: 3.1.0 + optionalDependencies: + '@volar/language-service': 2.4.28 + + volar-service-prettier@0.0.70(@volar/language-service@2.4.28)(prettier@3.8.3): + dependencies: + vscode-uri: 3.1.0 + optionalDependencies: + '@volar/language-service': 2.4.28 + prettier: 3.8.3 + + volar-service-typescript-twoslash-queries@0.0.70(@volar/language-service@2.4.28): + dependencies: + vscode-uri: 3.1.0 + optionalDependencies: + '@volar/language-service': 2.4.28 + + volar-service-typescript@0.0.70(@volar/language-service@2.4.28): + dependencies: + path-browserify: 1.0.1 + semver: 7.7.4 + typescript-auto-import-cache: 0.3.6 + vscode-languageserver-textdocument: 1.0.12 + vscode-nls: 5.2.0 + vscode-uri: 3.1.0 + optionalDependencies: + '@volar/language-service': 2.4.28 + + volar-service-yaml@0.0.70(@volar/language-service@2.4.28): + dependencies: + vscode-uri: 3.1.0 + yaml-language-server: 1.20.0 + optionalDependencies: + '@volar/language-service': 2.4.28 + + vscode-css-languageservice@6.3.10: + dependencies: + '@vscode/l10n': 0.0.18 + vscode-languageserver-textdocument: 1.0.12 + vscode-languageserver-types: 3.17.5 + vscode-uri: 3.1.0 + + vscode-html-languageservice@5.6.2: + dependencies: + '@vscode/l10n': 0.0.18 + vscode-languageserver-textdocument: 1.0.12 + vscode-languageserver-types: 3.17.5 + vscode-uri: 3.1.0 + + vscode-json-languageservice@4.1.8: + dependencies: + jsonc-parser: 3.3.1 + vscode-languageserver-textdocument: 1.0.12 + vscode-languageserver-types: 3.17.5 + vscode-nls: 5.2.0 + vscode-uri: 3.1.0 + + vscode-jsonrpc@8.2.0: {} + + vscode-languageserver-protocol@3.17.5: + dependencies: + vscode-jsonrpc: 8.2.0 + vscode-languageserver-types: 3.17.5 + + vscode-languageserver-textdocument@1.0.12: {} + + vscode-languageserver-types@3.17.5: {} + + vscode-languageserver@9.0.1: + dependencies: + vscode-languageserver-protocol: 3.17.5 + + vscode-nls@5.2.0: {} + + vscode-uri@3.1.0: {} + w3c-xmlserializer@5.0.0: dependencies: xml-name-validator: 5.0.0 + web-namespaces@2.0.1: {} + webidl-conversions@3.0.1: {} webidl-conversions@7.0.0: {} @@ -13456,6 +16186,8 @@ snapshots: tr46: 0.0.3 webidl-conversions: 3.0.1 + which-pm-runs@1.1.0: {} + which@2.0.2: dependencies: isexe: 2.0.0 @@ -13500,10 +16232,28 @@ snapshots: xtend@4.0.2: {} + xxhash-wasm@1.1.0: {} + y18n@5.0.8: {} yallist@3.1.1: {} + yaml-language-server@1.20.0: + dependencies: + '@vscode/l10n': 0.0.18 + ajv: 8.20.0 + ajv-draft-04: 1.0.0(ajv@8.20.0) + prettier: 3.8.3 + request-light: 0.5.8 + vscode-json-languageservice: 4.1.8 + vscode-languageserver: 9.0.1 + vscode-languageserver-textdocument: 1.0.12 + vscode-languageserver-types: 3.17.5 + vscode-uri: 3.1.0 + yaml: 2.7.1 + + yaml@2.7.1: {} + yaml@2.8.4: {} yargs-parser@21.1.1: {} @@ -13522,6 +16272,8 @@ snapshots: yocto-queue@0.1.0: {} + yocto-queue@1.2.2: {} + yoctocolors@2.1.2: {} yoga-layout@3.2.1: {} @@ -13544,3 +16296,7 @@ snapshots: zod: 3.25.76 zod@3.25.76: {} + + zod@4.4.3: {} + + zwitch@2.0.4: {}