diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..1a41310 --- /dev/null +++ b/.env.example @@ -0,0 +1,8 @@ +# Domain Scanner +DATABASE_URL=postgresql://scanner:scanner@localhost:5432/domain_scanner?schema=public +REDIS_URL=redis://localhost:6379 +NEXTAUTH_URL=http://localhost:3000 +NEXTAUTH_SECRET=change-me-to-a-long-random-string +NEXT_PUBLIC_APP_URL=http://localhost:3000 +IPINFO_TOKEN= +SCAN_RATE_LIMIT_PER_HOUR=30 diff --git a/.gitignore b/.gitignore index a7cbb0f..3fabbc6 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,41 @@ +# dependencies +/node_modules +/.pnp +.pnp.* +.yarn/* +!.yarn/patches +!.yarn/plugins +!.yarn/releases +!.yarn/versions + +# testing +/coverage + +# next.js +/.next/ +/out/ + +# production +/build + +# misc .DS_Store -*.log -premium/node_modules/ -premium/.next/ -premium/.env +*.pem .env +.env*.local + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* +.pnpm-debug.log* + +# vercel +.vercel + +# typescript +*.tsbuildinfo +next-env.d.ts + +# prisma +prisma/*.db diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..db976a8 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,29 @@ +FROM node:22-alpine AS base +RUN apk add --no-cache libc6-compat openssl +WORKDIR /app + +FROM base AS deps +COPY package.json package-lock.json* ./ +COPY prisma ./prisma/ +RUN npm ci + +FROM base AS builder +COPY --from=deps /app/node_modules ./node_modules +COPY . . +ENV NEXT_TELEMETRY_DISABLED=1 +RUN npx prisma generate && npm run build + +FROM base AS runner +ENV NODE_ENV=production +ENV NEXT_TELEMETRY_DISABLED=1 +RUN addgroup --system --gid 1001 nodejs && adduser --system --uid 1001 nextjs +COPY --from=builder /app/public ./public +COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./ +COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static +COPY --from=builder /app/prisma ./prisma +COPY --from=builder /app/node_modules/.prisma ./node_modules/.prisma +USER nextjs +EXPOSE 3000 +ENV PORT=3000 +ENV HOSTNAME=0.0.0.0 +CMD ["node", "server.js"] diff --git a/premium/LICENSE b/LICENSE similarity index 95% rename from premium/LICENSE rename to LICENSE index dcd3123..313e3ab 100644 --- a/premium/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2026 GeoExport Contributors +Copyright (c) 2026 Domain Scanner contributors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index 1cc885d..1f666ae 100644 --- a/README.md +++ b/README.md @@ -1,89 +1,83 @@ -# Domain_web — зеркало GeoExport +# Domain Scanner -Статическая копия интерфейса [geoexport.org](https://geoexport.org/) для развёртывания на VPS (nginx + Node.js). +Modern domain intelligence and infrastructure analysis platform with premium UX, real-time monitoring, and scalable self-hosted architecture. -## Быстрая установка (одна команда) +![Domain Scanner](https://img.shields.io/badge/Next.js-15-black?style=flat-square) +![TypeScript](https://img.shields.io/badge/TypeScript-5-blue?style=flat-square) +![Docker](https://img.shields.io/badge/Docker-ready-2496ED?style=flat-square) + +## Features + +- **DNS** — A, AAAA, CNAME, MX, TXT, NS resolution +- **WHOIS** — Registrar and domain metadata +- **SSL** — Certificate validity, issuer, expiry +- **HTTP** — Status, redirects, response headers +- **Geo/IP** — Location via ip-api.com (or IPinfo with token) +- **Security** — Header scoring and grade +- **Tech/CDN** — Server, Cloudflare, Vercel signals +- **Export** — JSON and CSV downloads +- **Live progress** — Server-Sent Events during scans +- **Monitoring** — DB schema + API for DNS/SSL monitors (workers: roadmap) + +## Quick start (Docker) + +```bash +git clone https://github.com/andrey271192/Domain_web.git +cd Domain_web +cp .env.example .env +docker compose up -d +docker compose exec web npx prisma db push +open http://localhost:3000 +``` + +## One-line VPS install ```bash curl -fsSL https://raw.githubusercontent.com/andrey271192/Domain_web/main/install.sh | sudo bash ``` -**URL скрипта установки:** -https://raw.githubusercontent.com/andrey271192/Domain_web/main/install.sh - -## Удаление (одна команда) +## Uninstall ```bash curl -fsSL https://raw.githubusercontent.com/andrey271192/Domain_web/main/uninstall.sh | sudo bash ``` -**URL скрипта удаления:** -https://raw.githubusercontent.com/andrey271192/Domain_web/main/uninstall.sh - -## Что устанавливается - -| Компонент | Путь / имя | -|-----------|------------| -| Клон репозитория | `/opt/domain_web` | -| Файлы сайта | `/opt/domain_web/site/` | -| Node-сервер (API + статика) | systemd `geoexport-site`, порт `127.0.0.1:4173` | -| Публичный доступ | nginx на порту **80** → прокси на Node | - -Скрипт установки: - -- ставит `nginx`, `git`, `curl`, `nodejs` (если нет); -- клонирует или обновляет этот репозиторий; -- поднимает `geoexport-site.service`; -- настраивает nginx как reverse proxy. - -Скрипт удаления: - -- останавливает и удаляет unit `geoexport-site`; -- убирает конфиг nginx `geoexport-site`; -- удаляет каталог `/opt/domain_web`; -- **не** удаляет nginx, node и остальные сервисы сервера. - -## Ручная установка +## Development ```bash -git clone https://github.com/andrey271192/Domain_web.git /opt/domain_web -cd /opt/domain_web -sudo bash install.sh +npm install +cp .env.example .env +# Start Postgres + Redis (docker compose up postgres redis -d) +npm run db:push +npm run dev ``` -## Локальный запуск (без VPS) +## Environment -```bash -cd site -npm start -# http://127.0.0.1:4173 -``` +| Variable | Description | +|----------|-------------| +| `DATABASE_URL` | PostgreSQL connection string | +| `REDIS_URL` | Redis for scan cache | +| `NEXTAUTH_SECRET` | Auth signing secret | +| `NEXTAUTH_URL` | Public app URL | +| `IPINFO_TOKEN` | Optional IPinfo token | +| `SCAN_RATE_LIMIT_PER_HOUR` | Per-IP scan limit (default 30) | -## Обновление данных на сервере +## API -```bash -cd /opt/domain_web/site -curl -fsSL https://geoexport.org/api/presets -o data/presets.json -curl -fsSL https://geoexport.org/api/sources -o data/sources.json -curl -fsSL https://geoexport.org/api/last-update -o data/last-update.json -curl -fsSL https://geoexport.org/api/routing/presets -o data/routing-presets.json -systemctl restart geoexport-site -``` +See [docs/API.md](docs/API.md) and the in-app `/api-docs` page. -## Ограничения +## Docs -- Экспорт, поиск и часть API проксируются на живой `geoexport.org` (нужен интернет на VPS). -- Это зеркало UI и кэшированных справочников, не полный автономный бэкенд. +- [Architecture](docs/ARCHITECTURE.md) +- [API](docs/API.md) +- [Deployment](docs/DEPLOYMENT.md) +- [Marketing](docs/MARKETING.md) -## Premium (Next.js roadmap) +## License -Каталог `premium/` — Next.js 16 scaffold (Docker, docs, `docker-compose.yml`). Продакшен на VPS сейчас использует **статическое зеркало** в `site/` (nginx + Node). Полный Next.js-стек — по `premium/docs/DEPLOYMENT.md`. +MIT — see [LICENSE](LICENSE) -## Структура репозитория +## Topics -``` -install.sh — установка на VPS -uninstall.sh — удаление с VPS -site/ — index.html, assets/, data/, server.mjs (production) -premium/ — Next.js rewrite, Docker, документация (roadmap) -``` +`domain-scanner` `dns` `whois` `ssl` `networking` `cybersecurity` `nextjs` `typescript` `self-hosted` `monitoring` `analytics` `infrastructure` `devtools` `open-source` `docker` `postgresql` `redis` `webapp` `dashboard` `security` diff --git a/premium/docker-compose.yml b/docker-compose.yml similarity index 60% rename from premium/docker-compose.yml rename to docker-compose.yml index 1ac86c9..3433822 100644 --- a/premium/docker-compose.yml +++ b/docker-compose.yml @@ -1,19 +1,15 @@ -# GeoExport — local / single-node production stack -# Prisma migrations and geo workers: see docs/DEPLOYMENT.md (roadmap) - services: web: - build: - context: . - dockerfile: Dockerfile + build: . ports: - "${PORT:-3000}:3000" environment: NODE_ENV: production - DATABASE_URL: postgresql://geoexport:geoexport@postgres:5432/geoexport?schema=public + DATABASE_URL: postgresql://scanner:scanner@postgres:5432/domain_scanner?schema=public REDIS_URL: redis://redis:6379 + NEXTAUTH_URL: ${NEXTAUTH_URL:-http://localhost:3000} + NEXTAUTH_SECRET: ${NEXTAUTH_SECRET:-change-me-in-production} NEXT_PUBLIC_APP_URL: ${NEXT_PUBLIC_APP_URL:-http://localhost:3000} - GEOEXPORT_UPSTREAM: ${GEOEXPORT_UPSTREAM:-https://geoexport.org} depends_on: postgres: condition: service_healthy @@ -24,15 +20,13 @@ services: postgres: image: postgres:16-alpine environment: - POSTGRES_USER: geoexport - POSTGRES_PASSWORD: geoexport - POSTGRES_DB: geoexport + POSTGRES_USER: scanner + POSTGRES_PASSWORD: scanner + POSTGRES_DB: domain_scanner volumes: - postgres_data:/var/lib/postgresql/data - ports: - - "5432:5432" healthcheck: - test: ["CMD-SHELL", "pg_isready -U geoexport -d geoexport"] + test: ["CMD-SHELL", "pg_isready -U scanner -d domain_scanner"] interval: 5s timeout: 5s retries: 5 @@ -43,8 +37,6 @@ services: command: redis-server --appendonly yes volumes: - redis_data:/data - ports: - - "6379:6379" healthcheck: test: ["CMD", "redis-cli", "ping"] interval: 5s diff --git a/docs/API.md b/docs/API.md new file mode 100644 index 0000000..d604cfd --- /dev/null +++ b/docs/API.md @@ -0,0 +1,51 @@ +# API Reference + +Base URL: your deployment origin (e.g. `http://SERVER_IP`) + +## POST /api/scan + +Start a domain scan. + +**Body** + +```json +{ "domain": "example.com" } +``` + +**Response** + +```json +{ "id": "clx...", "domain": "example.com" } +``` + +## GET /api/scan/:id + +Poll scan status and full result. + +## GET /api/scan/:id/stream + +Server-Sent Events. Each event: + +```json +{ "id": "...", "status": "RUNNING", "progress": 45, "domain": "example.com" } +``` + +## GET /api/export?id=:scanId&format=json|csv + +Download completed scan. + +## GET /api/scan + +List recent scans (public metadata). + +## POST /api/monitors + +Requires session. Body: `{ "domain": "example.com", "type": "DNS" }` + +## GET /api/health + +Database connectivity check. + +## Rate limits + +Default 30 scans/hour per IP (`SCAN_RATE_LIMIT_PER_HOUR`). diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md new file mode 100644 index 0000000..987d264 --- /dev/null +++ b/docs/ARCHITECTURE.md @@ -0,0 +1,44 @@ +# Architecture + +## Overview + +Domain Scanner is a Next.js 15 application with PostgreSQL (Prisma) and optional Redis caching. Scans run in-process on the web server using Node.js built-ins and public APIs. + +## Components + +``` +┌─────────────┐ ┌──────────────┐ ┌─────────────┐ +│ Browser │────▶│ Next.js App │────▶│ PostgreSQL │ +│ (SSE/REST) │ │ scan worker │ │ scans │ +└─────────────┘ └──────┬───────┘ └─────────────┘ + │ + ┌──────▼───────┐ + │ Redis │ + │ scan cache │ + └──────────────┘ +``` + +## Scan pipeline + +1. `POST /api/scan` validates domain, checks rate limit, creates `Scan` row +2. Async `runDomainScan()` updates progress in DB +3. Client subscribes to `GET /api/scan/:id/stream` (SSE) +4. Completed results cached in Redis (1h) and stored as JSON in Postgres + +## Data model + +- **User** — credentials auth (NextAuth) +- **Scan** — domain, status, progress, result JSON +- **Monitor** — per-user DNS/SSL/uptime watch (cron stub) +- **ApiRateLimit** — hourly per-IP counters + +## Competitive positioning + +Tools like DNSChecker and SecurityTrails focus on single-record lookups or enterprise datasets. Domain Scanner targets unified infrastructure snapshots with export and self-hosting — not a clone of their UI patterns. + +## Roadmap + +- BullMQ worker container for monitors +- GraphQL server (schema placeholder in `src/lib/graphql/schema.ts`) +- Blacklist checks (Spamhaus, etc.) +- DNS diff history diff --git a/docs/DEPLOYMENT.md b/docs/DEPLOYMENT.md new file mode 100644 index 0000000..d95aca7 --- /dev/null +++ b/docs/DEPLOYMENT.md @@ -0,0 +1,43 @@ +# Deployment + +## Docker (recommended) + +```bash +docker compose up -d --build +docker compose exec web npx prisma db push +``` + +## VPS one-liner + +```bash +curl -fsSL https://raw.githubusercontent.com/andrey271192/Domain_web/main/install.sh | sudo bash +``` + +Install path: `/opt/domain_web` +Service: Docker Compose (`web`, `postgres`, `redis`) +Nginx site: `domain-scanner` on port 80 → app `:3000` + +## Environment on server + +Edit `/opt/domain_web/.env`: + +- `NEXTAUTH_SECRET` — auto-generated on first install +- `NEXTAUTH_URL` / `NEXT_PUBLIC_APP_URL` — public IP or domain + +## Manual Node deploy + +```bash +npm ci && npm run build +DATABASE_URL=... npx prisma db push +PORT=3000 npm start +``` + +## SSL / HTTPS + +Place certbot in front of nginx or terminate TLS at a reverse proxy. + +## Upgrades + +```bash +cd /opt/domain_web && git pull && docker compose up -d --build +``` diff --git a/docs/MARKETING.md b/docs/MARKETING.md new file mode 100644 index 0000000..aee5997 --- /dev/null +++ b/docs/MARKETING.md @@ -0,0 +1,32 @@ +# Marketing + +## GitHub About + +Modern domain intelligence and infrastructure analysis platform with premium UX, real-time monitoring, and scalable self-hosted architecture. + +## Tagline + +See every layer of your domain. + +## Product Hunt (draft) + +**Title:** Domain Scanner — self-hosted domain intelligence +**Subtitle:** DNS, WHOIS, SSL, security headers, and exports in one premium dashboard. + +**First comment outline:** Built for SREs and indie hackers who want SecurityTrails-grade signals without SaaS lock-in. Docker + Postgres + real scans via Node. + +## Twitter / X + +Shipped Domain Scanner — open-source domain intel with cinematic UI, live SSE scans, JSON/CSV export. Self-host on your VPS in one command. + +## Dev.to outline + +1. Why unified domain scans beat tab-hopping +2. Architecture: Next.js + Prisma + Redis +3. Implementing SSL checks with Node tls +4. SSE for scan progress +5. One-line VPS deploy + +## SEO keywords + +domain scanner, dns lookup tool, ssl checker, whois api, self-hosted security tools diff --git a/eslint.config.mjs b/eslint.config.mjs new file mode 100644 index 0000000..bbd17c7 --- /dev/null +++ b/eslint.config.mjs @@ -0,0 +1,14 @@ +import { dirname } from "path"; +import { fileURLToPath } from "url"; +import { FlatCompat } from "@eslint/eslintrc"; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); + +const compat = new FlatCompat({ + baseDirectory: __dirname, +}); + +const eslintConfig = [...compat.extends("next/core-web-vitals", "next/typescript")]; + +export default eslintConfig; diff --git a/install.sh b/install.sh index 649296a..b78dfec 100755 --- a/install.sh +++ b/install.sh @@ -1,33 +1,34 @@ #!/usr/bin/env bash -# GeoExport site — one-command install (Ubuntu/Debian VPS) +# Domain Scanner — one-command install (Ubuntu/Debian VPS) set -euo pipefail -REPO_URL="${GEOEXPORT_REPO_URL:-https://github.com/andrey271192/Domain_web.git}" -BRANCH="${GEOEXPORT_BRANCH:-main}" -INSTALL_DIR="${GEOEXPORT_INSTALL_DIR:-/opt/domain_web}" -SITE_DIR="${INSTALL_DIR}/site" -SERVICE_NAME="geoexport-site" -NGINX_SITE="geoexport-site" -NODE_PORT="${GEOEXPORT_PORT:-4173}" +REPO_URL="${DOMAIN_SCANNER_REPO_URL:-https://github.com/andrey271192/Domain_web.git}" +BRANCH="${DOMAIN_SCANNER_BRANCH:-main}" +INSTALL_DIR="${DOMAIN_SCANNER_INSTALL_DIR:-/opt/domain_web}" +SERVICE_NAME="domain-scanner" +NGINX_SITE="domain-scanner" +APP_PORT="${DOMAIN_SCANNER_PORT:-3000}" export DEBIAN_FRONTEND=noninteractive -log() { echo "[geoexport-install] $*"; } +log() { echo "[domain-scanner-install] $*"; } need_root() { if [[ "${EUID:-$(id -u)}" -ne 0 ]]; then - echo "Запустите от root: sudo bash install.sh" >&2 + echo "Run as root: sudo bash install.sh" >&2 exit 1 fi } install_packages() { - log "Установка пакетов (nginx, git, curl, nodejs)..." + log "Installing nginx, git, curl, docker..." apt-get update -qq - apt-get install -y -qq nginx git curl ca-certificates nodejs npm >/dev/null + apt-get install -y -qq nginx git curl ca-certificates docker.io docker-compose-plugin >/dev/null + systemctl enable docker + systemctl start docker } clone_or_update() { - log "Клонирование/обновление репозитория в ${INSTALL_DIR}..." + log "Cloning/updating ${INSTALL_DIR}..." if [[ -d "${INSTALL_DIR}/.git" ]]; then git -C "${INSTALL_DIR}" fetch origin "${BRANCH}" git -C "${INSTALL_DIR}" checkout "${BRANCH}" @@ -36,57 +37,58 @@ clone_or_update() { rm -rf "${INSTALL_DIR}" git clone --depth 1 --branch "${BRANCH}" "${REPO_URL}" "${INSTALL_DIR}" fi - if [[ ! -f "${SITE_DIR}/server.mjs" ]]; then - echo "Ошибка: не найден ${SITE_DIR}/server.mjs" >&2 - exit 1 +} + +setup_env() { + log "Writing .env..." + if [[ ! -f "${INSTALL_DIR}/.env" ]]; then + SECRET=$(openssl rand -base64 32 2>/dev/null || head -c 32 /dev/urandom | base64) + cat >"${INSTALL_DIR}/.env" <"/etc/systemd/system/${SERVICE_NAME}.service" </dev/null || true } install_nginx() { - log "Настройка nginx (${NGINX_SITE})..." + log "Configuring nginx..." cat >"/etc/nginx/sites-available/${NGINX_SITE}" </dev/null || true + rm -f /etc/nginx/sites-available/geoexport-site 2>/dev/null || true + systemctl stop geoexport-site.service 2>/dev/null || true + systemctl disable geoexport-site.service 2>/dev/null || true + rm -f /etc/systemd/system/geoexport-site.service 2>/dev/null || true + systemctl daemon-reload rm -f /etc/nginx/sites-enabled/default 2>/dev/null || true ln -sf "/etc/nginx/sites-available/${NGINX_SITE}" "/etc/nginx/sites-enabled/${NGINX_SITE}" nginx -t @@ -95,26 +97,24 @@ EOF } verify() { - log "Проверка..." - sleep 2 - if ! systemctl is-active --quiet "${SERVICE_NAME}.service"; then - systemctl status "${SERVICE_NAME}.service" --no-pager || true + log "Verifying..." + sleep 3 + if ! curl -fsS -o /dev/null "http://127.0.0.1:${APP_PORT}/"; then + echo "App not responding on port ${APP_PORT}" >&2 + docker compose -f "${INSTALL_DIR}/docker-compose.yml" logs --tail=50 web || true exit 1 fi - if ! curl -fsS -o /dev/null "http://127.0.0.1:${NODE_PORT}/"; then - echo "Node-сервер не отвечает на порту ${NODE_PORT}" >&2 - exit 1 + TITLE=$(curl -fsS "http://127.0.0.1/" | head -c 2000 | grep -oi 'Domain Scanner' | head -1 || true) + if [[ -z "${TITLE}" ]]; then + log "Warning: page title may not include Domain Scanner yet" fi - if ! curl -fsS -o /dev/null "http://127.0.0.1/"; then - echo "nginx не отдаёт сайт на :80" >&2 - exit 1 - fi - log "Готово. Сайт доступен по http://$(hostname -I | awk '{print $1}')/" + log "Done. Open http://$(hostname -I | awk '{print $1}')/" } need_root install_packages clone_or_update -install_systemd +setup_env +deploy_docker install_nginx verify diff --git a/next.config.ts b/next.config.ts new file mode 100644 index 0000000..339a77b --- /dev/null +++ b/next.config.ts @@ -0,0 +1,24 @@ +import type { NextConfig } from "next"; + +const nextConfig: NextConfig = { + output: "standalone", + poweredByHeader: false, + async headers() { + return [ + { + source: "/(.*)", + headers: [ + { key: "X-Frame-Options", value: "DENY" }, + { key: "X-Content-Type-Options", value: "nosniff" }, + { key: "Referrer-Policy", value: "strict-origin-when-cross-origin" }, + { + key: "Permissions-Policy", + value: "camera=(), microphone=(), geolocation=()", + }, + ], + }, + ]; + }, +}; + +export default nextConfig; diff --git a/premium/package-lock.json b/package-lock.json similarity index 62% rename from premium/package-lock.json rename to package-lock.json index 3ab10ed..2bc0181 100644 --- a/premium/package-lock.json +++ b/package-lock.json @@ -1,24 +1,53 @@ { - "name": "geoexport-premium", - "version": "0.1.0", + "name": "domain-scanner", + "version": "1.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "geoexport-premium", - "version": "0.1.0", + "name": "domain-scanner", + "version": "1.0.0", + "hasInstallScript": true, "dependencies": { - "next": "16.2.6", - "react": "19.2.4", - "react-dom": "19.2.4" + "@prisma/client": "^6.9.0", + "@radix-ui/react-avatar": "^1.1.11", + "@radix-ui/react-dialog": "^1.1.15", + "@radix-ui/react-dropdown-menu": "^2.1.16", + "@radix-ui/react-label": "^2.1.8", + "@radix-ui/react-scroll-area": "^1.2.10", + "@radix-ui/react-select": "^2.2.6", + "@radix-ui/react-separator": "^1.1.8", + "@radix-ui/react-slot": "^1.2.4", + "@radix-ui/react-switch": "^1.2.6", + "@radix-ui/react-tabs": "^1.1.13", + "bcryptjs": "^3.0.2", + "bullmq": "^5.56.0", + "class-variance-authority": "^0.7.1", + "clsx": "^2.1.1", + "dns2": "^2.1.0", + "framer-motion": "^12.16.0", + "ioredis": "^5.6.1", + "lucide-react": "^0.513.0", + "next": "^15.3.3", + "next-auth": "^5.0.0-beta.28", + "next-themes": "^0.4.6", + "react": "^19.1.0", + "react-dom": "^19.1.0", + "recharts": "^2.15.3", + "sonner": "^2.0.5", + "tailwind-merge": "^3.3.0", + "whois": "^2.14.2", + "zod": "^3.25.51" }, "devDependencies": { "@tailwindcss/postcss": "^4", + "@types/bcryptjs": "^2.4.6", "@types/node": "^20", "@types/react": "^19", "@types/react-dom": "^19", "eslint": "^9", - "eslint-config-next": "16.2.6", + "eslint-config-next": "^15.3.3", + "prisma": "^6.9.0", "tailwindcss": "^4", "typescript": "^5" } @@ -36,242 +65,40 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@babel/code-frame": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", - "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", - "dev": true, - "license": "MIT", + "node_modules/@auth/core": { + "version": "0.41.2", + "resolved": "https://registry.npmjs.org/@auth/core/-/core-0.41.2.tgz", + "integrity": "sha512-Hx5MNBxN2fJTbJKGUKAA0wca43D0Akl3TvufY54Gn8lop7F+34vU1zA1pn0vQfIoVuLIrpfc2nkyjwIaPJMW7w==", + "license": "ISC", "dependencies": { - "@babel/helper-validator-identifier": "^7.28.5", - "js-tokens": "^4.0.0", - "picocolors": "^1.1.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/compat-data": { - "version": "7.29.3", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.3.tgz", - "integrity": "sha512-LIVqM46zQWZhj17qA8wb4nW/ixr2y1Nw+r1etiAWgRM6U1IqP+LNhL1yg440jYZR72jCWcWbLWzIosH+uP1fqg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/core": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz", - "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.29.0", - "@babel/generator": "^7.29.0", - "@babel/helper-compilation-targets": "^7.28.6", - "@babel/helper-module-transforms": "^7.28.6", - "@babel/helpers": "^7.28.6", - "@babel/parser": "^7.29.0", - "@babel/template": "^7.28.6", - "@babel/traverse": "^7.29.0", - "@babel/types": "^7.29.0", - "@jridgewell/remapping": "^2.3.5", - "convert-source-map": "^2.0.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.2", - "json5": "^2.2.3", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/babel" - } - }, - "node_modules/@babel/generator": { - "version": "7.29.1", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz", - "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.29.0", - "@babel/types": "^7.29.0", - "@jridgewell/gen-mapping": "^0.3.12", - "@jridgewell/trace-mapping": "^0.3.28", - "jsesc": "^3.0.2" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-compilation-targets": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", - "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/compat-data": "^7.28.6", - "@babel/helper-validator-option": "^7.27.1", - "browserslist": "^4.24.0", - "lru-cache": "^5.1.1", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-globals": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", - "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-module-imports": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", - "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/traverse": "^7.28.6", - "@babel/types": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-module-transforms": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", - "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-module-imports": "^7.28.6", - "@babel/helper-validator-identifier": "^7.28.5", - "@babel/traverse": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" + "@panva/hkdf": "^1.2.1", + "jose": "^6.0.6", + "oauth4webapi": "^3.3.0", + "preact": "10.24.3", + "preact-render-to-string": "6.5.11" }, "peerDependencies": { - "@babel/core": "^7.0.0" + "@simplewebauthn/browser": "^9.0.1", + "@simplewebauthn/server": "^9.0.2", + "nodemailer": "^7.0.7" + }, + "peerDependenciesMeta": { + "@simplewebauthn/browser": { + "optional": true + }, + "@simplewebauthn/server": { + "optional": true + }, + "nodemailer": { + "optional": true + } } }, - "node_modules/@babel/helper-string-parser": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", - "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-identifier": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", - "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-option": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", - "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helpers": { + "node_modules/@babel/runtime": { "version": "7.29.2", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.2.tgz", - "integrity": "sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw==", - "dev": true, + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.2.tgz", + "integrity": "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==", "license": "MIT", - "dependencies": { - "@babel/template": "^7.28.6", - "@babel/types": "^7.29.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/parser": { - "version": "7.29.3", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.3.tgz", - "integrity": "sha512-b3ctpQwp+PROvU/cttc4OYl4MzfJUWy6FZg+PMXfzmt/+39iHVF0sDfqay8TQM3JA2EUOyKcFZt75jWriQijsA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.29.0" - }, - "bin": { - "parser": "bin/babel-parser.js" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@babel/template": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", - "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.28.6", - "@babel/parser": "^7.28.6", - "@babel/types": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/traverse": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz", - "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.29.0", - "@babel/generator": "^7.29.0", - "@babel/helper-globals": "^7.28.0", - "@babel/parser": "^7.29.0", - "@babel/template": "^7.28.6", - "@babel/types": "^7.29.0", - "debug": "^4.3.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/types": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", - "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-string-parser": "^7.27.1", - "@babel/helper-validator-identifier": "^7.28.5" - }, "engines": { "node": ">=6.9.0" } @@ -453,6 +280,44 @@ "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, + "node_modules/@floating-ui/core": { + "version": "1.7.5", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.5.tgz", + "integrity": "sha512-1Ih4WTWyw0+lKyFMcBHGbb5U5FtuHJuujoyyr5zTaWS5EYMeT6Jb2AuDeftsCsEuchO+mM2ij5+q9crhydzLhQ==", + "license": "MIT", + "dependencies": { + "@floating-ui/utils": "^0.2.11" + } + }, + "node_modules/@floating-ui/dom": { + "version": "1.7.6", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.6.tgz", + "integrity": "sha512-9gZSAI5XM36880PPMm//9dfiEngYoC6Am2izES1FF406YFsjvyBMmeJ2g4SAju3xWwtuynNRFL2s9hgxpLI5SQ==", + "license": "MIT", + "dependencies": { + "@floating-ui/core": "^1.7.5", + "@floating-ui/utils": "^0.2.11" + } + }, + "node_modules/@floating-ui/react-dom": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.8.tgz", + "integrity": "sha512-cC52bHwM/n/CxS87FH0yWdngEZrjdtLW/qVruo68qg+prK7ZQ4YGdut2GyDVpoGeAYe/h899rVeOVm6Oi40k2A==", + "license": "MIT", + "dependencies": { + "@floating-ui/dom": "^1.7.6" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/@floating-ui/utils": { + "version": "0.2.11", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.11.tgz", + "integrity": "sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg==", + "license": "MIT" + }, "node_modules/@humanfs/core": { "version": "0.19.2", "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.2.tgz", @@ -1033,6 +898,12 @@ "url": "https://opencollective.com/libvips" } }, + "node_modules/@ioredis/commands": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/@ioredis/commands/-/commands-1.5.1.tgz", + "integrity": "sha512-JH8ZL/ywcJyR9MmJ5BNqZllXNZQqQbnVZOqpPQqE1vHiFgAw4NHbvE0FOduNU8IX9babitBT46571OnPTT0Zcw==", + "license": "MIT" + }, "node_modules/@jridgewell/gen-mapping": { "version": "0.3.13", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", @@ -1083,6 +954,84 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@msgpackr-extract/msgpackr-extract-darwin-arm64": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-darwin-arm64/-/msgpackr-extract-darwin-arm64-3.0.3.tgz", + "integrity": "sha512-QZHtlVgbAdy2zAqNA9Gu1UpIuI8Xvsd1v8ic6B2pZmeFnFcMWiPLfWXh7TVw4eGEZ/C9TH281KwhVoeQUKbyjw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@msgpackr-extract/msgpackr-extract-darwin-x64": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-darwin-x64/-/msgpackr-extract-darwin-x64-3.0.3.tgz", + "integrity": "sha512-mdzd3AVzYKuUmiWOQ8GNhl64/IoFGol569zNRdkLReh6LRLHOXxU4U8eq0JwaD8iFHdVGqSy4IjFL4reoWCDFw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@msgpackr-extract/msgpackr-extract-linux-arm": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-arm/-/msgpackr-extract-linux-arm-3.0.3.tgz", + "integrity": "sha512-fg0uy/dG/nZEXfYilKoRe7yALaNmHoYeIoJuJ7KJ+YyU2bvY8vPv27f7UKhGRpY6euFYqEVhxCFZgAUNQBM3nw==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@msgpackr-extract/msgpackr-extract-linux-arm64": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-arm64/-/msgpackr-extract-linux-arm64-3.0.3.tgz", + "integrity": "sha512-YxQL+ax0XqBJDZiKimS2XQaf+2wDGVa1enVRGzEvLLVFeqa5kx2bWbtcSXgsxjQB7nRqqIGFIcLteF/sHeVtQg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@msgpackr-extract/msgpackr-extract-linux-x64": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-x64/-/msgpackr-extract-linux-x64-3.0.3.tgz", + "integrity": "sha512-cvwNfbP07pKUfq1uH+S6KJ7dT9K8WOE4ZiAcsrSes+UY55E/0jLYc+vq+DO7jlmqRb5zAggExKm0H7O/CBaesg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@msgpackr-extract/msgpackr-extract-win32-x64": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-win32-x64/-/msgpackr-extract-win32-x64-3.0.3.tgz", + "integrity": "sha512-x0fWaQtYp4E6sktbsdAqnehxDgEc/VwM7uLsRCYWaiGu0ykYdZPiS8zCWdnjHwyiumousxfBm4SO31eXqwEZhQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, "node_modules/@napi-rs/wasm-runtime": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.4.tgz", @@ -1103,15 +1052,15 @@ } }, "node_modules/@next/env": { - "version": "16.2.6", - "resolved": "https://registry.npmjs.org/@next/env/-/env-16.2.6.tgz", - "integrity": "sha512-gd8HoHN4ufj73WmR3JmVolrpJR47ILK6LouP5xElPglaVxir6e1a7VzvTvDWkOoPXT9rkkTzyCxBu4yeZfZwcw==", + "version": "15.5.18", + "resolved": "https://registry.npmjs.org/@next/env/-/env-15.5.18.tgz", + "integrity": "sha512-hAV85Ckd9QR6RvH04MEKwsfLTksvFpO47j9xwtoIuvuPnlwecpSi+uZTtm8HirVbtlI2Fnz//xpcSTjFdyJk+g==", "license": "MIT" }, "node_modules/@next/eslint-plugin-next": { - "version": "16.2.6", - "resolved": "https://registry.npmjs.org/@next/eslint-plugin-next/-/eslint-plugin-next-16.2.6.tgz", - "integrity": "sha512-Z8l6o4JWKUl755x4R+wogD86KPeU+Ckw4K+SYG4kHeOJtRenDeK+OSbGcqZpDtbwn9DsJVdir2UxmwXuinUbUw==", + "version": "15.5.18", + "resolved": "https://registry.npmjs.org/@next/eslint-plugin-next/-/eslint-plugin-next-15.5.18.tgz", + "integrity": "sha512-w4MYq8M26a8PNrfto0JosLf5/3ssln1rsyP96g2DkC8uFVymStM5DLSz5ElxxrPRg2XnTMnFo3kREFlhYvxhWw==", "dev": true, "license": "MIT", "dependencies": { @@ -1119,9 +1068,9 @@ } }, "node_modules/@next/swc-darwin-arm64": { - "version": "16.2.6", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-16.2.6.tgz", - "integrity": "sha512-ZJGkkcNfYgrrMkqOdZ7zoLa1TOy0qpcMfk/z4Mh/FKUz40gVO+HNQWqmLxf67Z5WB64DRp0dhEbyHfel+6sJUg==", + "version": "15.5.18", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-15.5.18.tgz", + "integrity": "sha512-w0WvQf1n+txiwns/9pwIQteCJpZTbxzO2SE0FLcwuD4v0WEh1JPOjdyxWL21XwJsdpx8cFRjyzxzCS/siP7HcQ==", "cpu": [ "arm64" ], @@ -1135,9 +1084,9 @@ } }, "node_modules/@next/swc-darwin-x64": { - "version": "16.2.6", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-16.2.6.tgz", - "integrity": "sha512-v/YLBHIY132Ced3puBJ7YJKw1lqsCrgcNo2aRJlCEyQrrCeRJlvGlnmxhPxNQI3KE3N1DN5r9TPNPvka3nq5RQ==", + "version": "15.5.18", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-15.5.18.tgz", + "integrity": "sha512-znn71QmDuxm+BOaglihMZfvyySMnNljkVIY5Z2TCssBmm+WqL6c19VhtH5ktFkHa8EZ2bnTUpcNcmNSQsg67og==", "cpu": [ "x64" ], @@ -1151,9 +1100,9 @@ } }, "node_modules/@next/swc-linux-arm64-gnu": { - "version": "16.2.6", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-16.2.6.tgz", - "integrity": "sha512-RPOvqlYBbcQjkz9VQQDZ2T2bARIjXZV1KFlt+V2Mr6SW/e4I9fcKsaA0hdyf2FHoTlsV2xnBd5Y912rP/1Ce6w==", + "version": "15.5.18", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-15.5.18.tgz", + "integrity": "sha512-yPPe5MNL+igZUa+OsqQJisqSfh6oarIuA1Q0BDxljGJhRQyZeP+WRHh7rs/jZUGMh5aY0YdIjXZG0VohkKkUdw==", "cpu": [ "arm64" ], @@ -1170,9 +1119,9 @@ } }, "node_modules/@next/swc-linux-arm64-musl": { - "version": "16.2.6", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-16.2.6.tgz", - "integrity": "sha512-URUTu1+dMkxJsPFgm+OeEvq9wf5sujw0EvgYy80TDGHTSLTnIHeqb0Eu8A3sC95IRgjejQL+kC4mw+4yPxiAXA==", + "version": "15.5.18", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-15.5.18.tgz", + "integrity": "sha512-glaCczEWIrHsokFZ3pP08U4BpKxwIdnT+txdOM32OBgpL9Yw4aqx8NejmgtZQZOdstQ5f0L3CasIZudzCuD+nw==", "cpu": [ "arm64" ], @@ -1189,9 +1138,9 @@ } }, "node_modules/@next/swc-linux-x64-gnu": { - "version": "16.2.6", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-16.2.6.tgz", - "integrity": "sha512-DOj182mPV8G3UkrayLoREM5YEYI+Dk5wv7Ox9xl1fFibAELEsFD0lDPfHIeILlutMMfdyhlzYPELG3peuKaurw==", + "version": "15.5.18", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-15.5.18.tgz", + "integrity": "sha512-oUfg2EgJmU3R0OCOWiokGFUTvZiPfXtriXiuF3YNxRoROCdgvTedHIzYoeKH34gsZxS/V7mHbfq2hpAHwhH1/A==", "cpu": [ "x64" ], @@ -1208,9 +1157,9 @@ } }, "node_modules/@next/swc-linux-x64-musl": { - "version": "16.2.6", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-16.2.6.tgz", - "integrity": "sha512-HKQ5SP/V/ub73UvF7n/zeJlxk2kLmtL7Wzrg4WfmkjmNos5onJ2tKu7yZOPdL18A6Svfn3max29ym+ry7NkK4g==", + "version": "15.5.18", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-15.5.18.tgz", + "integrity": "sha512-JLxSP3KTd9iu/bvUMQxH7RJo9xKSHf55/6RPE4a6FTSZygGn7uvZbCej0AHXydwkggQGSD9UddSjwv6Xz5ESfA==", "cpu": [ "x64" ], @@ -1227,9 +1176,9 @@ } }, "node_modules/@next/swc-win32-arm64-msvc": { - "version": "16.2.6", - "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-16.2.6.tgz", - "integrity": "sha512-LZXpTlPyS5v7HhSmnvsLGP3iIYgYOBnc8r8ArlT55sGHV89bR2HlDdBjWQ+PY6SJMmk8TuVGFuxalnP3k/0Dwg==", + "version": "15.5.18", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-15.5.18.tgz", + "integrity": "sha512-ir1v7enP52K2HNz3tQQvwF+x7VNxBk1ciiZ18WBPvxf4C59IqdfmHPJYK3vH7rSxpuCVw/8C712wTXNAtEp+NA==", "cpu": [ "arm64" ], @@ -1243,9 +1192,9 @@ } }, "node_modules/@next/swc-win32-x64-msvc": { - "version": "16.2.6", - "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-16.2.6.tgz", - "integrity": "sha512-F0+4i0h9J6C4eE3EAPWsoCk7UW/dbzOjyzxY0qnDUOYFu6FFmdZ6l97/XdV3/Nz3VYyO7UWjyEJUXkGqcoXfMA==", + "version": "15.5.18", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-15.5.18.tgz", + "integrity": "sha512-LIu5me6QTANCd25E7I5uIEfvgQ06RK7tvHAbYo3zCb3VpxQEPvMcSpd87NwUABDT6MbGPdEGR5VRiK4PPTJhQg==", "cpu": [ "x64" ], @@ -1306,6 +1255,1702 @@ "node": ">=12.4.0" } }, + "node_modules/@panva/hkdf": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@panva/hkdf/-/hkdf-1.2.1.tgz", + "integrity": "sha512-6oclG6Y3PiDFcoyk8srjLfVKyMfVCKJ27JwNPViuXziFpmdz+MZnZN/aKY0JGXgYuO/VghU0jcOAZgWXZ1Dmrw==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, + "node_modules/@prisma/client": { + "version": "6.19.3", + "resolved": "https://registry.npmjs.org/@prisma/client/-/client-6.19.3.tgz", + "integrity": "sha512-mKq3jQFhjvko5LTJFHGilsuQs+W+T3Gm451NzuTDGQxwCzwXHYnIu2zGkRoW+Exq3Rob7yp2MfzSrdIiZVhrBg==", + "hasInstallScript": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "peerDependencies": { + "prisma": "*", + "typescript": ">=5.1.0" + }, + "peerDependenciesMeta": { + "prisma": { + "optional": true + }, + "typescript": { + "optional": true + } + } + }, + "node_modules/@prisma/config": { + "version": "6.19.3", + "resolved": "https://registry.npmjs.org/@prisma/config/-/config-6.19.3.tgz", + "integrity": "sha512-CBPT44BjlQxEt8kiMEauji2WHTDoVBOKl7UlewXmUgBPnr/oPRZC3psci5chJnYmH0ivEIog2OU9PGWoki3DLQ==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "c12": "3.1.0", + "deepmerge-ts": "7.1.5", + "effect": "3.21.0", + "empathic": "2.0.0" + } + }, + "node_modules/@prisma/debug": { + "version": "6.19.3", + "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-6.19.3.tgz", + "integrity": "sha512-ljkJ+SgpXNktLG0Q/n4JGYCkKf0f8oYLyjImS2I8e2q2WCfdRRtWER062ZV/ixaNP2M2VKlWXVJiGzZaUgbKZw==", + "devOptional": true, + "license": "Apache-2.0" + }, + "node_modules/@prisma/engines": { + "version": "6.19.3", + "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-6.19.3.tgz", + "integrity": "sha512-RSYxtlYFl5pJ8ZePgMv0lZ9IzVCOdTPOegrs2qcbAEFrBI1G33h6wyC9kjQvo0DnYEhEVY0X4LsuFHXLKQk88g==", + "devOptional": true, + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "@prisma/debug": "6.19.3", + "@prisma/engines-version": "7.1.1-3.c2990dca591cba766e3b7ef5d9e8a84796e47ab7", + "@prisma/fetch-engine": "6.19.3", + "@prisma/get-platform": "6.19.3" + } + }, + "node_modules/@prisma/engines-version": { + "version": "7.1.1-3.c2990dca591cba766e3b7ef5d9e8a84796e47ab7", + "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-7.1.1-3.c2990dca591cba766e3b7ef5d9e8a84796e47ab7.tgz", + "integrity": "sha512-03bgb1VD5gvuumNf+7fVGBzfpJPjmqV423l/WxsWk2cNQ42JD0/SsFBPhN6z8iAvdHs07/7ei77SKu7aZfq8bA==", + "devOptional": true, + "license": "Apache-2.0" + }, + "node_modules/@prisma/fetch-engine": { + "version": "6.19.3", + "resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-6.19.3.tgz", + "integrity": "sha512-tKtl/qco9Nt7LU5iKhpultD8O4vMCZcU2CHjNTnRrL1QvSUr5W/GcyFPjNL87GtRrwBc7ubXXD9xy4EvLvt8JA==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "@prisma/debug": "6.19.3", + "@prisma/engines-version": "7.1.1-3.c2990dca591cba766e3b7ef5d9e8a84796e47ab7", + "@prisma/get-platform": "6.19.3" + } + }, + "node_modules/@prisma/get-platform": { + "version": "6.19.3", + "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-6.19.3.tgz", + "integrity": "sha512-xFj1VcJ1N3MKooOQAGO0W5tsd0W2QzIvW7DD7c/8H14Zmp4jseeWAITm+w2LLoLrlhoHdPPh0NMZ8mfL6puoHA==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "@prisma/debug": "6.19.3" + } + }, + "node_modules/@radix-ui/number": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/number/-/number-1.1.1.tgz", + "integrity": "sha512-MkKCwxlXTgz6CFoJx3pCwn07GKp36+aZyu/u2Ln2VrA5DcdyCZkASEDBTd8x5whTQQL5CiYf4prXKLcgQdv29g==", + "license": "MIT" + }, + "node_modules/@radix-ui/primitive": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.3.tgz", + "integrity": "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==", + "license": "MIT" + }, + "node_modules/@radix-ui/react-arrow": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.1.7.tgz", + "integrity": "sha512-F+M1tLhO+mlQaOWspE8Wstg+z6PwxwRd8oQ8IXceWz92kfAmalTRf0EjrouQeo7QssEPfCn05B4Ihs1K9WQ/7w==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-arrow/node_modules/@radix-ui/react-primitive": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz", + "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-arrow/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-avatar": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/@radix-ui/react-avatar/-/react-avatar-1.1.11.tgz", + "integrity": "sha512-0Qk603AHGV28BOBO34p7IgD5m+V5Sg/YovfayABkoDDBM5d3NCx0Mp4gGrjzLGes1jV5eNOE1r3itqOR33VC6Q==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-context": "1.1.3", + "@radix-ui/react-primitive": "2.1.4", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-is-hydrated": "0.1.0", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-collection": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.7.tgz", + "integrity": "sha512-Fh9rGN0MoI4ZFUNyfFVNU4y9LUz93u9/0K+yLgA2bwRojxM8JU1DyvvMBabnZPBgMWREAJvU2jjVzq+LrFUglw==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-slot": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-context": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.2.tgz", + "integrity": "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-primitive": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz", + "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-compose-refs": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.2.tgz", + "integrity": "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-context": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.3.tgz", + "integrity": "sha512-ieIFACdMpYfMEjF0rEf5KLvfVyIkOz6PDGyNnP+u+4xQ6jny3VCgA4OgXOwNx2aUkxn8zx9fiVcM8CfFYv9Lxw==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dialog": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.1.15.tgz", + "integrity": "sha512-TCglVRtzlffRNxRMEyR36DGBLJpeusFcgMVD9PZEzAKnUs1lKCgX5u9BmC2Yg+LL9MgZDugFFs1Vl+Jp4t/PGw==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-dismissable-layer": "1.1.11", + "@radix-ui/react-focus-guards": "1.1.3", + "@radix-ui/react-focus-scope": "1.1.7", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-portal": "1.1.9", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-slot": "1.2.3", + "@radix-ui/react-use-controllable-state": "1.2.2", + "aria-hidden": "^1.2.4", + "react-remove-scroll": "^2.6.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-context": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.2.tgz", + "integrity": "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-primitive": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz", + "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-direction": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.1.1.tgz", + "integrity": "sha512-1UEWRX6jnOA2y4H5WczZ44gOOjTEmlqv1uNW4GAJEO5+bauCBhv8snY65Iw5/VOS/ghKN9gr2KjnLKxrsvoMVw==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dismissable-layer": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.11.tgz", + "integrity": "sha512-Nqcp+t5cTB8BinFkZgXiMJniQH0PsUt2k51FUhbdfeKvc4ACcG2uQniY/8+h1Yv6Kza4Q7lD7PQV0z0oicE0Mg==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-escape-keydown": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dismissable-layer/node_modules/@radix-ui/react-primitive": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz", + "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dismissable-layer/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dropdown-menu": { + "version": "2.1.16", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dropdown-menu/-/react-dropdown-menu-2.1.16.tgz", + "integrity": "sha512-1PLGQEynI/3OX/ftV54COn+3Sud/Mn8vALg2rWnBLnRaGtJDduNW/22XjlGgPdpcIbiQxjKtb7BkcjP00nqfJw==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-menu": "2.1.16", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-controllable-state": "1.2.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dropdown-menu/node_modules/@radix-ui/react-context": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.2.tgz", + "integrity": "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dropdown-menu/node_modules/@radix-ui/react-primitive": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz", + "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dropdown-menu/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-focus-guards": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.3.tgz", + "integrity": "sha512-0rFg/Rj2Q62NCm62jZw0QX7a3sz6QCQU0LpZdNrJX8byRGaGVTqbrW9jAoIAHyMQqsNpeZ81YgSizOt5WXq0Pw==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-focus-scope": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.7.tgz", + "integrity": "sha512-t2ODlkXBQyn7jkl6TNaw/MtVEVvIGelJDCG41Okq/KwUsJBwQ4XVZsHAVUkK4mBv3ewiAS3PGuUWuY2BoK4ZUw==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-focus-scope/node_modules/@radix-ui/react-primitive": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz", + "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-focus-scope/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-id": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.1.tgz", + "integrity": "sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-label": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/@radix-ui/react-label/-/react-label-2.1.8.tgz", + "integrity": "sha512-FmXs37I6hSBVDlO4y764TNz1rLgKwjJMQ0EGte6F3Cb3f4bIuHB/iLa/8I9VKkmOy+gNHq8rql3j686ACVV21A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.4" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-menu": { + "version": "2.1.16", + "resolved": "https://registry.npmjs.org/@radix-ui/react-menu/-/react-menu-2.1.16.tgz", + "integrity": "sha512-72F2T+PLlphrqLcAotYPp0uJMr5SjP5SL01wfEspJbru5Zs5vQaSHb4VB3ZMJPimgHHCHG7gMOeOB9H3Hdmtxg==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-collection": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-dismissable-layer": "1.1.11", + "@radix-ui/react-focus-guards": "1.1.3", + "@radix-ui/react-focus-scope": "1.1.7", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-popper": "1.2.8", + "@radix-ui/react-portal": "1.1.9", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-roving-focus": "1.1.11", + "@radix-ui/react-slot": "1.2.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "aria-hidden": "^1.2.4", + "react-remove-scroll": "^2.6.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-context": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.2.tgz", + "integrity": "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-primitive": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz", + "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-popper": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.2.8.tgz", + "integrity": "sha512-0NJQ4LFFUuWkE7Oxf0htBKS6zLkkjBH+hM1uk7Ng705ReR8m/uelduy1DBo0PyBXPKVnBA6YBlU94MBGXrSBCw==", + "license": "MIT", + "dependencies": { + "@floating-ui/react-dom": "^2.0.0", + "@radix-ui/react-arrow": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-layout-effect": "1.1.1", + "@radix-ui/react-use-rect": "1.1.1", + "@radix-ui/react-use-size": "1.1.1", + "@radix-ui/rect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-popper/node_modules/@radix-ui/react-context": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.2.tgz", + "integrity": "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-popper/node_modules/@radix-ui/react-primitive": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz", + "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-popper/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-portal": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.9.tgz", + "integrity": "sha512-bpIxvq03if6UNwXZ+HTK71JLh4APvnXntDc6XOX8UVq4XQOVl7lwok0AvIl+b8zgCw3fSaVTZMpAPPagXbKmHQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-portal/node_modules/@radix-ui/react-primitive": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz", + "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-portal/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-presence": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.5.tgz", + "integrity": "sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-primitive": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.4.tgz", + "integrity": "sha512-9hQc4+GNVtJAIEPEqlYqW5RiYdrr8ea5XQ0ZOnD6fgru+83kqT15mq2OCcbe8KnjRZl5vF3ks69AKz3kh1jrhg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.2.4" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-roving-focus": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.11.tgz", + "integrity": "sha512-7A6S9jSgm/S+7MdtNDSb+IU859vQqJ/QAtcYQcfFC6W8RS4IxIZDldLR0xqCFZ6DCyrQLjLPsxtTNch5jVA4lA==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-collection": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-controllable-state": "1.2.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-roving-focus/node_modules/@radix-ui/react-context": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.2.tgz", + "integrity": "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-roving-focus/node_modules/@radix-ui/react-primitive": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz", + "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-roving-focus/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-scroll-area": { + "version": "1.2.10", + "resolved": "https://registry.npmjs.org/@radix-ui/react-scroll-area/-/react-scroll-area-1.2.10.tgz", + "integrity": "sha512-tAXIa1g3sM5CGpVT0uIbUx/U3Gs5N8T52IICuCtObaos1S8fzsrPXG5WObkQN3S6NVl6wKgPhAIiBGbWnvc97A==", + "license": "MIT", + "dependencies": { + "@radix-ui/number": "1.1.1", + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-scroll-area/node_modules/@radix-ui/react-context": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.2.tgz", + "integrity": "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-scroll-area/node_modules/@radix-ui/react-primitive": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz", + "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-scroll-area/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-select": { + "version": "2.2.6", + "resolved": "https://registry.npmjs.org/@radix-ui/react-select/-/react-select-2.2.6.tgz", + "integrity": "sha512-I30RydO+bnn2PQztvo25tswPH+wFBjehVGtmagkU78yMdwTwVf12wnAOF+AeP8S2N8xD+5UPbGhkUfPyvT+mwQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/number": "1.1.1", + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-collection": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-dismissable-layer": "1.1.11", + "@radix-ui/react-focus-guards": "1.1.3", + "@radix-ui/react-focus-scope": "1.1.7", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-popper": "1.2.8", + "@radix-ui/react-portal": "1.1.9", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-slot": "1.2.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-controllable-state": "1.2.2", + "@radix-ui/react-use-layout-effect": "1.1.1", + "@radix-ui/react-use-previous": "1.1.1", + "@radix-ui/react-visually-hidden": "1.2.3", + "aria-hidden": "^1.2.4", + "react-remove-scroll": "^2.6.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-select/node_modules/@radix-ui/react-context": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.2.tgz", + "integrity": "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-select/node_modules/@radix-ui/react-primitive": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz", + "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-select/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-separator": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/@radix-ui/react-separator/-/react-separator-1.1.8.tgz", + "integrity": "sha512-sDvqVY4itsKwwSMEe0jtKgfTh+72Sy3gPmQpjqcQneqQ4PFmr/1I0YA+2/puilhggCe2gJcx5EBAYFkWkdpa5g==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.4" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-slot": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.4.tgz", + "integrity": "sha512-Jl+bCv8HxKnlTLVrcDE8zTMJ09R9/ukw4qBs/oZClOfoQk/cOTbDn+NceXfV7j09YPVQUryJPHurafcSg6EVKA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-switch": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@radix-ui/react-switch/-/react-switch-1.2.6.tgz", + "integrity": "sha512-bByzr1+ep1zk4VubeEVViV592vu2lHE2BZY5OnzehZqOOgogN80+mNtCqPkhn2gklJqOpxWgPoYTSnhBCqpOXQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-controllable-state": "1.2.2", + "@radix-ui/react-use-previous": "1.1.1", + "@radix-ui/react-use-size": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-switch/node_modules/@radix-ui/react-context": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.2.tgz", + "integrity": "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-switch/node_modules/@radix-ui/react-primitive": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz", + "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-switch/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-tabs": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/@radix-ui/react-tabs/-/react-tabs-1.1.13.tgz", + "integrity": "sha512-7xdcatg7/U+7+Udyoj2zodtI9H/IIopqo+YOIcZOq1nJwXWBZ9p8xiu5llXlekDbZkca79a/fozEYQXIA4sW6A==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-roving-focus": "1.1.11", + "@radix-ui/react-use-controllable-state": "1.2.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-tabs/node_modules/@radix-ui/react-context": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.2.tgz", + "integrity": "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-tabs/node_modules/@radix-ui/react-primitive": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz", + "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-tabs/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-callback-ref": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.1.tgz", + "integrity": "sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-controllable-state": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.2.2.tgz", + "integrity": "sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-effect-event": "0.0.2", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-effect-event": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-effect-event/-/react-use-effect-event-0.0.2.tgz", + "integrity": "sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-escape-keydown": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.1.1.tgz", + "integrity": "sha512-Il0+boE7w/XebUHyBjroE+DbByORGR9KKmITzbR7MyQ4akpORYP/ZmbhAr0DG7RmmBqoOnZdy2QlvajJ2QA59g==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-callback-ref": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-is-hydrated": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-is-hydrated/-/react-use-is-hydrated-0.1.0.tgz", + "integrity": "sha512-U+UORVEq+cTnRIaostJv9AGdV3G6Y+zbVd+12e18jQ5A3c0xL03IhnHuiU4UV69wolOQp5GfR58NW/EgdQhwOA==", + "license": "MIT", + "dependencies": { + "use-sync-external-store": "^1.5.0" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-layout-effect": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.1.tgz", + "integrity": "sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-previous": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-previous/-/react-use-previous-1.1.1.tgz", + "integrity": "sha512-2dHfToCj/pzca2Ck724OZ5L0EVrr3eHRNsG/b3xQJLA2hZpVCS99bLAX+hm1IHXDEnzU6by5z/5MIY794/a8NQ==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-rect": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-rect/-/react-use-rect-1.1.1.tgz", + "integrity": "sha512-QTYuDesS0VtuHNNvMh+CjlKJ4LJickCMUAqjlE3+j8w+RlRpwyX3apEQKGFzbZGdo7XNG1tXa+bQqIE7HIXT2w==", + "license": "MIT", + "dependencies": { + "@radix-ui/rect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-size": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-size/-/react-use-size-1.1.1.tgz", + "integrity": "sha512-ewrXRDTAqAXlkl6t/fkXWNAhFX9I+CkKlw6zjEwk86RSPKwZr3xpBRso655aqYafwtnbpHLj6toFzmd6xdVptQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-visually-hidden": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-visually-hidden/-/react-visually-hidden-1.2.3.tgz", + "integrity": "sha512-pzJq12tEaaIhqjbzpCuv/OypJY/BPavOofm+dbab+MHLajy277+1lLm6JFcGgF5eskJ6mquGirhXY2GD/8u8Ug==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-visually-hidden/node_modules/@radix-ui/react-primitive": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz", + "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-visually-hidden/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/rect": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/rect/-/rect-1.1.1.tgz", + "integrity": "sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==", + "license": "MIT" + }, "node_modules/@rtsao/scc": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz", @@ -1313,6 +2958,20 @@ "dev": true, "license": "MIT" }, + "node_modules/@rushstack/eslint-patch": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.16.1.tgz", + "integrity": "sha512-TvZbIpeKqGQQ7X0zSCvPH9riMSFQFSggnfBjFZ1mEoILW+UuXCKwOoPcgjMwiUtRqFZ8jWhPJc4um14vC6I4ag==", + "dev": true, + "license": "MIT" + }, + "node_modules/@standard-schema/spec": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", + "devOptional": true, + "license": "MIT" + }, "node_modules/@swc/helpers": { "version": "0.5.15", "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz", @@ -1616,6 +3275,76 @@ "tslib": "^2.4.0" } }, + "node_modules/@types/bcryptjs": { + "version": "2.4.6", + "resolved": "https://registry.npmjs.org/@types/bcryptjs/-/bcryptjs-2.4.6.tgz", + "integrity": "sha512-9xlo6R2qDs5uixm0bcIqCeMCE6HiQsIyel9KQySStiyqNl2tnj2mP3DX1Nf56MD6KMenNNlBBsy3LJ7gUEQPXQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3-array": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.2.tgz", + "integrity": "sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==", + "license": "MIT" + }, + "node_modules/@types/d3-color": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz", + "integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==", + "license": "MIT" + }, + "node_modules/@types/d3-ease": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.2.tgz", + "integrity": "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==", + "license": "MIT" + }, + "node_modules/@types/d3-interpolate": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz", + "integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==", + "license": "MIT", + "dependencies": { + "@types/d3-color": "*" + } + }, + "node_modules/@types/d3-path": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.1.1.tgz", + "integrity": "sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==", + "license": "MIT" + }, + "node_modules/@types/d3-scale": { + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.9.tgz", + "integrity": "sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==", + "license": "MIT", + "dependencies": { + "@types/d3-time": "*" + } + }, + "node_modules/@types/d3-shape": { + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.8.tgz", + "integrity": "sha512-lae0iWfcDeR7qt7rA88BNiqdvPS5pFVPpo5OfjElwNaT2yyekbM0C9vK+yqBqEmHr6lDkRnYNoTBYlAgJa7a4w==", + "license": "MIT", + "dependencies": { + "@types/d3-path": "*" + } + }, + "node_modules/@types/d3-time": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.4.tgz", + "integrity": "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==", + "license": "MIT" + }, + "node_modules/@types/d3-timer": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.2.tgz", + "integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==", + "license": "MIT" + }, "node_modules/@types/estree": { "version": "1.0.9", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", @@ -1651,7 +3380,7 @@ "version": "19.2.15", "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.15.tgz", "integrity": "sha512-eRwcGNHve+E8qtEQSSRl6urh+rFop4v8gm6O8rGv25CodbvFdLjA1vVQ1KkiFE0w0UPOnb8tDiFKL5lp0rtY5Q==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "csstype": "^3.2.2" @@ -1661,7 +3390,7 @@ "version": "19.2.3", "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz", "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", - "dev": true, + "devOptional": true, "license": "MIT", "peerDependencies": { "@types/react": "^19.2.0" @@ -1894,19 +3623,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { - "version": "7.8.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.1.tgz", - "integrity": "sha512-rkVq3IXh+4FDGch+KwzX3aV9W3kO54GyEgpvBzSyctDA6Xtd7RJQV1xmXbeQp5v7+VzLOfVqiutSE6GICgPFvg==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/@typescript-eslint/utils": { "version": "8.59.4", "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.59.4.tgz", @@ -2345,11 +4061,19 @@ "url": "https://github.com/sponsors/epoberezkin" } }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, "license": "MIT", "dependencies": { "color-convert": "^2.0.1" @@ -2368,6 +4092,18 @@ "dev": true, "license": "Python-2.0" }, + "node_modules/aria-hidden": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/aria-hidden/-/aria-hidden-1.2.6.tgz", + "integrity": "sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/aria-query": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz", @@ -2598,16 +4334,13 @@ "dev": true, "license": "MIT" }, - "node_modules/baseline-browser-mapping": { - "version": "2.10.32", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.32.tgz", - "integrity": "sha512-wbPvpyjJPC0zdfdKXxqEL3Ea+bOMD/87X4lftiJkkaBiuG6ALQy1SLmEd7BSmVCuwCQsBrCamgBoLyfFDD1EPg==", - "license": "Apache-2.0", + "node_modules/bcryptjs": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-3.0.3.tgz", + "integrity": "sha512-GlF5wPWnSa/X5LKM1o0wz0suXIINz1iHRLvTS+sLyi7XPbe5ycmYI3DlZqVGZZtDgl4DmasFg7gOB3JYbphV5g==", + "license": "BSD-3-Clause", "bin": { - "baseline-browser-mapping": "dist/cli.cjs" - }, - "engines": { - "node": ">=6.0.0" + "bcrypt": "bin/bcrypt" } }, "node_modules/brace-expansion": { @@ -2634,38 +4367,58 @@ "node": ">=8" } }, - "node_modules/browserslist": { - "version": "4.28.2", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.2.tgz", - "integrity": "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], + "node_modules/bullmq": { + "version": "5.77.2", + "resolved": "https://registry.npmjs.org/bullmq/-/bullmq-5.77.2.tgz", + "integrity": "sha512-3I3LdL8vR8/hetgPZghwFvsxVFhRJdUfhjeA4kXTQtHehRwQRTndIVzxOBwo/Bv+bP2uBzHJo894Or7qAhbwDw==", "license": "MIT", "dependencies": { - "baseline-browser-mapping": "^2.10.12", - "caniuse-lite": "^1.0.30001782", - "electron-to-chromium": "^1.5.328", - "node-releases": "^2.0.36", - "update-browserslist-db": "^1.2.3" - }, - "bin": { - "browserslist": "cli.js" + "cron-parser": "4.9.0", + "ioredis": "5.10.1", + "msgpackr": "2.0.1", + "node-abort-controller": "3.1.1", + "semver": "7.8.0", + "tslib": "2.8.1" }, "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + "node": ">=12.22.0" + }, + "peerDependencies": { + "redis": ">=5.0.0" + }, + "peerDependenciesMeta": { + "redis": { + "optional": true + } + } + }, + "node_modules/c12": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/c12/-/c12-3.1.0.tgz", + "integrity": "sha512-uWoS8OU1MEIsOv8p/5a82c3H31LsWVR5qiyXVfBNOzfffjUWtPnhAb4BYI2uG2HfGmZmFjCtui5XNWaps+iFuw==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "chokidar": "^4.0.3", + "confbox": "^0.2.2", + "defu": "^6.1.4", + "dotenv": "^16.6.1", + "exsolve": "^1.0.7", + "giget": "^2.0.0", + "jiti": "^2.4.2", + "ohash": "^2.0.11", + "pathe": "^2.0.3", + "perfect-debounce": "^1.0.0", + "pkg-types": "^2.2.0", + "rc9": "^2.1.2" + }, + "peerDependencies": { + "magicast": "^0.3.5" + }, + "peerDependenciesMeta": { + "magicast": { + "optional": true + } } }, "node_modules/call-bind": { @@ -2728,6 +4481,15 @@ "node": ">=6" } }, + "node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/caniuse-lite": { "version": "1.0.30001793", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001793.tgz", @@ -2765,17 +4527,83 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/citty": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/citty/-/citty-0.1.6.tgz", + "integrity": "sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "consola": "^3.2.3" + } + }, + "node_modules/class-variance-authority": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/class-variance-authority/-/class-variance-authority-0.7.1.tgz", + "integrity": "sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==", + "license": "Apache-2.0", + "dependencies": { + "clsx": "^2.1.1" + }, + "funding": { + "url": "https://polar.sh/cva" + } + }, "node_modules/client-only": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==", "license": "MIT" }, + "node_modules/cliui": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", + "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^6.2.0" + } + }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/cluster-key-slot": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/cluster-key-slot/-/cluster-key-slot-1.1.2.tgz", + "integrity": "sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==", + "license": "Apache-2.0", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, "license": "MIT", "dependencies": { "color-name": "~1.1.4" @@ -2788,7 +4616,6 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, "license": "MIT" }, "node_modules/concat-map": { @@ -2798,13 +4625,35 @@ "dev": true, "license": "MIT" }, - "node_modules/convert-source-map": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "dev": true, + "node_modules/confbox": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.2.4.tgz", + "integrity": "sha512-ysOGlgTFbN2/Y6Cg3Iye8YKulHw+R2fNXHrgSmXISQdMnomY6eNDprVdW9R5xBguEqI954+S6709UyiO7B+6OQ==", + "devOptional": true, "license": "MIT" }, + "node_modules/consola": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/consola/-/consola-3.4.2.tgz", + "integrity": "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": "^14.18.0 || >=16.10.0" + } + }, + "node_modules/cron-parser": { + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/cron-parser/-/cron-parser-4.9.0.tgz", + "integrity": "sha512-p0SaNjrHOnQeR8/VnfGbmg9te2kfyYSQ7Sc/j/6DtPL3JQvKxmjO9TSjNFpujqV3vEYYBvNNvXSxzyksBWAx1Q==", + "license": "MIT", + "dependencies": { + "luxon": "^3.2.1" + }, + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -2824,9 +4673,129 @@ "version": "3.2.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", - "dev": true, "license": "MIT" }, + "node_modules/d3-array": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz", + "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==", + "license": "ISC", + "dependencies": { + "internmap": "1 - 2" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-color": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", + "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-ease": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz", + "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-format": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.2.tgz", + "integrity": "sha512-AJDdYOdnyRDV5b6ArilzCPPwc1ejkHcoyFarqlPqT7zRYjhavcT3uSrqcMvsgh2CgoPbK3RCwyHaVyxYcP2Arg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-interpolate": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", + "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-path": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz", + "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-scale": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz", + "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==", + "license": "ISC", + "dependencies": { + "d3-array": "2.10.0 - 3", + "d3-format": "1 - 3", + "d3-interpolate": "1.2.0 - 3", + "d3-time": "2.1.1 - 3", + "d3-time-format": "2 - 4" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-shape": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz", + "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==", + "license": "ISC", + "dependencies": { + "d3-path": "^3.1.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz", + "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==", + "license": "ISC", + "dependencies": { + "d3-array": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time-format": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz", + "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==", + "license": "ISC", + "dependencies": { + "d3-time": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-timer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz", + "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, "node_modules/damerau-levenshtein": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz", @@ -2892,7 +4861,6 @@ "version": "4.4.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "dev": true, "license": "MIT", "dependencies": { "ms": "^2.1.3" @@ -2906,6 +4874,21 @@ } } }, + "node_modules/decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/decimal.js-light": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/decimal.js-light/-/decimal.js-light-2.5.1.tgz", + "integrity": "sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==", + "license": "MIT" + }, "node_modules/deep-is": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", @@ -2913,6 +4896,16 @@ "dev": true, "license": "MIT" }, + "node_modules/deepmerge-ts": { + "version": "7.1.5", + "resolved": "https://registry.npmjs.org/deepmerge-ts/-/deepmerge-ts-7.1.5.tgz", + "integrity": "sha512-HOJkrhaYsweh+W+e74Yn7YStZOilkoPb6fycpwNLKzSPtruFs48nYis0zy5yJz1+ktUhHxoRDJ27RQAWLIJVJw==", + "devOptional": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=16.0.0" + } + }, "node_modules/define-data-property": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", @@ -2949,6 +4942,29 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/defu": { + "version": "6.1.7", + "resolved": "https://registry.npmjs.org/defu/-/defu-6.1.7.tgz", + "integrity": "sha512-7z22QmUWiQ/2d0KkdYmANbRUVABpZ9SNYyH5vx6PZ+nE5bcC0l7uFvEfHlyld/HcGBFTL536ClDt3DEcSlEJAQ==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/denque": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz", + "integrity": "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==", + "license": "Apache-2.0", + "engines": { + "node": ">=0.10" + } + }, + "node_modules/destr": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/destr/-/destr-2.0.5.tgz", + "integrity": "sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==", + "devOptional": true, + "license": "MIT" + }, "node_modules/detect-libc": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", @@ -2959,6 +4975,18 @@ "node": ">=8" } }, + "node_modules/detect-node-es": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz", + "integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==", + "license": "MIT" + }, + "node_modules/dns2": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/dns2/-/dns2-2.1.0.tgz", + "integrity": "sha512-m27K11aQalRbmUs7RLaz6aPyceLjAoqjPRNTdE7qUouQpl+PC8Bi67O+i9SuJUPbQC8dxFrczAxfmTPuTKHNkw==", + "license": "MIT" + }, "node_modules/doctrine": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", @@ -2972,6 +5000,29 @@ "node": ">=0.10.0" } }, + "node_modules/dom-helpers": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", + "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.8.7", + "csstype": "^3.0.2" + } + }, + "node_modules/dotenv": { + "version": "16.6.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", + "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==", + "devOptional": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, "node_modules/dunder-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", @@ -2987,12 +5038,16 @@ "node": ">= 0.4" } }, - "node_modules/electron-to-chromium": { - "version": "1.5.361", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.361.tgz", - "integrity": "sha512-Q6Hts7N9FnJc5LeGRINFvLhCI9xZmNtTDe5ZbcVezQz7cU4a8Aua3GH1b8J2XY8Al9PF+OCwYqhgsOOheMdvkA==", - "dev": true, - "license": "ISC" + "node_modules/effect": { + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/effect/-/effect-3.21.0.tgz", + "integrity": "sha512-PPN80qRokCd1f015IANNhrwOnLO7GrrMQfk4/lnZRE/8j7UPWrNNjPV0uBrZutI/nHzernbW+J0hdqQysHiSnQ==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@standard-schema/spec": "^1.0.0", + "fast-check": "^3.23.1" + } }, "node_modules/emoji-regex": { "version": "9.2.2", @@ -3001,6 +5056,16 @@ "dev": true, "license": "MIT" }, + "node_modules/empathic": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/empathic/-/empathic-2.0.0.tgz", + "integrity": "sha512-i6UzDscO/XfAcNYD75CfICkmfLedpyPDdozrLMmQc5ORaQcdMoc21OnlEylMIqI7U8eniKrPMxxtj8k0vhmJhA==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=14" + } + }, "node_modules/enhanced-resolve": { "version": "5.22.0", "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.22.0.tgz", @@ -3192,16 +5257,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/escalade": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", - "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/escape-string-regexp": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", @@ -3276,24 +5331,25 @@ } }, "node_modules/eslint-config-next": { - "version": "16.2.6", - "resolved": "https://registry.npmjs.org/eslint-config-next/-/eslint-config-next-16.2.6.tgz", - "integrity": "sha512-z2ELYSkyrrJ6cuunTU8vhsT/RpouPkjaSah06nVW6Rg2Hpg0Vs8s497/e5s8G8qtdp4ccsiovz5P1rv+5VSW2Q==", + "version": "15.5.18", + "resolved": "https://registry.npmjs.org/eslint-config-next/-/eslint-config-next-15.5.18.tgz", + "integrity": "sha512-HuoJU6uUPD00eyiud78IBnT4HLhztFj2V+ild2Uon5ZUrYZKe0Olu2QRD99e9IgL4/H1eg5Onka3BsfRW2U0Xw==", "dev": true, "license": "MIT", "dependencies": { - "@next/eslint-plugin-next": "16.2.6", + "@next/eslint-plugin-next": "15.5.18", + "@rushstack/eslint-patch": "^1.10.3", + "@typescript-eslint/eslint-plugin": "^5.4.2 || ^6.0.0 || ^7.0.0 || ^8.0.0", + "@typescript-eslint/parser": "^5.4.2 || ^6.0.0 || ^7.0.0 || ^8.0.0", "eslint-import-resolver-node": "^0.3.6", "eslint-import-resolver-typescript": "^3.5.2", - "eslint-plugin-import": "^2.32.0", + "eslint-plugin-import": "^2.31.0", "eslint-plugin-jsx-a11y": "^6.10.0", "eslint-plugin-react": "^7.37.0", - "eslint-plugin-react-hooks": "^7.0.0", - "globals": "16.4.0", - "typescript-eslint": "^8.46.0" + "eslint-plugin-react-hooks": "^5.0.0" }, "peerDependencies": { - "eslint": ">=9.0.0", + "eslint": "^7.23.0 || ^8.0.0 || ^9.0.0", "typescript": ">=3.3.1" }, "peerDependenciesMeta": { @@ -3302,19 +5358,6 @@ } } }, - "node_modules/eslint-config-next/node_modules/globals": { - "version": "16.4.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-16.4.0.tgz", - "integrity": "sha512-ob/2LcVVaVGCYN+r14cnwnoDPUufjiYgSqRhiFD0Q1iI4Odora5RE8Iv1D24hAz5oMophRGkGz+yuvQmmUMnMw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/eslint-import-resolver-node": { "version": "0.3.10", "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.10.tgz", @@ -3444,6 +5487,16 @@ "ms": "^2.1.1" } }, + "node_modules/eslint-plugin-import/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, "node_modules/eslint-plugin-jsx-a11y": { "version": "6.10.2", "resolved": "https://registry.npmjs.org/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.10.2.tgz", @@ -3508,23 +5561,26 @@ } }, "node_modules/eslint-plugin-react-hooks": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-7.1.1.tgz", - "integrity": "sha512-f2I7Gw6JbvCexzIInuSbZpfdQ44D7iqdWX01FKLvrPgqxoE7oMj8clOfto8U6vYiz4yd5oKu39rRSVOe1zRu0g==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-5.2.0.tgz", + "integrity": "sha512-+f15FfK64YQwZdJNELETdn5ibXEUQmW1DZL6KXhNnc2heoy/sg9VJJeT7n8TlMWouzWqSWavFkIhHyIbIAEapg==", "dev": true, "license": "MIT", - "dependencies": { - "@babel/core": "^7.24.4", - "@babel/parser": "^7.24.4", - "hermes-parser": "^0.25.1", - "zod": "^3.25.0 || ^4.0.0", - "zod-validation-error": "^3.5.0 || ^4.0.0" - }, "engines": { - "node": ">=18" + "node": ">=10" }, "peerDependencies": { - "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 || ^10.0.0" + "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0" + } + }, + "node_modules/eslint-plugin-react/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" } }, "node_modules/eslint-scope": { @@ -3621,6 +5677,42 @@ "node": ">=0.10.0" } }, + "node_modules/eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", + "license": "MIT" + }, + "node_modules/exsolve": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/exsolve/-/exsolve-1.0.8.tgz", + "integrity": "sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/fast-check": { + "version": "3.23.2", + "resolved": "https://registry.npmjs.org/fast-check/-/fast-check-3.23.2.tgz", + "integrity": "sha512-h5+1OzzfCC3Ef7VbtKdcv7zsstUQwUDlYpUTvjeUsJAssPgLn7QzbboPtL5ro04Mq0rPOsMzl7q5hIbRs2wD1A==", + "devOptional": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/dubzzz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fast-check" + } + ], + "license": "MIT", + "dependencies": { + "pure-rand": "^6.1.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -3628,6 +5720,15 @@ "dev": true, "license": "MIT" }, + "node_modules/fast-equals": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/fast-equals/-/fast-equals-5.4.0.tgz", + "integrity": "sha512-jt2DW/aNFNwke7AUd+Z+e6pz39KO5rzdbbFCg2sGafS4mk13MI7Z8O5z9cADNn5lhGODIgLwug6TZO2ctf7kcw==", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/fast-glob": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.1.tgz", @@ -3762,6 +5863,33 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/framer-motion": { + "version": "12.40.0", + "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.40.0.tgz", + "integrity": "sha512-uaBd3qC1v3KQqBEjwTUd183K6PbS+j0yR9w9VmEOLWA/tnUcSn8Xa3uck7t4dgpDoUss8xQTcj8W2L07lrnLFg==", + "license": "MIT", + "dependencies": { + "motion-dom": "^12.40.0", + "motion-utils": "^12.39.0", + "tslib": "^2.4.0" + }, + "peerDependencies": { + "@emotion/is-prop-valid": "*", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/is-prop-valid": { + "optional": true + }, + "react": { + "optional": true + }, + "react-dom": { + "optional": true + } + } + }, "node_modules/function-bind": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", @@ -3813,14 +5941,13 @@ "node": ">= 0.4" } }, - "node_modules/gensync": { - "version": "1.0.0-beta.2", - "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", - "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", - "dev": true, - "license": "MIT", + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "license": "ISC", "engines": { - "node": ">=6.9.0" + "node": "6.* || 8.* || >= 10.*" } }, "node_modules/get-intrinsic": { @@ -3848,6 +5975,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/get-nonce": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-nonce/-/get-nonce-1.0.1.tgz", + "integrity": "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/get-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", @@ -3893,6 +6029,24 @@ "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" } }, + "node_modules/giget": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/giget/-/giget-2.0.0.tgz", + "integrity": "sha512-L5bGsVkxJbJgdnwyuheIunkGatUF/zssUoxxjACCseZYAVbaqdh9Tsmmlkl8vYan09H7sbvKt4pS8GqKLBrEzA==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "citty": "^0.1.6", + "consola": "^3.4.0", + "defu": "^6.1.4", + "node-fetch-native": "^1.6.6", + "nypm": "^0.6.0", + "pathe": "^2.0.3" + }, + "bin": { + "giget": "dist/cli.mjs" + } + }, "node_modules/glob-parent": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", @@ -4050,23 +6204,6 @@ "node": ">= 0.4" } }, - "node_modules/hermes-estree": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.25.1.tgz", - "integrity": "sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==", - "dev": true, - "license": "MIT" - }, - "node_modules/hermes-parser": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.25.1.tgz", - "integrity": "sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==", - "dev": true, - "license": "MIT", - "dependencies": { - "hermes-estree": "0.25.1" - } - }, "node_modules/ignore": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", @@ -4119,6 +6256,48 @@ "node": ">= 0.4" } }, + "node_modules/internmap": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", + "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/ioredis": { + "version": "5.10.1", + "resolved": "https://registry.npmjs.org/ioredis/-/ioredis-5.10.1.tgz", + "integrity": "sha512-HuEDBTI70aYdx1v6U97SbNx9F1+svQKBDo30o0b9fw055LMepzpOOd0Ccg9Q6tbqmBSJaMuY0fB7yw9/vjBYCA==", + "license": "MIT", + "dependencies": { + "@ioredis/commands": "1.5.1", + "cluster-key-slot": "^1.1.0", + "debug": "^4.3.4", + "denque": "^2.1.0", + "lodash.defaults": "^4.2.0", + "lodash.isarguments": "^3.1.0", + "redis-errors": "^1.2.0", + "redis-parser": "^3.0.0", + "standard-as-callback": "^2.1.0" + }, + "engines": { + "node": ">=12.22.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/ioredis" + } + }, + "node_modules/ip-address": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.2.0.tgz", + "integrity": "sha512-/+S6j4E9AHvW9SWMSEY9Xfy66O5PWvVEJ08O0y5JGyEKQpojb0K0GKpz/v5HJ/G0vi3D2sjGK78119oXZeE0qA==", + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, "node_modules/is-array-buffer": { "version": "3.0.5", "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz", @@ -4200,19 +6379,6 @@ "semver": "^7.7.1" } }, - "node_modules/is-bun-module/node_modules/semver": { - "version": "7.8.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.1.tgz", - "integrity": "sha512-rkVq3IXh+4FDGch+KwzX3aV9W3kO54GyEgpvBzSyctDA6Xtd7RJQV1xmXbeQp5v7+VzLOfVqiutSE6GICgPFvg==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/is-callable": { "version": "1.2.7", "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", @@ -4303,6 +6469,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/is-generator-function": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.2.tgz", @@ -4570,17 +6745,25 @@ "version": "2.7.0", "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.7.0.tgz", "integrity": "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==", - "dev": true, + "devOptional": true, "license": "MIT", "bin": { "jiti": "lib/jiti-cli.mjs" } }, + "node_modules/jose": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/jose/-/jose-6.2.3.tgz", + "integrity": "sha512-YYVDInQKFJfR/xa3ojUTl8c2KoTwiL1R5Wg9YCydwH0x0B9grbzlg5HC7mMjCtUJjbQ/YnGEZIhI5tCgfTb4Hw==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true, "license": "MIT" }, "node_modules/js-yaml": { @@ -4596,19 +6779,6 @@ "js-yaml": "bin/js-yaml.js" } }, - "node_modules/jsesc": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", - "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", - "dev": true, - "license": "MIT", - "bin": { - "jsesc": "bin/jsesc" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/json-buffer": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", @@ -4631,16 +6801,16 @@ "license": "MIT" }, "node_modules/json5": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", - "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", "dev": true, "license": "MIT", + "dependencies": { + "minimist": "^1.2.0" + }, "bin": { "json5": "lib/cli.js" - }, - "engines": { - "node": ">=6" } }, "node_modules/jsx-ast-utils": { @@ -4992,6 +7162,24 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/lodash": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", + "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==", + "license": "MIT" + }, + "node_modules/lodash.defaults": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", + "integrity": "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==", + "license": "MIT" + }, + "node_modules/lodash.isarguments": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz", + "integrity": "sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg==", + "license": "MIT" + }, "node_modules/lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", @@ -5003,7 +7191,6 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", - "dev": true, "license": "MIT", "dependencies": { "js-tokens": "^3.0.0 || ^4.0.0" @@ -5012,14 +7199,22 @@ "loose-envify": "cli.js" } }, - "node_modules/lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", - "dev": true, + "node_modules/lucide-react": { + "version": "0.513.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.513.0.tgz", + "integrity": "sha512-CJZKq2g8Y8yN4Aq002GahSXbG2JpFv9kXwyiOAMvUBv7pxeOFHUWKB0mO7MiY4ZVFCV4aNjv2BJFq/z3DgKPQg==", "license": "ISC", - "dependencies": { - "yallist": "^3.0.2" + "peerDependencies": { + "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/luxon": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/luxon/-/luxon-3.7.2.tgz", + "integrity": "sha512-vtEhXh/gNjI9Yg1u4jX/0YVPMvxzHuGgCm6tC5kZyb08yjGWGnqAjGJvcXbqQR2P3MyMEFnRbpcdFS6PBcLqew==", + "license": "MIT", + "engines": { + "node": ">=12" } }, "node_modules/magic-string": { @@ -5089,13 +7284,58 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/motion-dom": { + "version": "12.40.0", + "resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.40.0.tgz", + "integrity": "sha512-HxU3ZaBwNPVQUBQf1xxgq+7JrPNZvjLVxgbpEZL7RrWJnsxOf0/OM+yrHG9ogLQ31Do/r57Oz2gQWPK+6q62mg==", + "license": "MIT", + "dependencies": { + "motion-utils": "^12.39.0" + } + }, + "node_modules/motion-utils": { + "version": "12.39.0", + "resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-12.39.0.tgz", + "integrity": "sha512-8nadJAJjTtqRkmRF36FoJTrywK9nnFmnPwnSMyxaOCU7GDjN9RTMJIxx9De8ErM+vpPhMccr/6fo5WciyQLnMQ==", + "license": "MIT" + }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, "license": "MIT" }, + "node_modules/msgpackr": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/msgpackr/-/msgpackr-2.0.1.tgz", + "integrity": "sha512-9J+tqTEsbHqY8YohazYgty7LgerFIWxvMLpUjqETSmjHojtJm2WnX2kK/2a1fLI7CO7ERP1YSEUXMucz4j+yBA==", + "license": "MIT", + "optionalDependencies": { + "msgpackr-extract": "^3.0.2" + } + }, + "node_modules/msgpackr-extract": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/msgpackr-extract/-/msgpackr-extract-3.0.3.tgz", + "integrity": "sha512-P0efT1C9jIdVRefqjzOQ9Xml57zpOXnIuS+csaB4MdZbTdmGDLo8XhzBG1N7aO11gKDDkJvBLULeFTo46wwreA==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "dependencies": { + "node-gyp-build-optional-packages": "5.2.2" + }, + "bin": { + "download-msgpackr-prebuilds": "bin/download-prebuilds.js" + }, + "optionalDependencies": { + "@msgpackr-extract/msgpackr-extract-darwin-arm64": "3.0.3", + "@msgpackr-extract/msgpackr-extract-darwin-x64": "3.0.3", + "@msgpackr-extract/msgpackr-extract-linux-arm": "3.0.3", + "@msgpackr-extract/msgpackr-extract-linux-arm64": "3.0.3", + "@msgpackr-extract/msgpackr-extract-linux-x64": "3.0.3", + "@msgpackr-extract/msgpackr-extract-win32-x64": "3.0.3" + } + }, "node_modules/nanoid": { "version": "3.3.12", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz", @@ -5138,14 +7378,13 @@ "license": "MIT" }, "node_modules/next": { - "version": "16.2.6", - "resolved": "https://registry.npmjs.org/next/-/next-16.2.6.tgz", - "integrity": "sha512-qOVgKJg1+At15NpeUP+eJgCHvTCgXsogweq87Ri/Ix7PkqQHg4sdaXmSFqKlgaIXE4kW0g25LE68W87UANlHtw==", + "version": "15.5.18", + "resolved": "https://registry.npmjs.org/next/-/next-15.5.18.tgz", + "integrity": "sha512-eKL8zUJkX9Y5lE+RX/2YJoItVdGlIscyVyboeD9wSpp0PaGqjoA4tTpT2qPqz9ax+5IzGESyLSeZ/RCwbSZ2uQ==", "license": "MIT", "dependencies": { - "@next/env": "16.2.6", + "@next/env": "15.5.18", "@swc/helpers": "0.5.15", - "baseline-browser-mapping": "^2.9.19", "caniuse-lite": "^1.0.30001579", "postcss": "8.4.31", "styled-jsx": "5.1.6" @@ -5154,18 +7393,18 @@ "next": "dist/bin/next" }, "engines": { - "node": ">=20.9.0" + "node": "^18.18.0 || ^19.8.0 || >= 20.0.0" }, "optionalDependencies": { - "@next/swc-darwin-arm64": "16.2.6", - "@next/swc-darwin-x64": "16.2.6", - "@next/swc-linux-arm64-gnu": "16.2.6", - "@next/swc-linux-arm64-musl": "16.2.6", - "@next/swc-linux-x64-gnu": "16.2.6", - "@next/swc-linux-x64-musl": "16.2.6", - "@next/swc-win32-arm64-msvc": "16.2.6", - "@next/swc-win32-x64-msvc": "16.2.6", - "sharp": "^0.34.5" + "@next/swc-darwin-arm64": "15.5.18", + "@next/swc-darwin-x64": "15.5.18", + "@next/swc-linux-arm64-gnu": "15.5.18", + "@next/swc-linux-arm64-musl": "15.5.18", + "@next/swc-linux-x64-gnu": "15.5.18", + "@next/swc-linux-x64-musl": "15.5.18", + "@next/swc-win32-arm64-msvc": "15.5.18", + "@next/swc-win32-x64-msvc": "15.5.18", + "sharp": "^0.34.3" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0", @@ -5190,6 +7429,43 @@ } } }, + "node_modules/next-auth": { + "version": "5.0.0-beta.31", + "resolved": "https://registry.npmjs.org/next-auth/-/next-auth-5.0.0-beta.31.tgz", + "integrity": "sha512-1OBgCKPzo+S7UWWMp3xgvGvIJ0OpV7B3vR4ZDRqD9a4Ch+OT6dakLXG9ivhtmIWVa71nTSXattOHyCg8sNi8/Q==", + "license": "ISC", + "dependencies": { + "@auth/core": "0.41.2" + }, + "peerDependencies": { + "@simplewebauthn/browser": "^9.0.1", + "@simplewebauthn/server": "^9.0.2", + "next": "^14.0.0-0 || ^15.0.0 || ^16.0.0", + "nodemailer": "^7.0.7", + "react": "^18.2.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@simplewebauthn/browser": { + "optional": true + }, + "@simplewebauthn/server": { + "optional": true + }, + "nodemailer": { + "optional": true + } + } + }, + "node_modules/next-themes": { + "version": "0.4.6", + "resolved": "https://registry.npmjs.org/next-themes/-/next-themes-0.4.6.tgz", + "integrity": "sha512-pZvgD5L0IEvX5/9GWyHMf3m8BKiVQwsCMHfoFosXtXBMnaS0ZnIJ9ST4b4NqLVKDEm8QBxoNNGNaBv2JNF6XNA==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc" + } + }, "node_modules/next/node_modules/postcss": { "version": "8.4.31", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", @@ -5218,6 +7494,12 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/node-abort-controller": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/node-abort-controller/-/node-abort-controller-3.1.1.tgz", + "integrity": "sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ==", + "license": "MIT" + }, "node_modules/node-exports-info": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/node-exports-info/-/node-exports-info-1.6.0.tgz", @@ -5237,21 +7519,76 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/node-releases": { - "version": "2.0.46", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.46.tgz", - "integrity": "sha512-GYVXHE2KnrzAfsAjl4uP++evGFCrAU1jta4ubEjIG7YWt/64Gqv66a30yKwWczVjA6j3bM4nBwH7Pk1JmDHaxQ==", + "node_modules/node-exports-info/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/node-fetch-native": { + "version": "1.6.7", + "resolved": "https://registry.npmjs.org/node-fetch-native/-/node-fetch-native-1.6.7.tgz", + "integrity": "sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/node-gyp-build-optional-packages": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/node-gyp-build-optional-packages/-/node-gyp-build-optional-packages-5.2.2.tgz", + "integrity": "sha512-s+w+rBWnpTMwSFbaE0UXsRlg7hU4FjekKU4eyAih5T8nJuNZT1nNsskXpxmeqSK9UzkBl6UgRlnKc8hz8IEqOw==", "license": "MIT", + "optional": true, + "dependencies": { + "detect-libc": "^2.0.1" + }, + "bin": { + "node-gyp-build-optional-packages": "bin.js", + "node-gyp-build-optional-packages-optional": "optional.js", + "node-gyp-build-optional-packages-test": "build-test.js" + } + }, + "node_modules/nypm": { + "version": "0.6.6", + "resolved": "https://registry.npmjs.org/nypm/-/nypm-0.6.6.tgz", + "integrity": "sha512-vRyr0r4cbBapw07Xw8xrj9Teq3o7MUD35rSaTcanDbW+aK2XHDgJFiU6ZTj2GBw7Q12ysdsyFss+Vdz4hQ0Y6Q==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "citty": "^0.2.2", + "pathe": "^2.0.3", + "tinyexec": "^1.1.1" + }, + "bin": { + "nypm": "dist/cli.mjs" + }, "engines": { "node": ">=18" } }, + "node_modules/nypm/node_modules/citty": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/citty/-/citty-0.2.2.tgz", + "integrity": "sha512-+6vJA3L98yv+IdfKGZHBNiGW5KHn22e/JwID0Strsz8h4S/csAu/OuICwxrg44k5MRiZHWIo8XXuJgQTriRP4w==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/oauth4webapi": { + "version": "3.8.6", + "resolved": "https://registry.npmjs.org/oauth4webapi/-/oauth4webapi-3.8.6.tgz", + "integrity": "sha512-iwemM91xz8nryHti2yTmg5fhyEMVOkOXwHNqbvcATjyajb5oQxCQzrNOA6uElRHuMhQQTKUyFKV9y/CNyg25BQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -5370,6 +7707,13 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/ohash": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/ohash/-/ohash-2.0.11.tgz", + "integrity": "sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==", + "devOptional": true, + "license": "MIT" + }, "node_modules/optionator": { "version": "0.9.4", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", @@ -5438,6 +7782,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -5455,7 +7808,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -5478,6 +7830,20 @@ "dev": true, "license": "MIT" }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/perfect-debounce": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-1.0.0.tgz", + "integrity": "sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==", + "devOptional": true, + "license": "MIT" + }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", @@ -5497,6 +7863,18 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/pkg-types": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-2.3.1.tgz", + "integrity": "sha512-y+ichcgc2LrADuhLNAx8DFjVfgz91pRxfZdI3UDhxHvcVEZsenLO+7XaU5vOp0u/7V/wZ+plyuQxtrDlZJ+yeg==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "confbox": "^0.2.4", + "exsolve": "^1.0.8", + "pathe": "^2.0.3" + } + }, "node_modules/possible-typed-array-names": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", @@ -5536,6 +7914,25 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/preact": { + "version": "10.24.3", + "resolved": "https://registry.npmjs.org/preact/-/preact-10.24.3.tgz", + "integrity": "sha512-Z2dPnBnMUfyQfSQ+GBdsGa16hz35YmLmtTLhM169uW944hYL6xzTYkJjC07j+Wosz733pMWx0fgON3JNw1jJQA==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/preact" + } + }, + "node_modules/preact-render-to-string": { + "version": "6.5.11", + "resolved": "https://registry.npmjs.org/preact-render-to-string/-/preact-render-to-string-6.5.11.tgz", + "integrity": "sha512-ubnauqoGczeGISiOh6RjX0/cdaF8v/oDXIjO85XALCQjwQP+SB4RDXXtvZ6yTYSjG+PC1QRP2AhPgCEsM2EvUw==", + "license": "MIT", + "peerDependencies": { + "preact": ">=10" + } + }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -5546,11 +7943,36 @@ "node": ">= 0.8.0" } }, + "node_modules/prisma": { + "version": "6.19.3", + "resolved": "https://registry.npmjs.org/prisma/-/prisma-6.19.3.tgz", + "integrity": "sha512-++ZJ0ijLrDJF6hNB4t4uxg2br3fC4H9Yc9tcbjr2fcNFP3rh/SBNrAgjhsqBU4Ght8JPrVofG/ZkXfnSfnYsFg==", + "devOptional": true, + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "@prisma/config": "6.19.3", + "@prisma/engines": "6.19.3" + }, + "bin": { + "prisma": "build/index.js" + }, + "engines": { + "node": ">=18.18" + }, + "peerDependencies": { + "typescript": ">=5.1.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, "node_modules/prop-types": { "version": "15.8.1", "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", - "dev": true, "license": "MIT", "dependencies": { "loose-envify": "^1.4.0", @@ -5568,6 +7990,23 @@ "node": ">=6" } }, + "node_modules/pure-rand": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz", + "integrity": "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==", + "devOptional": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/dubzzz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fast-check" + } + ], + "license": "MIT" + }, "node_modules/queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -5589,34 +8028,218 @@ ], "license": "MIT" }, + "node_modules/rc9": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/rc9/-/rc9-2.1.2.tgz", + "integrity": "sha512-btXCnMmRIBINM2LDZoEmOogIZU7Qe7zn4BpomSKZ/ykbLObuBdvG+mFq11DL6fjH1DRwHhrlgtYWG96bJiC7Cg==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "defu": "^6.1.4", + "destr": "^2.0.3" + } + }, "node_modules/react": { - "version": "19.2.4", - "resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz", - "integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==", + "version": "19.2.6", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.6.tgz", + "integrity": "sha512-sfWGGfavi0xr8Pg0sVsyHMAOziVYKgPLNrS7ig+ivMNb3wbCBw3KxtflsGBAwD3gYQlE/AEZsTLgToRrSCjb0Q==", "license": "MIT", "engines": { "node": ">=0.10.0" } }, "node_modules/react-dom": { - "version": "19.2.4", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.4.tgz", - "integrity": "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==", + "version": "19.2.6", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.6.tgz", + "integrity": "sha512-0prMI+hvBbPjsWnxDLxlCGyM8PN6UuWjEUCYmZhO67xIV9Xasa/r/vDnq+Xyq4Lo27g8QSbO5YzARu0D1Sps3g==", "license": "MIT", "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { - "react": "^19.2.4" + "react": "^19.2.6" } }, "node_modules/react-is": { "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", - "dev": true, "license": "MIT" }, + "node_modules/react-remove-scroll": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.7.2.tgz", + "integrity": "sha512-Iqb9NjCCTt6Hf+vOdNIZGdTiH1QSqr27H/Ek9sv/a97gfueI/5h1s3yRi1nngzMUaOOToin5dI1dXKdXiF+u0Q==", + "license": "MIT", + "dependencies": { + "react-remove-scroll-bar": "^2.3.7", + "react-style-singleton": "^2.2.3", + "tslib": "^2.1.0", + "use-callback-ref": "^1.3.3", + "use-sidecar": "^1.1.3" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/react-remove-scroll-bar": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.8.tgz", + "integrity": "sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==", + "license": "MIT", + "dependencies": { + "react-style-singleton": "^2.2.2", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/react-smooth": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/react-smooth/-/react-smooth-4.0.4.tgz", + "integrity": "sha512-gnGKTpYwqL0Iii09gHobNolvX4Kiq4PKx6eWBCYYix+8cdw+cGo3do906l1NBPKkSWx1DghC1dlWG9L2uGd61Q==", + "license": "MIT", + "dependencies": { + "fast-equals": "^5.0.1", + "prop-types": "^15.8.1", + "react-transition-group": "^4.4.5" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/react-style-singleton": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.3.tgz", + "integrity": "sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==", + "license": "MIT", + "dependencies": { + "get-nonce": "^1.0.0", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/react-transition-group": { + "version": "4.4.5", + "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", + "integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==", + "license": "BSD-3-Clause", + "dependencies": { + "@babel/runtime": "^7.5.5", + "dom-helpers": "^5.0.1", + "loose-envify": "^1.4.0", + "prop-types": "^15.6.2" + }, + "peerDependencies": { + "react": ">=16.6.0", + "react-dom": ">=16.6.0" + } + }, + "node_modules/readdirp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">= 14.18.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/recharts": { + "version": "2.15.4", + "resolved": "https://registry.npmjs.org/recharts/-/recharts-2.15.4.tgz", + "integrity": "sha512-UT/q6fwS3c1dHbXv2uFgYJ9BMFHu3fwnd7AYZaEQhXuYQ4hgsxLvsUXzGdKeZrW5xopzDCvuA2N41WJ88I7zIw==", + "deprecated": "1.x and 2.x branches are no longer active. Bump to Recharts v3 to receive latest features and bugfixes. See https://github.com/recharts/recharts/wiki/3.0-migration-guide", + "license": "MIT", + "dependencies": { + "clsx": "^2.0.0", + "eventemitter3": "^4.0.1", + "lodash": "^4.17.21", + "react-is": "^18.3.1", + "react-smooth": "^4.0.4", + "recharts-scale": "^0.4.4", + "tiny-invariant": "^1.3.1", + "victory-vendor": "^36.6.8" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "react": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/recharts-scale": { + "version": "0.4.5", + "resolved": "https://registry.npmjs.org/recharts-scale/-/recharts-scale-0.4.5.tgz", + "integrity": "sha512-kivNFO+0OcUNu7jQquLXAxz1FIwZj8nrj+YkOKc5694NbjCvcT6aSZiIzNzd2Kul4o4rTto8QVR9lMNtxD4G1w==", + "license": "MIT", + "dependencies": { + "decimal.js-light": "^2.4.1" + } + }, + "node_modules/recharts/node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "license": "MIT" + }, + "node_modules/redis-errors": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/redis-errors/-/redis-errors-1.2.0.tgz", + "integrity": "sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/redis-parser": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/redis-parser/-/redis-parser-3.0.0.tgz", + "integrity": "sha512-DJnGAeenTdpMEH6uAJRK/uiyEIH9WVsUmoLwzudwGJUwZPp80PDBWPHXSAGNPwNvIXAbe7MSUB1zQFugFml66A==", + "license": "MIT", + "dependencies": { + "redis-errors": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/reflect.getprototypeof": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", @@ -5661,6 +8284,21 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-main-filename": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", + "license": "ISC" + }, "node_modules/resolve": { "version": "2.0.0-next.7", "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.7.tgz", @@ -5802,15 +8440,23 @@ "license": "MIT" }, "node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.0.tgz", + "integrity": "sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA==", "license": "ISC", "bin": { "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" } }, + "node_modules/set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", + "license": "ISC" + }, "node_modules/set-function-length": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", @@ -5905,19 +8551,6 @@ "@img/sharp-win32-x64": "0.34.5" } }, - "node_modules/sharp/node_modules/semver": { - "version": "7.8.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.1.tgz", - "integrity": "sha512-rkVq3IXh+4FDGch+KwzX3aV9W3kO54GyEgpvBzSyctDA6Xtd7RJQV1xmXbeQp5v7+VzLOfVqiutSE6GICgPFvg==", - "license": "ISC", - "optional": true, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -6017,6 +8650,40 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/smart-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", + "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", + "license": "MIT", + "engines": { + "node": ">= 6.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks": { + "version": "2.8.9", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.9.tgz", + "integrity": "sha512-LJhUYUvItdQ0LkJTmPeaEObWXAqFyfmP85x0tch/ez9cahmhlBBLbIqDFnvBnUJGagb0JbIQrkBs1wJ+yRYpEw==", + "license": "MIT", + "dependencies": { + "ip-address": "^10.1.1", + "smart-buffer": "^4.2.0" + }, + "engines": { + "node": ">= 10.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/sonner": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/sonner/-/sonner-2.0.7.tgz", + "integrity": "sha512-W6ZN4p58k8aDKA4XPcx2hpIQXBRAgyiWVkYhT7CvK6D3iAu7xjvVyhQHg2/iaKJZ1XVJ4r7XuwGL+WGEK37i9w==", + "license": "MIT", + "peerDependencies": { + "react": "^18.0.0 || ^19.0.0 || ^19.0.0-rc", + "react-dom": "^18.0.0 || ^19.0.0 || ^19.0.0-rc" + } + }, "node_modules/source-map-js": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", @@ -6033,6 +8700,12 @@ "dev": true, "license": "MIT" }, + "node_modules/standard-as-callback": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/standard-as-callback/-/standard-as-callback-2.1.0.tgz", + "integrity": "sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A==", + "license": "MIT" + }, "node_modules/stop-iteration-iterator": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz", @@ -6047,6 +8720,26 @@ "node": ">= 0.4" } }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, "node_modules/string.prototype.includes": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/string.prototype.includes/-/string.prototype.includes-2.0.1.tgz", @@ -6160,6 +8853,18 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/strip-bom": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", @@ -6232,6 +8937,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/tailwind-merge": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.6.0.tgz", + "integrity": "sha512-uxL7qAVQriqRQPAyK3pj66VqskWqoZ37PW94jwOTwNfq/z9oyu1V+eqrZqtR2+fCiXdYOZe/Modt8GtvqNzu+w==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/dcastil" + } + }, "node_modules/tailwindcss": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.3.0.tgz", @@ -6253,6 +8968,22 @@ "url": "https://opencollective.com/webpack" } }, + "node_modules/tiny-invariant": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz", + "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==", + "license": "MIT" + }, + "node_modules/tinyexec": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.2.2.tgz", + "integrity": "sha512-M/Q0B2cp4K7kynaT/vnED1j8TlLY+Pp7C6Wl2bl/7u/F0mUVwdyOpwomQb8JpYLitHUssAJRmLZdMCGsrx7i+g==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/tinyglobby": { "version": "0.2.16", "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz", @@ -6340,19 +9071,6 @@ "strip-bom": "^3.0.0" } }, - "node_modules/tsconfig-paths/node_modules/json5": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", - "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", - "dev": true, - "license": "MIT", - "dependencies": { - "minimist": "^1.2.0" - }, - "bin": { - "json5": "lib/cli.js" - } - }, "node_modules/tslib": { "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", @@ -6454,7 +9172,7 @@ "version": "5.9.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", @@ -6464,30 +9182,6 @@ "node": ">=14.17" } }, - "node_modules/typescript-eslint": { - "version": "8.59.4", - "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.59.4.tgz", - "integrity": "sha512-Rw6+44QNFaXtgHSjPy+Kw8hrJniMYzR85E9yLmOLcfZ91/rz+JXQbDTCmc6ccxMPY6K6PgAq26f0JCBfR7LIPQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/eslint-plugin": "8.59.4", - "@typescript-eslint/parser": "8.59.4", - "@typescript-eslint/typescript-estree": "8.59.4", - "@typescript-eslint/utils": "8.59.4" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", - "typescript": ">=4.8.4 <6.1.0" - } - }, "node_modules/unbox-primitive": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz", @@ -6507,6 +9201,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/underscore": { + "version": "1.13.8", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.8.tgz", + "integrity": "sha512-DXtD3ZtEQzc7M8m4cXotyHR+FAS18C64asBYY5vqZexfYryNNnDc02W4hKg3rdQuqOYas1jkseX0+nZXjTXnvQ==", + "license": "MIT" + }, "node_modules/undici-types": { "version": "6.21.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", @@ -6552,37 +9252,6 @@ "@unrs/resolver-binding-win32-x64-msvc": "1.12.2" } }, - "node_modules/update-browserslist-db": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", - "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "escalade": "^3.2.0", - "picocolors": "^1.1.1" - }, - "bin": { - "update-browserslist-db": "cli.js" - }, - "peerDependencies": { - "browserslist": ">= 4.21.0" - } - }, "node_modules/uri-js": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", @@ -6593,6 +9262,80 @@ "punycode": "^2.1.0" } }, + "node_modules/use-callback-ref": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.3.tgz", + "integrity": "sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/use-sidecar": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/use-sidecar/-/use-sidecar-1.1.3.tgz", + "integrity": "sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==", + "license": "MIT", + "dependencies": { + "detect-node-es": "^1.1.0", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/use-sync-external-store": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz", + "integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/victory-vendor": { + "version": "36.9.2", + "resolved": "https://registry.npmjs.org/victory-vendor/-/victory-vendor-36.9.2.tgz", + "integrity": "sha512-PnpQQMuxlwYdocC8fIJqVXvkeViHYzotI+NJrCuav0ZYFoq912ZHBk3mCeuj+5/VpodOjPe1z0Fk2ihgzlXqjQ==", + "license": "MIT AND ISC", + "dependencies": { + "@types/d3-array": "^3.0.3", + "@types/d3-ease": "^3.0.0", + "@types/d3-interpolate": "^3.0.1", + "@types/d3-scale": "^4.0.2", + "@types/d3-shape": "^3.1.0", + "@types/d3-time": "^3.0.0", + "@types/d3-timer": "^3.0.0", + "d3-array": "^3.1.6", + "d3-ease": "^3.0.1", + "d3-interpolate": "^3.0.1", + "d3-scale": "^4.0.2", + "d3-shape": "^3.1.0", + "d3-time": "^3.0.0", + "d3-timer": "^3.0.1" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -6676,6 +9419,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/which-module": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.1.tgz", + "integrity": "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==", + "license": "ISC" + }, "node_modules/which-typed-array": { "version": "1.1.20", "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.20.tgz", @@ -6698,6 +9447,20 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/whois": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/whois/-/whois-2.16.1.tgz", + "integrity": "sha512-gCxr+knAuXzsHJlHaIi8bCZoRU9anysyKbo0mgjAkkanNdOvUhBZShgG/ckcpKryDUVs2cH684MU+wOLjBYlAA==", + "license": "FreeBSD", + "dependencies": { + "socks": "^2.2.2", + "underscore": "^1.9.1", + "yargs": "^15.4.1" + }, + "bin": { + "whois": "bin.js" + } + }, "node_modules/word-wrap": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", @@ -6708,13 +9471,113 @@ "node": ">=0.10.0" } }, - "node_modules/yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "dev": true, + "node_modules/wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/y18n": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", + "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", "license": "ISC" }, + "node_modules/yargs": { + "version": "15.4.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", + "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", + "license": "MIT", + "dependencies": { + "cliui": "^6.0.0", + "decamelize": "^1.2.0", + "find-up": "^4.1.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^4.2.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^18.1.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs-parser": { + "version": "18.1.3", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", + "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", + "license": "ISC", + "dependencies": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/yargs/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "license": "MIT", + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "license": "MIT", + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "license": "MIT", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/yargs/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "license": "MIT", + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", @@ -6729,27 +9592,13 @@ } }, "node_modules/zod": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/zod/-/zod-4.4.3.tgz", - "integrity": "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==", - "dev": true, + "version": "3.25.76", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", + "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", "license": "MIT", "funding": { "url": "https://github.com/sponsors/colinhacks" } - }, - "node_modules/zod-validation-error": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/zod-validation-error/-/zod-validation-error-4.0.2.tgz", - "integrity": "sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18.0.0" - }, - "peerDependencies": { - "zod": "^3.25.0 || ^4.0.0" - } } } } diff --git a/package.json b/package.json new file mode 100644 index 0000000..0590846 --- /dev/null +++ b/package.json @@ -0,0 +1,57 @@ +{ + "name": "domain-scanner", + "version": "1.0.0", + "private": true, + "scripts": { + "dev": "next dev", + "build": "prisma generate && next build", + "start": "next start", + "lint": "eslint", + "postinstall": "prisma generate", + "db:push": "prisma db push", + "db:studio": "prisma studio" + }, + "dependencies": { + "@prisma/client": "^6.9.0", + "@radix-ui/react-avatar": "^1.1.11", + "@radix-ui/react-dialog": "^1.1.15", + "@radix-ui/react-dropdown-menu": "^2.1.16", + "@radix-ui/react-label": "^2.1.8", + "@radix-ui/react-scroll-area": "^1.2.10", + "@radix-ui/react-select": "^2.2.6", + "@radix-ui/react-separator": "^1.1.8", + "@radix-ui/react-slot": "^1.2.4", + "@radix-ui/react-switch": "^1.2.6", + "@radix-ui/react-tabs": "^1.1.13", + "bcryptjs": "^3.0.2", + "bullmq": "^5.56.0", + "class-variance-authority": "^0.7.1", + "clsx": "^2.1.1", + "dns2": "^2.1.0", + "framer-motion": "^12.16.0", + "ioredis": "^5.6.1", + "lucide-react": "^0.513.0", + "next": "^15.3.3", + "next-auth": "^5.0.0-beta.28", + "next-themes": "^0.4.6", + "react": "^19.1.0", + "react-dom": "^19.1.0", + "recharts": "^2.15.3", + "sonner": "^2.0.5", + "tailwind-merge": "^3.3.0", + "whois": "^2.14.2", + "zod": "^3.25.51" + }, + "devDependencies": { + "@tailwindcss/postcss": "^4", + "@types/bcryptjs": "^2.4.6", + "@types/node": "^20", + "@types/react": "^19", + "@types/react-dom": "^19", + "eslint": "^9", + "eslint-config-next": "^15.3.3", + "prisma": "^6.9.0", + "tailwindcss": "^4", + "typescript": "^5" + } +} diff --git a/premium/postcss.config.mjs b/postcss.config.mjs similarity index 100% rename from premium/postcss.config.mjs rename to postcss.config.mjs diff --git a/premium/.gitignore b/premium/.gitignore deleted file mode 100644 index 5ef6a52..0000000 --- a/premium/.gitignore +++ /dev/null @@ -1,41 +0,0 @@ -# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. - -# dependencies -/node_modules -/.pnp -.pnp.* -.yarn/* -!.yarn/patches -!.yarn/plugins -!.yarn/releases -!.yarn/versions - -# testing -/coverage - -# next.js -/.next/ -/out/ - -# production -/build - -# misc -.DS_Store -*.pem - -# debug -npm-debug.log* -yarn-debug.log* -yarn-error.log* -.pnpm-debug.log* - -# env files (can opt-in for committing if needed) -.env* - -# vercel -.vercel - -# typescript -*.tsbuildinfo -next-env.d.ts diff --git a/premium/AGENTS.md b/premium/AGENTS.md deleted file mode 100644 index 8bd0e39..0000000 --- a/premium/AGENTS.md +++ /dev/null @@ -1,5 +0,0 @@ - -# This is NOT the Next.js you know - -This version has breaking changes — APIs, conventions, and file structure may all differ from your training data. Read the relevant guide in `node_modules/next/dist/docs/` before writing any code. Heed deprecation notices. - diff --git a/premium/CLAUDE.md b/premium/CLAUDE.md deleted file mode 100644 index 43c994c..0000000 --- a/premium/CLAUDE.md +++ /dev/null @@ -1 +0,0 @@ -@AGENTS.md diff --git a/premium/Dockerfile b/premium/Dockerfile deleted file mode 100644 index 95bec73..0000000 --- a/premium/Dockerfile +++ /dev/null @@ -1,26 +0,0 @@ -# GeoExport — production image (Next.js) -FROM node:22-alpine AS base -RUN apk add --no-cache libc6-compat -WORKDIR /app - -FROM base AS deps -COPY package.json ./ -RUN npm install - -FROM base AS builder -COPY --from=deps /app/node_modules ./node_modules -COPY . . -ENV NEXT_TELEMETRY_DISABLED=1 -RUN npm run build - -FROM base AS runner -ENV NODE_ENV=production -ENV NEXT_TELEMETRY_DISABLED=1 -COPY --from=builder /app/package.json ./ -COPY --from=builder /app/node_modules ./node_modules -COPY --from=builder /app/.next ./.next -COPY --from=builder /app/public ./public -EXPOSE 3000 -ENV PORT=3000 -ENV HOSTNAME=0.0.0.0 -CMD ["npm", "run", "start"] diff --git a/premium/README.md b/premium/README.md deleted file mode 100644 index f25b942..0000000 --- a/premium/README.md +++ /dev/null @@ -1,261 +0,0 @@ -# GeoExport - -[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE) -[![Next.js](https://img.shields.io/badge/Next.js-16-black?logo=next.js)](https://nextjs.org) -[![TypeScript](https://img.shields.io/badge/TypeScript-5-blue?logo=typescript)](https://www.typescriptlang.org) -[![Docker](https://img.shields.io/badge/Docker-ready-2496ED?logo=docker)](docker-compose.yml) -[![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](docs/CONTRIBUTING.md) - -Self-hosted platform to browse, export, and generate routing rules from **geoip** and **geosite** databases — built for VPN stacks: **Xray**, **3x-ui**, **V2Ray**, **Mihomo**, **Sing-box**. - -GeoExport is an open-source, API-first alternative to hosted geo list tools: fast UI, privacy-friendly defaults, and a deployment model you control. - -> **Status:** This repository is the **premium Next.js rewrite** (`geoexport-premium`). The UI scaffold is active; PostgreSQL, Prisma, Redis, and full API routes are on the roadmap. For a working static mirror today, see [andrey271192/Domain_web](https://github.com/andrey271192/Domain_web). - ---- - -## Table of contents - -- [Why GeoExport](#why-geoexport) -- [Features](#features) -- [Architecture](#architecture) -- [Screenshots](#screenshots) -- [Quick start](#quick-start) -- [Configuration](#configuration) -- [Docker](#docker) -- [API](#api) -- [Deployment](#deployment) -- [Security](#security) -- [Performance](#performance) -- [Roadmap](#roadmap) -- [Contributing](#contributing) -- [License](#license) - ---- - -## Why GeoExport - -| | Hosted geo tools | GeoExport | -|---|------------------|-----------| -| **Data location** | Third-party SaaS | Your VPS / private cloud | -| **API access** | Opaque or limited | REST-first, documented | -| **UI** | Functional | Linear-grade layout, dark/light | -| **Stack** | Unknown | Next.js, TypeScript, Prisma, Redis | -| **Scale** | Shared tenancy | Horizontal app + cache layer | - -GeoExport does not replace community rule repos (Loyalsoldier, RuNet Freedom, v2fly, etc.) — it **indexes** them, maps services to categories, and gives you export and routing helpers in one place. - ---- - -## Features - -### Presets & sources - -- **9 preset groups**, **59 curated services** (messengers, video, social, AI, games, work, CDN, RU-blocked lists, …) -- **4 rule sources**: RuNet Freedom, Loyalsoldier, DanielLavrushin (b4geoip), v2fly — with per-source `geoip.dat` / `geosite.dat` links - -### Discovery & export - -- Full-text **search** across geoip/geosite categories -- **DNS lookup**: resolve domains to IPs/subdomains and cross-check against loaded databases -- One-click **export** to `.txt` for client import -- **Routing generator** with saved routing presets (Xray / Mihomo / Sing-box oriented) - -### Platform - -- **Dark / light** theme -- **REST API** for automation (GraphQL optional on roadmap) -- **Self-hosted** via Docker or bare Node -- **Edge-ready** Next.js deployment (Vercel, Cloudflare, or nginx reverse proxy) - ---- - -## Architecture - -High-level layout: - -``` -┌─────────────┐ ┌──────────────┐ ┌─────────────────┐ -│ Browser │────▶│ Next.js │────▶│ PostgreSQL │ -│ (React) │ │ App Router │ │ (Prisma) │ -└─────────────┘ │ + API routes│ └─────────────────┘ - │ │────▶┌─────────────────┐ - │ │ │ Redis (cache) │ - └──────┬───────┘ └─────────────────┘ - │ - ▼ - ┌──────────────┐ - │ Geo workers │──▶ upstream .dat / GitHub releases - └──────────────┘ -``` - -Details: [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md) - -### Stack - -| Layer | Technology | -|-------|------------| -| Frontend | Next.js 16, React 19, TypeScript, Tailwind CSS 4, Framer Motion, shadcn/ui | -| API | Next.js Route Handlers (REST); GraphQL optional later | -| Data | PostgreSQL + Prisma | -| Cache | Redis | -| Ops | Docker, docker-compose, nginx (production) | - ---- - -## Screenshots - -| Presets browser | DNS lookup | Routing generator | -|-----------------|------------|-------------------| -| _Coming soon — add `docs/images/presets.png`_ | _Coming soon_ | _Coming soon_ | - -Place assets under `docs/images/` and reference them here before release. - ---- - -## Quick start - -### Requirements - -- **Node.js** 20+ (22 LTS recommended) -- **npm**, **pnpm**, or **bun** - -### Local development - -```bash -git clone https://github.com/andrey271192/geoexport.git -cd geoexport # or geoexport-premium while the repo is being renamed - -cp .env.example .env -npm install -npm run dev -``` - -Open [http://localhost:3000](http://localhost:3000). - -### Production build - -```bash -npm run build -npm run start -``` - -### Lint - -```bash -npm run lint -``` - ---- - -## Configuration - -Copy `.env.example` to `.env` and adjust: - -| Variable | Description | -|----------|-------------| -| `DATABASE_URL` | PostgreSQL connection string (Prisma) | -| `REDIS_URL` | Redis for API/cache layers | -| `NEXT_PUBLIC_APP_URL` | Public URL (CORS, auth callbacks) | -| `GEOEXPORT_UPSTREAM` | Upstream host for mirror/proxy mode during migration | - -Full reference: [.env.example](.env.example) - ---- - -## Docker - -Run the full stack (app + PostgreSQL + Redis): - -```bash -cp .env.example .env -docker compose up -d --build -``` - -| Service | Port (host) | Notes | -|---------|-------------|--------| -| `web` | 3000 | Next.js (`npm run start` in container) | -| `postgres` | 5432 | Persistent volume `postgres_data` | -| `redis` | 6379 | Cache / rate limit | - -Health check: `curl -f http://localhost:3000` - -> Prisma migrations and geo data seeding are not wired in Docker yet — track progress in [Roadmap](#roadmap). - ---- - -## API - -Base path: `/api` - -| Method | Path | Description | -|--------|------|-------------| -| `GET` | `/api/presets` | Service preset groups | -| `GET` | `/api/sources` | Rule source metadata | -| `GET` | `/api/last-update` | Last database refresh timestamp | -| `GET` | `/api/routing/presets` | Saved routing templates | -| `POST` | `/api/routing/generate` | Generate routing config | -| `GET` | `/api/search?q=` | Search categories | -| `GET` | `/api/export?...` | Export list as plain text | -| `GET` | `/api/lookup?domain=` | DNS + geodb cross-check | -| `GET` | `/api/update/status` | Background update job status | - -Contract details: [docs/API.md](docs/API.md) - ---- - -## Deployment - -- **Docker Compose** — single VPS, see [docs/DEPLOYMENT.md](docs/DEPLOYMENT.md) -- **Vercel / edge** — frontend + serverless API routes (Redis/Postgres via managed providers) -- **nginx** — terminate TLS, proxy to `127.0.0.1:3000` (pattern used in [Domain_web](https://github.com/andrey271192/Domain_web)) - ---- - -## Security - -- Run behind HTTPS in production -- Do not commit `.env` or API keys -- Rate-limit public `/api/*` when exposed to the internet -- Report issues: [docs/SECURITY.md](docs/SECURITY.md) - ---- - -## Performance - -- Static preset JSON can be CDN-cached (`Cache-Control` on read-only catalog endpoints) -- Redis backs hot search/export paths -- Next.js App Router + edge caching for public pages -- Geo `.dat` parsing runs in workers — not on the request thread - ---- - -## Roadmap - -- [ ] Port Domain_web UI into Next.js App Router -- [ ] Prisma schema + PostgreSQL seed from geoexport.org catalogs -- [ ] Self-contained geo workers (no upstream proxy) -- [ ] API keys + rate limiting -- [ ] GraphQL read API (optional) -- [ ] Helm chart / Terraform module - ---- - -## Contributing - -See [docs/CONTRIBUTING.md](docs/CONTRIBUTING.md). Marketing copy and launch templates: [docs/MARKETING.md](docs/MARKETING.md). - ---- - -## Related projects - -| Repo | Role | -|------|------| -| [andrey271192/Domain_web](https://github.com/andrey271192/Domain_web) | Production VPS mirror (nginx + Node static server) | -| [geoexport-clone](https://github.com/andrey271192/geoexport-clone) | Local dev mirror of geoexport.org UI | - ---- - -## License - -[MIT](LICENSE) — Copyright (c) 2026 GeoExport Contributors diff --git a/premium/docs/API.md b/premium/docs/API.md deleted file mode 100644 index 6c743c5..0000000 --- a/premium/docs/API.md +++ /dev/null @@ -1,239 +0,0 @@ -# API reference - -GeoExport exposes a **REST** API under `/api`. The contract matches [geoexport.org](https://geoexport.org) so existing clients and mirrors stay compatible. - -**Base URL:** `https://your-instance.example.com` -**Content-Type:** `application/json` unless noted -**Errors:** JSON body `{ "error": "message" }` with appropriate HTTP status - ---- - -## Catalog (cacheable) - -### `GET /api/presets` - -Returns preset groups with nested services and geoip/geosite category mappings. - -**Response:** `200` — JSON array - -```json -[ - { - "group": "Мессенджеры", - "icon": "💬", - "slug": "messengers", - "services": [ - { - "name": "Telegram", - "domain": "telegram.org", - "keywords": ["telegram"], - "categories": [ - { - "full_name": "geosite:telegram", - "name": "telegram", - "type": "geosite", - "source_slug": "loyalsoldier", - "source_name": "Loyalsoldier" - } - ] - } - ] - } -] -``` - ---- - -### `GET /api/sources` - -Rule database sources (download URLs, compatibility). - -**Response:** `200` — JSON array - -```json -[ - { - "slug": "loyalsoldier", - "name": "Loyalsoldier", - "description": "…", - "compatible": "Xray, 3x-ui, V2Ray, Mihomo", - "geoip_url": "https://github.com/.../geoip.dat", - "geosite_url": "https://github.com/.../geosite.dat" - } -] -``` - ---- - -### `GET /api/last-update` - -Timestamp of the last successful geo database refresh. - -**Response:** `200` - -```json -{ - "updated_at": "2026-05-24T12:00:00Z" -} -``` - ---- - -### `GET /api/routing/presets` - -Saved routing templates for the generator UI. - -**Response:** `200` — JSON array of preset objects (structure varies by client target). - ---- - -## Search & export - -### `GET /api/search` - -Search geoip/geosite categories. - -**Query parameters** - -| Param | Required | Description | -|-------|----------|-------------| -| `q` | yes | Search string | -| `source` | no | Filter by `source_slug` | -| `type` | no | `geoip` or `geosite` | - -**Example** - -```http -GET /api/search?q=telegram&source=loyalsoldier -``` - -**Response:** `200` — JSON array of matching categories - ---- - -### `GET /api/export` - -Export resolved entries as **plain text** (one category or domain per line depending on mode). - -**Query parameters** - -| Param | Required | Description | -|-------|----------|-------------| -| `category` | conditional | Full name, e.g. `geosite:telegram` | -| `source` | no | Source slug | -| `format` | no | Output variant (client-specific) | - -**Response:** `200` — `text/plain` - -**Example** - -```bash -curl -s 'https://your-instance/api/export?category=geosite:telegram&source=loyalsoldier' -o telegram.txt -``` - ---- - -## DNS lookup - -### `GET /api/lookup` - -Resolve a domain and cross-check against loaded geo databases. - -**Query parameters** - -| Param | Required | Description | -|-------|----------|-------------| -| `domain` | yes | FQDN, e.g. `example.com` | - -**Example** - -```http -GET /api/lookup?domain=anydesk.com -``` - -**Response:** `200` — JSON with IPs, subdomains, and geodb hits (exact schema mirrors upstream). - ---- - -## Routing generator - -### `POST /api/routing/generate` - -Generate a routing configuration snippet from selected rules and a template. - -**Request body:** `application/json` - -```json -{ - "preset_id": "xray-default", - "rules": ["geosite:telegram", "geoip:netflix"], - "outbound": "proxy", - "client": "xray" -} -``` - -**Response:** `200` — JSON or plain text snippet (depends on `client`) - -**Errors:** `400` validation, `422` unsupported combination - ---- - -## Operations - -### `GET /api/update/status` - -Background geo database refresh status (worker jobs). - -**Response:** `200` - -```json -{ - "status": "idle", - "last_run": "2026-05-24T06:00:00Z", - "progress": null -} -``` - ---- - -## Rate limiting (planned) - -Public instances should enforce per-IP limits on: - -- `/api/lookup` -- `/api/search` -- `/api/export` - -Recommended defaults: **120 req/min** per IP (configurable via `GEOEXPORT_RATE_LIMIT_PER_MINUTE`). - ---- - -## Authentication (planned) - -Optional header: - -```http -Authorization: Bearer -``` - -Required only when `GEOEXPORT_API_KEY` is set server-side. - ---- - -## Compatibility notes - -| Endpoint | Local JSON (mirror) | Full backend | -|----------|---------------------|--------------| -| `/api/presets` | Served from `data/presets.json` | PostgreSQL or static cache | -| `/api/sources` | `data/sources.json` | Same | -| `/api/last-update` | `data/last-update.json` | Same | -| `/api/routing/presets` | `data/routing-presets.json` | Same | -| `/api/search`, `/api/export`, `/api/lookup`, `/api/routing/generate` | Proxied upstream | Native handlers | - -Mirror behavior is implemented in [Domain_web `server.mjs`](https://github.com/andrey271192/Domain_web/blob/main/site/server.mjs). - ---- - -## GraphQL (roadmap) - -A read-only GraphQL layer may expose `presets`, `sources`, and `search` for panel integrations. REST remains the stable contract. diff --git a/premium/docs/ARCHITECTURE.md b/premium/docs/ARCHITECTURE.md deleted file mode 100644 index 9e3b893..0000000 --- a/premium/docs/ARCHITECTURE.md +++ /dev/null @@ -1,145 +0,0 @@ -# Architecture - -GeoExport is a full-stack web application for browsing geoip/geosite rule catalogs, exporting plain-text lists, and generating client routing snippets. This document describes the **target** architecture for `geoexport-premium` and how it relates to the existing static mirror. - -## Goals - -1. **Self-hosted** — operators run their own instance; no mandatory third-party backend. -2. **API-first** — every UI action has a REST equivalent for scripts and panels. -3. **Fast catalog UX** — presets and sources are served from cache/DB, not parsed on each page view. -4. **Heavy work off the hot path** — `.dat` ingestion and DNS resolution run in workers or upstream services. - -## System context - -```mermaid -flowchart LR - subgraph clients [Clients] - Browser[Web UI] - CLI[Scripts / panels] - end - - subgraph geoexport [GeoExport] - Next[Next.js App Router] - API[REST /api] - Worker[Geo workers] - end - - subgraph data [Data plane] - PG[(PostgreSQL)] - RD[(Redis)] - GH[GitHub rule releases] - end - - Browser --> Next - CLI --> API - Next --> API - API --> PG - API --> RD - Worker --> PG - Worker --> GH - API --> Worker -``` - -## Layers - -### Presentation (`src/app`, `src/components`) - -- **Next.js App Router** — server components for catalog pages; client components for search, export, DNS lookup, routing builder. -- **Tailwind CSS 4** + **shadcn/ui** — design system aligned with Linear/Vercel density. -- **Framer Motion** — transitions for panels, modals, and theme toggle. - -### API (`src/app/api` — planned) - -Route handlers mirror the public contract documented in [API.md](./API.md): - -| Concern | Implementation | -|---------|----------------| -| Catalog | Read presets/sources from PostgreSQL or baked JSON during bootstrap | -| Search | Redis-backed index or PostgreSQL full-text | -| Export | Stream plain text from resolved category lists | -| Lookup | DNS resolver + geodb membership check | -| Routing | Template engine for Xray / Mihomo / Sing-box snippets | - -During migration, some routes may **proxy** to `GEOEXPORT_UPSTREAM` (default `https://geoexport.org`), matching behavior in [Domain_web](https://github.com/andrey271192/Domain_web) `server.mjs`. - -### Data (`prisma/` — planned) - -| Model area | Purpose | -|------------|---------| -| `Source` | Rule repo metadata (slug, URLs, compatibility) | -| `PresetGroup` / `Service` | Curated UI presets | -| `Category` | geoip:/geosite: entries per source | -| `RoutingPreset` | Saved routing templates | -| `UpdateJob` | Refresh status for `.dat` pulls | - -### Cache (`redis`) - -- Catalog responses (`presets`, `sources`, `last-update`) -- Search result pages -- Rate limiting counters for public API - -### Workers (planned) - -Background jobs: - -1. Download `geoip.dat` / `geosite.dat` from configured GitHub releases. -2. Parse and upsert category index into PostgreSQL. -3. Expose progress via `GET /api/update/status`. - -## Deployment topologies - -### A — Single VPS (Docker Compose) - -`web` + `postgres` + `redis` on one host. nginx terminates TLS and proxies to port 3000. - -### B — Split managed - -- Next.js on Vercel / Cloudflare Pages -- Neon / RDS for PostgreSQL -- Upstash for Redis - -### C — Mirror-only (legacy) - -[Domain_web](https://github.com/andrey271192/Domain_web): static SPA + Node proxy on port 4173. No PostgreSQL. Suitable until the Next.js port is feature-complete. - -## Repository layout (target) - -``` -geoexport-premium/ -├── src/ -│ ├── app/ # routes, layouts, API handlers -│ ├── components/ # UI primitives (shadcn) -│ └── lib/ # db, redis, geo parsers -├── prisma/ -│ └── schema.prisma -├── public/ -├── docs/ -├── docker-compose.yml -├── Dockerfile -└── .env.example -``` - -## Compatibility matrix - -| Client | Import format | Notes | -|--------|---------------|-------| -| Xray | geoip.dat, geosite.dat, routing rules | Primary target | -| 3x-ui | Same as Xray | Panel import | -| V2Ray | `.dat` lists | v2fly source | -| Mihomo | YAML routing | Generator output | -| Sing-box | JSON / rule-set | Generator output | - -## Security boundaries - -- Public read APIs are unauthenticated by default; operators should place GeoExport behind VPN or enable API keys (roadmap). -- DNS lookup must be rate-limited to prevent abuse as an open resolver. -- Upstream proxy mode forwards only whitelisted `/api/*` paths — never arbitrary SSRF targets. - -## Evolution from Domain_web - -| Domain_web | geoexport-premium | -|------------|-------------------| -| Static `index.html` + `server.mjs` | Next.js SSR/ISR | -| JSON files in `data/` | PostgreSQL + optional JSON seed | -| Proxied dynamic API | Native handlers + workers | -| systemd + nginx install script | Docker Compose + generic reverse proxy | diff --git a/premium/docs/CONTRIBUTING.md b/premium/docs/CONTRIBUTING.md deleted file mode 100644 index 776f933..0000000 --- a/premium/docs/CONTRIBUTING.md +++ /dev/null @@ -1,72 +0,0 @@ -# Contributing to GeoExport - -Thanks for helping improve GeoExport. This project aims for production-grade quality — clear PRs, tested behavior, and docs that match the code. - -## Ways to contribute - -- Report bugs and gaps in [GitHub Issues](https://github.com/andrey271192/geoexport/issues) -- Improve docs (README, `docs/`, API examples) -- Port UI features from [Domain_web](https://github.com/andrey271192/Domain_web) into Next.js -- Implement API routes and Prisma models -- Add tests for parsers, export formatters, and routing generator - -## Development setup - -```bash -git clone https://github.com/andrey271192/geoexport.git -cd geoexport -cp .env.example .env -npm install -npm run dev -``` - -Open http://localhost:3000 - -### With Docker data services only - -```bash -docker compose up -d postgres redis -# Run app on host: -npm run dev -``` - -## Branching - -- `main` — stable, deployable -- `feat/*` — features -- `fix/*` — bug fixes -- `docs/*` — documentation only - -## Pull request checklist - -- [ ] `npm run lint` passes -- [ ] `npm run build` succeeds -- [ ] User-facing changes documented in README or `docs/` -- [ ] No secrets, `.env`, or large binary blobs committed -- [ ] PR description explains **why**, not only **what** - -## Code style - -- **TypeScript** strict mode -- **React** — functional components, hooks -- **Tailwind** — utility-first; use shadcn patterns for forms/dialogs -- **API routes** — validate input, return consistent JSON errors -- Keep diffs focused; avoid drive-by refactors - -## Commit messages - -Use [Conventional Commits](https://www.conventionalcommits.org/): - -``` -feat(api): add lookup rate limiter -fix(export): handle empty category -docs: update Docker deployment steps -``` - -## Security - -Do not open public issues for vulnerabilities. See [SECURITY.md](./SECURITY.md). - -## License - -By contributing, you agree that your contributions are licensed under the [MIT License](../LICENSE). diff --git a/premium/docs/DEPLOYMENT.md b/premium/docs/DEPLOYMENT.md deleted file mode 100644 index 47be008..0000000 --- a/premium/docs/DEPLOYMENT.md +++ /dev/null @@ -1,195 +0,0 @@ -# Deployment - -This guide covers deploying **GeoExport** (`geoexport-premium`) on a VPS or container platform. For the **static mirror** that is production-ready today, use [andrey271192/Domain_web](https://github.com/andrey271192/Domain_web). - -## Prerequisites - -| Resource | Minimum | Recommended | -|----------|---------|-------------| -| CPU | 1 vCPU | 2 vCPU | -| RAM | 1 GB | 2 GB+ | -| Disk | 10 GB | 20 GB+ (geo databases grow) | -| OS | Linux (Debian/Ubuntu/Alpine) | Ubuntu 22.04 LTS | - -Software: - -- Docker 24+ and Docker Compose v2, **or** -- Node.js 20+ with PostgreSQL 16 and Redis 7 - ---- - -## Option 1 — Docker Compose (recommended) - -### 1. Clone and configure - -```bash -git clone https://github.com/andrey271192/geoexport.git geoexport -cd geoexport -cp .env.example .env -# Edit DATABASE_URL / secrets if not using compose defaults -``` - -### 2. Start stack - -```bash -docker compose up -d --build -``` - -Services: - -| Name | Image | Host port | -|------|-------|-----------| -| `web` | Built from `Dockerfile` | `3000` | -| `postgres` | `postgres:16-alpine` | `5432` | -| `redis` | `redis:7-alpine` | `6379` | - -Verify: - -```bash -curl -f http://localhost:3000 -docker compose logs -f web -``` - -### 3. Reverse proxy (nginx) - -```nginx -server { - listen 80; - server_name geo.example.com; - - location / { - proxy_pass http://127.0.0.1:3000; - proxy_http_version 1.1; - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - } -} -``` - -Add TLS with Certbot or Caddy. Set `NEXT_PUBLIC_APP_URL=https://geo.example.com` in `.env` and restart `web`. - ---- - -## Option 2 — Bare Node.js - -```bash -git clone https://github.com/andrey271192/geoexport.git geoexport -cd geoexport -cp .env.example .env -# Point DATABASE_URL and REDIS_URL to your instances - -npm install -npm run build -NODE_ENV=production npm run start -``` - -Use **pm2** or **systemd** for process supervision: - -```ini -# /etc/systemd/system/geoexport.service -[Unit] -Description=GeoExport Next.js -After=network.target - -[Service] -Type=simple -User=geoexport -WorkingDirectory=/opt/geoexport -EnvironmentFile=/opt/geoexport/.env -ExecStart=/usr/bin/npm run start -Restart=on-failure - -[Install] -WantedBy=multi-user.target -``` - ---- - -## Option 3 — Domain_web mirror (available now) - -One-line install on a fresh VPS: - -```bash -curl -fsSL https://raw.githubusercontent.com/andrey271192/Domain_web/main/install.sh | sudo bash -``` - -- Site files: `/opt/domain_web/site/` -- Node service: `geoexport-site` on `127.0.0.1:4173` -- nginx on port 80 - -Update cached JSON: - -```bash -cd /opt/domain_web/site -curl -fsSL https://geoexport.org/api/presets -o data/presets.json -curl -fsSL https://geoexport.org/api/sources -o data/sources.json -curl -fsSL https://geoexport.org/api/last-update -o data/last-update.json -curl -fsSL https://geoexport.org/api/routing/presets -o data/routing-presets.json -sudo systemctl restart geoexport-site -``` - ---- - -## Environment variables - -See [.env.example](../.env.example). Critical production values: - -| Variable | Notes | -|----------|-------| -| `NEXT_PUBLIC_APP_URL` | Canonical public URL | -| `DATABASE_URL` | Required when Prisma is enabled | -| `REDIS_URL` | Required for cache/rate limits | -| `GEOEXPORT_UPSTREAM` | Set only for hybrid/mirror mode | - ---- - -## Database migrations (roadmap) - -When Prisma lands in this repo: - -```bash -npx prisma migrate deploy -npx prisma db seed -``` - -Until then, Docker Postgres is provisioned but unused by the app. - ---- - -## Updates - -**Docker:** - -```bash -git pull -docker compose up -d --build -``` - -**Domain_web:** - -```bash -cd /opt/domain_web && sudo git pull && sudo systemctl restart geoexport-site -``` - ---- - -## Health checks - -| Check | Command | -|-------|---------| -| HTTP | `curl -sf http://localhost:3000` | -| Postgres | `docker compose exec postgres pg_isready -U geoexport` | -| Redis | `docker compose exec redis redis-cli ping` | - ---- - -## Troubleshooting - -| Symptom | Likely cause | Fix | -|---------|--------------|-----| -| Export/search 502 | Upstream unreachable | Check `GEOEXPORT_UPSTREAM`, outbound HTTPS | -| Empty presets | DB not seeded | Use cached JSON or run seed (roadmap) | -| Docker build fails | Missing lockfile | `npm install` locally, commit `package-lock.json` | -| Port 3000 in use | Conflict | Set `PORT=3001` in `.env` and compose | diff --git a/premium/docs/MARKETING.md b/premium/docs/MARKETING.md deleted file mode 100644 index cafe945..0000000 --- a/premium/docs/MARKETING.md +++ /dev/null @@ -1,214 +0,0 @@ -# Marketing & launch copy - -Repository: **GeoExport** (`geoexport`) — open-source geoip/geosite export platform. -English README: [../README.md](../README.md). Russian snippets below where noted. - ---- - -## GitHub short description (≤350 chars) - -``` -Self-hosted geoip/geosite export & routing platform for Xray, 3x-ui, V2Ray, Mihomo, Sing-box. 59 service presets, DNS lookup, routing generator, REST API. Next.js, PostgreSQL, Redis, Docker. Privacy-friendly alternative to hosted geo list tools. -``` - ---- - -## GitHub About section - -**Website:** `https://github.com/andrey271192/geoexport` (or your docs URL when live) - -**Description:** (same as short description above) - -**Topics:** see [Topics list](#github-topics-15-20) - ---- - -## GitHub Topics (15–20) - -``` -geoip -geosite -v2ray -xray -sing-box -mihomo -3x-ui -vpn -routing -proxy -self-hosted -nextjs -typescript -docker -postgresql -open-source -privacy -networking -devtools -russia-vpn -``` - ---- - -## Repository structure tree - -``` -geoexport/ -├── src/ -│ ├── app/ # Next.js App Router (pages + API) -│ │ ├── layout.tsx -│ │ ├── page.tsx -│ │ └── api/ # REST handlers (planned) -│ ├── components/ # UI (shadcn/ui) — planned -│ └── lib/ # db, redis, geo utils — planned -├── prisma/ # schema & migrations — planned -├── public/ # static assets -├── docs/ -│ ├── ARCHITECTURE.md -│ ├── API.md -│ ├── DEPLOYMENT.md -│ ├── CONTRIBUTING.md -│ ├── SECURITY.md -│ └── MARKETING.md # this file -├── docker-compose.yml -├── Dockerfile -├── .env.example -├── LICENSE -├── package.json -└── README.md -``` - ---- - -## SEO meta description (~155 chars) - -``` -GeoExport — self-hosted geoip & geosite lists for Xray, V2Ray, Mihomo. Export .txt, DNS lookup, routing rules. Open source, Docker, REST API. -``` - ---- - -## Product Hunt tagline & blurb - -**Tagline:** Self-hosted geo lists for your VPN stack - -**Description:** - -GeoExport helps you browse, search, and export geoip/geosite categories from Loyalsoldier, RuNet Freedom, v2fly, and b4geoip — without relying on a closed SaaS. - -- 59 service presets across 9 groups -- DNS lookup with geodb cross-check -- Routing snippet generator for Xray / Mihomo / Sing-box -- REST API + Docker deploy - -Open source. Run it on your VPS in minutes. - ---- - -## Telegram post (RU) - -``` -🌍 GeoExport — open-source замена geoexport.org на своём сервере - -Экспорт geoip/geosite для Xray, 3x-ui, V2Ray, Mihomo, Sing-box: -• 9 групп пресетов, 59 сервисов -• поиск по категориям -• DNS lookup + проверка в базах -• генератор routing-правил -• экспорт .txt -• REST API + Docker - -Стек: Next.js, PostgreSQL, Redis. -Код: github.com/andrey271192/geoexport - -Звезда на GitHub = поддержка проекта ⭐ -``` - ---- - -## Twitter / X launch - -``` -We shipped GeoExport — self-hosted geoip/geosite export for Xray, V2Ray, Mihomo, Sing-box. - -59 presets · DNS lookup · routing generator · REST API · Docker - -Open source. Your VPS. Your data. - -⭐ github.com/andrey271192/geoexport -``` - ---- - -## Hacker News — Show HN - -**Title:** Show HN: GeoExport – self-hosted geoip/geosite export for Xray/V2Ray/Mihomo - -**Body:** - -I built GeoExport, an open-source platform to browse and export geoip/geosite rule lists used by VPN clients (Xray, 3x-ui, V2Ray, Mihomo, Sing-box). - -It indexes categories from Loyalsoldier, RuNet Freedom, v2fly, and DanielLavrushin b4geoip, with 59 curated service presets (Telegram, YouTube, Steam, RU-blocked lists, etc.). - -Features: -- Search and export plain-text lists -- DNS lookup with geodb cross-check -- Routing config generator -- REST API -- Docker Compose deploy (Next.js + Postgres + Redis) - -Motivation: operators often depend on hosted geo tools with no self-host path. GeoExport is API-first and designed to run on a cheap VPS. - -Repo: https://github.com/andrey271192/geoexport - -A static mirror installer already exists (Domain_web); this repo is the full Next.js rewrite. - -Feedback on API shape and self-hosted geo DB ingestion is especially welcome. - ---- - -## Dev.to post outline - -**Title:** Self-hosting geoip/geosite lists with GeoExport - -1. **Problem** — VPN panels need fresh geoip/geosite data; hosted tools are convenient but opaque. -2. **What GeoExport does** — catalog UI, export, lookup, routing generator. -3. **Architecture sketch** — Next.js, Postgres, Redis, workers (diagram from ARCHITECTURE.md). -4. **Quick start** — `docker compose up`, open localhost:3000. -5. **API examples** — `curl` for presets, search, export. -6. **Comparison** — mirror mode (Domain_web) vs full stack. -7. **Roadmap** — Prisma, native workers, API keys. -8. **CTA** — star the repo, contribute via CONTRIBUTING.md. - -Tags: `#opensource` `#vpn` `#nextjs` `#docker` `#devtools` - ---- - -## Suggested repo naming - -| Use case | Name | -|----------|------| -| Product | **GeoExport** | -| Premium fork folder | `geoexport-premium` | -| GitHub repo (recommended) | `geoexport` | -| Legacy mirror | `Domain_web` (keep for install scripts) | - -Rename path when publishing: - -```bash -# Example: push premium tree to new repo -git remote add origin git@github.com:andrey271192/geoexport.git -git push -u origin main -``` - -Or continue on `andrey271192/Domain_web` by merging `geoexport-premium` into `site/` when the Next port is ready. - ---- - -## One-line pitch (EN) - -**GeoExport:** run your own geoip/geosite export API and UI for Xray-class clients — open source, Docker-ready. - -## One-line pitch (RU) - -**GeoExport:** свой сервер для экспорта geoip/geosite и routing-правил под Xray и Mihomo — open source, Docker. diff --git a/premium/docs/SECURITY.md b/premium/docs/SECURITY.md deleted file mode 100644 index 26e06ba..0000000 --- a/premium/docs/SECURITY.md +++ /dev/null @@ -1,69 +0,0 @@ -# Security policy - -## Supported versions - -| Version | Supported | -|---------|-----------| -| `main` (latest) | Yes | -| Older tags | Best effort | - -## Reporting a vulnerability - -**Please do not** file public GitHub issues for security problems. - -1. Email or DM the maintainer with a description and reproduction steps. -2. Allow up to **90 days** for a fix before public disclosure. -3. We will acknowledge receipt within **72 hours** when possible. - -Include: - -- Affected endpoints or components -- Impact (data leak, SSRF, RCE, etc.) -- Proof of concept if available -- Suggested fix (optional) - -## Threat model (self-hosted) - -GeoExport is typically deployed on a private VPS or internal network. Common risks: - -| Risk | Mitigation | -|------|------------| -| Open DNS lookup abuse | Rate-limit `/api/lookup`; block private IP ranges in resolver | -| SSRF via upstream proxy | Whitelist paths; fixed `GEOEXPORT_UPSTREAM` host only | -| API scraping / DoS | Redis rate limits; nginx `limit_req` | -| Leaked `.env` | Never commit secrets; rotate DB passwords on deploy | -| Outdated geo databases | Monitor `/api/last-update`; automate refresh jobs | - -## Hardening checklist (production) - -- [ ] HTTPS only (TLS 1.2+) -- [ ] `NEXT_PUBLIC_APP_URL` matches real hostname -- [ ] Strong PostgreSQL password (not compose defaults) -- [ ] Redis bound to localhost or private network -- [ ] Firewall: expose only 80/443 -- [ ] Disable directory listing on nginx -- [ ] Keep Node.js and base images patched (`docker compose pull`) - -## Dependencies - -- Run `npm audit` before releases -- Pin Docker image digests in production compose overrides -- Subscribe to GitHub security advisories for this repo - -## Data privacy - -GeoExport processes **domains and IPs** users submit for lookup. Operators should: - -- Document retention (if logging lookups) -- Avoid shipping lookup logs to third parties without consent -- Prefer self-hosted rule databases over permanent upstream proxy when feasible - -## Safe defaults - -- No default API keys in repository -- Example compose passwords are for **local dev only** — change before internet exposure -- CSP headers on Next.js responses (planned) - -## Recognition - -We credit reporters in release notes when they agree to be named. diff --git a/premium/eslint.config.mjs b/premium/eslint.config.mjs deleted file mode 100644 index 05e726d..0000000 --- a/premium/eslint.config.mjs +++ /dev/null @@ -1,18 +0,0 @@ -import { defineConfig, globalIgnores } from "eslint/config"; -import nextVitals from "eslint-config-next/core-web-vitals"; -import nextTs from "eslint-config-next/typescript"; - -const eslintConfig = defineConfig([ - ...nextVitals, - ...nextTs, - // Override default ignores of eslint-config-next. - globalIgnores([ - // Default ignores of eslint-config-next: - ".next/**", - "out/**", - "build/**", - "next-env.d.ts", - ]), -]); - -export default eslintConfig; diff --git a/premium/next.config.ts b/premium/next.config.ts deleted file mode 100644 index e9ffa30..0000000 --- a/premium/next.config.ts +++ /dev/null @@ -1,7 +0,0 @@ -import type { NextConfig } from "next"; - -const nextConfig: NextConfig = { - /* config options here */ -}; - -export default nextConfig; diff --git a/premium/package.json b/premium/package.json deleted file mode 100644 index 07098e5..0000000 --- a/premium/package.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "name": "geoexport-premium", - "version": "0.1.0", - "private": true, - "scripts": { - "dev": "next dev", - "build": "next build", - "start": "next start", - "lint": "eslint" - }, - "dependencies": { - "next": "16.2.6", - "react": "19.2.4", - "react-dom": "19.2.4" - }, - "devDependencies": { - "@tailwindcss/postcss": "^4", - "@types/node": "^20", - "@types/react": "^19", - "@types/react-dom": "^19", - "eslint": "^9", - "eslint-config-next": "16.2.6", - "tailwindcss": "^4", - "typescript": "^5" - } -} diff --git a/premium/public/file.svg b/premium/public/file.svg deleted file mode 100644 index 004145c..0000000 --- a/premium/public/file.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/premium/public/globe.svg b/premium/public/globe.svg deleted file mode 100644 index 567f17b..0000000 --- a/premium/public/globe.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/premium/public/next.svg b/premium/public/next.svg deleted file mode 100644 index 5174b28..0000000 --- a/premium/public/next.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/premium/public/vercel.svg b/premium/public/vercel.svg deleted file mode 100644 index 7705396..0000000 --- a/premium/public/vercel.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/premium/public/window.svg b/premium/public/window.svg deleted file mode 100644 index b2b2a44..0000000 --- a/premium/public/window.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/premium/src/app/favicon.ico b/premium/src/app/favicon.ico deleted file mode 100644 index 718d6fe..0000000 Binary files a/premium/src/app/favicon.ico and /dev/null differ diff --git a/premium/src/app/globals.css b/premium/src/app/globals.css deleted file mode 100644 index a2dc41e..0000000 --- a/premium/src/app/globals.css +++ /dev/null @@ -1,26 +0,0 @@ -@import "tailwindcss"; - -:root { - --background: #ffffff; - --foreground: #171717; -} - -@theme inline { - --color-background: var(--background); - --color-foreground: var(--foreground); - --font-sans: var(--font-geist-sans); - --font-mono: var(--font-geist-mono); -} - -@media (prefers-color-scheme: dark) { - :root { - --background: #0a0a0a; - --foreground: #ededed; - } -} - -body { - background: var(--background); - color: var(--foreground); - font-family: Arial, Helvetica, sans-serif; -} diff --git a/premium/src/app/layout.tsx b/premium/src/app/layout.tsx deleted file mode 100644 index 976eb90..0000000 --- a/premium/src/app/layout.tsx +++ /dev/null @@ -1,33 +0,0 @@ -import type { Metadata } from "next"; -import { Geist, Geist_Mono } from "next/font/google"; -import "./globals.css"; - -const geistSans = Geist({ - variable: "--font-geist-sans", - subsets: ["latin"], -}); - -const geistMono = Geist_Mono({ - variable: "--font-geist-mono", - subsets: ["latin"], -}); - -export const metadata: Metadata = { - title: "Create Next App", - description: "Generated by create next app", -}; - -export default function RootLayout({ - children, -}: Readonly<{ - children: React.ReactNode; -}>) { - return ( - - {children} - - ); -} diff --git a/premium/src/app/page.tsx b/premium/src/app/page.tsx deleted file mode 100644 index 3f36f7c..0000000 --- a/premium/src/app/page.tsx +++ /dev/null @@ -1,65 +0,0 @@ -import Image from "next/image"; - -export default function Home() { - return ( -
-
- Next.js logo -
-

- To get started, edit the page.tsx file. -

-

- Looking for a starting point or more instructions? Head over to{" "} - - Templates - {" "} - or the{" "} - - Learning - {" "} - center. -

-
- -
-
- ); -} diff --git a/prisma/schema.prisma b/prisma/schema.prisma new file mode 100644 index 0000000..a7b82c0 --- /dev/null +++ b/prisma/schema.prisma @@ -0,0 +1,83 @@ +generator client { + provider = "prisma-client-js" +} + +datasource db { + provider = "postgresql" + url = env("DATABASE_URL") +} + +enum Role { + USER + ADMIN +} + +enum ScanStatus { + PENDING + RUNNING + COMPLETED + FAILED +} + +enum MonitorType { + DNS + SSL + UPTIME +} + +model User { + id String @id @default(cuid()) + email String @unique + name String? + passwordHash String + role Role @default(USER) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + scans Scan[] + monitors Monitor[] +} + +model Scan { + id String @id @default(cuid()) + domain String + status ScanStatus @default(PENDING) + progress Int @default(0) + result Json? + error String? + userId String? + user User? @relation(fields: [userId], references: [id], onDelete: SetNull) + ip String? + userAgent String? + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + @@index([domain]) + @@index([createdAt]) + @@map("scans") +} + +model Monitor { + id String @id @default(cuid()) + domain String + type MonitorType + enabled Boolean @default(true) + lastChecked DateTime? + lastResult Json? + userId String + user User @relation(fields: [userId], references: [id], onDelete: Cascade) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + @@index([userId]) + @@map("monitors") +} + +model ApiRateLimit { + id String @id @default(cuid()) + key String @unique + count Int @default(0) + windowEnd DateTime + updatedAt DateTime @updatedAt + + @@map("api_rate_limits") +} diff --git a/site/.gitignore b/site/.gitignore deleted file mode 100644 index 2752eb9..0000000 --- a/site/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -node_modules/ -.DS_Store diff --git a/site/README.md b/site/README.md deleted file mode 100644 index 28ad043..0000000 --- a/site/README.md +++ /dev/null @@ -1,37 +0,0 @@ -# GeoExport — локальная копия - -Зеркало интерфейса [geoexport.org](https://geoexport.org/): экспорт списков geoip/geosite для VPN-клиентов (Xray, 3x-ui, V2Ray). - -## Запуск - -```bash -cd ~/geoexport-clone -npm start -``` - -Откройте в браузере: **http://127.0.0.1:4173** - -Другой порт: `PORT=8080 npm start` - -## Состав - -| Путь | Назначение | -|------|------------| -| `index.html` | Точка входа SPA | -| `assets/` | Собранный фронтенд (React + Tailwind) с оригинала | -| `data/*.json` | Кэш API: пресеты, источники, дата обновления | -| `server.mjs` | Локальный сервер: статика + JSON + прокси динамических API | - -## Обновление данных - -```bash -curl -sL https://geoexport.org/api/presets -o data/presets.json -curl -sL https://geoexport.org/api/sources -o data/sources.json -curl -sL https://geoexport.org/api/last-update -o data/last-update.json -curl -sL https://geoexport.org/api/routing/presets -o data/routing-presets.json -``` - -## Ограничения - -- Экспорт, поиск, DNS lookup и генерация routing проксируются на живой `geoexport.org` (нужен интернет). -- Это копия UI и кэшированных справочников, не полный автономный бэкенд с базами `.dat`. diff --git a/site/assets/index-VQj200iM.js b/site/assets/index-VQj200iM.js deleted file mode 100644 index ee8d28d..0000000 --- a/site/assets/index-VQj200iM.js +++ /dev/null @@ -1,69 +0,0 @@ -function od(e,t){for(var n=0;nr[l]})}}}return Object.freeze(Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}))}(function(){const t=document.createElement("link").relList;if(t&&t.supports&&t.supports("modulepreload"))return;for(const l of document.querySelectorAll('link[rel="modulepreload"]'))r(l);new MutationObserver(l=>{for(const s of l)if(s.type==="childList")for(const o of s.addedNodes)o.tagName==="LINK"&&o.rel==="modulepreload"&&r(o)}).observe(document,{childList:!0,subtree:!0});function n(l){const s={};return l.integrity&&(s.integrity=l.integrity),l.referrerPolicy&&(s.referrerPolicy=l.referrerPolicy),l.crossOrigin==="use-credentials"?s.credentials="include":l.crossOrigin==="anonymous"?s.credentials="omit":s.credentials="same-origin",s}function r(l){if(l.ep)return;l.ep=!0;const s=n(l);fetch(l.href,s)}})();function id(e){return e&&e.__esModule&&Object.prototype.hasOwnProperty.call(e,"default")?e.default:e}var Ma={exports:{}},El={},$a={exports:{}},M={};/** - * @license React - * react.production.min.js - * - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */var pr=Symbol.for("react.element"),ad=Symbol.for("react.portal"),ud=Symbol.for("react.fragment"),cd=Symbol.for("react.strict_mode"),dd=Symbol.for("react.profiler"),fd=Symbol.for("react.provider"),pd=Symbol.for("react.context"),md=Symbol.for("react.forward_ref"),hd=Symbol.for("react.suspense"),vd=Symbol.for("react.memo"),gd=Symbol.for("react.lazy"),gi=Symbol.iterator;function xd(e){return e===null||typeof e!="object"?null:(e=gi&&e[gi]||e["@@iterator"],typeof e=="function"?e:null)}var Da={isMounted:function(){return!1},enqueueForceUpdate:function(){},enqueueReplaceState:function(){},enqueueSetState:function(){}},Fa=Object.assign,Ba={};function wn(e,t,n){this.props=e,this.context=t,this.refs=Ba,this.updater=n||Da}wn.prototype.isReactComponent={};wn.prototype.setState=function(e,t){if(typeof e!="object"&&typeof e!="function"&&e!=null)throw Error("setState(...): takes an object of state variables to update or a function which returns an object of state variables.");this.updater.enqueueSetState(this,e,t,"setState")};wn.prototype.forceUpdate=function(e){this.updater.enqueueForceUpdate(this,e,"forceUpdate")};function Aa(){}Aa.prototype=wn.prototype;function xo(e,t,n){this.props=e,this.context=t,this.refs=Ba,this.updater=n||Da}var yo=xo.prototype=new Aa;yo.constructor=xo;Fa(yo,wn.prototype);yo.isPureReactComponent=!0;var xi=Array.isArray,Va=Object.prototype.hasOwnProperty,ko={current:null},Wa={key:!0,ref:!0,__self:!0,__source:!0};function Ha(e,t,n){var r,l={},s=null,o=null;if(t!=null)for(r in t.ref!==void 0&&(o=t.ref),t.key!==void 0&&(s=""+t.key),t)Va.call(t,r)&&!Wa.hasOwnProperty(r)&&(l[r]=t[r]);var i=arguments.length-2;if(i===1)l.children=n;else if(1>>1,ne=L[Y];if(0>>1;Yl(Hl,I))Ltl(jr,Hl)?(L[Y]=jr,L[Lt]=I,Y=Lt):(L[Y]=Hl,L[Pt]=I,Y=Pt);else if(Ltl(jr,I))L[Y]=jr,L[Lt]=I,Y=Lt;else break e}}return O}function l(L,O){var I=L.sortIndex-O.sortIndex;return I!==0?I:L.id-O.id}if(typeof performance=="object"&&typeof performance.now=="function"){var s=performance;e.unstable_now=function(){return s.now()}}else{var o=Date,i=o.now();e.unstable_now=function(){return o.now()-i}}var a=[],c=[],h=1,m=null,v=3,g=!1,y=!1,j=!1,C=typeof setTimeout=="function"?setTimeout:null,f=typeof clearTimeout=="function"?clearTimeout:null,d=typeof setImmediate<"u"?setImmediate:null;typeof navigator<"u"&&navigator.scheduling!==void 0&&navigator.scheduling.isInputPending!==void 0&&navigator.scheduling.isInputPending.bind(navigator.scheduling);function p(L){for(var O=n(c);O!==null;){if(O.callback===null)r(c);else if(O.startTime<=L)r(c),O.sortIndex=O.expirationTime,t(a,O);else break;O=n(c)}}function x(L){if(j=!1,p(L),!y)if(n(a)!==null)y=!0,F(E);else{var O=n(c);O!==null&&V(x,O.startTime-L)}}function E(L,O){y=!1,j&&(j=!1,f(P),P=-1),g=!0;var I=v;try{for(p(O),m=n(a);m!==null&&(!(m.expirationTime>O)||L&&!$());){var Y=m.callback;if(typeof Y=="function"){m.callback=null,v=m.priorityLevel;var ne=Y(m.expirationTime<=O);O=e.unstable_now(),typeof ne=="function"?m.callback=ne:m===n(a)&&r(a),p(O)}else r(a);m=n(a)}if(m!==null)var Sr=!0;else{var Pt=n(c);Pt!==null&&V(x,Pt.startTime-O),Sr=!1}return Sr}finally{m=null,v=I,g=!1}}var w=!1,S=null,P=-1,z=5,T=-1;function $(){return!(e.unstable_now()-TL||125Y?(L.sortIndex=I,t(c,L),n(a)===null&&L===n(c)&&(j?(f(P),P=-1):j=!0,V(x,I-Y))):(L.sortIndex=ne,t(a,L),y||g||(y=!0,F(E))),L},e.unstable_shouldYield=$,e.unstable_wrapCallback=function(L){var O=v;return function(){var I=v;v=O;try{return L.apply(this,arguments)}finally{v=I}}}})(Xa);Ga.exports=Xa;var Rd=Ga.exports;/** - * @license React - * react-dom.production.min.js - * - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */var Td=k,Ce=Rd;function _(e){for(var t="https://reactjs.org/docs/error-decoder.html?invariant="+e,n=1;n"u"||typeof window.document>"u"||typeof window.document.createElement>"u"),ws=Object.prototype.hasOwnProperty,zd=/^[:A-Z_a-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD][:A-Z_a-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD\-.0-9\u00B7\u0300-\u036F\u203F-\u2040]*$/,ki={},wi={};function Od(e){return ws.call(wi,e)?!0:ws.call(ki,e)?!1:zd.test(e)?wi[e]=!0:(ki[e]=!0,!1)}function Id(e,t,n,r){if(n!==null&&n.type===0)return!1;switch(typeof t){case"function":case"symbol":return!0;case"boolean":return r?!1:n!==null?!n.acceptsBooleans:(e=e.toLowerCase().slice(0,5),e!=="data-"&&e!=="aria-");default:return!1}}function Ud(e,t,n,r){if(t===null||typeof t>"u"||Id(e,t,n,r))return!0;if(r)return!1;if(n!==null)switch(n.type){case 3:return!t;case 4:return t===!1;case 5:return isNaN(t);case 6:return isNaN(t)||1>t}return!1}function ge(e,t,n,r,l,s,o){this.acceptsBooleans=t===2||t===3||t===4,this.attributeName=r,this.attributeNamespace=l,this.mustUseProperty=n,this.propertyName=e,this.type=t,this.sanitizeURL=s,this.removeEmptyString=o}var ie={};"children dangerouslySetInnerHTML defaultValue defaultChecked innerHTML suppressContentEditableWarning suppressHydrationWarning style".split(" ").forEach(function(e){ie[e]=new ge(e,0,!1,e,null,!1,!1)});[["acceptCharset","accept-charset"],["className","class"],["htmlFor","for"],["httpEquiv","http-equiv"]].forEach(function(e){var t=e[0];ie[t]=new ge(t,1,!1,e[1],null,!1,!1)});["contentEditable","draggable","spellCheck","value"].forEach(function(e){ie[e]=new ge(e,2,!1,e.toLowerCase(),null,!1,!1)});["autoReverse","externalResourcesRequired","focusable","preserveAlpha"].forEach(function(e){ie[e]=new ge(e,2,!1,e,null,!1,!1)});"allowFullScreen async autoFocus autoPlay controls default defer disabled disablePictureInPicture disableRemotePlayback formNoValidate hidden loop noModule noValidate open playsInline readOnly required reversed scoped seamless itemScope".split(" ").forEach(function(e){ie[e]=new ge(e,3,!1,e.toLowerCase(),null,!1,!1)});["checked","multiple","muted","selected"].forEach(function(e){ie[e]=new ge(e,3,!0,e,null,!1,!1)});["capture","download"].forEach(function(e){ie[e]=new ge(e,4,!1,e,null,!1,!1)});["cols","rows","size","span"].forEach(function(e){ie[e]=new ge(e,6,!1,e,null,!1,!1)});["rowSpan","start"].forEach(function(e){ie[e]=new ge(e,5,!1,e.toLowerCase(),null,!1,!1)});var So=/[\-:]([a-z])/g;function jo(e){return e[1].toUpperCase()}"accent-height alignment-baseline arabic-form baseline-shift cap-height clip-path clip-rule color-interpolation color-interpolation-filters color-profile color-rendering dominant-baseline enable-background fill-opacity fill-rule flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight glyph-name glyph-orientation-horizontal glyph-orientation-vertical horiz-adv-x horiz-origin-x image-rendering letter-spacing lighting-color marker-end marker-mid marker-start overline-position overline-thickness paint-order panose-1 pointer-events rendering-intent shape-rendering stop-color stop-opacity strikethrough-position strikethrough-thickness stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width text-anchor text-decoration text-rendering underline-position underline-thickness unicode-bidi unicode-range units-per-em v-alphabetic v-hanging v-ideographic v-mathematical vector-effect vert-adv-y vert-origin-x vert-origin-y word-spacing writing-mode xmlns:xlink x-height".split(" ").forEach(function(e){var t=e.replace(So,jo);ie[t]=new ge(t,1,!1,e,null,!1,!1)});"xlink:actuate xlink:arcrole xlink:role xlink:show xlink:title xlink:type".split(" ").forEach(function(e){var t=e.replace(So,jo);ie[t]=new ge(t,1,!1,e,"http://www.w3.org/1999/xlink",!1,!1)});["xml:base","xml:lang","xml:space"].forEach(function(e){var t=e.replace(So,jo);ie[t]=new ge(t,1,!1,e,"http://www.w3.org/XML/1998/namespace",!1,!1)});["tabIndex","crossOrigin"].forEach(function(e){ie[e]=new ge(e,1,!1,e.toLowerCase(),null,!1,!1)});ie.xlinkHref=new ge("xlinkHref",1,!1,"xlink:href","http://www.w3.org/1999/xlink",!0,!1);["src","href","action","formAction"].forEach(function(e){ie[e]=new ge(e,1,!1,e.toLowerCase(),null,!0,!0)});function No(e,t,n,r){var l=ie.hasOwnProperty(t)?ie[t]:null;(l!==null?l.type!==0:r||!(2i||l[o]!==s[i]){var a=` -`+l[o].replace(" at new "," at ");return e.displayName&&a.includes("")&&(a=a.replace("",e.displayName)),a}while(1<=o&&0<=i);break}}}finally{Kl=!1,Error.prepareStackTrace=n}return(e=e?e.displayName||e.name:"")?Un(e):""}function Md(e){switch(e.tag){case 5:return Un(e.type);case 16:return Un("Lazy");case 13:return Un("Suspense");case 19:return Un("SuspenseList");case 0:case 2:case 15:return e=Gl(e.type,!1),e;case 11:return e=Gl(e.type.render,!1),e;case 1:return e=Gl(e.type,!0),e;default:return""}}function _s(e){if(e==null)return null;if(typeof e=="function")return e.displayName||e.name||null;if(typeof e=="string")return e;switch(e){case Xt:return"Fragment";case Gt:return"Portal";case Ss:return"Profiler";case _o:return"StrictMode";case js:return"Suspense";case Ns:return"SuspenseList"}if(typeof e=="object")switch(e.$$typeof){case Za:return(e.displayName||"Context")+".Consumer";case Ja:return(e._context.displayName||"Context")+".Provider";case Eo:var t=e.render;return e=e.displayName,e||(e=t.displayName||t.name||"",e=e!==""?"ForwardRef("+e+")":"ForwardRef"),e;case Co:return t=e.displayName||null,t!==null?t:_s(e.type)||"Memo";case st:t=e._payload,e=e._init;try{return _s(e(t))}catch{}}return null}function $d(e){var t=e.type;switch(e.tag){case 24:return"Cache";case 9:return(t.displayName||"Context")+".Consumer";case 10:return(t._context.displayName||"Context")+".Provider";case 18:return"DehydratedFragment";case 11:return e=t.render,e=e.displayName||e.name||"",t.displayName||(e!==""?"ForwardRef("+e+")":"ForwardRef");case 7:return"Fragment";case 5:return t;case 4:return"Portal";case 3:return"Root";case 6:return"Text";case 16:return _s(t);case 8:return t===_o?"StrictMode":"Mode";case 22:return"Offscreen";case 12:return"Profiler";case 21:return"Scope";case 13:return"Suspense";case 19:return"SuspenseList";case 25:return"TracingMarker";case 1:case 0:case 17:case 2:case 14:case 15:if(typeof t=="function")return t.displayName||t.name||null;if(typeof t=="string")return t}return null}function wt(e){switch(typeof e){case"boolean":case"number":case"string":case"undefined":return e;case"object":return e;default:return""}}function eu(e){var t=e.type;return(e=e.nodeName)&&e.toLowerCase()==="input"&&(t==="checkbox"||t==="radio")}function Dd(e){var t=eu(e)?"checked":"value",n=Object.getOwnPropertyDescriptor(e.constructor.prototype,t),r=""+e[t];if(!e.hasOwnProperty(t)&&typeof n<"u"&&typeof n.get=="function"&&typeof n.set=="function"){var l=n.get,s=n.set;return Object.defineProperty(e,t,{configurable:!0,get:function(){return l.call(this)},set:function(o){r=""+o,s.call(this,o)}}),Object.defineProperty(e,t,{enumerable:n.enumerable}),{getValue:function(){return r},setValue:function(o){r=""+o},stopTracking:function(){e._valueTracker=null,delete e[t]}}}}function Er(e){e._valueTracker||(e._valueTracker=Dd(e))}function tu(e){if(!e)return!1;var t=e._valueTracker;if(!t)return!0;var n=t.getValue(),r="";return e&&(r=eu(e)?e.checked?"true":"false":e.value),e=r,e!==n?(t.setValue(e),!0):!1}function el(e){if(e=e||(typeof document<"u"?document:void 0),typeof e>"u")return null;try{return e.activeElement||e.body}catch{return e.body}}function Es(e,t){var n=t.checked;return G({},t,{defaultChecked:void 0,defaultValue:void 0,value:void 0,checked:n??e._wrapperState.initialChecked})}function ji(e,t){var n=t.defaultValue==null?"":t.defaultValue,r=t.checked!=null?t.checked:t.defaultChecked;n=wt(t.value!=null?t.value:n),e._wrapperState={initialChecked:r,initialValue:n,controlled:t.type==="checkbox"||t.type==="radio"?t.checked!=null:t.value!=null}}function nu(e,t){t=t.checked,t!=null&&No(e,"checked",t,!1)}function Cs(e,t){nu(e,t);var n=wt(t.value),r=t.type;if(n!=null)r==="number"?(n===0&&e.value===""||e.value!=n)&&(e.value=""+n):e.value!==""+n&&(e.value=""+n);else if(r==="submit"||r==="reset"){e.removeAttribute("value");return}t.hasOwnProperty("value")?Ps(e,t.type,n):t.hasOwnProperty("defaultValue")&&Ps(e,t.type,wt(t.defaultValue)),t.checked==null&&t.defaultChecked!=null&&(e.defaultChecked=!!t.defaultChecked)}function Ni(e,t,n){if(t.hasOwnProperty("value")||t.hasOwnProperty("defaultValue")){var r=t.type;if(!(r!=="submit"&&r!=="reset"||t.value!==void 0&&t.value!==null))return;t=""+e._wrapperState.initialValue,n||t===e.value||(e.value=t),e.defaultValue=t}n=e.name,n!==""&&(e.name=""),e.defaultChecked=!!e._wrapperState.initialChecked,n!==""&&(e.name=n)}function Ps(e,t,n){(t!=="number"||el(e.ownerDocument)!==e)&&(n==null?e.defaultValue=""+e._wrapperState.initialValue:e.defaultValue!==""+n&&(e.defaultValue=""+n))}var Mn=Array.isArray;function on(e,t,n,r){if(e=e.options,t){t={};for(var l=0;l"+t.valueOf().toString()+"",t=Cr.firstChild;e.firstChild;)e.removeChild(e.firstChild);for(;t.firstChild;)e.appendChild(t.firstChild)}});function Xn(e,t){if(t){var n=e.firstChild;if(n&&n===e.lastChild&&n.nodeType===3){n.nodeValue=t;return}}e.textContent=t}var Fn={animationIterationCount:!0,aspectRatio:!0,borderImageOutset:!0,borderImageSlice:!0,borderImageWidth:!0,boxFlex:!0,boxFlexGroup:!0,boxOrdinalGroup:!0,columnCount:!0,columns:!0,flex:!0,flexGrow:!0,flexPositive:!0,flexShrink:!0,flexNegative:!0,flexOrder:!0,gridArea:!0,gridRow:!0,gridRowEnd:!0,gridRowSpan:!0,gridRowStart:!0,gridColumn:!0,gridColumnEnd:!0,gridColumnSpan:!0,gridColumnStart:!0,fontWeight:!0,lineClamp:!0,lineHeight:!0,opacity:!0,order:!0,orphans:!0,tabSize:!0,widows:!0,zIndex:!0,zoom:!0,fillOpacity:!0,floodOpacity:!0,stopOpacity:!0,strokeDasharray:!0,strokeDashoffset:!0,strokeMiterlimit:!0,strokeOpacity:!0,strokeWidth:!0},Fd=["Webkit","ms","Moz","O"];Object.keys(Fn).forEach(function(e){Fd.forEach(function(t){t=t+e.charAt(0).toUpperCase()+e.substring(1),Fn[t]=Fn[e]})});function ou(e,t,n){return t==null||typeof t=="boolean"||t===""?"":n||typeof t!="number"||t===0||Fn.hasOwnProperty(e)&&Fn[e]?(""+t).trim():t+"px"}function iu(e,t){e=e.style;for(var n in t)if(t.hasOwnProperty(n)){var r=n.indexOf("--")===0,l=ou(n,t[n],r);n==="float"&&(n="cssFloat"),r?e.setProperty(n,l):e[n]=l}}var Bd=G({menuitem:!0},{area:!0,base:!0,br:!0,col:!0,embed:!0,hr:!0,img:!0,input:!0,keygen:!0,link:!0,meta:!0,param:!0,source:!0,track:!0,wbr:!0});function Ts(e,t){if(t){if(Bd[e]&&(t.children!=null||t.dangerouslySetInnerHTML!=null))throw Error(_(137,e));if(t.dangerouslySetInnerHTML!=null){if(t.children!=null)throw Error(_(60));if(typeof t.dangerouslySetInnerHTML!="object"||!("__html"in t.dangerouslySetInnerHTML))throw Error(_(61))}if(t.style!=null&&typeof t.style!="object")throw Error(_(62))}}function zs(e,t){if(e.indexOf("-")===-1)return typeof t.is=="string";switch(e){case"annotation-xml":case"color-profile":case"font-face":case"font-face-src":case"font-face-uri":case"font-face-format":case"font-face-name":case"missing-glyph":return!1;default:return!0}}var Os=null;function Po(e){return e=e.target||e.srcElement||window,e.correspondingUseElement&&(e=e.correspondingUseElement),e.nodeType===3?e.parentNode:e}var Is=null,an=null,un=null;function Ci(e){if(e=vr(e)){if(typeof Is!="function")throw Error(_(280));var t=e.stateNode;t&&(t=Tl(t),Is(e.stateNode,e.type,t))}}function au(e){an?un?un.push(e):un=[e]:an=e}function uu(){if(an){var e=an,t=un;if(un=an=null,Ci(e),t)for(e=0;e>>=0,e===0?32:31-(Jd(e)/Zd|0)|0}var Pr=64,Lr=4194304;function $n(e){switch(e&-e){case 1:return 1;case 2:return 2;case 4:return 4;case 8:return 8;case 16:return 16;case 32:return 32;case 64:case 128:case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:case 262144:case 524288:case 1048576:case 2097152:return e&4194240;case 4194304:case 8388608:case 16777216:case 33554432:case 67108864:return e&130023424;case 134217728:return 134217728;case 268435456:return 268435456;case 536870912:return 536870912;case 1073741824:return 1073741824;default:return e}}function ll(e,t){var n=e.pendingLanes;if(n===0)return 0;var r=0,l=e.suspendedLanes,s=e.pingedLanes,o=n&268435455;if(o!==0){var i=o&~l;i!==0?r=$n(i):(s&=o,s!==0&&(r=$n(s)))}else o=n&~l,o!==0?r=$n(o):s!==0&&(r=$n(s));if(r===0)return 0;if(t!==0&&t!==r&&!(t&l)&&(l=r&-r,s=t&-t,l>=s||l===16&&(s&4194240)!==0))return t;if(r&4&&(r|=n&16),t=e.entangledLanes,t!==0)for(e=e.entanglements,t&=r;0n;n++)t.push(e);return t}function mr(e,t,n){e.pendingLanes|=t,t!==536870912&&(e.suspendedLanes=0,e.pingedLanes=0),e=e.eventTimes,t=31-Be(t),e[t]=n}function nf(e,t){var n=e.pendingLanes&~t;e.pendingLanes=t,e.suspendedLanes=0,e.pingedLanes=0,e.expiredLanes&=t,e.mutableReadLanes&=t,e.entangledLanes&=t,t=e.entanglements;var r=e.eventTimes;for(e=e.expirationTimes;0=An),Mi=" ",$i=!1;function Lu(e,t){switch(e){case"keyup":return Tf.indexOf(t.keyCode)!==-1;case"keydown":return t.keyCode!==229;case"keypress":case"mousedown":case"focusout":return!0;default:return!1}}function Ru(e){return e=e.detail,typeof e=="object"&&"data"in e?e.data:null}var Yt=!1;function Of(e,t){switch(e){case"compositionend":return Ru(t);case"keypress":return t.which!==32?null:($i=!0,Mi);case"textInput":return e=t.data,e===Mi&&$i?null:e;default:return null}}function If(e,t){if(Yt)return e==="compositionend"||!Mo&&Lu(e,t)?(e=Cu(),Hr=Oo=ut=null,Yt=!1,e):null;switch(e){case"paste":return null;case"keypress":if(!(t.ctrlKey||t.altKey||t.metaKey)||t.ctrlKey&&t.altKey){if(t.char&&1=t)return{node:n,offset:t-e};e=r}e:{for(;n;){if(n.nextSibling){n=n.nextSibling;break e}n=n.parentNode}n=void 0}n=Ai(n)}}function Iu(e,t){return e&&t?e===t?!0:e&&e.nodeType===3?!1:t&&t.nodeType===3?Iu(e,t.parentNode):"contains"in e?e.contains(t):e.compareDocumentPosition?!!(e.compareDocumentPosition(t)&16):!1:!1}function Uu(){for(var e=window,t=el();t instanceof e.HTMLIFrameElement;){try{var n=typeof t.contentWindow.location.href=="string"}catch{n=!1}if(n)e=t.contentWindow;else break;t=el(e.document)}return t}function $o(e){var t=e&&e.nodeName&&e.nodeName.toLowerCase();return t&&(t==="input"&&(e.type==="text"||e.type==="search"||e.type==="tel"||e.type==="url"||e.type==="password")||t==="textarea"||e.contentEditable==="true")}function Wf(e){var t=Uu(),n=e.focusedElem,r=e.selectionRange;if(t!==n&&n&&n.ownerDocument&&Iu(n.ownerDocument.documentElement,n)){if(r!==null&&$o(n)){if(t=r.start,e=r.end,e===void 0&&(e=t),"selectionStart"in n)n.selectionStart=t,n.selectionEnd=Math.min(e,n.value.length);else if(e=(t=n.ownerDocument||document)&&t.defaultView||window,e.getSelection){e=e.getSelection();var l=n.textContent.length,s=Math.min(r.start,l);r=r.end===void 0?s:Math.min(r.end,l),!e.extend&&s>r&&(l=r,r=s,s=l),l=Vi(n,s);var o=Vi(n,r);l&&o&&(e.rangeCount!==1||e.anchorNode!==l.node||e.anchorOffset!==l.offset||e.focusNode!==o.node||e.focusOffset!==o.offset)&&(t=t.createRange(),t.setStart(l.node,l.offset),e.removeAllRanges(),s>r?(e.addRange(t),e.extend(o.node,o.offset)):(t.setEnd(o.node,o.offset),e.addRange(t)))}}for(t=[],e=n;e=e.parentNode;)e.nodeType===1&&t.push({element:e,left:e.scrollLeft,top:e.scrollTop});for(typeof n.focus=="function"&&n.focus(),n=0;n=document.documentMode,Jt=null,Bs=null,Wn=null,As=!1;function Wi(e,t,n){var r=n.window===n?n.document:n.nodeType===9?n:n.ownerDocument;As||Jt==null||Jt!==el(r)||(r=Jt,"selectionStart"in r&&$o(r)?r={start:r.selectionStart,end:r.selectionEnd}:(r=(r.ownerDocument&&r.ownerDocument.defaultView||window).getSelection(),r={anchorNode:r.anchorNode,anchorOffset:r.anchorOffset,focusNode:r.focusNode,focusOffset:r.focusOffset}),Wn&&tr(Wn,r)||(Wn=r,r=il(Bs,"onSelect"),0en||(e.current=Ks[en],Ks[en]=null,en--)}function A(e,t){en++,Ks[en]=e.current,e.current=t}var St={},fe=Nt(St),ke=Nt(!1),$t=St;function mn(e,t){var n=e.type.contextTypes;if(!n)return St;var r=e.stateNode;if(r&&r.__reactInternalMemoizedUnmaskedChildContext===t)return r.__reactInternalMemoizedMaskedChildContext;var l={},s;for(s in n)l[s]=t[s];return r&&(e=e.stateNode,e.__reactInternalMemoizedUnmaskedChildContext=t,e.__reactInternalMemoizedMaskedChildContext=l),l}function we(e){return e=e.childContextTypes,e!=null}function ul(){H(ke),H(fe)}function Yi(e,t,n){if(fe.current!==St)throw Error(_(168));A(fe,t),A(ke,n)}function Hu(e,t,n){var r=e.stateNode;if(t=t.childContextTypes,typeof r.getChildContext!="function")return n;r=r.getChildContext();for(var l in r)if(!(l in t))throw Error(_(108,$d(e)||"Unknown",l));return G({},n,r)}function cl(e){return e=(e=e.stateNode)&&e.__reactInternalMemoizedMergedChildContext||St,$t=fe.current,A(fe,e),A(ke,ke.current),!0}function Ji(e,t,n){var r=e.stateNode;if(!r)throw Error(_(169));n?(e=Hu(e,t,$t),r.__reactInternalMemoizedMergedChildContext=e,H(ke),H(fe),A(fe,e)):H(ke),A(ke,n)}var Xe=null,zl=!1,as=!1;function bu(e){Xe===null?Xe=[e]:Xe.push(e)}function tp(e){zl=!0,bu(e)}function _t(){if(!as&&Xe!==null){as=!0;var e=0,t=B;try{var n=Xe;for(B=1;e>=o,l-=o,Ye=1<<32-Be(t)+l|n<P?(z=S,S=null):z=S.sibling;var T=v(f,S,p[P],x);if(T===null){S===null&&(S=z);break}e&&S&&T.alternate===null&&t(f,S),d=s(T,d,P),w===null?E=T:w.sibling=T,w=T,S=z}if(P===p.length)return n(f,S),b&&Rt(f,P),E;if(S===null){for(;PP?(z=S,S=null):z=S.sibling;var $=v(f,S,T.value,x);if($===null){S===null&&(S=z);break}e&&S&&$.alternate===null&&t(f,S),d=s($,d,P),w===null?E=$:w.sibling=$,w=$,S=z}if(T.done)return n(f,S),b&&Rt(f,P),E;if(S===null){for(;!T.done;P++,T=p.next())T=m(f,T.value,x),T!==null&&(d=s(T,d,P),w===null?E=T:w.sibling=T,w=T);return b&&Rt(f,P),E}for(S=r(f,S);!T.done;P++,T=p.next())T=g(S,f,P,T.value,x),T!==null&&(e&&T.alternate!==null&&S.delete(T.key===null?P:T.key),d=s(T,d,P),w===null?E=T:w.sibling=T,w=T);return e&&S.forEach(function(Et){return t(f,Et)}),b&&Rt(f,P),E}function C(f,d,p,x){if(typeof p=="object"&&p!==null&&p.type===Xt&&p.key===null&&(p=p.props.children),typeof p=="object"&&p!==null){switch(p.$$typeof){case _r:e:{for(var E=p.key,w=d;w!==null;){if(w.key===E){if(E=p.type,E===Xt){if(w.tag===7){n(f,w.sibling),d=l(w,p.props.children),d.return=f,f=d;break e}}else if(w.elementType===E||typeof E=="object"&&E!==null&&E.$$typeof===st&&ea(E)===w.type){n(f,w.sibling),d=l(w,p.props),d.ref=Rn(f,w,p),d.return=f,f=d;break e}n(f,w);break}else t(f,w);w=w.sibling}p.type===Xt?(d=Mt(p.props.children,f.mode,x,p.key),d.return=f,f=d):(x=Zr(p.type,p.key,p.props,null,f.mode,x),x.ref=Rn(f,d,p),x.return=f,f=x)}return o(f);case Gt:e:{for(w=p.key;d!==null;){if(d.key===w)if(d.tag===4&&d.stateNode.containerInfo===p.containerInfo&&d.stateNode.implementation===p.implementation){n(f,d.sibling),d=l(d,p.children||[]),d.return=f,f=d;break e}else{n(f,d);break}else t(f,d);d=d.sibling}d=vs(p,f.mode,x),d.return=f,f=d}return o(f);case st:return w=p._init,C(f,d,w(p._payload),x)}if(Mn(p))return y(f,d,p,x);if(_n(p))return j(f,d,p,x);Mr(f,p)}return typeof p=="string"&&p!==""||typeof p=="number"?(p=""+p,d!==null&&d.tag===6?(n(f,d.sibling),d=l(d,p),d.return=f,f=d):(n(f,d),d=hs(p,f.mode,x),d.return=f,f=d),o(f)):n(f,d)}return C}var vn=Xu(!0),Yu=Xu(!1),pl=Nt(null),ml=null,rn=null,Ao=null;function Vo(){Ao=rn=ml=null}function Wo(e){var t=pl.current;H(pl),e._currentValue=t}function Ys(e,t,n){for(;e!==null;){var r=e.alternate;if((e.childLanes&t)!==t?(e.childLanes|=t,r!==null&&(r.childLanes|=t)):r!==null&&(r.childLanes&t)!==t&&(r.childLanes|=t),e===n)break;e=e.return}}function dn(e,t){ml=e,Ao=rn=null,e=e.dependencies,e!==null&&e.firstContext!==null&&(e.lanes&t&&(ye=!0),e.firstContext=null)}function Ie(e){var t=e._currentValue;if(Ao!==e)if(e={context:e,memoizedValue:t,next:null},rn===null){if(ml===null)throw Error(_(308));rn=e,ml.dependencies={lanes:0,firstContext:e}}else rn=rn.next=e;return t}var Ot=null;function Ho(e){Ot===null?Ot=[e]:Ot.push(e)}function Ju(e,t,n,r){var l=t.interleaved;return l===null?(n.next=n,Ho(t)):(n.next=l.next,l.next=n),t.interleaved=n,tt(e,r)}function tt(e,t){e.lanes|=t;var n=e.alternate;for(n!==null&&(n.lanes|=t),n=e,e=e.return;e!==null;)e.childLanes|=t,n=e.alternate,n!==null&&(n.childLanes|=t),n=e,e=e.return;return n.tag===3?n.stateNode:null}var ot=!1;function bo(e){e.updateQueue={baseState:e.memoizedState,firstBaseUpdate:null,lastBaseUpdate:null,shared:{pending:null,interleaved:null,lanes:0},effects:null}}function Zu(e,t){e=e.updateQueue,t.updateQueue===e&&(t.updateQueue={baseState:e.baseState,firstBaseUpdate:e.firstBaseUpdate,lastBaseUpdate:e.lastBaseUpdate,shared:e.shared,effects:e.effects})}function Ze(e,t){return{eventTime:e,lane:t,tag:0,payload:null,callback:null,next:null}}function vt(e,t,n){var r=e.updateQueue;if(r===null)return null;if(r=r.shared,D&2){var l=r.pending;return l===null?t.next=t:(t.next=l.next,l.next=t),r.pending=t,tt(e,n)}return l=r.interleaved,l===null?(t.next=t,Ho(r)):(t.next=l.next,l.next=t),r.interleaved=t,tt(e,n)}function Qr(e,t,n){if(t=t.updateQueue,t!==null&&(t=t.shared,(n&4194240)!==0)){var r=t.lanes;r&=e.pendingLanes,n|=r,t.lanes=n,Ro(e,n)}}function ta(e,t){var n=e.updateQueue,r=e.alternate;if(r!==null&&(r=r.updateQueue,n===r)){var l=null,s=null;if(n=n.firstBaseUpdate,n!==null){do{var o={eventTime:n.eventTime,lane:n.lane,tag:n.tag,payload:n.payload,callback:n.callback,next:null};s===null?l=s=o:s=s.next=o,n=n.next}while(n!==null);s===null?l=s=t:s=s.next=t}else l=s=t;n={baseState:r.baseState,firstBaseUpdate:l,lastBaseUpdate:s,shared:r.shared,effects:r.effects},e.updateQueue=n;return}e=n.lastBaseUpdate,e===null?n.firstBaseUpdate=t:e.next=t,n.lastBaseUpdate=t}function hl(e,t,n,r){var l=e.updateQueue;ot=!1;var s=l.firstBaseUpdate,o=l.lastBaseUpdate,i=l.shared.pending;if(i!==null){l.shared.pending=null;var a=i,c=a.next;a.next=null,o===null?s=c:o.next=c,o=a;var h=e.alternate;h!==null&&(h=h.updateQueue,i=h.lastBaseUpdate,i!==o&&(i===null?h.firstBaseUpdate=c:i.next=c,h.lastBaseUpdate=a))}if(s!==null){var m=l.baseState;o=0,h=c=a=null,i=s;do{var v=i.lane,g=i.eventTime;if((r&v)===v){h!==null&&(h=h.next={eventTime:g,lane:0,tag:i.tag,payload:i.payload,callback:i.callback,next:null});e:{var y=e,j=i;switch(v=t,g=n,j.tag){case 1:if(y=j.payload,typeof y=="function"){m=y.call(g,m,v);break e}m=y;break e;case 3:y.flags=y.flags&-65537|128;case 0:if(y=j.payload,v=typeof y=="function"?y.call(g,m,v):y,v==null)break e;m=G({},m,v);break e;case 2:ot=!0}}i.callback!==null&&i.lane!==0&&(e.flags|=64,v=l.effects,v===null?l.effects=[i]:v.push(i))}else g={eventTime:g,lane:v,tag:i.tag,payload:i.payload,callback:i.callback,next:null},h===null?(c=h=g,a=m):h=h.next=g,o|=v;if(i=i.next,i===null){if(i=l.shared.pending,i===null)break;v=i,i=v.next,v.next=null,l.lastBaseUpdate=v,l.shared.pending=null}}while(!0);if(h===null&&(a=m),l.baseState=a,l.firstBaseUpdate=c,l.lastBaseUpdate=h,t=l.shared.interleaved,t!==null){l=t;do o|=l.lane,l=l.next;while(l!==t)}else s===null&&(l.shared.lanes=0);Bt|=o,e.lanes=o,e.memoizedState=m}}function na(e,t,n){if(e=t.effects,t.effects=null,e!==null)for(t=0;tn?n:4,e(!0);var r=cs.transition;cs.transition={};try{e(!1),t()}finally{B=n,cs.transition=r}}function hc(){return Ue().memoizedState}function sp(e,t,n){var r=xt(e);if(n={lane:r,action:n,hasEagerState:!1,eagerState:null,next:null},vc(e))gc(t,n);else if(n=Ju(e,t,n,r),n!==null){var l=he();Ae(n,e,r,l),xc(n,t,r)}}function op(e,t,n){var r=xt(e),l={lane:r,action:n,hasEagerState:!1,eagerState:null,next:null};if(vc(e))gc(t,l);else{var s=e.alternate;if(e.lanes===0&&(s===null||s.lanes===0)&&(s=t.lastRenderedReducer,s!==null))try{var o=t.lastRenderedState,i=s(o,n);if(l.hasEagerState=!0,l.eagerState=i,Ve(i,o)){var a=t.interleaved;a===null?(l.next=l,Ho(t)):(l.next=a.next,a.next=l),t.interleaved=l;return}}catch{}finally{}n=Ju(e,t,l,r),n!==null&&(l=he(),Ae(n,e,r,l),xc(n,t,r))}}function vc(e){var t=e.alternate;return e===K||t!==null&&t===K}function gc(e,t){Hn=gl=!0;var n=e.pending;n===null?t.next=t:(t.next=n.next,n.next=t),e.pending=t}function xc(e,t,n){if(n&4194240){var r=t.lanes;r&=e.pendingLanes,n|=r,t.lanes=n,Ro(e,n)}}var xl={readContext:Ie,useCallback:ae,useContext:ae,useEffect:ae,useImperativeHandle:ae,useInsertionEffect:ae,useLayoutEffect:ae,useMemo:ae,useReducer:ae,useRef:ae,useState:ae,useDebugValue:ae,useDeferredValue:ae,useTransition:ae,useMutableSource:ae,useSyncExternalStore:ae,useId:ae,unstable_isNewReconciler:!1},ip={readContext:Ie,useCallback:function(e,t){return He().memoizedState=[e,t===void 0?null:t],e},useContext:Ie,useEffect:la,useImperativeHandle:function(e,t,n){return n=n!=null?n.concat([e]):null,Gr(4194308,4,cc.bind(null,t,e),n)},useLayoutEffect:function(e,t){return Gr(4194308,4,e,t)},useInsertionEffect:function(e,t){return Gr(4,2,e,t)},useMemo:function(e,t){var n=He();return t=t===void 0?null:t,e=e(),n.memoizedState=[e,t],e},useReducer:function(e,t,n){var r=He();return t=n!==void 0?n(t):t,r.memoizedState=r.baseState=t,e={pending:null,interleaved:null,lanes:0,dispatch:null,lastRenderedReducer:e,lastRenderedState:t},r.queue=e,e=e.dispatch=sp.bind(null,K,e),[r.memoizedState,e]},useRef:function(e){var t=He();return e={current:e},t.memoizedState=e},useState:ra,useDebugValue:qo,useDeferredValue:function(e){return He().memoizedState=e},useTransition:function(){var e=ra(!1),t=e[0];return e=lp.bind(null,e[1]),He().memoizedState=e,[t,e]},useMutableSource:function(){},useSyncExternalStore:function(e,t,n){var r=K,l=He();if(b){if(n===void 0)throw Error(_(407));n=n()}else{if(n=t(),le===null)throw Error(_(349));Ft&30||nc(r,t,n)}l.memoizedState=n;var s={value:n,getSnapshot:t};return l.queue=s,la(lc.bind(null,r,s,e),[e]),r.flags|=2048,ur(9,rc.bind(null,r,s,n,t),void 0,null),n},useId:function(){var e=He(),t=le.identifierPrefix;if(b){var n=Je,r=Ye;n=(r&~(1<<32-Be(r)-1)).toString(32)+n,t=":"+t+"R"+n,n=ir++,0<\/script>",e=e.removeChild(e.firstChild)):typeof r.is=="string"?e=o.createElement(n,{is:r.is}):(e=o.createElement(n),n==="select"&&(o=e,r.multiple?o.multiple=!0:r.size&&(o.size=r.size))):e=o.createElementNS(e,n),e[be]=t,e[lr]=r,Pc(e,t,!1,!1),t.stateNode=e;e:{switch(o=zs(n,r),n){case"dialog":W("cancel",e),W("close",e),l=r;break;case"iframe":case"object":case"embed":W("load",e),l=r;break;case"video":case"audio":for(l=0;lyn&&(t.flags|=128,r=!0,Tn(s,!1),t.lanes=4194304)}else{if(!r)if(e=vl(o),e!==null){if(t.flags|=128,r=!0,n=e.updateQueue,n!==null&&(t.updateQueue=n,t.flags|=4),Tn(s,!0),s.tail===null&&s.tailMode==="hidden"&&!o.alternate&&!b)return ue(t),null}else 2*J()-s.renderingStartTime>yn&&n!==1073741824&&(t.flags|=128,r=!0,Tn(s,!1),t.lanes=4194304);s.isBackwards?(o.sibling=t.child,t.child=o):(n=s.last,n!==null?n.sibling=o:t.child=o,s.last=o)}return s.tail!==null?(t=s.tail,s.rendering=t,s.tail=t.sibling,s.renderingStartTime=J(),t.sibling=null,n=Q.current,A(Q,r?n&1|2:n&1),t):(ue(t),null);case 22:case 23:return si(),r=t.memoizedState!==null,e!==null&&e.memoizedState!==null!==r&&(t.flags|=8192),r&&t.mode&1?je&1073741824&&(ue(t),t.subtreeFlags&6&&(t.flags|=8192)):ue(t),null;case 24:return null;case 25:return null}throw Error(_(156,t.tag))}function hp(e,t){switch(Fo(t),t.tag){case 1:return we(t.type)&&ul(),e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 3:return gn(),H(ke),H(fe),Go(),e=t.flags,e&65536&&!(e&128)?(t.flags=e&-65537|128,t):null;case 5:return Ko(t),null;case 13:if(H(Q),e=t.memoizedState,e!==null&&e.dehydrated!==null){if(t.alternate===null)throw Error(_(340));hn()}return e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 19:return H(Q),null;case 4:return gn(),null;case 10:return Wo(t.type._context),null;case 22:case 23:return si(),null;case 24:return null;default:return null}}var Dr=!1,ce=!1,vp=typeof WeakSet=="function"?WeakSet:Set,R=null;function ln(e,t){var n=e.ref;if(n!==null)if(typeof n=="function")try{n(null)}catch(r){X(e,t,r)}else n.current=null}function so(e,t,n){try{n()}catch(r){X(e,t,r)}}var ha=!1;function gp(e,t){if(Vs=sl,e=Uu(),$o(e)){if("selectionStart"in e)var n={start:e.selectionStart,end:e.selectionEnd};else e:{n=(n=e.ownerDocument)&&n.defaultView||window;var r=n.getSelection&&n.getSelection();if(r&&r.rangeCount!==0){n=r.anchorNode;var l=r.anchorOffset,s=r.focusNode;r=r.focusOffset;try{n.nodeType,s.nodeType}catch{n=null;break e}var o=0,i=-1,a=-1,c=0,h=0,m=e,v=null;t:for(;;){for(var g;m!==n||l!==0&&m.nodeType!==3||(i=o+l),m!==s||r!==0&&m.nodeType!==3||(a=o+r),m.nodeType===3&&(o+=m.nodeValue.length),(g=m.firstChild)!==null;)v=m,m=g;for(;;){if(m===e)break t;if(v===n&&++c===l&&(i=o),v===s&&++h===r&&(a=o),(g=m.nextSibling)!==null)break;m=v,v=m.parentNode}m=g}n=i===-1||a===-1?null:{start:i,end:a}}else n=null}n=n||{start:0,end:0}}else n=null;for(Ws={focusedElem:e,selectionRange:n},sl=!1,R=t;R!==null;)if(t=R,e=t.child,(t.subtreeFlags&1028)!==0&&e!==null)e.return=t,R=e;else for(;R!==null;){t=R;try{var y=t.alternate;if(t.flags&1024)switch(t.tag){case 0:case 11:case 15:break;case 1:if(y!==null){var j=y.memoizedProps,C=y.memoizedState,f=t.stateNode,d=f.getSnapshotBeforeUpdate(t.elementType===t.type?j:$e(t.type,j),C);f.__reactInternalSnapshotBeforeUpdate=d}break;case 3:var p=t.stateNode.containerInfo;p.nodeType===1?p.textContent="":p.nodeType===9&&p.documentElement&&p.removeChild(p.documentElement);break;case 5:case 6:case 4:case 17:break;default:throw Error(_(163))}}catch(x){X(t,t.return,x)}if(e=t.sibling,e!==null){e.return=t.return,R=e;break}R=t.return}return y=ha,ha=!1,y}function bn(e,t,n){var r=t.updateQueue;if(r=r!==null?r.lastEffect:null,r!==null){var l=r=r.next;do{if((l.tag&e)===e){var s=l.destroy;l.destroy=void 0,s!==void 0&&so(t,n,s)}l=l.next}while(l!==r)}}function Ul(e,t){if(t=t.updateQueue,t=t!==null?t.lastEffect:null,t!==null){var n=t=t.next;do{if((n.tag&e)===e){var r=n.create;n.destroy=r()}n=n.next}while(n!==t)}}function oo(e){var t=e.ref;if(t!==null){var n=e.stateNode;switch(e.tag){case 5:e=n;break;default:e=n}typeof t=="function"?t(e):t.current=e}}function Tc(e){var t=e.alternate;t!==null&&(e.alternate=null,Tc(t)),e.child=null,e.deletions=null,e.sibling=null,e.tag===5&&(t=e.stateNode,t!==null&&(delete t[be],delete t[lr],delete t[Qs],delete t[qf],delete t[ep])),e.stateNode=null,e.return=null,e.dependencies=null,e.memoizedProps=null,e.memoizedState=null,e.pendingProps=null,e.stateNode=null,e.updateQueue=null}function zc(e){return e.tag===5||e.tag===3||e.tag===4}function va(e){e:for(;;){for(;e.sibling===null;){if(e.return===null||zc(e.return))return null;e=e.return}for(e.sibling.return=e.return,e=e.sibling;e.tag!==5&&e.tag!==6&&e.tag!==18;){if(e.flags&2||e.child===null||e.tag===4)continue e;e.child.return=e,e=e.child}if(!(e.flags&2))return e.stateNode}}function io(e,t,n){var r=e.tag;if(r===5||r===6)e=e.stateNode,t?n.nodeType===8?n.parentNode.insertBefore(e,t):n.insertBefore(e,t):(n.nodeType===8?(t=n.parentNode,t.insertBefore(e,n)):(t=n,t.appendChild(e)),n=n._reactRootContainer,n!=null||t.onclick!==null||(t.onclick=al));else if(r!==4&&(e=e.child,e!==null))for(io(e,t,n),e=e.sibling;e!==null;)io(e,t,n),e=e.sibling}function ao(e,t,n){var r=e.tag;if(r===5||r===6)e=e.stateNode,t?n.insertBefore(e,t):n.appendChild(e);else if(r!==4&&(e=e.child,e!==null))for(ao(e,t,n),e=e.sibling;e!==null;)ao(e,t,n),e=e.sibling}var se=null,De=!1;function lt(e,t,n){for(n=n.child;n!==null;)Oc(e,t,n),n=n.sibling}function Oc(e,t,n){if(Qe&&typeof Qe.onCommitFiberUnmount=="function")try{Qe.onCommitFiberUnmount(Cl,n)}catch{}switch(n.tag){case 5:ce||ln(n,t);case 6:var r=se,l=De;se=null,lt(e,t,n),se=r,De=l,se!==null&&(De?(e=se,n=n.stateNode,e.nodeType===8?e.parentNode.removeChild(n):e.removeChild(n)):se.removeChild(n.stateNode));break;case 18:se!==null&&(De?(e=se,n=n.stateNode,e.nodeType===8?is(e.parentNode,n):e.nodeType===1&&is(e,n),qn(e)):is(se,n.stateNode));break;case 4:r=se,l=De,se=n.stateNode.containerInfo,De=!0,lt(e,t,n),se=r,De=l;break;case 0:case 11:case 14:case 15:if(!ce&&(r=n.updateQueue,r!==null&&(r=r.lastEffect,r!==null))){l=r=r.next;do{var s=l,o=s.destroy;s=s.tag,o!==void 0&&(s&2||s&4)&&so(n,t,o),l=l.next}while(l!==r)}lt(e,t,n);break;case 1:if(!ce&&(ln(n,t),r=n.stateNode,typeof r.componentWillUnmount=="function"))try{r.props=n.memoizedProps,r.state=n.memoizedState,r.componentWillUnmount()}catch(i){X(n,t,i)}lt(e,t,n);break;case 21:lt(e,t,n);break;case 22:n.mode&1?(ce=(r=ce)||n.memoizedState!==null,lt(e,t,n),ce=r):lt(e,t,n);break;default:lt(e,t,n)}}function ga(e){var t=e.updateQueue;if(t!==null){e.updateQueue=null;var n=e.stateNode;n===null&&(n=e.stateNode=new vp),t.forEach(function(r){var l=Ep.bind(null,e,r);n.has(r)||(n.add(r),r.then(l,l))})}}function Me(e,t){var n=t.deletions;if(n!==null)for(var r=0;rl&&(l=o),r&=~s}if(r=l,r=J()-r,r=(120>r?120:480>r?480:1080>r?1080:1920>r?1920:3e3>r?3e3:4320>r?4320:1960*yp(r/1960))-r,10e?16:e,ct===null)var r=!1;else{if(e=ct,ct=null,wl=0,D&6)throw Error(_(331));var l=D;for(D|=4,R=e.current;R!==null;){var s=R,o=s.child;if(R.flags&16){var i=s.deletions;if(i!==null){for(var a=0;aJ()-ri?Ut(e,0):ni|=n),Se(e,t)}function Ac(e,t){t===0&&(e.mode&1?(t=Lr,Lr<<=1,!(Lr&130023424)&&(Lr=4194304)):t=1);var n=he();e=tt(e,t),e!==null&&(mr(e,t,n),Se(e,n))}function _p(e){var t=e.memoizedState,n=0;t!==null&&(n=t.retryLane),Ac(e,n)}function Ep(e,t){var n=0;switch(e.tag){case 13:var r=e.stateNode,l=e.memoizedState;l!==null&&(n=l.retryLane);break;case 19:r=e.stateNode;break;default:throw Error(_(314))}r!==null&&r.delete(t),Ac(e,n)}var Vc;Vc=function(e,t,n){if(e!==null)if(e.memoizedProps!==t.pendingProps||ke.current)ye=!0;else{if(!(e.lanes&n)&&!(t.flags&128))return ye=!1,pp(e,t,n);ye=!!(e.flags&131072)}else ye=!1,b&&t.flags&1048576&&Qu(t,fl,t.index);switch(t.lanes=0,t.tag){case 2:var r=t.type;Xr(e,t),e=t.pendingProps;var l=mn(t,fe.current);dn(t,n),l=Yo(null,t,r,e,l,n);var s=Jo();return t.flags|=1,typeof l=="object"&&l!==null&&typeof l.render=="function"&&l.$$typeof===void 0?(t.tag=1,t.memoizedState=null,t.updateQueue=null,we(r)?(s=!0,cl(t)):s=!1,t.memoizedState=l.state!==null&&l.state!==void 0?l.state:null,bo(t),l.updater=Il,t.stateNode=l,l._reactInternals=t,Zs(t,r,e,n),t=to(null,t,r,!0,s,n)):(t.tag=0,b&&s&&Do(t),pe(null,t,l,n),t=t.child),t;case 16:r=t.elementType;e:{switch(Xr(e,t),e=t.pendingProps,l=r._init,r=l(r._payload),t.type=r,l=t.tag=Pp(r),e=$e(r,e),l){case 0:t=eo(null,t,r,e,n);break e;case 1:t=fa(null,t,r,e,n);break e;case 11:t=ca(null,t,r,e,n);break e;case 14:t=da(null,t,r,$e(r.type,e),n);break e}throw Error(_(306,r,""))}return t;case 0:return r=t.type,l=t.pendingProps,l=t.elementType===r?l:$e(r,l),eo(e,t,r,l,n);case 1:return r=t.type,l=t.pendingProps,l=t.elementType===r?l:$e(r,l),fa(e,t,r,l,n);case 3:e:{if(_c(t),e===null)throw Error(_(387));r=t.pendingProps,s=t.memoizedState,l=s.element,Zu(e,t),hl(t,r,null,n);var o=t.memoizedState;if(r=o.element,s.isDehydrated)if(s={element:r,isDehydrated:!1,cache:o.cache,pendingSuspenseBoundaries:o.pendingSuspenseBoundaries,transitions:o.transitions},t.updateQueue.baseState=s,t.memoizedState=s,t.flags&256){l=xn(Error(_(423)),t),t=pa(e,t,r,n,l);break e}else if(r!==l){l=xn(Error(_(424)),t),t=pa(e,t,r,n,l);break e}else for(_e=ht(t.stateNode.containerInfo.firstChild),Ee=t,b=!0,Fe=null,n=Yu(t,null,r,n),t.child=n;n;)n.flags=n.flags&-3|4096,n=n.sibling;else{if(hn(),r===l){t=nt(e,t,n);break e}pe(e,t,r,n)}t=t.child}return t;case 5:return qu(t),e===null&&Xs(t),r=t.type,l=t.pendingProps,s=e!==null?e.memoizedProps:null,o=l.children,Hs(r,l)?o=null:s!==null&&Hs(r,s)&&(t.flags|=32),Nc(e,t),pe(e,t,o,n),t.child;case 6:return e===null&&Xs(t),null;case 13:return Ec(e,t,n);case 4:return Qo(t,t.stateNode.containerInfo),r=t.pendingProps,e===null?t.child=vn(t,null,r,n):pe(e,t,r,n),t.child;case 11:return r=t.type,l=t.pendingProps,l=t.elementType===r?l:$e(r,l),ca(e,t,r,l,n);case 7:return pe(e,t,t.pendingProps,n),t.child;case 8:return pe(e,t,t.pendingProps.children,n),t.child;case 12:return pe(e,t,t.pendingProps.children,n),t.child;case 10:e:{if(r=t.type._context,l=t.pendingProps,s=t.memoizedProps,o=l.value,A(pl,r._currentValue),r._currentValue=o,s!==null)if(Ve(s.value,o)){if(s.children===l.children&&!ke.current){t=nt(e,t,n);break e}}else for(s=t.child,s!==null&&(s.return=t);s!==null;){var i=s.dependencies;if(i!==null){o=s.child;for(var a=i.firstContext;a!==null;){if(a.context===r){if(s.tag===1){a=Ze(-1,n&-n),a.tag=2;var c=s.updateQueue;if(c!==null){c=c.shared;var h=c.pending;h===null?a.next=a:(a.next=h.next,h.next=a),c.pending=a}}s.lanes|=n,a=s.alternate,a!==null&&(a.lanes|=n),Ys(s.return,n,t),i.lanes|=n;break}a=a.next}}else if(s.tag===10)o=s.type===t.type?null:s.child;else if(s.tag===18){if(o=s.return,o===null)throw Error(_(341));o.lanes|=n,i=o.alternate,i!==null&&(i.lanes|=n),Ys(o,n,t),o=s.sibling}else o=s.child;if(o!==null)o.return=s;else for(o=s;o!==null;){if(o===t){o=null;break}if(s=o.sibling,s!==null){s.return=o.return,o=s;break}o=o.return}s=o}pe(e,t,l.children,n),t=t.child}return t;case 9:return l=t.type,r=t.pendingProps.children,dn(t,n),l=Ie(l),r=r(l),t.flags|=1,pe(e,t,r,n),t.child;case 14:return r=t.type,l=$e(r,t.pendingProps),l=$e(r.type,l),da(e,t,r,l,n);case 15:return Sc(e,t,t.type,t.pendingProps,n);case 17:return r=t.type,l=t.pendingProps,l=t.elementType===r?l:$e(r,l),Xr(e,t),t.tag=1,we(r)?(e=!0,cl(t)):e=!1,dn(t,n),yc(t,r,l),Zs(t,r,l,n),to(null,t,r,!0,e,n);case 19:return Cc(e,t,n);case 22:return jc(e,t,n)}throw Error(_(156,t.tag))};function Wc(e,t){return vu(e,t)}function Cp(e,t,n,r){this.tag=e,this.key=n,this.sibling=this.child=this.return=this.stateNode=this.type=this.elementType=null,this.index=0,this.ref=null,this.pendingProps=t,this.dependencies=this.memoizedState=this.updateQueue=this.memoizedProps=null,this.mode=r,this.subtreeFlags=this.flags=0,this.deletions=null,this.childLanes=this.lanes=0,this.alternate=null}function ze(e,t,n,r){return new Cp(e,t,n,r)}function ii(e){return e=e.prototype,!(!e||!e.isReactComponent)}function Pp(e){if(typeof e=="function")return ii(e)?1:0;if(e!=null){if(e=e.$$typeof,e===Eo)return 11;if(e===Co)return 14}return 2}function yt(e,t){var n=e.alternate;return n===null?(n=ze(e.tag,t,e.key,e.mode),n.elementType=e.elementType,n.type=e.type,n.stateNode=e.stateNode,n.alternate=e,e.alternate=n):(n.pendingProps=t,n.type=e.type,n.flags=0,n.subtreeFlags=0,n.deletions=null),n.flags=e.flags&14680064,n.childLanes=e.childLanes,n.lanes=e.lanes,n.child=e.child,n.memoizedProps=e.memoizedProps,n.memoizedState=e.memoizedState,n.updateQueue=e.updateQueue,t=e.dependencies,n.dependencies=t===null?null:{lanes:t.lanes,firstContext:t.firstContext},n.sibling=e.sibling,n.index=e.index,n.ref=e.ref,n}function Zr(e,t,n,r,l,s){var o=2;if(r=e,typeof e=="function")ii(e)&&(o=1);else if(typeof e=="string")o=5;else e:switch(e){case Xt:return Mt(n.children,l,s,t);case _o:o=8,l|=8;break;case Ss:return e=ze(12,n,t,l|2),e.elementType=Ss,e.lanes=s,e;case js:return e=ze(13,n,t,l),e.elementType=js,e.lanes=s,e;case Ns:return e=ze(19,n,t,l),e.elementType=Ns,e.lanes=s,e;case qa:return $l(n,l,s,t);default:if(typeof e=="object"&&e!==null)switch(e.$$typeof){case Ja:o=10;break e;case Za:o=9;break e;case Eo:o=11;break e;case Co:o=14;break e;case st:o=16,r=null;break e}throw Error(_(130,e==null?e:typeof e,""))}return t=ze(o,n,t,l),t.elementType=e,t.type=r,t.lanes=s,t}function Mt(e,t,n,r){return e=ze(7,e,r,t),e.lanes=n,e}function $l(e,t,n,r){return e=ze(22,e,r,t),e.elementType=qa,e.lanes=n,e.stateNode={isHidden:!1},e}function hs(e,t,n){return e=ze(6,e,null,t),e.lanes=n,e}function vs(e,t,n){return t=ze(4,e.children!==null?e.children:[],e.key,t),t.lanes=n,t.stateNode={containerInfo:e.containerInfo,pendingChildren:null,implementation:e.implementation},t}function Lp(e,t,n,r,l){this.tag=t,this.containerInfo=e,this.finishedWork=this.pingCache=this.current=this.pendingChildren=null,this.timeoutHandle=-1,this.callbackNode=this.pendingContext=this.context=null,this.callbackPriority=0,this.eventTimes=Yl(0),this.expirationTimes=Yl(-1),this.entangledLanes=this.finishedLanes=this.mutableReadLanes=this.expiredLanes=this.pingedLanes=this.suspendedLanes=this.pendingLanes=0,this.entanglements=Yl(0),this.identifierPrefix=r,this.onRecoverableError=l,this.mutableSourceEagerHydrationData=null}function ai(e,t,n,r,l,s,o,i,a){return e=new Lp(e,t,n,i,a),t===1?(t=1,s===!0&&(t|=8)):t=0,s=ze(3,null,null,t),e.current=s,s.stateNode=e,s.memoizedState={element:r,isDehydrated:n,cache:null,transitions:null,pendingSuspenseBoundaries:null},bo(s),e}function Rp(e,t,n){var r=3"u"||typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE!="function"))try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(Kc)}catch(e){console.error(e)}}Kc(),Ka.exports=Pe;var Up=Ka.exports,_a=Up;ks.createRoot=_a.createRoot,ks.hydrateRoot=_a.hydrateRoot;/** - * @remix-run/router v1.23.2 - * - * Copyright (c) Remix Software Inc. - * - * This source code is licensed under the MIT license found in the - * LICENSE.md file in the root directory of this source tree. - * - * @license MIT - */function dr(){return dr=Object.assign?Object.assign.bind():function(e){for(var t=1;t"u")throw new Error(t)}function fi(e,t){if(!e){typeof console<"u"&&console.warn(t);try{throw new Error(t)}catch{}}}function $p(){return Math.random().toString(36).substr(2,8)}function Ca(e,t){return{usr:e.state,key:e.key,idx:t}}function mo(e,t,n,r){return n===void 0&&(n=null),dr({pathname:typeof e=="string"?e:e.pathname,search:"",hash:""},typeof t=="string"?Nn(t):t,{state:n,key:t&&t.key||r||$p()})}function Nl(e){let{pathname:t="/",search:n="",hash:r=""}=e;return n&&n!=="?"&&(t+=n.charAt(0)==="?"?n:"?"+n),r&&r!=="#"&&(t+=r.charAt(0)==="#"?r:"#"+r),t}function Nn(e){let t={};if(e){let n=e.indexOf("#");n>=0&&(t.hash=e.substr(n),e=e.substr(0,n));let r=e.indexOf("?");r>=0&&(t.search=e.substr(r),e=e.substr(0,r)),e&&(t.pathname=e)}return t}function Dp(e,t,n,r){r===void 0&&(r={});let{window:l=document.defaultView,v5Compat:s=!1}=r,o=l.history,i=dt.Pop,a=null,c=h();c==null&&(c=0,o.replaceState(dr({},o.state,{idx:c}),""));function h(){return(o.state||{idx:null}).idx}function m(){i=dt.Pop;let C=h(),f=C==null?null:C-c;c=C,a&&a({action:i,location:j.location,delta:f})}function v(C,f){i=dt.Push;let d=mo(j.location,C,f);c=h()+1;let p=Ca(d,c),x=j.createHref(d);try{o.pushState(p,"",x)}catch(E){if(E instanceof DOMException&&E.name==="DataCloneError")throw E;l.location.assign(x)}s&&a&&a({action:i,location:j.location,delta:1})}function g(C,f){i=dt.Replace;let d=mo(j.location,C,f);c=h();let p=Ca(d,c),x=j.createHref(d);o.replaceState(p,"",x),s&&a&&a({action:i,location:j.location,delta:0})}function y(C){let f=l.location.origin!=="null"?l.location.origin:l.location.href,d=typeof C=="string"?C:Nl(C);return d=d.replace(/ $/,"%20"),q(f,"No window.location.(origin|href) available to create URL for href: "+d),new URL(d,f)}let j={get action(){return i},get location(){return e(l,o)},listen(C){if(a)throw new Error("A history only accepts one active listener");return l.addEventListener(Ea,m),a=C,()=>{l.removeEventListener(Ea,m),a=null}},createHref(C){return t(l,C)},createURL:y,encodeLocation(C){let f=y(C);return{pathname:f.pathname,search:f.search,hash:f.hash}},push:v,replace:g,go(C){return o.go(C)}};return j}var Pa;(function(e){e.data="data",e.deferred="deferred",e.redirect="redirect",e.error="error"})(Pa||(Pa={}));function Fp(e,t,n){return n===void 0&&(n="/"),Bp(e,t,n)}function Bp(e,t,n,r){let l=typeof t=="string"?Nn(t):t,s=pi(l.pathname||"/",n);if(s==null)return null;let o=Gc(e);Ap(o);let i=null;for(let a=0;i==null&&a{let a={relativePath:i===void 0?s.path||"":i,caseSensitive:s.caseSensitive===!0,childrenIndex:o,route:s};a.relativePath.startsWith("/")&&(q(a.relativePath.startsWith(r),'Absolute route path "'+a.relativePath+'" nested under path '+('"'+r+'" is not valid. An absolute child route path ')+"must start with the combined path of all its parent routes."),a.relativePath=a.relativePath.slice(r.length));let c=kt([r,a.relativePath]),h=n.concat(a);s.children&&s.children.length>0&&(q(s.index!==!0,"Index routes must not have child routes. Please remove "+('all child routes from route path "'+c+'".')),Gc(s.children,t,h,c)),!(s.path==null&&!s.index)&&t.push({path:c,score:Gp(c,s.index),routesMeta:h})};return e.forEach((s,o)=>{var i;if(s.path===""||!((i=s.path)!=null&&i.includes("?")))l(s,o);else for(let a of Xc(s.path))l(s,o,a)}),t}function Xc(e){let t=e.split("/");if(t.length===0)return[];let[n,...r]=t,l=n.endsWith("?"),s=n.replace(/\?$/,"");if(r.length===0)return l?[s,""]:[s];let o=Xc(r.join("/")),i=[];return i.push(...o.map(a=>a===""?s:[s,a].join("/"))),l&&i.push(...o),i.map(a=>e.startsWith("/")&&a===""?"/":a)}function Ap(e){e.sort((t,n)=>t.score!==n.score?n.score-t.score:Xp(t.routesMeta.map(r=>r.childrenIndex),n.routesMeta.map(r=>r.childrenIndex)))}const Vp=/^:[\w-]+$/,Wp=3,Hp=2,bp=1,Qp=10,Kp=-2,La=e=>e==="*";function Gp(e,t){let n=e.split("/"),r=n.length;return n.some(La)&&(r+=Kp),t&&(r+=Hp),n.filter(l=>!La(l)).reduce((l,s)=>l+(Vp.test(s)?Wp:s===""?bp:Qp),r)}function Xp(e,t){return e.length===t.length&&e.slice(0,-1).every((r,l)=>r===t[l])?e[e.length-1]-t[t.length-1]:0}function Yp(e,t,n){let{routesMeta:r}=e,l={},s="/",o=[];for(let i=0;i{let{paramName:v,isOptional:g}=h;if(v==="*"){let j=i[m]||"";o=s.slice(0,s.length-j.length).replace(/(.)\/+$/,"$1")}const y=i[m];return g&&!y?c[v]=void 0:c[v]=(y||"").replace(/%2F/g,"/"),c},{}),pathname:s,pathnameBase:o,pattern:e}}function Zp(e,t,n){t===void 0&&(t=!1),n===void 0&&(n=!0),fi(e==="*"||!e.endsWith("*")||e.endsWith("/*"),'Route path "'+e+'" will be treated as if it were '+('"'+e.replace(/\*$/,"/*")+'" because the `*` character must ')+"always follow a `/` in the pattern. To get rid of this warning, "+('please change the route path to "'+e.replace(/\*$/,"/*")+'".'));let r=[],l="^"+e.replace(/\/*\*?$/,"").replace(/^\/*/,"/").replace(/[\\.*+^${}|()[\]]/g,"\\$&").replace(/\/:([\w-]+)(\?)?/g,(o,i,a)=>(r.push({paramName:i,isOptional:a!=null}),a?"/?([^\\/]+)?":"/([^\\/]+)"));return e.endsWith("*")?(r.push({paramName:"*"}),l+=e==="*"||e==="/*"?"(.*)$":"(?:\\/(.+)|\\/*)$"):n?l+="\\/*$":e!==""&&e!=="/"&&(l+="(?:(?=\\/|$))"),[new RegExp(l,t?void 0:"i"),r]}function qp(e){try{return e.split("/").map(t=>decodeURIComponent(t).replace(/\//g,"%2F")).join("/")}catch(t){return fi(!1,'The URL path "'+e+'" could not be decoded because it is is a malformed URL segment. This is probably due to a bad percent '+("encoding ("+t+").")),e}}function pi(e,t){if(t==="/")return e;if(!e.toLowerCase().startsWith(t.toLowerCase()))return null;let n=t.endsWith("/")?t.length-1:t.length,r=e.charAt(n);return r&&r!=="/"?null:e.slice(n)||"/"}const em=/^(?:[a-z][a-z0-9+.-]*:|\/\/)/i,tm=e=>em.test(e);function nm(e,t){t===void 0&&(t="/");let{pathname:n,search:r="",hash:l=""}=typeof e=="string"?Nn(e):e,s;if(n)if(tm(n))s=n;else{if(n.includes("//")){let o=n;n=n.replace(/\/\/+/g,"/"),fi(!1,"Pathnames cannot have embedded double slashes - normalizing "+(o+" -> "+n))}n.startsWith("/")?s=Ra(n.substring(1),"/"):s=Ra(n,t)}else s=t;return{pathname:s,search:sm(r),hash:om(l)}}function Ra(e,t){let n=t.replace(/\/+$/,"").split("/");return e.split("/").forEach(l=>{l===".."?n.length>1&&n.pop():l!=="."&&n.push(l)}),n.length>1?n.join("/"):"/"}function gs(e,t,n,r){return"Cannot include a '"+e+"' character in a manually specified "+("`to."+t+"` field ["+JSON.stringify(r)+"]. Please separate it out to the ")+("`to."+n+"` field. Alternatively you may provide the full path as ")+'a string in and the router will parse it for you.'}function rm(e){return e.filter((t,n)=>n===0||t.route.path&&t.route.path.length>0)}function Yc(e,t){let n=rm(e);return t?n.map((r,l)=>l===n.length-1?r.pathname:r.pathnameBase):n.map(r=>r.pathnameBase)}function Jc(e,t,n,r){r===void 0&&(r=!1);let l;typeof e=="string"?l=Nn(e):(l=dr({},e),q(!l.pathname||!l.pathname.includes("?"),gs("?","pathname","search",l)),q(!l.pathname||!l.pathname.includes("#"),gs("#","pathname","hash",l)),q(!l.search||!l.search.includes("#"),gs("#","search","hash",l)));let s=e===""||l.pathname==="",o=s?"/":l.pathname,i;if(o==null)i=n;else{let m=t.length-1;if(!r&&o.startsWith("..")){let v=o.split("/");for(;v[0]==="..";)v.shift(),m-=1;l.pathname=v.join("/")}i=m>=0?t[m]:"/"}let a=nm(l,i),c=o&&o!=="/"&&o.endsWith("/"),h=(s||o===".")&&n.endsWith("/");return!a.pathname.endsWith("/")&&(c||h)&&(a.pathname+="/"),a}const kt=e=>e.join("/").replace(/\/\/+/g,"/"),lm=e=>e.replace(/\/+$/,"").replace(/^\/*/,"/"),sm=e=>!e||e==="?"?"":e.startsWith("?")?e:"?"+e,om=e=>!e||e==="#"?"":e.startsWith("#")?e:"#"+e;function im(e){return e!=null&&typeof e.status=="number"&&typeof e.statusText=="string"&&typeof e.internal=="boolean"&&"data"in e}const Zc=["post","put","patch","delete"];new Set(Zc);const am=["get",...Zc];new Set(am);/** - * React Router v6.30.3 - * - * Copyright (c) Remix Software Inc. - * - * This source code is licensed under the MIT license found in the - * LICENSE.md file in the root directory of this source tree. - * - * @license MIT - */function fr(){return fr=Object.assign?Object.assign.bind():function(e){for(var t=1;t{i.current=!0}),k.useCallback(function(c,h){if(h===void 0&&(h={}),!i.current)return;if(typeof c=="number"){r.go(c);return}let m=Jc(c,JSON.parse(o),s,h.relative==="path");e==null&&t!=="/"&&(m.pathname=m.pathname==="/"?t:kt([t,m.pathname])),(h.replace?r.replace:r.push)(m,h.state,h)},[t,r,o,s,e])}function td(e,t){let{relative:n}=t===void 0?{}:t,{future:r}=k.useContext(bt),{matches:l}=k.useContext(Qt),{pathname:s}=yr(),o=JSON.stringify(Yc(l,r.v7_relativeSplatPath));return k.useMemo(()=>Jc(e,JSON.parse(o),s,n==="path"),[e,o,s,n])}function fm(e,t){return pm(e,t)}function pm(e,t,n,r){xr()||q(!1);let{navigator:l}=k.useContext(bt),{matches:s}=k.useContext(Qt),o=s[s.length-1],i=o?o.params:{};o&&o.pathname;let a=o?o.pathnameBase:"/";o&&o.route;let c=yr(),h;if(t){var m;let C=typeof t=="string"?Nn(t):t;a==="/"||(m=C.pathname)!=null&&m.startsWith(a)||q(!1),h=C}else h=c;let v=h.pathname||"/",g=v;if(a!=="/"){let C=a.replace(/^\//,"").split("/");g="/"+v.replace(/^\//,"").split("/").slice(C.length).join("/")}let y=Fp(e,{pathname:g}),j=xm(y&&y.map(C=>Object.assign({},C,{params:Object.assign({},i,C.params),pathname:kt([a,l.encodeLocation?l.encodeLocation(C.pathname).pathname:C.pathname]),pathnameBase:C.pathnameBase==="/"?a:kt([a,l.encodeLocation?l.encodeLocation(C.pathnameBase).pathname:C.pathnameBase])})),s,n,r);return t&&j?k.createElement(Vl.Provider,{value:{location:fr({pathname:"/",search:"",hash:"",state:null,key:"default"},h),navigationType:dt.Pop}},j):j}function mm(){let e=Sm(),t=im(e)?e.status+" "+e.statusText:e instanceof Error?e.message:JSON.stringify(e),n=e instanceof Error?e.stack:null,l={padding:"0.5rem",backgroundColor:"rgba(200,200,200, 0.5)"};return k.createElement(k.Fragment,null,k.createElement("h2",null,"Unexpected Application Error!"),k.createElement("h3",{style:{fontStyle:"italic"}},t),n?k.createElement("pre",{style:l},n):null,null)}const hm=k.createElement(mm,null);class vm extends k.Component{constructor(t){super(t),this.state={location:t.location,revalidation:t.revalidation,error:t.error}}static getDerivedStateFromError(t){return{error:t}}static getDerivedStateFromProps(t,n){return n.location!==t.location||n.revalidation!=="idle"&&t.revalidation==="idle"?{error:t.error,location:t.location,revalidation:t.revalidation}:{error:t.error!==void 0?t.error:n.error,location:n.location,revalidation:t.revalidation||n.revalidation}}componentDidCatch(t,n){console.error("React Router caught the following error during render",t,n)}render(){return this.state.error!==void 0?k.createElement(Qt.Provider,{value:this.props.routeContext},k.createElement(qc.Provider,{value:this.state.error,children:this.props.component})):this.props.children}}function gm(e){let{routeContext:t,match:n,children:r}=e,l=k.useContext(mi);return l&&l.static&&l.staticContext&&(n.route.errorElement||n.route.ErrorBoundary)&&(l.staticContext._deepestRenderedBoundaryId=n.route.id),k.createElement(Qt.Provider,{value:t},r)}function xm(e,t,n,r){var l;if(t===void 0&&(t=[]),n===void 0&&(n=null),r===void 0&&(r=null),e==null){var s;if(!n)return null;if(n.errors)e=n.matches;else if((s=r)!=null&&s.v7_partialHydration&&t.length===0&&!n.initialized&&n.matches.length>0)e=n.matches;else return null}let o=e,i=(l=n)==null?void 0:l.errors;if(i!=null){let h=o.findIndex(m=>m.route.id&&(i==null?void 0:i[m.route.id])!==void 0);h>=0||q(!1),o=o.slice(0,Math.min(o.length,h+1))}let a=!1,c=-1;if(n&&r&&r.v7_partialHydration)for(let h=0;h=0?o=o.slice(0,c+1):o=[o[0]];break}}}return o.reduceRight((h,m,v)=>{let g,y=!1,j=null,C=null;n&&(g=i&&m.route.id?i[m.route.id]:void 0,j=m.route.errorElement||hm,a&&(c<0&&v===0?(Nm("route-fallback"),y=!0,C=null):c===v&&(y=!0,C=m.route.hydrateFallbackElement||null)));let f=t.concat(o.slice(0,v+1)),d=()=>{let p;return g?p=j:y?p=C:m.route.Component?p=k.createElement(m.route.Component,null):m.route.element?p=m.route.element:p=h,k.createElement(gm,{match:m,routeContext:{outlet:h,matches:f,isDataRoute:n!=null},children:p})};return n&&(m.route.ErrorBoundary||m.route.errorElement||v===0)?k.createElement(vm,{location:n.location,revalidation:n.revalidation,component:j,error:g,children:d(),routeContext:{outlet:null,matches:f,isDataRoute:!0}}):d()},null)}var nd=function(e){return e.UseBlocker="useBlocker",e.UseRevalidator="useRevalidator",e.UseNavigateStable="useNavigate",e}(nd||{}),rd=function(e){return e.UseBlocker="useBlocker",e.UseLoaderData="useLoaderData",e.UseActionData="useActionData",e.UseRouteError="useRouteError",e.UseNavigation="useNavigation",e.UseRouteLoaderData="useRouteLoaderData",e.UseMatches="useMatches",e.UseRevalidator="useRevalidator",e.UseNavigateStable="useNavigate",e.UseRouteId="useRouteId",e}(rd||{});function ym(e){let t=k.useContext(mi);return t||q(!1),t}function km(e){let t=k.useContext(um);return t||q(!1),t}function wm(e){let t=k.useContext(Qt);return t||q(!1),t}function ld(e){let t=wm(),n=t.matches[t.matches.length-1];return n.route.id||q(!1),n.route.id}function Sm(){var e;let t=k.useContext(qc),n=km(),r=ld();return t!==void 0?t:(e=n.errors)==null?void 0:e[r]}function jm(){let{router:e}=ym(nd.UseNavigateStable),t=ld(rd.UseNavigateStable),n=k.useRef(!1);return ed(()=>{n.current=!0}),k.useCallback(function(l,s){s===void 0&&(s={}),n.current&&(typeof l=="number"?e.navigate(l):e.navigate(l,fr({fromRouteId:t},s)))},[e,t])}const Ta={};function Nm(e,t,n){Ta[e]||(Ta[e]=!0)}function _m(e,t){e==null||e.v7_startTransition,e==null||e.v7_relativeSplatPath}function qr(e){q(!1)}function Em(e){let{basename:t="/",children:n=null,location:r,navigationType:l=dt.Pop,navigator:s,static:o=!1,future:i}=e;xr()&&q(!1);let a=t.replace(/^\/*/,"/"),c=k.useMemo(()=>({basename:a,navigator:s,static:o,future:fr({v7_relativeSplatPath:!1},i)}),[a,i,s,o]);typeof r=="string"&&(r=Nn(r));let{pathname:h="/",search:m="",hash:v="",state:g=null,key:y="default"}=r,j=k.useMemo(()=>{let C=pi(h,a);return C==null?null:{location:{pathname:C,search:m,hash:v,state:g,key:y},navigationType:l}},[a,h,m,v,g,y,l]);return j==null?null:k.createElement(bt.Provider,{value:c},k.createElement(Vl.Provider,{children:n,value:j}))}function Cm(e){let{children:t,location:n}=e;return fm(ho(t),n)}new Promise(()=>{});function ho(e,t){t===void 0&&(t=[]);let n=[];return k.Children.forEach(e,(r,l)=>{if(!k.isValidElement(r))return;let s=[...t,l];if(r.type===k.Fragment){n.push.apply(n,ho(r.props.children,s));return}r.type!==qr&&q(!1),!r.props.index||!r.props.children||q(!1);let o={id:r.props.id||s.join("-"),caseSensitive:r.props.caseSensitive,element:r.props.element,Component:r.props.Component,index:r.props.index,path:r.props.path,loader:r.props.loader,action:r.props.action,errorElement:r.props.errorElement,ErrorBoundary:r.props.ErrorBoundary,hasErrorBoundary:r.props.ErrorBoundary!=null||r.props.errorElement!=null,shouldRevalidate:r.props.shouldRevalidate,handle:r.props.handle,lazy:r.props.lazy};r.props.children&&(o.children=ho(r.props.children,s)),n.push(o)}),n}/** - * React Router DOM v6.30.3 - * - * Copyright (c) Remix Software Inc. - * - * This source code is licensed under the MIT license found in the - * LICENSE.md file in the root directory of this source tree. - * - * @license MIT - */function vo(){return vo=Object.assign?Object.assign.bind():function(e){for(var t=1;t=0)&&(n[l]=e[l]);return n}function Lm(e){return!!(e.metaKey||e.altKey||e.ctrlKey||e.shiftKey)}function Rm(e,t){return e.button===0&&(!t||t==="_self")&&!Lm(e)}function go(e){return e===void 0&&(e=""),new URLSearchParams(typeof e=="string"||Array.isArray(e)||e instanceof URLSearchParams?e:Object.keys(e).reduce((t,n)=>{let r=e[n];return t.concat(Array.isArray(r)?r.map(l=>[n,l]):[[n,r]])},[]))}function Tm(e,t){let n=go(e);return t&&t.forEach((r,l)=>{n.has(l)||t.getAll(l).forEach(s=>{n.append(l,s)})}),n}const zm=["onClick","relative","reloadDocument","replace","state","target","to","preventScrollReset","viewTransition"],Om="6";try{window.__reactRouterVersion=Om}catch{}const Im="startTransition",za=jd[Im];function Um(e){let{basename:t,children:n,future:r,window:l}=e,s=k.useRef();s.current==null&&(s.current=Mp({window:l,v5Compat:!0}));let o=s.current,[i,a]=k.useState({action:o.action,location:o.location}),{v7_startTransition:c}=r||{},h=k.useCallback(m=>{c&&za?za(()=>a(m)):a(m)},[a,c]);return k.useLayoutEffect(()=>o.listen(h),[o,h]),k.useEffect(()=>_m(r),[r]),k.createElement(Em,{basename:t,children:n,location:i.location,navigationType:i.action,navigator:o,future:r})}const Mm=typeof window<"u"&&typeof window.document<"u"&&typeof window.document.createElement<"u",$m=/^(?:[a-z][a-z0-9+.-]*:|\/\/)/i,Oa=k.forwardRef(function(t,n){let{onClick:r,relative:l,reloadDocument:s,replace:o,state:i,target:a,to:c,preventScrollReset:h,viewTransition:m}=t,v=Pm(t,zm),{basename:g}=k.useContext(bt),y,j=!1;if(typeof c=="string"&&$m.test(c)&&(y=c,Mm))try{let p=new URL(window.location.href),x=c.startsWith("//")?new URL(p.protocol+c):new URL(c),E=pi(x.pathname,g);x.origin===p.origin&&E!=null?c=E+x.search+x.hash:j=!0}catch{}let C=cm(c,{relative:l}),f=Dm(c,{replace:o,state:i,target:a,preventScrollReset:h,relative:l,viewTransition:m});function d(p){r&&r(p),p.defaultPrevented||f(p)}return k.createElement("a",vo({},v,{href:y||C,onClick:j||s?r:d,ref:n,target:a}))});var Ia;(function(e){e.UseScrollRestoration="useScrollRestoration",e.UseSubmit="useSubmit",e.UseSubmitFetcher="useSubmitFetcher",e.UseFetcher="useFetcher",e.useViewTransitionState="useViewTransitionState"})(Ia||(Ia={}));var Ua;(function(e){e.UseFetcher="useFetcher",e.UseFetchers="useFetchers",e.UseScrollRestoration="useScrollRestoration"})(Ua||(Ua={}));function Dm(e,t){let{target:n,replace:r,state:l,preventScrollReset:s,relative:o,viewTransition:i}=t===void 0?{}:t,a=kr(),c=yr(),h=td(e,{relative:o});return k.useCallback(m=>{if(Rm(m,n)){m.preventDefault();let v=r!==void 0?r:Nl(c)===Nl(h);a(e,{replace:v,state:l,preventScrollReset:s,relative:o,viewTransition:i})}},[c,a,h,r,l,n,e,s,o,i])}function hi(e){let t=k.useRef(go(e)),n=k.useRef(!1),r=yr(),l=k.useMemo(()=>Tm(r.search,n.current?null:t.current),[r.search]),s=kr(),o=k.useCallback((i,a)=>{const c=go(typeof i=="function"?i(l):i);n.current=!0,s("?"+c,a)},[s,l]);return[l,o]}const me="transition active:brightness-95 active:scale-[0.99] focus:outline-none focus-visible:ring-2 focus-visible:ring-slate-400/60 dark:focus-visible:ring-slate-500/50",wr="rounded-xl px-3 py-2 text-sm border bg-white text-slate-900 border-slate-300 placeholder:text-slate-400 focus:outline-none focus:ring-2 focus:ring-slate-300 dark:bg-slate-950/60 dark:text-slate-100 dark:border-slate-700 dark:placeholder:text-slate-500 dark:focus:ring-slate-700",Vt=wr,de="rounded-2xl border bg-white/80 border-slate-200 shadow-sm dark:bg-slate-900/40 dark:border-slate-800",sd=[{value:"plain-lines",label:"Построчно"},{value:"single-line",label:"Одной строкой"},{value:"keenetic-ip",label:"Keenetic (.bat IPv4 маршруты)"},{value:"json",label:"JSON"}],Fm={runetfreedom:"bg-rose-100 text-rose-700 dark:bg-rose-900/40 dark:text-rose-300",loyalsoldier:"bg-violet-100 text-violet-700 dark:bg-violet-900/40 dark:text-violet-300",daniellavrushin:"bg-amber-100 text-amber-700 dark:bg-amber-900/40 dark:text-amber-300",v2fly:"bg-sky-100 text-sky-700 dark:bg-sky-900/40 dark:text-sky-300"},Bm="bg-slate-100 text-slate-600 dark:bg-slate-800 dark:text-slate-400";function Am(){const[e,t]=k.useState(()=>{var r,l;const n=localStorage.getItem("theme");return n==="dark"?!0:n==="light"?!1:((l=(r=window.matchMedia)==null?void 0:r.call(window,"(prefers-color-scheme: dark)"))==null?void 0:l.matches)??!0});return k.useEffect(()=>{document.documentElement.classList.toggle("dark",e),localStorage.setItem("theme",e?"dark":"light")},[e]),u.jsx("button",{onClick:()=>t(n=>!n),className:`text-xs px-3 py-1 rounded-xl border bg-white text-slate-900 border-slate-200 hover:bg-slate-50 dark:bg-slate-800 dark:text-slate-200 dark:border-slate-700 dark:hover:bg-slate-700 ${me}`,type:"button",children:e?"☀ Light":"☾ Dark"})}const Vm=[{id:"popular",label:"Популярные"},{id:"search",label:"Поиск"},{id:"lookup",label:"DNS поиск"},{id:"routing",label:"Для приложений"}];function vi({back:e,tab:t,setTab:n}){return u.jsxs("div",{className:"mb-4",children:[u.jsxs("div",{className:"flex items-center justify-between mb-3",children:[u.jsx("div",{className:"flex items-center gap-3",children:e?u.jsxs(Oa,{to:e.to,className:"text-sm underline text-slate-600 hover:text-slate-900 dark:text-slate-400 dark:hover:text-white",children:["← ",e.label]}):u.jsx("div",{className:"text-xl font-bold tracking-tight",children:"GeoExport"})}),u.jsxs("div",{className:"flex items-center gap-2",children:[!e&&u.jsx(Oa,{to:"/sources",className:"text-xs px-3 py-1.5 rounded-lg hover:bg-slate-200 dark:hover:bg-slate-800 text-slate-600 dark:text-slate-400",children:"Источники"}),u.jsx(Am,{})]})]}),t&&n&&u.jsx("div",{className:"flex gap-1 overflow-x-auto pb-1 -mx-4 px-4 scrollbar-hide",children:Vm.map(r=>u.jsx("button",{onClick:()=>n(r.id),className:`text-xs px-3 py-2 rounded-lg transition-colors whitespace-nowrap shrink-0 ${t===r.id?"bg-slate-900 text-white dark:bg-slate-100 dark:text-slate-900":"bg-slate-100 dark:bg-slate-800 text-slate-600 dark:text-slate-400 hover:bg-slate-200 dark:hover:bg-slate-700"}`,type:"button",children:r.label},r.id))})]})}function kn(e){const[t,n]=e.split(":",2),r=n.replace(/^geoip_|^geosite_/,"");return`${t}:${r}`}function _l({text:e}){const[t,n]=Ne.useState(!1),r=l=>{l.stopPropagation(),navigator.clipboard.writeText(e),n(!0),setTimeout(()=>n(!1),1500)};return u.jsx("button",{onClick:r,title:"Скопировать",className:`text-xs px-2 py-0.5 rounded border border-slate-200 dark:border-slate-700 hover:bg-slate-100 dark:hover:bg-slate-700 font-mono transition-colors ${me}`,type:"button",children:t?"✓":"⎘"})}function Wm({type:e}){return u.jsx("span",{className:`text-xs px-2 py-0.5 rounded-full font-mono shrink-0 ${e==="geoip"?"bg-blue-100 text-blue-700 dark:bg-blue-900/40 dark:text-blue-300":"bg-emerald-100 text-emerald-700 dark:bg-emerald-900/40 dark:text-emerald-300"}`,children:e})}function Wl({slug:e,name:t}){const n=Fm[e]??Bm;return u.jsx("span",{className:`text-xs px-2 py-0.5 rounded-full shrink-0 ${n}`,children:t})}function Hm({domain:e,name:t}){const[n,r]=Ne.useState(!1);return!e||n?u.jsx("div",{className:"w-7 h-7 rounded-lg bg-slate-200 dark:bg-slate-700 flex items-center justify-center text-xs font-bold text-slate-500 dark:text-slate-400 shrink-0",children:t[0]}):u.jsx("img",{src:`https://www.google.com/s2/favicons?domain=${e}&sz=64`,alt:t,className:"w-7 h-7 rounded-lg object-contain bg-white dark:bg-slate-800 p-0.5 shrink-0",onError:()=>r(!0)})}function bm({svc:e,sources:t,sourceFilter:n}){const[r,l]=Ne.useState(!1),[s,o]=Ne.useState(""),[i,a]=Ne.useState(!1),[c,h]=Ne.useState("plain-lines"),[m,v]=Ne.useState("domains,ip4,ip6"),[g,y]=Ne.useState(!1),j=kr(),C=Ne.useMemo(()=>{const w=n==="all"?e.categories:e.categories.filter(S=>S.source_slug===n);return w.length>0?w:e.categories},[e.categories,n]),f=C.length>0,d=[...new Set(C.map(w=>w.source_slug))],p=async()=>{if(f){a(!0);try{const w=new URLSearchParams;C.forEach(z=>w.append("category",z.full_name)),w.set("type",m),w.set("format",c==="single-line"?"plain-lines":c),n!=="all"&&w.set("source",n);let P=await(await fetch(`/api/export?${w}`)).text();c==="single-line"&&(P=P.trim().split(/\r?\n/).join(",")),o(P)}finally{a(!1)}}};k.useEffect(()=>{r&&p()},[r,c,m,n]);const x=()=>{const w=new Blob([s],{type:"text/plain;charset=utf-8"}),S=URL.createObjectURL(w),P=document.createElement("a");P.href=S,P.download=`${e.name.replace(/[^a-z0-9]/gi,"_").toLowerCase()}.txt`,P.click(),URL.revokeObjectURL(S)},E=()=>{navigator.clipboard.writeText(s),y(!0),setTimeout(()=>y(!1),1500)};return u.jsxs("div",{className:`${de} overflow-hidden`,children:[u.jsxs("button",{onClick:()=>f&&l(w=>!w),disabled:!f,className:`w-full flex items-center gap-3 px-4 py-3 text-left ${f?"hover:bg-white dark:hover:bg-slate-900/70 "+me:"opacity-40 cursor-not-allowed"}`,type:"button",children:[u.jsx(Hm,{domain:e.domain,name:e.name}),u.jsxs("div",{className:"flex-1 min-w-0",children:[u.jsx("div",{className:"text-sm font-medium text-slate-900 dark:text-slate-100",children:e.name}),f?u.jsxs("div",{className:"flex items-center gap-1 flex-wrap mt-0.5",children:[u.jsxs("span",{className:"text-xs text-slate-400",children:[C.length," ",C.length===1?"категория":C.length<5?"категории":"категорий"]}),d.map(w=>{var P;const S=t.find(z=>z.slug===w);return u.jsx(Wl,{slug:w,name:((P=S==null?void 0:S.name)==null?void 0:P.split(" ")[0])??w},w)})]}):u.jsx("div",{className:"text-xs text-slate-400",children:"Нет в базах данных"})]}),f&&u.jsx("svg",{className:`w-4 h-4 text-slate-400 transition-transform shrink-0 ${r?"rotate-180":""}`,viewBox:"0 0 20 20",fill:"currentColor",children:u.jsx("path",{fillRule:"evenodd",d:"M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z",clipRule:"evenodd"})})]}),r&&u.jsxs("div",{className:"border-t border-slate-100 dark:border-slate-800 px-4 py-3 space-y-3",children:[u.jsx("div",{className:"flex flex-wrap gap-1",children:C.map(w=>u.jsxs("span",{className:`inline-flex items-center gap-1 text-xs px-2 py-0.5 rounded-full font-mono ${w.type==="geoip"?"bg-blue-100 text-blue-700 dark:bg-blue-900/40 dark:text-blue-300":"bg-emerald-100 text-emerald-700 dark:bg-emerald-900/40 dark:text-emerald-300"}`,children:[kn(w.full_name),u.jsx(_l,{text:kn(w.full_name)})]},w.full_name+w.source_slug))}),u.jsxs("div",{className:"flex gap-2 flex-wrap",children:[u.jsxs("select",{value:m,onChange:w=>v(w.target.value),className:`text-xs ${Vt}`,children:[u.jsx("option",{value:"domains,ip4,ip6",children:"Домены + IPv4 + IPv6"}),u.jsx("option",{value:"domains,ip4",children:"Домены + IPv4"}),u.jsx("option",{value:"ip4,ip6",children:"IPv4 + IPv6"}),u.jsx("option",{value:"domains",children:"Только домены"}),u.jsx("option",{value:"ip4",children:"Только IPv4"}),u.jsx("option",{value:"ip6",children:"Только IPv6"})]}),u.jsx("select",{value:c,onChange:w=>h(w.target.value),className:`text-xs ${Vt}`,children:sd.map(w=>u.jsx("option",{value:w.value,children:w.label},w.value))})]}),u.jsx("textarea",{readOnly:!0,value:i?"Загрузка…":s,className:"w-full h-28 rounded-xl px-3 py-2 text-xs border bg-white text-slate-900 border-slate-200 dark:bg-slate-950/60 dark:text-slate-100 dark:border-slate-700 font-mono resize-none"}),u.jsxs("div",{className:"flex gap-2 flex-wrap",children:[u.jsx("button",{onClick:x,className:`px-3 py-1.5 text-xs rounded-xl bg-slate-900 text-white hover:bg-slate-800 dark:bg-slate-100 dark:text-slate-900 dark:hover:bg-white ${me}`,children:"↓ Скачать"}),u.jsx("button",{onClick:E,className:`px-3 py-1.5 text-xs rounded-xl border border-slate-300 dark:border-slate-700 hover:bg-slate-50 dark:hover:bg-slate-800 ${me}`,children:g?"✓ Скопировано":"Скопировать"}),u.jsx("button",{onClick:()=>{const w=new URLSearchParams;C.forEach(S=>w.append("category",S.full_name)),w.set("type",m),w.set("format",c),n!=="all"&&w.set("source",n),j(`/export?${w}`)},className:`px-3 py-1.5 text-xs rounded-xl border border-slate-300 dark:border-slate-700 hover:bg-slate-50 dark:hover:bg-slate-800 ${me}`,children:"Открыть экспорт →"})]})]})]})}function Qm({sources:e}){const[t,n]=k.useState([]),[r,l]=k.useState(!0),[s,o]=k.useState("all"),[i,a]=k.useState(new Set(["messengers"]));k.useEffect(()=>{fetch("/api/presets").then(h=>h.json()).then(n).finally(()=>l(!1))},[]);const c=h=>{a(m=>{const v=new Set(m);return v.has(h)?v.delete(h):v.add(h),v})};return r?u.jsx("div",{className:"py-12 text-center text-sm text-slate-400",children:"Загрузка…"}):u.jsxs("div",{className:"space-y-2",children:[e.length>0&&u.jsx("div",{className:`${de} p-3 mb-4`,children:u.jsxs("div",{className:"flex items-center gap-2 flex-wrap",children:[u.jsx("span",{className:"text-xs text-slate-500",children:"Источник:"}),u.jsx("button",{onClick:()=>o("all"),className:`text-xs px-2.5 py-1 rounded-full border transition-colors ${s==="all"?"bg-slate-900 text-white dark:bg-slate-100 dark:text-slate-900 border-transparent":"border-slate-300 dark:border-slate-600 hover:bg-slate-100 dark:hover:bg-slate-800"}`,type:"button",children:"Все"}),e.map(h=>u.jsx("button",{onClick:()=>o(h.slug),className:`text-xs px-2.5 py-1 rounded-full border transition-colors ${s===h.slug?"bg-slate-900 text-white dark:bg-slate-100 dark:text-slate-900 border-transparent":"border-slate-300 dark:border-slate-600 hover:bg-slate-100 dark:hover:bg-slate-800"}`,type:"button",children:h.name},h.slug))]})}),t.map(h=>u.jsxs("div",{className:de,children:[u.jsxs("button",{onClick:()=>c(h.slug),className:`w-full flex items-center justify-between px-4 py-3 ${me}`,type:"button",children:[u.jsxs("div",{className:"flex items-center gap-2",children:[u.jsx("span",{className:"text-sm font-semibold text-slate-900 dark:text-slate-100",children:h.group}),u.jsxs("span",{className:"text-xs text-slate-400",children:[h.services.length," сервисов"]})]}),u.jsx("svg",{className:`w-4 h-4 text-slate-400 transition-transform ${i.has(h.slug)?"rotate-180":""}`,viewBox:"0 0 20 20",fill:"currentColor",children:u.jsx("path",{fillRule:"evenodd",d:"M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z",clipRule:"evenodd"})})]}),i.has(h.slug)&&u.jsx("div",{className:"border-t border-slate-100 dark:border-slate-800 p-3 space-y-1.5",children:h.services.map(m=>u.jsx(bm,{svc:m,sources:e,sourceFilter:s},m.name))})]},h.slug))]})}function Km({sources:e,lastUpdate:t,updateRunning:n}){const[r,l]=hi(),[s,o]=k.useState(()=>r.get("q")||""),[i,a]=k.useState(()=>r.get("source")||"all"),[c,h]=k.useState([]),[m,v]=k.useState(!1),g=kr();k.useEffect(()=>{const f=r.get("q");if(f&&f.length>=2){const d=r.get("source")||"all";v(!0);const p=new URLSearchParams({q:f,cat_type:"all",limit:"100"});d!=="all"&&p.set("source",d),fetch(`/api/search?${p}`).then(x=>x.json()).then(h).finally(()=>v(!1))}},[]),k.useEffect(()=>{if(s.trim().length<2){s.trim().length===0&&h([]);return}const f=setTimeout(()=>y(s,i),350);return()=>clearTimeout(f)},[s]);const y=async(f,d)=>{const p=f??s,x=d??i;if(p.trim().length<2)return;v(!0);const E=new URLSearchParams;E.set("q",p),E.set("tab","search"),x!=="all"&&E.set("source",x),l(E,{replace:!0});try{const w=new URLSearchParams({q:p,cat_type:"all",limit:"100"});x!=="all"&&w.set("source",x);const S=await fetch(`/api/search?${w}`);h(await S.json())}finally{v(!1)}},j=f=>{var T;const d=f.full_name,p=f.source_slug,[x,E]=d.split(":",2),w=E.replace(/^geoip_|^geosite_/,""),S=x==="geoip"?"geosite":"geoip",P=((T=c.find($=>$.source_slug===p&&(($==null?void 0:$.full_name)===`${S}:${S}_${w}`||($==null?void 0:$.full_name)===`${S}:${w}`))||c.find($=>($==null?void 0:$.full_name)===`${S}:${S}_${w}`||($==null?void 0:$.full_name)===`${S}:${w}`))==null?void 0:T.full_name)??null,z=new URLSearchParams;z.append("category",d),P&&z.append("category",P),p&&z.set("source",p),g(`/export?${z}`)},C=c.reduce((f,d)=>{const p=d.source_slug;return f[p]||(f[p]=[]),f[p].push(d),f},{});return u.jsxs("div",{children:[u.jsxs("div",{className:`${de} p-4 mb-4`,children:[u.jsx("div",{className:"text-sm mb-3 text-slate-600 dark:text-slate-400",children:"Поиск по категориям (geosite / geoip) из всех источников"}),u.jsxs("div",{className:"relative",children:[u.jsx("input",{value:s,onChange:f=>o(f.target.value),className:`w-full ${wr} pr-10`,placeholder:"Категория (google, ru), домен (whatsapp.com) или слово (whatsapp)…",autoFocus:!0}),m&&u.jsx("div",{className:"absolute right-3 top-1/2 -translate-y-1/2",children:u.jsx("svg",{className:"animate-spin w-4 h-4 text-slate-400",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",children:u.jsx("path",{d:"M21 12a9 9 0 1 1-6.219-8.56"})})}),!m&&s&&u.jsx("button",{onClick:()=>{o(""),h([]),l({tab:"search"},{replace:!0})},className:"absolute right-3 top-1/2 -translate-y-1/2 text-slate-400 hover:text-slate-600 dark:hover:text-slate-200",type:"button",children:"✕"})]}),e.length>0&&u.jsxs("div",{className:"flex items-center gap-2 mt-3 flex-wrap",children:[u.jsx("span",{className:"text-xs text-slate-500",children:"Источник:"}),u.jsx("button",{onClick:()=>{a("all"),c.length>0&&y(s,"all")},className:`text-xs px-2.5 py-1 rounded-full border transition-colors ${i==="all"?"bg-slate-900 text-white dark:bg-slate-100 dark:text-slate-900 border-transparent":"border-slate-300 dark:border-slate-600 hover:bg-slate-100 dark:hover:bg-slate-800"}`,type:"button",children:"Все"}),e.map(f=>u.jsx("button",{onClick:()=>{a(f.slug),c.length>0&&y(s,f.slug)},className:`text-xs px-2.5 py-1 rounded-full border transition-colors ${i===f.slug?"bg-slate-900 text-white dark:bg-slate-100 dark:text-slate-900 border-transparent":"border-slate-300 dark:border-slate-600 hover:bg-slate-100 dark:hover:bg-slate-800"}`,type:"button",children:f.name},f.slug))]}),u.jsx("div",{className:"flex items-center gap-2 mt-3 pt-3 border-t border-slate-100 dark:border-slate-800",children:n?u.jsxs("div",{className:"flex items-center gap-2 text-xs text-amber-600 dark:text-amber-400",children:[u.jsx("svg",{className:"animate-spin w-3 h-3",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",children:u.jsx("path",{d:"M21 12a9 9 0 1 1-6.219-8.56"})}),"База обновляется, подождите…"]}):u.jsxs("div",{className:"text-xs text-slate-500",children:["База обновлена: ",u.jsx("span",{className:"text-slate-700 dark:text-slate-300 font-medium",children:t??"нет данных"})]})})]}),Object.keys(C).length>0&&u.jsx("div",{className:"space-y-4",children:Object.entries(C).map(([f,d])=>{const p=e.find(x=>x.slug===f);return u.jsxs("div",{children:[u.jsxs("div",{className:"flex items-center gap-2 mb-2 px-1",children:[u.jsx(Wl,{slug:f,name:(p==null?void 0:p.name)??f}),u.jsx("span",{className:"text-xs text-slate-400",children:p==null?void 0:p.compatible})]}),u.jsx("div",{className:"space-y-1.5",children:d.map(x=>{var E;return u.jsxs("button",{onClick:()=>j(x),className:`w-full text-left ${de} hover:bg-white dark:hover:bg-slate-900/70 px-4 py-3 ${me}`,type:"button",children:[u.jsxs("div",{className:"flex items-center justify-between gap-2",children:[u.jsxs("div",{className:"flex items-center gap-2 min-w-0",children:[u.jsx("span",{className:"text-sm font-medium font-mono truncate",children:kn(x.full_name)}),u.jsx(_l,{text:kn(x.full_name)}),x.match_type==="content"&&u.jsx("span",{className:"text-xs px-1.5 py-0.5 rounded bg-orange-100 text-orange-600 dark:bg-orange-900/40 dark:text-orange-400 shrink-0",children:"по доменам"}),x.match_type==="domain"&&u.jsx("span",{className:"text-xs px-1.5 py-0.5 rounded bg-teal-100 text-teal-600 dark:bg-teal-900/40 dark:text-teal-400 shrink-0",children:"домен найден"})]}),u.jsx(Wm,{type:x.type})]}),x.region&&u.jsxs("div",{className:"text-xs text-slate-500 mt-1",children:["Регион: ",x.region]}),((E=x.matched_values)==null?void 0:E.length)>0&&u.jsx("div",{className:"flex flex-wrap gap-1 mt-2",children:x.matched_values.map(w=>u.jsx("span",{className:"text-xs font-mono px-2 py-0.5 rounded bg-slate-100 dark:bg-slate-800 text-slate-600 dark:text-slate-400",children:w},w))})]},x.full_name+x.source_slug)})})]},f)})}),c.length===0&&s&&!m&&u.jsx("div",{className:"mt-8 text-center text-sm text-slate-400",children:"Ничего не найдено"})]})}const Gm={runetfreedom:"bg-rose-100 text-rose-700 dark:bg-rose-900/40 dark:text-rose-300",loyalsoldier:"bg-violet-100 text-violet-700 dark:bg-violet-900/40 dark:text-violet-300",daniellavrushin:"bg-amber-100 text-amber-700 dark:bg-amber-900/40 dark:text-amber-300",v2fly:"bg-sky-100 text-sky-700 dark:bg-sky-900/40 dark:text-sky-300"};function xs({matches:e}){if(!e||e.length===0)return u.jsx("span",{className:"text-xs text-slate-400",children:"—"});const t=e.filter((n,r,l)=>l.findIndex(s=>s.full_name===n.full_name&&s.source_slug===n.source_slug)===r);return u.jsxs("div",{className:"flex flex-wrap gap-1",children:[t.slice(0,3).map(n=>u.jsx("span",{className:`text-xs px-1.5 py-0.5 rounded-full font-mono ${Gm[n.source_slug]??"bg-slate-100 text-slate-600 dark:bg-slate-800 dark:text-slate-400"}`,children:n.full_name.replace(/^geoip_|^geosite_/,"").split(":").map(r=>r.replace(/^geoip_|^geosite_/,"")).join(":")},n.full_name+n.source_slug)),t.length>3&&u.jsxs("span",{className:"text-xs text-slate-400",children:["+",t.length-3]})]})}function On({item:e,checked:t,onChange:n}){return u.jsxs("label",{className:`flex items-start gap-3 px-3 py-3 sm:py-2.5 rounded-xl border cursor-pointer transition-colors ${t?"bg-slate-50 border-slate-300 dark:bg-slate-800/60 dark:border-slate-600":"border-slate-200 dark:border-slate-800 hover:bg-slate-50 dark:hover:bg-slate-800/30"}`,children:[u.jsx("input",{type:"checkbox",checked:t,onChange:r=>n(e.id,r.target.checked),className:"mt-0.5 shrink-0 accent-slate-900 dark:accent-slate-100 w-4 h-4"}),u.jsxs("div",{className:"min-w-0",children:[u.jsx("div",{className:"text-sm font-medium text-slate-900 dark:text-slate-100",children:e.label}),u.jsx("div",{className:"text-xs text-slate-500 dark:text-slate-400 mt-0.5",children:e.description}),u.jsx("code",{className:"text-xs font-mono text-slate-400 dark:text-slate-500",children:e.value})]})]})}function ys({title:e,description:t,children:n}){return u.jsxs("div",{className:`${de} overflow-hidden`,children:[u.jsxs("div",{className:"px-4 py-3 border-b border-slate-100 dark:border-slate-800",children:[u.jsx("div",{className:"font-semibold text-slate-900 dark:text-slate-100",children:e}),u.jsx("div",{className:"text-xs text-slate-500 dark:text-slate-400 mt-0.5",children:t})]}),u.jsx("div",{className:"p-3 space-y-1.5",children:n})]})}function In({onAdd:e}){const[t,n]=k.useState(""),[r,l]=k.useState([]),[s,o]=k.useState(!1),[i,a]=k.useState(!1),c=Ne.useRef(null),h=Ne.useRef(null),m=g=>{if(g.trim().length<2){l([]),o(!1);return}clearTimeout(c.current),c.current=setTimeout(async()=>{a(!0);try{const y=new URLSearchParams({q:g.trim(),cat_type:"all",limit:"20"}),f=(await(await fetch(`/api/search?${y}`)).json()).map(d=>d.full_name);l(f),o(f.length>0)}finally{a(!1)}},350)},v=g=>{g.trim()&&(e(g.trim()),n(""),l([]),o(!1))};return k.useEffect(()=>{const g=y=>{h.current&&!h.current.contains(y.target)&&o(!1)};return document.addEventListener("mousedown",g),()=>document.removeEventListener("mousedown",g)},[]),u.jsxs("div",{ref:h,className:"relative mt-1.5",children:[u.jsxs("div",{className:"flex gap-2",children:[u.jsxs("div",{className:"relative flex-1",children:[u.jsx("input",{value:t,onChange:g=>{n(g.target.value),m(g.target.value)},onKeyDown:g=>{g.key==="Enter"&&v(t),g.key==="Escape"&&o(!1)},onFocus:g=>{r.length>0&&o(!0),setTimeout(()=>g.target.scrollIntoView({behavior:"smooth",block:"center"}),100)},placeholder:"Своя категория (напр. geosite:google)",className:`${wr} w-full text-xs pr-7`}),i&&u.jsx("span",{className:"absolute right-2.5 top-1/2 -translate-y-1/2 text-slate-400 text-xs",children:"…"})]}),u.jsx("button",{onClick:()=>v(t),className:"px-3 py-1.5 text-xs rounded-xl border border-slate-300 dark:border-slate-700 hover:bg-slate-50 dark:hover:bg-slate-800 text-slate-600 dark:text-slate-300 shrink-0",children:"+ Добавить"})]}),s&&r.length>0&&u.jsx("div",{className:"absolute z-50 left-0 right-12 mt-1 rounded-xl border border-slate-200 dark:border-slate-700 bg-white dark:bg-slate-900 shadow-lg overflow-hidden max-h-52 overflow-y-auto",children:r.map(g=>u.jsx("button",{onMouseDown:y=>{y.preventDefault(),v(g)},className:"w-full text-left px-3 py-2 text-xs font-mono hover:bg-slate-50 dark:hover:bg-slate-800 text-slate-700 dark:text-slate-300 border-b border-slate-100 dark:border-slate-800 last:border-0",children:g},g))})]})}function Xm(){const[e,t]=k.useState(null),[n,r]=k.useState(!0),[l,s]=k.useState(!1),[o,i]=k.useState(null),[a,c]=k.useState(!1),[h,m]=k.useState("GeoExport"),[v,g]=k.useState("loyalsoldier"),[y,j]=k.useState("block-direct-proxy"),[C,f]=k.useState(!1),[d,p]=k.useState({}),[x,E]=k.useState({direct_sites:[],direct_ips:[],proxy_sites:[],proxy_ips:[],block_sites:[],block_ips:[]}),[w,S]=k.useState({direct_sites:{},direct_ips:{},proxy_sites:{},proxy_ips:{},block_sites:{},block_ips:{}});k.useEffect(()=>{fetch("/api/routing/presets").then(N=>N.json()).then(N=>{t(N);const U={};for(const F of Object.values(N.presets))for(const V of F)U[V.id]=V.checked;p(U)}).finally(()=>r(!1))},[]);const P=(N,U)=>p(F=>({...F,[N]:U})),z=(N,U)=>{E(F=>({...F,[N]:[...F[N]||[],U]})),S(F=>({...F,[N]:{...F[N]||{},[U]:!0}}))},T=(N,U,F)=>{S(V=>({...V,[N]:{...V[N]||{},[U]:F}}))},$=N=>{if(!e)return[];const U=e.presets[N].filter(V=>d[V.id]).map(V=>V.value),F=(x[N]||[]).filter(V=>{var L;return(L=w[N])==null?void 0:L[V]});return[...U,...F]},Et=async()=>{s(!0),i(null);try{const U=await(await fetch("/api/routing/generate",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:h,geo_source:v,global_proxy:C,route_order:y,direct_sites:$("direct_sites"),direct_ips:$("direct_ips"),proxy_sites:$("proxy_sites"),proxy_ips:$("proxy_ips"),block_sites:$("block_sites"),block_ips:$("block_ips")})})).json();i(U)}finally{s(!1)}},Ct=N=>{navigator.clipboard.writeText(N),c(!0),setTimeout(()=>c(!1),1500)};return n?u.jsx("div",{className:"py-12 text-center text-sm text-slate-400",children:"Загрузка…"}):e?u.jsxs("div",{className:"space-y-4",children:[u.jsxs("div",{className:`${de} p-4`,children:[u.jsx("h2",{className:"text-base font-semibold mb-0.5",children:"Маршрутизация для приложений"}),u.jsxs("p",{className:"text-xs text-slate-500 dark:text-slate-400",children:["Настройте правила и получите deeplink для ",u.jsx("strong",{children:"Happ"})," (iOS / Android / macOS / Windows). Приложение автоматически применит выбранные правила."]})]}),u.jsxs("div",{className:`${de} p-4`,children:[u.jsx("div",{className:"text-xs font-semibold text-slate-500 uppercase tracking-wide mb-3",children:"Настройки профиля"}),u.jsxs("div",{className:"grid grid-cols-1 gap-3",children:[u.jsxs("div",{children:[u.jsx("label",{className:"text-xs text-slate-500 mb-1 block",children:"Название профиля"}),u.jsx("input",{value:h,onChange:N=>m(N.target.value),className:wr,placeholder:"GeoExport"})]}),u.jsxs("div",{children:[u.jsx("label",{className:"text-xs text-slate-500 mb-1 block",children:"База geoip/geosite"}),u.jsx("select",{value:v,onChange:N=>g(N.target.value),className:Vt,children:Object.entries(e.geo_sources).map(([N,U])=>u.jsx("option",{value:N,children:U.label},N))})]}),u.jsxs("div",{children:[u.jsx("label",{className:"text-xs text-slate-500 mb-1 block",children:"Порядок правил"}),u.jsx("select",{value:y,onChange:N=>j(N.target.value),className:Vt,children:e.route_orders.map(N=>u.jsx("option",{value:N.value,children:N.label},N.value))})]}),u.jsxs("div",{className:"flex items-center gap-3 pt-5",children:[u.jsxs("label",{className:"flex items-center gap-2 cursor-pointer",children:[u.jsx("input",{type:"checkbox",checked:C,onChange:N=>f(N.target.checked),className:"accent-slate-900 dark:accent-slate-100"}),u.jsx("span",{className:"text-sm text-slate-700 dark:text-slate-300",children:"Глобальный прокси"})]}),u.jsx("span",{className:"text-xs text-slate-400",children:"Весь трафик через VPN"})]})]})]}),u.jsxs(ys,{title:"Блокировать",description:"Домены и IP блокируются полностью — ни через VPN, ни напрямую. Для блокировки рекламы и трекеров.",children:[e.presets.block_sites.map(N=>u.jsx(On,{item:N,checked:!!d[N.id],onChange:P},N.id)),(x.block_sites||[]).map(N=>{var U,F;return u.jsxs("label",{className:`flex items-start gap-3 px-3 py-2.5 rounded-xl border cursor-pointer transition-colors ${(U=w.block_sites)!=null&&U[N]?"bg-slate-50 border-slate-300 dark:bg-slate-800/60 dark:border-slate-600":"border-slate-200 dark:border-slate-800 hover:bg-slate-50 dark:hover:bg-slate-800/30"}`,children:[u.jsx("input",{type:"checkbox",checked:!!((F=w.block_sites)!=null&&F[N]),onChange:V=>T("block_sites",N,V.target.checked),className:"mt-0.5 shrink-0 accent-slate-900 dark:accent-slate-100 w-4 h-4"}),u.jsxs("div",{className:"min-w-0",children:[u.jsx("div",{className:"text-sm font-medium text-slate-900 dark:text-slate-100",children:"Своя категория"}),u.jsx("code",{className:"text-xs font-mono text-slate-400 dark:text-slate-500",children:N})]})]},N)}),u.jsx(In,{onAdd:N=>z("block_sites",N)})]}),u.jsxs(ys,{title:"Прямой трафик (без VPN)",description:"Сайты и IP открываются напрямую, минуя VPN. Добавьте российские сервисы — это ускорит соединение и снизит нагрузку на сервер.",children:[u.jsx("div",{className:"text-xs font-medium text-slate-500 mb-1",children:"Домены"}),e.presets.direct_sites.map(N=>u.jsx(On,{item:N,checked:!!d[N.id],onChange:P},N.id)),(x.direct_sites||[]).map(N=>{var U,F;return u.jsxs("label",{className:`flex items-start gap-3 px-3 py-2.5 rounded-xl border cursor-pointer transition-colors ${(U=w.direct_sites)!=null&&U[N]?"bg-slate-50 border-slate-300 dark:bg-slate-800/60 dark:border-slate-600":"border-slate-200 dark:border-slate-800 hover:bg-slate-50 dark:hover:bg-slate-800/30"}`,children:[u.jsx("input",{type:"checkbox",checked:!!((F=w.direct_sites)!=null&&F[N]),onChange:V=>T("direct_sites",N,V.target.checked),className:"mt-0.5 shrink-0 accent-slate-900 dark:accent-slate-100 w-4 h-4"}),u.jsxs("div",{className:"min-w-0",children:[u.jsx("div",{className:"text-sm font-medium text-slate-900 dark:text-slate-100",children:"Своя категория"}),u.jsx("code",{className:"text-xs font-mono text-slate-400 dark:text-slate-500",children:N})]})]},N)}),u.jsx(In,{onAdd:N=>z("direct_sites",N)}),u.jsx("div",{className:"text-xs font-medium text-slate-500 mt-3 mb-1",children:"IP-адреса"}),e.presets.direct_ips.map(N=>u.jsx(On,{item:N,checked:!!d[N.id],onChange:P},N.id)),(x.direct_ips||[]).map(N=>{var U,F;return u.jsxs("label",{className:`flex items-start gap-3 px-3 py-2.5 rounded-xl border cursor-pointer transition-colors ${(U=w.direct_ips)!=null&&U[N]?"bg-slate-50 border-slate-300 dark:bg-slate-800/60 dark:border-slate-600":"border-slate-200 dark:border-slate-800 hover:bg-slate-50 dark:hover:bg-slate-800/30"}`,children:[u.jsx("input",{type:"checkbox",checked:!!((F=w.direct_ips)!=null&&F[N]),onChange:V=>T("direct_ips",N,V.target.checked),className:"mt-0.5 shrink-0 accent-slate-900 dark:accent-slate-100 w-4 h-4"}),u.jsxs("div",{className:"min-w-0",children:[u.jsx("div",{className:"text-sm font-medium text-slate-900 dark:text-slate-100",children:"Своя категория"}),u.jsx("code",{className:"text-xs font-mono text-slate-400 dark:text-slate-500",children:N})]})]},N)}),u.jsx(In,{onAdd:N=>z("direct_ips",N)})]}),u.jsxs(ys,{title:"Через VPN",description:"Сайты и IP направляются через VPN-сервер. Добавьте заблокированные в РФ сервисы.",children:[u.jsx("div",{className:"text-xs font-medium text-slate-500 mb-1",children:"Домены"}),e.presets.proxy_sites.map(N=>u.jsx(On,{item:N,checked:!!d[N.id],onChange:P},N.id)),(x.proxy_sites||[]).map(N=>{var U,F;return u.jsxs("label",{className:`flex items-start gap-3 px-3 py-2.5 rounded-xl border cursor-pointer transition-colors ${(U=w.proxy_sites)!=null&&U[N]?"bg-slate-50 border-slate-300 dark:bg-slate-800/60 dark:border-slate-600":"border-slate-200 dark:border-slate-800 hover:bg-slate-50 dark:hover:bg-slate-800/30"}`,children:[u.jsx("input",{type:"checkbox",checked:!!((F=w.proxy_sites)!=null&&F[N]),onChange:V=>T("proxy_sites",N,V.target.checked),className:"mt-0.5 shrink-0 accent-slate-900 dark:accent-slate-100 w-4 h-4"}),u.jsxs("div",{className:"min-w-0",children:[u.jsx("div",{className:"text-sm font-medium text-slate-900 dark:text-slate-100",children:"Своя категория"}),u.jsx("code",{className:"text-xs font-mono text-slate-400 dark:text-slate-500",children:N})]})]},N)}),u.jsx(In,{onAdd:N=>z("proxy_sites",N)}),u.jsx("div",{className:"text-xs font-medium text-slate-500 mt-3 mb-1",children:"IP-адреса"}),e.presets.proxy_ips.map(N=>u.jsx(On,{item:N,checked:!!d[N.id],onChange:P},N.id)),(x.proxy_ips||[]).map(N=>{var U,F;return u.jsxs("label",{className:`flex items-start gap-3 px-3 py-2.5 rounded-xl border cursor-pointer transition-colors ${(U=w.proxy_ips)!=null&&U[N]?"bg-slate-50 border-slate-300 dark:bg-slate-800/60 dark:border-slate-600":"border-slate-200 dark:border-slate-800 hover:bg-slate-50 dark:hover:bg-slate-800/30"}`,children:[u.jsx("input",{type:"checkbox",checked:!!((F=w.proxy_ips)!=null&&F[N]),onChange:V=>T("proxy_ips",N,V.target.checked),className:"mt-0.5 shrink-0 accent-slate-900 dark:accent-slate-100 w-4 h-4"}),u.jsxs("div",{className:"min-w-0",children:[u.jsx("div",{className:"text-sm font-medium text-slate-900 dark:text-slate-100",children:"Своя категория"}),u.jsx("code",{className:"text-xs font-mono text-slate-400 dark:text-slate-500",children:N})]})]},N)}),u.jsx(In,{onAdd:N=>z("proxy_ips",N)})]}),u.jsx("div",{className:"flex justify-center px-0",children:u.jsx("button",{onClick:Et,disabled:l,className:`w-full sm:w-auto px-8 py-3 text-sm font-medium rounded-xl bg-slate-900 text-white hover:bg-slate-800 dark:bg-slate-100 dark:text-slate-900 dark:hover:bg-white disabled:opacity-50 ${me}`,children:l?"Генерация…":"Сгенерировать и получить ссылку"})}),o&&u.jsxs("div",{className:`${de} p-4 pb-5`,style:{borderColor:"rgb(167 243 208)",borderWidth:"2px"},children:[u.jsx("div",{className:"text-sm font-semibold text-emerald-700 dark:text-emerald-300 mb-3",children:"Профиль сгенерирован"}),Object.entries(o.instructions).map(([N,U])=>u.jsxs("div",{className:"space-y-2",children:[u.jsxs("div",{className:"text-xs text-slate-500",children:[U.name," (",U.platforms.join(", "),")"]}),u.jsxs("div",{className:"text-xs text-slate-500",children:[u.jsx("span",{className:"font-medium",children:"Инструкция:"})," ",U.steps.join(" → ")]}),u.jsx("div",{className:"mt-3 pt-2 border-t border-slate-100 dark:border-slate-800",children:u.jsxs("div",{className:"text-xs font-mono text-slate-400 break-all mb-3",children:[o.deeplink.slice(0,100),"…"]})}),u.jsxs("div",{className:"flex flex-col sm:flex-row items-stretch sm:items-center gap-2",children:[u.jsxs("a",{href:o.deeplink,className:`w-full sm:w-auto text-center px-4 py-3 sm:py-2 text-sm rounded-xl bg-slate-900 text-white hover:bg-slate-800 dark:bg-slate-100 dark:text-slate-900 dark:hover:bg-white ${me}`,children:["Открыть в ",U.name]}),u.jsx("button",{onClick:()=>Ct(o.deeplink),className:`w-full sm:w-auto text-center px-3 py-3 sm:py-2 text-xs rounded-xl border border-slate-300 dark:border-slate-700 hover:bg-slate-50 dark:hover:bg-slate-800 ${me}`,children:a?"✓ Скопировано":"Скопировать ссылку"})]})]},N))]})]}):null}function Ym(){const[e,t]=k.useState(""),[n,r]=k.useState(!1),[l,s]=k.useState(null),[o,i]=k.useState(null),[a,c]=k.useState("plain-lines");kr();const h=async()=>{const g=e.trim();if(g){r(!0),i(null),s(null);try{const y=await fetch(`/api/lookup?domain=${encodeURIComponent(g)}`);if(!y.ok){const j=await y.json().catch(()=>({}));i(j.detail??"Ошибка запроса");return}s(await y.json())}catch(y){i(y.message??"Ошибка сети")}finally{r(!1)}}},m=()=>{if(!l)return;const g=l.all_ips_v4;let y="";a==="plain-lines"?y=g.join(` -`):a==="keenetic-ip"?y=g.map(d=>{const p=d.split("/"),x=p[0],E=parseInt(p[1]??"32"),w=E===32?"255.255.255.255":Array.from({length:4},(S,P)=>65280>>Math.min(8,Math.max(0,E-P*8))&255).join(".");return`route ADD ${x} MASK ${w} 0.0.0.0 & rem ${l.domain}`}).join(` -`):y=g.join(",");const j=new Blob([y],{type:"text/plain;charset=utf-8"}),C=URL.createObjectURL(j),f=document.createElement("a");f.href=C,f.download=`${l.domain}_ips.txt`,f.click(),URL.revokeObjectURL(C)},v=g=>!g||g.length!==2?"":String.fromCodePoint(...[...g.toUpperCase()].map(y=>127391+y.charCodeAt(0)));return u.jsxs("div",{children:[u.jsxs("div",{className:`${de} p-4 mb-4`,children:[u.jsx("div",{className:"text-sm mb-3 text-slate-600 dark:text-slate-400",children:"Введите домен для получения IP адресов и субдоменов. Результаты проверяются в базах geoip/geosite"}),u.jsxs("div",{className:"flex gap-2",children:[u.jsx("input",{value:e,onChange:g=>t(g.target.value),onKeyDown:g=>g.key==="Enter"&&h(),className:`flex-1 ${wr}`,placeholder:"например: example.com, anydesk.com…",autoFocus:!0}),u.jsx("button",{onClick:h,disabled:n,className:`px-4 py-2 text-sm rounded-xl bg-slate-900 text-white hover:bg-slate-800 dark:bg-slate-100 dark:text-slate-900 dark:hover:bg-white disabled:opacity-50 ${me}`,type:"button",children:n?u.jsxs("span",{className:"flex items-center gap-2",children:[u.jsx("svg",{className:"animate-spin w-4 h-4",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",children:u.jsx("path",{d:"M21 12a9 9 0 1 1-6.219-8.56"})}),"Поиск…"]}):"Найти"})]}),o&&u.jsxs("div",{className:"mt-2 text-xs text-red-600 dark:text-red-400 bg-red-50 dark:bg-red-900/20 rounded-lg px-3 py-2",children:["Ошибка: ",o]})]}),l&&u.jsxs("div",{className:"space-y-3",children:[u.jsxs("div",{className:`${de} p-4`,children:[u.jsxs("div",{className:"flex items-center justify-between flex-wrap gap-2 mb-3",children:[u.jsxs("div",{children:[u.jsx("span",{className:"text-base font-semibold font-mono",children:l.domain}),u.jsxs("span",{className:"ml-2 text-xs text-slate-500",children:[l.subdomains_total," субдоменов · ",l.all_ips.length," уникальных IP"]})]}),u.jsxs("div",{className:"flex items-center gap-2",children:[u.jsxs("select",{value:a,onChange:g=>c(g.target.value),className:`text-xs ${Vt}`,children:[u.jsx("option",{value:"plain-lines",children:"Построчно"}),u.jsx("option",{value:"single-line",children:"Одной строкой"}),u.jsx("option",{value:"keenetic-ip",children:"Keenetic .bat"})]}),u.jsxs("button",{onClick:m,className:`px-3 py-1.5 text-xs rounded-xl bg-slate-900 text-white hover:bg-slate-800 dark:bg-slate-100 dark:text-slate-900 ${me}`,children:["↓ Скачать IPv4 (",l.all_ips_v4.length,")"]})]})]}),u.jsxs("div",{className:"mb-3 pb-3 border-b border-slate-100 dark:border-slate-800",children:[u.jsx("div",{className:"text-xs font-medium text-slate-500 mb-2",children:"Основной домен"}),u.jsx("div",{className:"flex items-start gap-3 flex-wrap",children:u.jsxs("div",{children:[u.jsxs("div",{className:"flex flex-wrap gap-1 mb-1",children:[l.main_dns.ipv4.map(g=>u.jsx("span",{className:"text-xs font-mono px-2 py-0.5 rounded bg-blue-50 dark:bg-blue-900/20 text-blue-700 dark:text-blue-300",children:g},g)),l.main_dns.ipv6.map(g=>u.jsx("span",{className:"text-xs font-mono px-2 py-0.5 rounded bg-slate-100 dark:bg-slate-800 text-slate-600 dark:text-slate-400",children:g},g)),l.main_dns.cname&&u.jsxs("span",{className:"text-xs font-mono px-2 py-0.5 rounded bg-amber-50 dark:bg-amber-900/20 text-amber-700 dark:text-amber-300",children:["CNAME: ",l.main_dns.cname]})]}),u.jsx(xs,{matches:l.main_dns.in_db})]})})]}),l.all_ips.length>0&&u.jsxs("div",{children:[u.jsx("div",{className:"text-xs font-medium text-slate-500 mb-2",children:"Все IP адреса"}),u.jsx("div",{className:"space-y-1 max-h-64 overflow-y-auto",children:l.all_ips.map(g=>u.jsxs("div",{className:"flex items-center gap-2 text-xs py-1 border-b border-slate-50 dark:border-slate-900 last:border-0",children:[u.jsx("span",{className:"font-mono text-slate-700 dark:text-slate-300 w-36 shrink-0",children:g.ip}),g.country_code&&u.jsx("span",{className:"shrink-0",children:v(g.country_code)}),u.jsx("span",{className:"text-slate-500 truncate flex-1",children:g.org||g.asn}),u.jsx(xs,{matches:g.in_db})]},g.ip))})]})]}),l.subdomains.length>0&&u.jsxs("div",{className:`${de} p-4`,children:[u.jsxs("div",{className:"text-xs font-medium text-slate-500 mb-3",children:["Субдомены (",l.subdomains.length," из ",l.subdomains_total,")"]}),u.jsx("div",{className:"space-y-1.5 max-h-96 overflow-y-auto",children:l.subdomains.map(g=>u.jsxs("div",{className:"flex items-start gap-2 text-xs py-1.5 border-b border-slate-50 dark:border-slate-900 last:border-0",children:[u.jsx("span",{className:"font-mono text-slate-700 dark:text-slate-300 w-56 shrink-0 truncate",children:g.domain}),u.jsxs("div",{className:"flex flex-wrap gap-1 flex-1 min-w-0",children:[g.ipv4.slice(0,3).map(y=>u.jsx("span",{className:"font-mono px-1.5 py-0.5 rounded bg-blue-50 dark:bg-blue-900/20 text-blue-700 dark:text-blue-300",children:y},y)),g.ipv4.length>3&&u.jsxs("span",{className:"text-slate-400",children:["+",g.ipv4.length-3]}),g.cname&&u.jsxs("span",{className:"font-mono px-1.5 py-0.5 rounded bg-amber-50 dark:bg-amber-900/20 text-amber-700 dark:text-amber-300 truncate max-w-xs",children:["→ ",g.cname]})]}),u.jsx(xs,{matches:g.in_db})]},g.domain))})]})]})]})}function Jm(){const[e,t]=hi(),[n,r]=k.useState(()=>e.get("tab")||"popular"),[l,s]=k.useState([]),[o,i]=k.useState(null),[a,c]=k.useState(!1);k.useEffect(()=>{fetch("/api/last-update").then(m=>m.json()).then(m=>i(m.last_update)),fetch("/api/update/status").then(m=>m.json()).then(m=>c(m.running)).catch(()=>{}),fetch("/api/sources").then(m=>m.json()).then(s).catch(()=>{})},[]),k.useEffect(()=>{if(!a)return;const m=setInterval(async()=>{const v=await fetch("/api/update/status").then(g=>g.json()).catch(()=>({running:!1}));if(c(v.running),!v.running){const g=await fetch("/api/last-update").then(y=>y.json());i(g.last_update)}},3e3);return()=>clearInterval(m)},[a]);const h=m=>{r(m),t({tab:m},{replace:!0})};return u.jsx("div",{className:"min-h-screen bg-slate-100 text-slate-900 dark:bg-slate-950 dark:text-slate-100 px-3 py-4 sm:px-4 sm:py-8",children:u.jsxs("div",{className:"max-w-3xl mx-auto w-full",children:[u.jsx(vi,{tab:n,setTab:h}),n==="popular"&&u.jsx(Qm,{sources:l}),n==="search"&&u.jsx(Km,{sources:l,lastUpdate:o,updateRunning:a}),n==="lookup"&&u.jsx(Ym,{}),n==="routing"&&u.jsx(Xm,{})]})})}function Zm(){const[e,t]=k.useState([]),[n,r]=k.useState(null);return k.useEffect(()=>{fetch("/api/sources").then(l=>l.json()).then(t).catch(()=>{}),fetch("/api/last-update").then(l=>l.json()).then(r).catch(()=>{})},[]),u.jsx("div",{className:"min-h-screen bg-slate-100 text-slate-900 dark:bg-slate-950 dark:text-slate-100 px-3 py-4 sm:px-4 sm:py-8",children:u.jsxs("div",{className:"max-w-3xl mx-auto w-full",children:[u.jsx(vi,{back:{to:"/",label:"Назад"}}),u.jsx("h1",{className:"text-lg font-semibold mb-4",children:"Источники баз данных"}),u.jsx("div",{className:"space-y-3",children:e.map(l=>{var o;const s=(o=n==null?void 0:n.sources)==null?void 0:o.find(i=>i.source_slug===l.slug);return u.jsx("div",{className:`${de} p-4`,children:u.jsx("div",{className:"flex items-start justify-between gap-3 flex-wrap",children:u.jsxs("div",{className:"flex-1 min-w-0",children:[u.jsxs("div",{className:"flex items-center gap-2 mb-1",children:[u.jsx(Wl,{slug:l.slug,name:l.name}),u.jsx("span",{className:"text-xs text-slate-500",children:s!=null&&s.last_update?`обновлено: ${s.last_update}`:"нет данных"})]}),u.jsx("div",{className:"text-sm text-slate-700 dark:text-slate-300 mb-2",children:l.description}),u.jsxs("div",{className:"text-xs text-slate-500",children:["Совместимо с: ",u.jsx("span",{className:"font-medium text-slate-700 dark:text-slate-300",children:l.compatible})]}),u.jsxs("div",{className:"mt-2 space-y-1",children:[l.geoip_url&&u.jsxs("div",{className:"text-xs",children:[u.jsx("span",{className:"text-slate-400 mr-1",children:"geoip.dat:"}),u.jsx("a",{href:l.geoip_url,target:"_blank",rel:"noreferrer",className:"font-mono text-blue-600 dark:text-blue-400 hover:underline break-all",children:l.geoip_url})]}),l.geosite_url&&u.jsxs("div",{className:"text-xs",children:[u.jsx("span",{className:"text-slate-400 mr-1",children:"geosite.dat:"}),u.jsx("a",{href:l.geosite_url,target:"_blank",rel:"noreferrer",className:"font-mono text-blue-600 dark:text-blue-400 hover:underline break-all",children:l.geosite_url})]}),!l.geosite_url&&u.jsx("div",{className:"text-xs text-slate-400 italic",children:"geosite.dat — нет в этом источнике"})]})]})})},l.slug)})}),u.jsxs("div",{className:`${de} p-4 mt-4 text-sm text-slate-600 dark:text-slate-400`,children:[u.jsx("p",{className:"font-medium text-slate-800 dark:text-slate-200 mb-1",children:"Как использовать с 3x-ui / Xray"}),u.jsxs("p",{className:"text-xs",children:["Скопируйте имя категории (например ",u.jsx("code",{className:"font-mono bg-slate-100 dark:bg-slate-800 px-1 rounded",children:"geoip:akamai"}),") и вставьте в поле маршрутизации 3x-ui. Используйте только категории из источников ",u.jsx("strong",{children:"RuNet Freedom"}),", ",u.jsx("strong",{children:"Loyalsoldier"})," или ",u.jsx("strong",{children:"DanielLavrushin"})," — они совместимы с Xray."]})]})]})})}function qm(){const[e]=hi(),t=e.getAll("category"),n=e.get("source"),[r,l]=k.useState("domains,ip4,ip6"),[s,o]=k.useState("plain-lines"),[i,a]=k.useState(""),[c,h]=k.useState(0),[m,v]=k.useState(null),[g,y]=k.useState(!1),[j,C]=k.useState(null),[f,d]=k.useState(null);k.useEffect(()=>{fetch("/api/last-update").then(S=>S.json()).then(S=>v(S.last_update)),n&&fetch("/api/sources").then(S=>S.json()).then(S=>{d(S.find(P=>P.slug===n)??null)}).catch(()=>{})},[n]),k.useEffect(()=>{s==="keenetic-ip"&&!r.includes("ip4")&&!r.includes("ip6")&&o("plain-lines")},[r]);const p=k.useCallback(async()=>{if(t.length){y(!0),C(null);try{const S=new URLSearchParams;t.forEach(T=>S.append("category",T)),S.set("type",r),S.set("format",s==="single-line"?"plain-lines":s),n&&S.set("source",n);const P=await fetch(`/api/export?${S}`);if(!P.ok){const T=await P.json().catch(()=>({}));C(T.detail??P.statusText),a(""),h(0);return}let z=await P.text();s==="single-line"&&(z=z.trim().split(/\r?\n/).join(",")),a(z),h(z.trim()?z.trim().split(/[,\r?\n]/).filter(Boolean).length:0)}finally{y(!1)}}},[t.join(","),r,s,n]);k.useEffect(()=>{p()},[p]);const x=()=>navigator.clipboard.writeText(i),E=()=>{const S=new Blob([i],{type:"text/plain;charset=utf-8"}),P=URL.createObjectURL(S),z=document.createElement("a");z.href=P,z.download=`export_${(t[0]??"data").replace(":","_")}.txt`,z.click(),URL.revokeObjectURL(P)},w=`${window.location.origin}/api/export?`+t.map(S=>`category=${encodeURIComponent(S)}`).join("&")+`&type=${encodeURIComponent(r)}&format=${encodeURIComponent(s)}`+(n?`&source=${encodeURIComponent(n)}`:"");return u.jsx("div",{className:"min-h-screen bg-slate-100 text-slate-900 dark:bg-slate-950 dark:text-slate-100 px-3 py-4 sm:px-4 sm:py-8",children:u.jsxs("div",{className:"max-w-3xl mx-auto w-full",children:[u.jsx(vi,{back:{to:"/",label:"Назад"}}),u.jsxs("div",{className:`${de} p-4 mb-4`,children:[u.jsxs("div",{className:"flex items-start justify-between gap-2 flex-wrap mb-4",children:[u.jsxs("div",{className:"flex-1 min-w-0",children:[u.jsx("div",{className:"text-xs text-slate-500 mb-1",children:"Категории:"}),u.jsx("div",{className:"flex flex-wrap gap-1 mb-2",children:t.map(S=>u.jsxs("span",{className:`inline-flex items-center gap-1 text-xs px-2 py-0.5 rounded-full font-mono ${S.startsWith("geoip:")?"bg-blue-100 text-blue-700 dark:bg-blue-900/40 dark:text-blue-300":"bg-emerald-100 text-emerald-700 dark:bg-emerald-900/40 dark:text-emerald-300"}`,children:[kn(S),u.jsx(_l,{text:kn(S)})]},S))}),f&&u.jsxs("div",{className:"flex items-center gap-2 text-xs text-slate-500",children:[u.jsx("span",{children:"Источник:"}),u.jsx(Wl,{slug:f.slug,name:f.name}),u.jsxs("span",{className:"text-slate-400",children:["· ",f.compatible]})]})]}),u.jsx("div",{className:"text-sm font-medium tabular-nums text-slate-600 dark:text-slate-400",children:g?"…":`${c.toLocaleString()} строк`})]}),u.jsxs("div",{className:"grid grid-cols-1 sm:grid-cols-2 gap-2",children:[u.jsxs("select",{value:r,onChange:S=>l(S.target.value),className:Vt,children:[u.jsx("option",{value:"domains,ip4,ip6",children:"Домены + IPv4 + IPv6"}),u.jsx("option",{value:"domains,ip4",children:"Домены + IPv4"}),u.jsx("option",{value:"ip4,ip6",children:"IPv4 + IPv6"}),u.jsx("option",{value:"domains",children:"Только домены"}),u.jsx("option",{value:"ip4",children:"Только IPv4"}),u.jsx("option",{value:"ip6",children:"Только IPv6"})]}),u.jsx("select",{value:s,onChange:S=>o(S.target.value),className:Vt,children:sd.map(S=>u.jsx("option",{value:S.value,children:S.label},S.value))})]}),s==="keenetic-ip"&&u.jsxs("div",{className:"mt-2 text-xs text-slate-500 bg-slate-50 dark:bg-slate-900 rounded-lg px-3 py-2",children:["Формат: ",u.jsx("code",{className:"font-mono",children:"route ADD MASK <маска> 0.0.0.0 & rem <категория>"})]}),j&&u.jsxs("div",{className:"mt-2 text-xs text-red-600 dark:text-red-400 bg-red-50 dark:bg-red-900/20 rounded-lg px-3 py-2",children:["Ошибка: ",j]}),u.jsxs("div",{className:"flex gap-2 mt-4",children:[u.jsx("button",{onClick:E,className:`px-4 py-2 text-sm rounded-xl bg-slate-900 text-white hover:bg-slate-800 dark:bg-slate-100 dark:text-slate-900 dark:hover:bg-white ${me}`,children:"↓ Скачать"}),u.jsx("button",{onClick:x,className:`px-4 py-2 text-sm rounded-xl border border-slate-300 dark:border-slate-700 hover:bg-slate-50 dark:hover:bg-slate-800 ${me}`,children:"Скопировать"})]}),u.jsxs("div",{className:"mt-4 pt-3 border-t border-slate-100 dark:border-slate-800",children:[u.jsx("div",{className:"text-xs text-slate-500 mb-1",children:"Прямая ссылка на API:"}),u.jsxs("div",{className:"flex items-center gap-2",children:[u.jsx("div",{className:"flex-1 text-xs break-all font-mono bg-slate-50 dark:bg-slate-900 rounded-lg px-3 py-2 text-slate-700 dark:text-slate-300 select-all cursor-text",children:w}),u.jsx(_l,{text:w})]})]}),u.jsxs("div",{className:"text-xs text-slate-500 mt-2",children:["Обновлено: ",u.jsx("span",{className:"text-slate-700 dark:text-slate-300",children:m??"—"})]})]}),u.jsx("div",{className:`${de} p-4`,children:u.jsx("textarea",{value:g?"Загрузка…":j?"":i,readOnly:!0,className:"w-full h-80 rounded-xl px-3 py-2 text-xs border bg-white text-slate-900 border-slate-200 dark:bg-slate-950/60 dark:text-slate-100 dark:border-slate-700 font-mono resize-none"})})]})})}function e0(){return u.jsxs(Cm,{children:[u.jsx(qr,{path:"/",element:u.jsx(Jm,{})}),u.jsx(qr,{path:"/sources",element:u.jsx(Zm,{})}),u.jsx(qr,{path:"/export",element:u.jsx(qm,{})})]})}ks.createRoot(document.getElementById("root")).render(u.jsx(Ne.StrictMode,{children:u.jsx(Um,{children:u.jsx(e0,{})})})); diff --git a/site/assets/index-uS40y9LP.css b/site/assets/index-uS40y9LP.css deleted file mode 100644 index 0fa7f57..0000000 --- a/site/assets/index-uS40y9LP.css +++ /dev/null @@ -1 +0,0 @@ -*,:before,:after{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }::backdrop{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }*,:before,:after{box-sizing:border-box;border-width:0;border-style:solid;border-color:#e5e7eb}:before,:after{--tw-content: ""}html,:host{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji",Segoe UI Symbol,"Noto Color Emoji";font-feature-settings:normal;font-variation-settings:normal;-webkit-tap-highlight-color:transparent}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-feature-settings:normal;font-variation-settings:normal;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;letter-spacing:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dl,dd,h1,h2,h3,h4,h5,h6,hr,figure,p,pre{margin:0}fieldset{margin:0;padding:0}legend{padding:0}ol,ul,menu{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}button,[role=button]{cursor:pointer}:disabled{cursor:default}img,svg,video,canvas,audio,iframe,embed,object{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]:where(:not([hidden=until-found])){display:none}.absolute{position:absolute}.relative{position:relative}.left-0{left:0}.right-12{right:3rem}.right-2\.5{right:.625rem}.right-3{right:.75rem}.top-1\/2{top:50%}.z-50{z-index:50}.-mx-4{margin-left:-1rem;margin-right:-1rem}.mx-auto{margin-left:auto;margin-right:auto}.mb-0\.5{margin-bottom:.125rem}.mb-1{margin-bottom:.25rem}.mb-2{margin-bottom:.5rem}.mb-3{margin-bottom:.75rem}.mb-4{margin-bottom:1rem}.ml-2{margin-left:.5rem}.mr-1{margin-right:.25rem}.mt-0\.5{margin-top:.125rem}.mt-1{margin-top:.25rem}.mt-1\.5{margin-top:.375rem}.mt-2{margin-top:.5rem}.mt-3{margin-top:.75rem}.mt-4{margin-top:1rem}.mt-8{margin-top:2rem}.block{display:block}.flex{display:flex}.inline-flex{display:inline-flex}.grid{display:grid}.h-28{height:7rem}.h-3{height:.75rem}.h-4{height:1rem}.h-7{height:1.75rem}.h-80{height:20rem}.max-h-52{max-height:13rem}.max-h-64{max-height:16rem}.max-h-96{max-height:24rem}.min-h-screen{min-height:100vh}.w-3{width:.75rem}.w-36{width:9rem}.w-4{width:1rem}.w-56{width:14rem}.w-7{width:1.75rem}.w-full{width:100%}.min-w-0{min-width:0px}.max-w-3xl{max-width:48rem}.max-w-xs{max-width:20rem}.flex-1{flex:1 1 0%}.shrink-0{flex-shrink:0}.-translate-y-1\/2{--tw-translate-y: -50%;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.rotate-180{--tw-rotate: 180deg;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}@keyframes spin{to{transform:rotate(360deg)}}.animate-spin{animation:spin 1s linear infinite}.cursor-not-allowed{cursor:not-allowed}.cursor-pointer{cursor:pointer}.cursor-text{cursor:text}.select-all{-webkit-user-select:all;-moz-user-select:all;user-select:all}.resize-none{resize:none}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.items-start{align-items:flex-start}.items-center{align-items:center}.items-stretch{align-items:stretch}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.gap-1{gap:.25rem}.gap-2{gap:.5rem}.gap-3{gap:.75rem}.space-y-1>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.25rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.25rem * var(--tw-space-y-reverse))}.space-y-1\.5>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.375rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.375rem * var(--tw-space-y-reverse))}.space-y-2>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.5rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.5rem * var(--tw-space-y-reverse))}.space-y-3>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.75rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.75rem * var(--tw-space-y-reverse))}.space-y-4>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(1rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1rem * var(--tw-space-y-reverse))}.overflow-hidden{overflow:hidden}.overflow-x-auto{overflow-x:auto}.overflow-y-auto{overflow-y:auto}.truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.whitespace-nowrap{white-space:nowrap}.break-all{word-break:break-all}.rounded{border-radius:.25rem}.rounded-2xl{border-radius:1rem}.rounded-full{border-radius:9999px}.rounded-lg{border-radius:.5rem}.rounded-xl{border-radius:.75rem}.border{border-width:1px}.border-b{border-bottom-width:1px}.border-t{border-top-width:1px}.border-slate-100{--tw-border-opacity: 1;border-color:rgb(241 245 249 / var(--tw-border-opacity, 1))}.border-slate-200{--tw-border-opacity: 1;border-color:rgb(226 232 240 / var(--tw-border-opacity, 1))}.border-slate-300{--tw-border-opacity: 1;border-color:rgb(203 213 225 / var(--tw-border-opacity, 1))}.border-slate-50{--tw-border-opacity: 1;border-color:rgb(248 250 252 / var(--tw-border-opacity, 1))}.border-transparent{border-color:transparent}.bg-amber-100{--tw-bg-opacity: 1;background-color:rgb(254 243 199 / var(--tw-bg-opacity, 1))}.bg-amber-50{--tw-bg-opacity: 1;background-color:rgb(255 251 235 / var(--tw-bg-opacity, 1))}.bg-blue-100{--tw-bg-opacity: 1;background-color:rgb(219 234 254 / var(--tw-bg-opacity, 1))}.bg-blue-50{--tw-bg-opacity: 1;background-color:rgb(239 246 255 / var(--tw-bg-opacity, 1))}.bg-emerald-100{--tw-bg-opacity: 1;background-color:rgb(209 250 229 / var(--tw-bg-opacity, 1))}.bg-orange-100{--tw-bg-opacity: 1;background-color:rgb(255 237 213 / var(--tw-bg-opacity, 1))}.bg-red-50{--tw-bg-opacity: 1;background-color:rgb(254 242 242 / var(--tw-bg-opacity, 1))}.bg-rose-100{--tw-bg-opacity: 1;background-color:rgb(255 228 230 / var(--tw-bg-opacity, 1))}.bg-sky-100{--tw-bg-opacity: 1;background-color:rgb(224 242 254 / var(--tw-bg-opacity, 1))}.bg-slate-100{--tw-bg-opacity: 1;background-color:rgb(241 245 249 / var(--tw-bg-opacity, 1))}.bg-slate-200{--tw-bg-opacity: 1;background-color:rgb(226 232 240 / var(--tw-bg-opacity, 1))}.bg-slate-50{--tw-bg-opacity: 1;background-color:rgb(248 250 252 / var(--tw-bg-opacity, 1))}.bg-slate-900{--tw-bg-opacity: 1;background-color:rgb(15 23 42 / var(--tw-bg-opacity, 1))}.bg-teal-100{--tw-bg-opacity: 1;background-color:rgb(204 251 241 / var(--tw-bg-opacity, 1))}.bg-violet-100{--tw-bg-opacity: 1;background-color:rgb(237 233 254 / var(--tw-bg-opacity, 1))}.bg-white{--tw-bg-opacity: 1;background-color:rgb(255 255 255 / var(--tw-bg-opacity, 1))}.bg-white\/80{background-color:#fffc}.object-contain{-o-object-fit:contain;object-fit:contain}.p-0\.5{padding:.125rem}.p-3{padding:.75rem}.p-4{padding:1rem}.px-0{padding-left:0;padding-right:0}.px-1{padding-left:.25rem;padding-right:.25rem}.px-1\.5{padding-left:.375rem;padding-right:.375rem}.px-2{padding-left:.5rem;padding-right:.5rem}.px-2\.5{padding-left:.625rem;padding-right:.625rem}.px-3{padding-left:.75rem;padding-right:.75rem}.px-4{padding-left:1rem;padding-right:1rem}.px-8{padding-left:2rem;padding-right:2rem}.py-0\.5{padding-top:.125rem;padding-bottom:.125rem}.py-1{padding-top:.25rem;padding-bottom:.25rem}.py-1\.5{padding-top:.375rem;padding-bottom:.375rem}.py-12{padding-top:3rem;padding-bottom:3rem}.py-2{padding-top:.5rem;padding-bottom:.5rem}.py-2\.5{padding-top:.625rem;padding-bottom:.625rem}.py-3{padding-top:.75rem;padding-bottom:.75rem}.py-4{padding-top:1rem;padding-bottom:1rem}.pb-1{padding-bottom:.25rem}.pb-3{padding-bottom:.75rem}.pb-5{padding-bottom:1.25rem}.pr-10{padding-right:2.5rem}.pr-7{padding-right:1.75rem}.pt-2{padding-top:.5rem}.pt-3{padding-top:.75rem}.pt-5{padding-top:1.25rem}.text-left{text-align:left}.text-center{text-align:center}.font-mono{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace}.text-base{font-size:1rem;line-height:1.5rem}.text-lg{font-size:1.125rem;line-height:1.75rem}.text-sm{font-size:.875rem;line-height:1.25rem}.text-xl{font-size:1.25rem;line-height:1.75rem}.text-xs{font-size:.75rem;line-height:1rem}.font-bold{font-weight:700}.font-medium{font-weight:500}.font-semibold{font-weight:600}.uppercase{text-transform:uppercase}.italic{font-style:italic}.tabular-nums{--tw-numeric-spacing: tabular-nums;font-variant-numeric:var(--tw-ordinal) var(--tw-slashed-zero) var(--tw-numeric-figure) var(--tw-numeric-spacing) var(--tw-numeric-fraction)}.tracking-tight{letter-spacing:-.025em}.tracking-wide{letter-spacing:.025em}.text-amber-300{--tw-text-opacity: 1;color:rgb(252 211 77 / var(--tw-text-opacity, 1))}.text-amber-600{--tw-text-opacity: 1;color:rgb(217 119 6 / var(--tw-text-opacity, 1))}.text-amber-700{--tw-text-opacity: 1;color:rgb(180 83 9 / var(--tw-text-opacity, 1))}.text-blue-600{--tw-text-opacity: 1;color:rgb(37 99 235 / var(--tw-text-opacity, 1))}.text-blue-700{--tw-text-opacity: 1;color:rgb(29 78 216 / var(--tw-text-opacity, 1))}.text-emerald-700{--tw-text-opacity: 1;color:rgb(4 120 87 / var(--tw-text-opacity, 1))}.text-orange-600{--tw-text-opacity: 1;color:rgb(234 88 12 / var(--tw-text-opacity, 1))}.text-red-600{--tw-text-opacity: 1;color:rgb(220 38 38 / var(--tw-text-opacity, 1))}.text-rose-700{--tw-text-opacity: 1;color:rgb(190 18 60 / var(--tw-text-opacity, 1))}.text-sky-700{--tw-text-opacity: 1;color:rgb(3 105 161 / var(--tw-text-opacity, 1))}.text-slate-400{--tw-text-opacity: 1;color:rgb(148 163 184 / var(--tw-text-opacity, 1))}.text-slate-500{--tw-text-opacity: 1;color:rgb(100 116 139 / var(--tw-text-opacity, 1))}.text-slate-600{--tw-text-opacity: 1;color:rgb(71 85 105 / var(--tw-text-opacity, 1))}.text-slate-700{--tw-text-opacity: 1;color:rgb(51 65 85 / var(--tw-text-opacity, 1))}.text-slate-800{--tw-text-opacity: 1;color:rgb(30 41 59 / var(--tw-text-opacity, 1))}.text-slate-900{--tw-text-opacity: 1;color:rgb(15 23 42 / var(--tw-text-opacity, 1))}.text-teal-600{--tw-text-opacity: 1;color:rgb(13 148 136 / var(--tw-text-opacity, 1))}.text-violet-700{--tw-text-opacity: 1;color:rgb(109 40 217 / var(--tw-text-opacity, 1))}.text-white{--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity, 1))}.underline{text-decoration-line:underline}.accent-slate-900{accent-color:#0f172a}.opacity-40{opacity:.4}.shadow-lg{--tw-shadow: 0 10px 15px -3px rgb(0 0 0 / .1), 0 4px 6px -4px rgb(0 0 0 / .1);--tw-shadow-colored: 0 10px 15px -3px var(--tw-shadow-color), 0 4px 6px -4px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-sm{--tw-shadow: 0 1px 2px 0 rgb(0 0 0 / .05);--tw-shadow-colored: 0 1px 2px 0 var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.filter{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.transition{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-colors{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-transform{transition-property:transform;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.scrollbar-hide{-ms-overflow-style:none;scrollbar-width:none}.scrollbar-hide::-webkit-scrollbar{display:none}@media (max-width: 640px){input[type=checkbox]{width:18px;height:18px;min-width:18px}select,input[type=text],input[type=search]{min-height:44px}}.placeholder\:text-slate-400::-moz-placeholder{--tw-text-opacity: 1;color:rgb(148 163 184 / var(--tw-text-opacity, 1))}.placeholder\:text-slate-400::placeholder{--tw-text-opacity: 1;color:rgb(148 163 184 / var(--tw-text-opacity, 1))}.last\:border-0:last-child{border-width:0px}.hover\:bg-slate-100:hover{--tw-bg-opacity: 1;background-color:rgb(241 245 249 / var(--tw-bg-opacity, 1))}.hover\:bg-slate-200:hover{--tw-bg-opacity: 1;background-color:rgb(226 232 240 / var(--tw-bg-opacity, 1))}.hover\:bg-slate-50:hover{--tw-bg-opacity: 1;background-color:rgb(248 250 252 / var(--tw-bg-opacity, 1))}.hover\:bg-slate-800:hover{--tw-bg-opacity: 1;background-color:rgb(30 41 59 / var(--tw-bg-opacity, 1))}.hover\:bg-white:hover{--tw-bg-opacity: 1;background-color:rgb(255 255 255 / var(--tw-bg-opacity, 1))}.hover\:text-slate-600:hover{--tw-text-opacity: 1;color:rgb(71 85 105 / var(--tw-text-opacity, 1))}.hover\:text-slate-900:hover{--tw-text-opacity: 1;color:rgb(15 23 42 / var(--tw-text-opacity, 1))}.hover\:underline:hover{text-decoration-line:underline}.focus\:outline-none:focus{outline:2px solid transparent;outline-offset:2px}.focus\:ring-2:focus{--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.focus\:ring-slate-300:focus{--tw-ring-opacity: 1;--tw-ring-color: rgb(203 213 225 / var(--tw-ring-opacity, 1))}.focus-visible\:ring-2:focus-visible{--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.focus-visible\:ring-slate-400\/60:focus-visible{--tw-ring-color: rgb(148 163 184 / .6)}.active\:scale-\[0\.99\]:active{--tw-scale-x: .99;--tw-scale-y: .99;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.active\:brightness-95:active{--tw-brightness: brightness(.95);filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.disabled\:opacity-50:disabled{opacity:.5}.dark\:border-slate-600:is(.dark *){--tw-border-opacity: 1;border-color:rgb(71 85 105 / var(--tw-border-opacity, 1))}.dark\:border-slate-700:is(.dark *){--tw-border-opacity: 1;border-color:rgb(51 65 85 / var(--tw-border-opacity, 1))}.dark\:border-slate-800:is(.dark *){--tw-border-opacity: 1;border-color:rgb(30 41 59 / var(--tw-border-opacity, 1))}.dark\:border-slate-900:is(.dark *){--tw-border-opacity: 1;border-color:rgb(15 23 42 / var(--tw-border-opacity, 1))}.dark\:bg-amber-900\/20:is(.dark *){background-color:#78350f33}.dark\:bg-amber-900\/40:is(.dark *){background-color:#78350f66}.dark\:bg-blue-900\/20:is(.dark *){background-color:#1e3a8a33}.dark\:bg-blue-900\/40:is(.dark *){background-color:#1e3a8a66}.dark\:bg-emerald-900\/40:is(.dark *){background-color:#064e3b66}.dark\:bg-orange-900\/40:is(.dark *){background-color:#7c2d1266}.dark\:bg-red-900\/20:is(.dark *){background-color:#7f1d1d33}.dark\:bg-rose-900\/40:is(.dark *){background-color:#88133766}.dark\:bg-sky-900\/40:is(.dark *){background-color:#0c4a6e66}.dark\:bg-slate-100:is(.dark *){--tw-bg-opacity: 1;background-color:rgb(241 245 249 / var(--tw-bg-opacity, 1))}.dark\:bg-slate-700:is(.dark *){--tw-bg-opacity: 1;background-color:rgb(51 65 85 / var(--tw-bg-opacity, 1))}.dark\:bg-slate-800:is(.dark *){--tw-bg-opacity: 1;background-color:rgb(30 41 59 / var(--tw-bg-opacity, 1))}.dark\:bg-slate-800\/60:is(.dark *){background-color:#1e293b99}.dark\:bg-slate-900:is(.dark *){--tw-bg-opacity: 1;background-color:rgb(15 23 42 / var(--tw-bg-opacity, 1))}.dark\:bg-slate-900\/40:is(.dark *){background-color:#0f172a66}.dark\:bg-slate-950:is(.dark *){--tw-bg-opacity: 1;background-color:rgb(2 6 23 / var(--tw-bg-opacity, 1))}.dark\:bg-slate-950\/60:is(.dark *){background-color:#02061799}.dark\:bg-teal-900\/40:is(.dark *){background-color:#134e4a66}.dark\:bg-violet-900\/40:is(.dark *){background-color:#4c1d9566}.dark\:text-amber-300:is(.dark *){--tw-text-opacity: 1;color:rgb(252 211 77 / var(--tw-text-opacity, 1))}.dark\:text-amber-400:is(.dark *){--tw-text-opacity: 1;color:rgb(251 191 36 / var(--tw-text-opacity, 1))}.dark\:text-blue-300:is(.dark *){--tw-text-opacity: 1;color:rgb(147 197 253 / var(--tw-text-opacity, 1))}.dark\:text-blue-400:is(.dark *){--tw-text-opacity: 1;color:rgb(96 165 250 / var(--tw-text-opacity, 1))}.dark\:text-emerald-300:is(.dark *){--tw-text-opacity: 1;color:rgb(110 231 183 / var(--tw-text-opacity, 1))}.dark\:text-orange-400:is(.dark *){--tw-text-opacity: 1;color:rgb(251 146 60 / var(--tw-text-opacity, 1))}.dark\:text-red-400:is(.dark *){--tw-text-opacity: 1;color:rgb(248 113 113 / var(--tw-text-opacity, 1))}.dark\:text-rose-300:is(.dark *){--tw-text-opacity: 1;color:rgb(253 164 175 / var(--tw-text-opacity, 1))}.dark\:text-sky-300:is(.dark *){--tw-text-opacity: 1;color:rgb(125 211 252 / var(--tw-text-opacity, 1))}.dark\:text-slate-100:is(.dark *){--tw-text-opacity: 1;color:rgb(241 245 249 / var(--tw-text-opacity, 1))}.dark\:text-slate-200:is(.dark *){--tw-text-opacity: 1;color:rgb(226 232 240 / var(--tw-text-opacity, 1))}.dark\:text-slate-300:is(.dark *){--tw-text-opacity: 1;color:rgb(203 213 225 / var(--tw-text-opacity, 1))}.dark\:text-slate-400:is(.dark *){--tw-text-opacity: 1;color:rgb(148 163 184 / var(--tw-text-opacity, 1))}.dark\:text-slate-500:is(.dark *){--tw-text-opacity: 1;color:rgb(100 116 139 / var(--tw-text-opacity, 1))}.dark\:text-slate-900:is(.dark *){--tw-text-opacity: 1;color:rgb(15 23 42 / var(--tw-text-opacity, 1))}.dark\:text-teal-400:is(.dark *){--tw-text-opacity: 1;color:rgb(45 212 191 / var(--tw-text-opacity, 1))}.dark\:text-violet-300:is(.dark *){--tw-text-opacity: 1;color:rgb(196 181 253 / var(--tw-text-opacity, 1))}.dark\:accent-slate-100:is(.dark *){accent-color:#f1f5f9}.dark\:placeholder\:text-slate-500:is(.dark *)::-moz-placeholder{--tw-text-opacity: 1;color:rgb(100 116 139 / var(--tw-text-opacity, 1))}.dark\:placeholder\:text-slate-500:is(.dark *)::placeholder{--tw-text-opacity: 1;color:rgb(100 116 139 / var(--tw-text-opacity, 1))}.dark\:hover\:bg-slate-700:hover:is(.dark *){--tw-bg-opacity: 1;background-color:rgb(51 65 85 / var(--tw-bg-opacity, 1))}.dark\:hover\:bg-slate-800:hover:is(.dark *){--tw-bg-opacity: 1;background-color:rgb(30 41 59 / var(--tw-bg-opacity, 1))}.dark\:hover\:bg-slate-800\/30:hover:is(.dark *){background-color:#1e293b4d}.dark\:hover\:bg-slate-900\/70:hover:is(.dark *){background-color:#0f172ab3}.dark\:hover\:bg-white:hover:is(.dark *){--tw-bg-opacity: 1;background-color:rgb(255 255 255 / var(--tw-bg-opacity, 1))}.dark\:hover\:text-slate-200:hover:is(.dark *){--tw-text-opacity: 1;color:rgb(226 232 240 / var(--tw-text-opacity, 1))}.dark\:hover\:text-white:hover:is(.dark *){--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity, 1))}.dark\:focus\:ring-slate-700:focus:is(.dark *){--tw-ring-opacity: 1;--tw-ring-color: rgb(51 65 85 / var(--tw-ring-opacity, 1))}.dark\:focus-visible\:ring-slate-500\/50:focus-visible:is(.dark *){--tw-ring-color: rgb(100 116 139 / .5)}@media (min-width: 640px){.sm\:w-auto{width:auto}.sm\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.sm\:flex-row{flex-direction:row}.sm\:items-center{align-items:center}.sm\:px-4{padding-left:1rem;padding-right:1rem}.sm\:py-2{padding-top:.5rem;padding-bottom:.5rem}.sm\:py-2\.5{padding-top:.625rem;padding-bottom:.625rem}.sm\:py-8{padding-top:2rem;padding-bottom:2rem}} diff --git a/site/data/last-update.json b/site/data/last-update.json deleted file mode 100644 index 957fb8b..0000000 --- a/site/data/last-update.json +++ /dev/null @@ -1 +0,0 @@ -{"last_update":"2026-05-24","updating":false,"sources":[{"source_slug":"runetfreedom","source_name":"RuNet Freedom","last_update":"2026-05-24","compatible":"Xray, 3x-ui, V2Ray"},{"source_slug":"loyalsoldier","source_name":"Loyalsoldier","last_update":"2026-05-24","compatible":"Xray, 3x-ui, V2Ray, Mihomo"},{"source_slug":"daniellavrushin","source_name":"DanielLavrushin (b4geoip)","last_update":"2026-05-24","compatible":"Xray, 3x-ui"},{"source_slug":"v2fly","source_name":"v2fly (официальный)","last_update":"2026-05-24","compatible":"Xray, 3x-ui, V2Ray, Sing-box"}]} \ No newline at end of file diff --git a/site/data/presets.json b/site/data/presets.json deleted file mode 100644 index b967cdf..0000000 --- a/site/data/presets.json +++ /dev/null @@ -1 +0,0 @@ -[{"group":"Мессенджеры","icon":"💬","slug":"messengers","services":[{"name":"Telegram","domain":"telegram.org","keywords":["telegram"],"categories":[{"full_name":"geoip:telegram","name":"telegram","type":"geoip","source_slug":"daniellavrushin","source_name":"DanielLavrushin (b4geoip)"},{"full_name":"geoip:telegram","name":"telegram","type":"geoip","source_slug":"loyalsoldier","source_name":"Loyalsoldier"},{"full_name":"geoip:telegram","name":"telegram","type":"geoip","source_slug":"runetfreedom","source_name":"RuNet Freedom"},{"full_name":"geosite:telegram","name":"telegram","type":"geosite","source_slug":"loyalsoldier","source_name":"Loyalsoldier"},{"full_name":"geosite:telegram","name":"telegram","type":"geosite","source_slug":"runetfreedom","source_name":"RuNet Freedom"},{"full_name":"geosite:telegram","name":"telegram","type":"geosite","source_slug":"v2fly","source_name":"v2fly (официальный)"}]},{"name":"WhatsApp","domain":"whatsapp.com","keywords":["whatsapp"],"categories":[{"full_name":"geosite:whatsapp","name":"whatsapp","type":"geosite","source_slug":"loyalsoldier","source_name":"Loyalsoldier"},{"full_name":"geosite:whatsapp","name":"whatsapp","type":"geosite","source_slug":"runetfreedom","source_name":"RuNet Freedom"},{"full_name":"geosite:whatsapp","name":"whatsapp","type":"geosite","source_slug":"v2fly","source_name":"v2fly (официальный)"}]},{"name":"Viber","domain":"viber.com","keywords":["viber"],"categories":[{"full_name":"geosite:viber","name":"viber","type":"geosite","source_slug":"loyalsoldier","source_name":"Loyalsoldier"},{"full_name":"geosite:viber","name":"viber","type":"geosite","source_slug":"runetfreedom","source_name":"RuNet Freedom"},{"full_name":"geosite:viber","name":"viber","type":"geosite","source_slug":"v2fly","source_name":"v2fly (официальный)"}]},{"name":"Signal","domain":"signal.org","keywords":["signal"],"categories":[{"full_name":"geosite:signal","name":"signal","type":"geosite","source_slug":"loyalsoldier","source_name":"Loyalsoldier"},{"full_name":"geosite:signal","name":"signal","type":"geosite","source_slug":"runetfreedom","source_name":"RuNet Freedom"},{"full_name":"geosite:signal","name":"signal","type":"geosite","source_slug":"v2fly","source_name":"v2fly (официальный)"}]},{"name":"Discord","domain":"discord.com","keywords":["discord"],"categories":[{"full_name":"geosite:discord","name":"discord","type":"geosite","source_slug":"loyalsoldier","source_name":"Loyalsoldier"},{"full_name":"geosite:discord","name":"discord","type":"geosite","source_slug":"runetfreedom","source_name":"RuNet Freedom"},{"full_name":"geosite:discord","name":"discord","type":"geosite","source_slug":"v2fly","source_name":"v2fly (официальный)"}]},{"name":"Skype","domain":"skype.com","keywords":["skype"],"categories":[{"full_name":"geosite:skyperfect","name":"skyperfect","type":"geosite","source_slug":"loyalsoldier","source_name":"Loyalsoldier"},{"full_name":"geosite:skyperfect","name":"skyperfect","type":"geosite","source_slug":"runetfreedom","source_name":"RuNet Freedom"},{"full_name":"geosite:skyperfect","name":"skyperfect","type":"geosite","source_slug":"v2fly","source_name":"v2fly (официальный)"}]}]},{"group":"Видео","icon":"🎬","slug":"video","services":[{"name":"YouTube","domain":"youtube.com","keywords":["youtube","googlevideo","ytimg"],"categories":[{"full_name":"geosite:youtube","name":"youtube","type":"geosite","source_slug":"loyalsoldier","source_name":"Loyalsoldier"},{"full_name":"geosite:youtube","name":"youtube","type":"geosite","source_slug":"runetfreedom","source_name":"RuNet Freedom"},{"full_name":"geosite:youtube","name":"youtube","type":"geosite","source_slug":"v2fly","source_name":"v2fly (официальный)"}]},{"name":"Netflix","domain":"netflix.com","keywords":["netflix"],"categories":[{"full_name":"geoip:netflix","name":"netflix","type":"geoip","source_slug":"daniellavrushin","source_name":"DanielLavrushin (b4geoip)"},{"full_name":"geoip:netflix","name":"netflix","type":"geoip","source_slug":"loyalsoldier","source_name":"Loyalsoldier"},{"full_name":"geoip:netflix","name":"netflix","type":"geoip","source_slug":"runetfreedom","source_name":"RuNet Freedom"},{"full_name":"geosite:netflix","name":"netflix","type":"geosite","source_slug":"loyalsoldier","source_name":"Loyalsoldier"},{"full_name":"geosite:netflix","name":"netflix","type":"geosite","source_slug":"runetfreedom","source_name":"RuNet Freedom"},{"full_name":"geosite:netflix","name":"netflix","type":"geosite","source_slug":"v2fly","source_name":"v2fly (официальный)"}]},{"name":"Twitch","domain":"twitch.tv","keywords":["twitch"],"categories":[{"full_name":"geosite:twitch","name":"twitch","type":"geosite","source_slug":"loyalsoldier","source_name":"Loyalsoldier"},{"full_name":"geosite:twitch","name":"twitch","type":"geosite","source_slug":"runetfreedom","source_name":"RuNet Freedom"},{"full_name":"geosite:twitch","name":"twitch","type":"geosite","source_slug":"v2fly","source_name":"v2fly (официальный)"}]},{"name":"TikTok","domain":"tiktok.com","keywords":["tiktok"],"categories":[{"full_name":"geosite:tiktok","name":"tiktok","type":"geosite","source_slug":"loyalsoldier","source_name":"Loyalsoldier"},{"full_name":"geosite:tiktok","name":"tiktok","type":"geosite","source_slug":"runetfreedom","source_name":"RuNet Freedom"},{"full_name":"geosite:tiktok","name":"tiktok","type":"geosite","source_slug":"v2fly","source_name":"v2fly (официальный)"}]},{"name":"Vimeo","domain":"vimeo.com","keywords":["vimeo"],"categories":[{"full_name":"geosite:vimeo","name":"vimeo","type":"geosite","source_slug":"loyalsoldier","source_name":"Loyalsoldier"},{"full_name":"geosite:vimeo","name":"vimeo","type":"geosite","source_slug":"runetfreedom","source_name":"RuNet Freedom"},{"full_name":"geosite:vimeo","name":"vimeo","type":"geosite","source_slug":"v2fly","source_name":"v2fly (официальный)"}]},{"name":"Rutube","domain":"rutube.ru","keywords":["rutube"],"categories":[{"full_name":"geosite:rutube","name":"rutube","type":"geosite","source_slug":"loyalsoldier","source_name":"Loyalsoldier"},{"full_name":"geosite:rutube","name":"rutube","type":"geosite","source_slug":"runetfreedom","source_name":"RuNet Freedom"},{"full_name":"geosite:rutube","name":"rutube","type":"geosite","source_slug":"v2fly","source_name":"v2fly (официальный)"}]}]},{"group":"Социальные сети","icon":"🌐","slug":"social","services":[{"name":"Facebook","domain":"facebook.com","keywords":["facebook","meta"],"categories":[{"full_name":"geoip:facebook","name":"facebook","type":"geoip","source_slug":"daniellavrushin","source_name":"DanielLavrushin (b4geoip)"},{"full_name":"geoip:facebook","name":"facebook","type":"geoip","source_slug":"loyalsoldier","source_name":"Loyalsoldier"},{"full_name":"geoip:facebook","name":"facebook","type":"geoip","source_slug":"runetfreedom","source_name":"RuNet Freedom"},{"full_name":"geosite:facebook","name":"facebook","type":"geosite","source_slug":"loyalsoldier","source_name":"Loyalsoldier"},{"full_name":"geosite:facebook","name":"facebook","type":"geosite","source_slug":"runetfreedom","source_name":"RuNet Freedom"},{"full_name":"geosite:facebook","name":"facebook","type":"geosite","source_slug":"v2fly","source_name":"v2fly (официальный)"},{"full_name":"geosite:facebook-dev","name":"facebook-dev","type":"geosite","source_slug":"loyalsoldier","source_name":"Loyalsoldier"},{"full_name":"geosite:facebook-dev","name":"facebook-dev","type":"geosite","source_slug":"runetfreedom","source_name":"RuNet Freedom"},{"full_name":"geosite:facebook-dev","name":"facebook-dev","type":"geosite","source_slug":"v2fly","source_name":"v2fly (официальный)"},{"full_name":"geoip:meta","name":"meta","type":"geoip","source_slug":"daniellavrushin","source_name":"DanielLavrushin (b4geoip)"},{"full_name":"geosite:meta","name":"meta","type":"geosite","source_slug":"loyalsoldier","source_name":"Loyalsoldier"},{"full_name":"geosite:meta","name":"meta","type":"geosite","source_slug":"runetfreedom","source_name":"RuNet Freedom"},{"full_name":"geosite:meta","name":"meta","type":"geosite","source_slug":"v2fly","source_name":"v2fly (официальный)"},{"full_name":"geosite:metabrainz","name":"metabrainz","type":"geosite","source_slug":"loyalsoldier","source_name":"Loyalsoldier"},{"full_name":"geosite:metabrainz","name":"metabrainz","type":"geosite","source_slug":"runetfreedom","source_name":"RuNet Freedom"},{"full_name":"geosite:metabrainz","name":"metabrainz","type":"geosite","source_slug":"v2fly","source_name":"v2fly (официальный)"},{"full_name":"geosite:metacritic","name":"metacritic","type":"geosite","source_slug":"loyalsoldier","source_name":"Loyalsoldier"},{"full_name":"geosite:metacritic","name":"metacritic","type":"geosite","source_slug":"runetfreedom","source_name":"RuNet Freedom"},{"full_name":"geosite:metacritic","name":"metacritic","type":"geosite","source_slug":"v2fly","source_name":"v2fly (официальный)"}]},{"name":"Instagram","domain":"instagram.com","keywords":["instagram"],"categories":[{"full_name":"geosite:instagram","name":"instagram","type":"geosite","source_slug":"loyalsoldier","source_name":"Loyalsoldier"},{"full_name":"geosite:instagram","name":"instagram","type":"geosite","source_slug":"runetfreedom","source_name":"RuNet Freedom"},{"full_name":"geosite:instagram","name":"instagram","type":"geosite","source_slug":"v2fly","source_name":"v2fly (официальный)"}]},{"name":"Twitter/X","domain":"x.com","keywords":["twitter"],"categories":[{"full_name":"geoip:twitter","name":"twitter","type":"geoip","source_slug":"daniellavrushin","source_name":"DanielLavrushin (b4geoip)"},{"full_name":"geoip:twitter","name":"twitter","type":"geoip","source_slug":"loyalsoldier","source_name":"Loyalsoldier"},{"full_name":"geoip:twitter","name":"twitter","type":"geoip","source_slug":"runetfreedom","source_name":"RuNet Freedom"},{"full_name":"geosite:twitter","name":"twitter","type":"geosite","source_slug":"loyalsoldier","source_name":"Loyalsoldier"},{"full_name":"geosite:twitter","name":"twitter","type":"geosite","source_slug":"runetfreedom","source_name":"RuNet Freedom"},{"full_name":"geosite:twitter","name":"twitter","type":"geosite","source_slug":"v2fly","source_name":"v2fly (официальный)"}]},{"name":"LinkedIn","domain":"linkedin.com","keywords":["linkedin"],"categories":[{"full_name":"geosite:linkedin","name":"linkedin","type":"geosite","source_slug":"loyalsoldier","source_name":"Loyalsoldier"},{"full_name":"geosite:linkedin","name":"linkedin","type":"geosite","source_slug":"runetfreedom","source_name":"RuNet Freedom"},{"full_name":"geosite:linkedin","name":"linkedin","type":"geosite","source_slug":"v2fly","source_name":"v2fly (официальный)"}]},{"name":"Pinterest","domain":"pinterest.com","keywords":["pinterest"],"categories":[{"full_name":"geosite:pinterest","name":"pinterest","type":"geosite","source_slug":"loyalsoldier","source_name":"Loyalsoldier"},{"full_name":"geosite:pinterest","name":"pinterest","type":"geosite","source_slug":"runetfreedom","source_name":"RuNet Freedom"},{"full_name":"geosite:pinterest","name":"pinterest","type":"geosite","source_slug":"v2fly","source_name":"v2fly (официальный)"}]},{"name":"Reddit","domain":"reddit.com","keywords":["reddit"],"categories":[{"full_name":"geosite:reddit","name":"reddit","type":"geosite","source_slug":"loyalsoldier","source_name":"Loyalsoldier"},{"full_name":"geosite:reddit","name":"reddit","type":"geosite","source_slug":"runetfreedom","source_name":"RuNet Freedom"},{"full_name":"geosite:reddit","name":"reddit","type":"geosite","source_slug":"v2fly","source_name":"v2fly (официальный)"}]},{"name":"Tumblr","domain":"tumblr.com","keywords":["tumblr"],"categories":[{"full_name":"geosite:tumblr","name":"tumblr","type":"geosite","source_slug":"loyalsoldier","source_name":"Loyalsoldier"},{"full_name":"geosite:tumblr","name":"tumblr","type":"geosite","source_slug":"runetfreedom","source_name":"RuNet Freedom"},{"full_name":"geosite:tumblr","name":"tumblr","type":"geosite","source_slug":"v2fly","source_name":"v2fly (официальный)"}]}]},{"group":"Музыка","icon":"🎵","slug":"music","services":[{"name":"Spotify","domain":"spotify.com","keywords":["spotify"],"categories":[{"full_name":"geosite:spotify","name":"spotify","type":"geosite","source_slug":"loyalsoldier","source_name":"Loyalsoldier"},{"full_name":"geosite:spotify","name":"spotify","type":"geosite","source_slug":"runetfreedom","source_name":"RuNet Freedom"},{"full_name":"geosite:spotify","name":"spotify","type":"geosite","source_slug":"v2fly","source_name":"v2fly (официальный)"}]},{"name":"Apple Music","domain":"apple.com","keywords":["apple","itunes"],"categories":[{"full_name":"geoip:apple","name":"apple","type":"geoip","source_slug":"daniellavrushin","source_name":"DanielLavrushin (b4geoip)"},{"full_name":"geosite:apple","name":"apple","type":"geosite","source_slug":"loyalsoldier","source_name":"Loyalsoldier"},{"full_name":"geosite:apple","name":"apple","type":"geosite","source_slug":"runetfreedom","source_name":"RuNet Freedom"},{"full_name":"geosite:apple","name":"apple","type":"geosite","source_slug":"v2fly","source_name":"v2fly (официальный)"},{"full_name":"geosite:apple-cn","name":"apple-cn","type":"geosite","source_slug":"loyalsoldier","source_name":"Loyalsoldier"},{"full_name":"geosite:apple-dev","name":"apple-dev","type":"geosite","source_slug":"loyalsoldier","source_name":"Loyalsoldier"},{"full_name":"geosite:apple-dev","name":"apple-dev","type":"geosite","source_slug":"runetfreedom","source_name":"RuNet Freedom"},{"full_name":"geosite:apple-dev","name":"apple-dev","type":"geosite","source_slug":"v2fly","source_name":"v2fly (официальный)"},{"full_name":"geosite:apple-intelligence","name":"apple-intelligence","type":"geosite","source_slug":"loyalsoldier","source_name":"Loyalsoldier"},{"full_name":"geosite:apple-intelligence","name":"apple-intelligence","type":"geosite","source_slug":"runetfreedom","source_name":"RuNet Freedom"},{"full_name":"geosite:itunes","name":"itunes","type":"geosite","source_slug":"loyalsoldier","source_name":"Loyalsoldier"},{"full_name":"geosite:itunes","name":"itunes","type":"geosite","source_slug":"runetfreedom","source_name":"RuNet Freedom"},{"full_name":"geosite:itunes","name":"itunes","type":"geosite","source_slug":"v2fly","source_name":"v2fly (официальный)"}]},{"name":"SoundCloud","domain":"soundcloud.com","keywords":["soundcloud"],"categories":[{"full_name":"geosite:soundcloud","name":"soundcloud","type":"geosite","source_slug":"loyalsoldier","source_name":"Loyalsoldier"},{"full_name":"geosite:soundcloud","name":"soundcloud","type":"geosite","source_slug":"runetfreedom","source_name":"RuNet Freedom"},{"full_name":"geosite:soundcloud","name":"soundcloud","type":"geosite","source_slug":"v2fly","source_name":"v2fly (официальный)"}]},{"name":"Deezer","domain":"deezer.com","keywords":["deezer"],"categories":[{"full_name":"geosite:deezer","name":"deezer","type":"geosite","source_slug":"loyalsoldier","source_name":"Loyalsoldier"},{"full_name":"geosite:deezer","name":"deezer","type":"geosite","source_slug":"runetfreedom","source_name":"RuNet Freedom"},{"full_name":"geosite:deezer","name":"deezer","type":"geosite","source_slug":"v2fly","source_name":"v2fly (официальный)"}]},{"name":"Last.fm","domain":"last.fm","keywords":["lastfm"],"categories":[{"full_name":"geosite:lastfm","name":"lastfm","type":"geosite","source_slug":"loyalsoldier","source_name":"Loyalsoldier"},{"full_name":"geosite:lastfm","name":"lastfm","type":"geosite","source_slug":"runetfreedom","source_name":"RuNet Freedom"},{"full_name":"geosite:lastfm","name":"lastfm","type":"geosite","source_slug":"v2fly","source_name":"v2fly (официальный)"}]}]},{"group":"Искусственный интеллект","icon":"🤖","slug":"ai","services":[{"name":"ChatGPT","domain":"openai.com","keywords":["openai","chatgpt"],"categories":[{"full_name":"geosite:openai","name":"openai","type":"geosite","source_slug":"loyalsoldier","source_name":"Loyalsoldier"},{"full_name":"geosite:openai","name":"openai","type":"geosite","source_slug":"runetfreedom","source_name":"RuNet Freedom"},{"full_name":"geosite:openai","name":"openai","type":"geosite","source_slug":"v2fly","source_name":"v2fly (официальный)"}]},{"name":"Claude","domain":"anthropic.com","keywords":["claude","anthropic"],"categories":[{"full_name":"geoip:anthropic","name":"anthropic","type":"geoip","source_slug":"daniellavrushin","source_name":"DanielLavrushin (b4geoip)"},{"full_name":"geosite:anthropic","name":"anthropic","type":"geosite","source_slug":"loyalsoldier","source_name":"Loyalsoldier"},{"full_name":"geosite:anthropic","name":"anthropic","type":"geosite","source_slug":"runetfreedom","source_name":"RuNet Freedom"},{"full_name":"geosite:anthropic","name":"anthropic","type":"geosite","source_slug":"v2fly","source_name":"v2fly (официальный)"}]},{"name":"Gemini","domain":"gemini.google.com","keywords":["gemini","bard"],"categories":[{"full_name":"geosite:google-gemini","name":"google-gemini","type":"geosite","source_slug":"loyalsoldier","source_name":"Loyalsoldier"},{"full_name":"geosite:google-gemini","name":"google-gemini","type":"geosite","source_slug":"runetfreedom","source_name":"RuNet Freedom"},{"full_name":"geosite:google-gemini","name":"google-gemini","type":"geosite","source_slug":"v2fly","source_name":"v2fly (официальный)"}]},{"name":"Midjourney","domain":"midjourney.com","keywords":["midjourney"],"categories":[]},{"name":"Hugging Face","domain":"huggingface.co","keywords":["huggingface"],"categories":[{"full_name":"geosite:huggingface","name":"huggingface","type":"geosite","source_slug":"loyalsoldier","source_name":"Loyalsoldier"},{"full_name":"geosite:huggingface","name":"huggingface","type":"geosite","source_slug":"runetfreedom","source_name":"RuNet Freedom"},{"full_name":"geosite:huggingface","name":"huggingface","type":"geosite","source_slug":"v2fly","source_name":"v2fly (официальный)"}]},{"name":"Perplexity","domain":"perplexity.ai","keywords":["perplexity"],"categories":[{"full_name":"geosite:perplexity","name":"perplexity","type":"geosite","source_slug":"loyalsoldier","source_name":"Loyalsoldier"},{"full_name":"geosite:perplexity","name":"perplexity","type":"geosite","source_slug":"runetfreedom","source_name":"RuNet Freedom"},{"full_name":"geosite:perplexity","name":"perplexity","type":"geosite","source_slug":"v2fly","source_name":"v2fly (официальный)"}]},{"name":"Stability AI","domain":"stability.ai","keywords":["stability"],"categories":[]}]},{"group":"Игры","icon":"🎮","slug":"games","services":[{"name":"Steam","domain":"store.steampowered.com","keywords":["steam","steamcontent"],"categories":[{"full_name":"geosite:steam","name":"steam","type":"geosite","source_slug":"loyalsoldier","source_name":"Loyalsoldier"},{"full_name":"geosite:steam","name":"steam","type":"geosite","source_slug":"runetfreedom","source_name":"RuNet Freedom"},{"full_name":"geosite:steam","name":"steam","type":"geosite","source_slug":"v2fly","source_name":"v2fly (официальный)"},{"full_name":"geosite:steaminventoryhelper","name":"steaminventoryhelper","type":"geosite","source_slug":"loyalsoldier","source_name":"Loyalsoldier"},{"full_name":"geosite:steaminventoryhelper","name":"steaminventoryhelper","type":"geosite","source_slug":"runetfreedom","source_name":"RuNet Freedom"},{"full_name":"geosite:steaminventoryhelper","name":"steaminventoryhelper","type":"geosite","source_slug":"v2fly","source_name":"v2fly (официальный)"},{"full_name":"geosite:steamunlocked","name":"steamunlocked","type":"geosite","source_slug":"loyalsoldier","source_name":"Loyalsoldier"},{"full_name":"geosite:steamunlocked","name":"steamunlocked","type":"geosite","source_slug":"runetfreedom","source_name":"RuNet Freedom"},{"full_name":"geosite:steamunlocked","name":"steamunlocked","type":"geosite","source_slug":"v2fly","source_name":"v2fly (официальный)"}]},{"name":"Epic Games","domain":"epicgames.com","keywords":["epicgames","epic"],"categories":[{"full_name":"geoip:epicgames","name":"epicgames","type":"geoip","source_slug":"daniellavrushin","source_name":"DanielLavrushin (b4geoip)"},{"full_name":"geosite:epicgames","name":"epicgames","type":"geosite","source_slug":"loyalsoldier","source_name":"Loyalsoldier"},{"full_name":"geosite:epicgames","name":"epicgames","type":"geosite","source_slug":"runetfreedom","source_name":"RuNet Freedom"},{"full_name":"geosite:epicgames","name":"epicgames","type":"geosite","source_slug":"v2fly","source_name":"v2fly (официальный)"},{"full_name":"geosite:epicbrowser","name":"epicbrowser","type":"geosite","source_slug":"loyalsoldier","source_name":"Loyalsoldier"},{"full_name":"geosite:epicbrowser","name":"epicbrowser","type":"geosite","source_slug":"runetfreedom","source_name":"RuNet Freedom"},{"full_name":"geosite:epicbrowser","name":"epicbrowser","type":"geosite","source_slug":"v2fly","source_name":"v2fly (официальный)"}]},{"name":"PlayStation","domain":"playstation.com","keywords":["playstation","psn"],"categories":[{"full_name":"geosite:playstation","name":"playstation","type":"geosite","source_slug":"loyalsoldier","source_name":"Loyalsoldier"},{"full_name":"geosite:playstation","name":"playstation","type":"geosite","source_slug":"runetfreedom","source_name":"RuNet Freedom"},{"full_name":"geosite:playstation","name":"playstation","type":"geosite","source_slug":"v2fly","source_name":"v2fly (официальный)"}]},{"name":"Xbox","domain":"xbox.com","keywords":["xbox","xboxlive"],"categories":[{"full_name":"geosite:xbox","name":"xbox","type":"geosite","source_slug":"loyalsoldier","source_name":"Loyalsoldier"},{"full_name":"geosite:xbox","name":"xbox","type":"geosite","source_slug":"runetfreedom","source_name":"RuNet Freedom"},{"full_name":"geosite:xbox","name":"xbox","type":"geosite","source_slug":"v2fly","source_name":"v2fly (официальный)"}]},{"name":"EA / Origin","domain":"ea.com","keywords":["ea.com","origin","easports"],"categories":[{"full_name":"geosite:origin","name":"origin","type":"geosite","source_slug":"loyalsoldier","source_name":"Loyalsoldier"},{"full_name":"geosite:origin","name":"origin","type":"geosite","source_slug":"runetfreedom","source_name":"RuNet Freedom"},{"full_name":"geosite:origin","name":"origin","type":"geosite","source_slug":"v2fly","source_name":"v2fly (официальный)"}]},{"name":"Blizzard","domain":"blizzard.com","keywords":["blizzard","battle.net"],"categories":[{"full_name":"geoip:blizzard","name":"blizzard","type":"geoip","source_slug":"daniellavrushin","source_name":"DanielLavrushin (b4geoip)"},{"full_name":"geosite:blizzard","name":"blizzard","type":"geosite","source_slug":"loyalsoldier","source_name":"Loyalsoldier"},{"full_name":"geosite:blizzard","name":"blizzard","type":"geosite","source_slug":"runetfreedom","source_name":"RuNet Freedom"},{"full_name":"geosite:blizzard","name":"blizzard","type":"geosite","source_slug":"v2fly","source_name":"v2fly (официальный)"}]},{"name":"Riot Games","domain":"riotgames.com","keywords":["riotgames","valorant"],"categories":[]},{"name":"Roblox","domain":"roblox.com","keywords":["roblox"],"categories":[{"full_name":"geoip:roblox","name":"roblox","type":"geoip","source_slug":"daniellavrushin","source_name":"DanielLavrushin (b4geoip)"},{"full_name":"geosite:roblox","name":"roblox","type":"geosite","source_slug":"loyalsoldier","source_name":"Loyalsoldier"},{"full_name":"geosite:roblox","name":"roblox","type":"geosite","source_slug":"runetfreedom","source_name":"RuNet Freedom"},{"full_name":"geosite:roblox","name":"roblox","type":"geosite","source_slug":"v2fly","source_name":"v2fly (официальный)"}]},{"name":"Minecraft","domain":"minecraft.net","keywords":["minecraft","mojang"],"categories":[{"full_name":"geosite:mojang","name":"mojang","type":"geosite","source_slug":"loyalsoldier","source_name":"Loyalsoldier"},{"full_name":"geosite:mojang","name":"mojang","type":"geosite","source_slug":"runetfreedom","source_name":"RuNet Freedom"},{"full_name":"geosite:mojang","name":"mojang","type":"geosite","source_slug":"v2fly","source_name":"v2fly (официальный)"}]},{"name":"GOG","domain":"gog.com","keywords":["gog"],"categories":[{"full_name":"geosite:gog","name":"gog","type":"geosite","source_slug":"loyalsoldier","source_name":"Loyalsoldier"},{"full_name":"geosite:gog","name":"gog","type":"geosite","source_slug":"runetfreedom","source_name":"RuNet Freedom"},{"full_name":"geosite:gog","name":"gog","type":"geosite","source_slug":"v2fly","source_name":"v2fly (официальный)"}]}]},{"group":"Работа и инструменты","icon":"💼","slug":"work","services":[{"name":"Google","domain":"google.com","keywords":["google","googleapis"],"categories":[{"full_name":"geoip:google","name":"google","type":"geoip","source_slug":"daniellavrushin","source_name":"DanielLavrushin (b4geoip)"},{"full_name":"geoip:google","name":"google","type":"geoip","source_slug":"loyalsoldier","source_name":"Loyalsoldier"},{"full_name":"geoip:google","name":"google","type":"geoip","source_slug":"runetfreedom","source_name":"RuNet Freedom"},{"full_name":"geosite:google","name":"google","type":"geosite","source_slug":"loyalsoldier","source_name":"Loyalsoldier"},{"full_name":"geosite:google","name":"google","type":"geosite","source_slug":"runetfreedom","source_name":"RuNet Freedom"},{"full_name":"geosite:google","name":"google","type":"geosite","source_slug":"v2fly","source_name":"v2fly (официальный)"},{"full_name":"geosite:google-cn","name":"google-cn","type":"geosite","source_slug":"loyalsoldier","source_name":"Loyalsoldier"},{"full_name":"geosite:google-deepmind","name":"google-deepmind","type":"geosite","source_slug":"loyalsoldier","source_name":"Loyalsoldier"},{"full_name":"geosite:google-deepmind","name":"google-deepmind","type":"geosite","source_slug":"runetfreedom","source_name":"RuNet Freedom"},{"full_name":"geosite:google-deepmind","name":"google-deepmind","type":"geosite","source_slug":"v2fly","source_name":"v2fly (официальный)"}]},{"name":"Microsoft","domain":"microsoft.com","keywords":["microsoft","office365"],"categories":[{"full_name":"geosite:microsoft","name":"microsoft","type":"geosite","source_slug":"loyalsoldier","source_name":"Loyalsoldier"},{"full_name":"geosite:microsoft","name":"microsoft","type":"geosite","source_slug":"runetfreedom","source_name":"RuNet Freedom"},{"full_name":"geosite:microsoft","name":"microsoft","type":"geosite","source_slug":"v2fly","source_name":"v2fly (официальный)"},{"full_name":"geosite:microsoft-dev","name":"microsoft-dev","type":"geosite","source_slug":"loyalsoldier","source_name":"Loyalsoldier"},{"full_name":"geosite:microsoft-dev","name":"microsoft-dev","type":"geosite","source_slug":"runetfreedom","source_name":"RuNet Freedom"},{"full_name":"geosite:microsoft-dev","name":"microsoft-dev","type":"geosite","source_slug":"v2fly","source_name":"v2fly (официальный)"},{"full_name":"geosite:microsoft-pki","name":"microsoft-pki","type":"geosite","source_slug":"loyalsoldier","source_name":"Loyalsoldier"},{"full_name":"geosite:microsoft-pki","name":"microsoft-pki","type":"geosite","source_slug":"runetfreedom","source_name":"RuNet Freedom"},{"full_name":"geosite:microsoft-pki","name":"microsoft-pki","type":"geosite","source_slug":"v2fly","source_name":"v2fly (официальный)"}]},{"name":"Zoom","domain":"zoom.us","keywords":["zoom"],"categories":[{"full_name":"geosite:zoom","name":"zoom","type":"geosite","source_slug":"loyalsoldier","source_name":"Loyalsoldier"},{"full_name":"geosite:zoom","name":"zoom","type":"geosite","source_slug":"runetfreedom","source_name":"RuNet Freedom"},{"full_name":"geosite:zoom","name":"zoom","type":"geosite","source_slug":"v2fly","source_name":"v2fly (официальный)"}]},{"name":"Slack","domain":"slack.com","keywords":["slack"],"categories":[{"full_name":"geosite:slack","name":"slack","type":"geosite","source_slug":"loyalsoldier","source_name":"Loyalsoldier"},{"full_name":"geosite:slack","name":"slack","type":"geosite","source_slug":"runetfreedom","source_name":"RuNet Freedom"},{"full_name":"geosite:slack","name":"slack","type":"geosite","source_slug":"v2fly","source_name":"v2fly (официальный)"}]},{"name":"Notion","domain":"notion.so","keywords":["notion"],"categories":[{"full_name":"geosite:notion","name":"notion","type":"geosite","source_slug":"loyalsoldier","source_name":"Loyalsoldier"},{"full_name":"geosite:notion","name":"notion","type":"geosite","source_slug":"runetfreedom","source_name":"RuNet Freedom"},{"full_name":"geosite:notion","name":"notion","type":"geosite","source_slug":"v2fly","source_name":"v2fly (официальный)"}]},{"name":"Figma","domain":"figma.com","keywords":["figma"],"categories":[{"full_name":"geosite:figma","name":"figma","type":"geosite","source_slug":"loyalsoldier","source_name":"Loyalsoldier"},{"full_name":"geosite:figma","name":"figma","type":"geosite","source_slug":"runetfreedom","source_name":"RuNet Freedom"},{"full_name":"geosite:figma","name":"figma","type":"geosite","source_slug":"v2fly","source_name":"v2fly (официальный)"}]},{"name":"GitHub","domain":"github.com","keywords":["github","githubusercontent"],"categories":[{"full_name":"geoip:github","name":"github","type":"geoip","source_slug":"daniellavrushin","source_name":"DanielLavrushin (b4geoip)"},{"full_name":"geosite:github","name":"github","type":"geosite","source_slug":"loyalsoldier","source_name":"Loyalsoldier"},{"full_name":"geosite:github","name":"github","type":"geosite","source_slug":"runetfreedom","source_name":"RuNet Freedom"},{"full_name":"geosite:github","name":"github","type":"geosite","source_slug":"v2fly","source_name":"v2fly (официальный)"},{"full_name":"geosite:github-copilot","name":"github-copilot","type":"geosite","source_slug":"loyalsoldier","source_name":"Loyalsoldier"},{"full_name":"geosite:github-copilot","name":"github-copilot","type":"geosite","source_slug":"runetfreedom","source_name":"RuNet Freedom"},{"full_name":"geosite:github-copilot","name":"github-copilot","type":"geosite","source_slug":"v2fly","source_name":"v2fly (официальный)"},{"full_name":"geosite:github1s","name":"github1s","type":"geosite","source_slug":"loyalsoldier","source_name":"Loyalsoldier"},{"full_name":"geosite:github1s","name":"github1s","type":"geosite","source_slug":"runetfreedom","source_name":"RuNet Freedom"},{"full_name":"geosite:github1s","name":"github1s","type":"geosite","source_slug":"v2fly","source_name":"v2fly (официальный)"}]},{"name":"AnyDesk","domain":"anydesk.com","keywords":["anydesk"],"categories":[]},{"name":"TeamViewer","domain":"teamviewer.com","keywords":["teamviewer"],"categories":[{"full_name":"geosite:teamviewer","name":"teamviewer","type":"geosite","source_slug":"loyalsoldier","source_name":"Loyalsoldier"},{"full_name":"geosite:teamviewer","name":"teamviewer","type":"geosite","source_slug":"runetfreedom","source_name":"RuNet Freedom"},{"full_name":"geosite:teamviewer","name":"teamviewer","type":"geosite","source_slug":"v2fly","source_name":"v2fly (официальный)"}]},{"name":"Atlassian","domain":"atlassian.com","keywords":["atlassian","jira"],"categories":[{"full_name":"geosite:atlassian","name":"atlassian","type":"geosite","source_slug":"loyalsoldier","source_name":"Loyalsoldier"},{"full_name":"geosite:atlassian","name":"atlassian","type":"geosite","source_slug":"runetfreedom","source_name":"RuNet Freedom"},{"full_name":"geosite:atlassian","name":"atlassian","type":"geosite","source_slug":"v2fly","source_name":"v2fly (официальный)"}]}]},{"group":"CDN и инфраструктура","icon":"☁️","slug":"cdn","services":[{"name":"Cloudflare","domain":"cloudflare.com","keywords":["cloudflare"],"categories":[{"full_name":"geoip:cloudflare","name":"cloudflare","type":"geoip","source_slug":"daniellavrushin","source_name":"DanielLavrushin (b4geoip)"},{"full_name":"geoip:cloudflare","name":"cloudflare","type":"geoip","source_slug":"loyalsoldier","source_name":"Loyalsoldier"},{"full_name":"geoip:cloudflare","name":"cloudflare","type":"geoip","source_slug":"runetfreedom","source_name":"RuNet Freedom"},{"full_name":"geosite:cloudflare","name":"cloudflare","type":"geosite","source_slug":"loyalsoldier","source_name":"Loyalsoldier"},{"full_name":"geosite:cloudflare","name":"cloudflare","type":"geosite","source_slug":"runetfreedom","source_name":"RuNet Freedom"},{"full_name":"geosite:cloudflare","name":"cloudflare","type":"geosite","source_slug":"v2fly","source_name":"v2fly (официальный)"},{"full_name":"geosite:cloudflare-cn","name":"cloudflare-cn","type":"geosite","source_slug":"loyalsoldier","source_name":"Loyalsoldier"},{"full_name":"geosite:cloudflare-cn","name":"cloudflare-cn","type":"geosite","source_slug":"runetfreedom","source_name":"RuNet Freedom"},{"full_name":"geosite:cloudflare-cn","name":"cloudflare-cn","type":"geosite","source_slug":"v2fly","source_name":"v2fly (официальный)"},{"full_name":"geosite:cloudflare-ipfs","name":"cloudflare-ipfs","type":"geosite","source_slug":"loyalsoldier","source_name":"Loyalsoldier"}]},{"name":"Akamai","domain":"akamai.com","keywords":["akamai"],"categories":[{"full_name":"geoip:akamai","name":"akamai","type":"geoip","source_slug":"daniellavrushin","source_name":"DanielLavrushin (b4geoip)"},{"full_name":"geosite:akamai","name":"akamai","type":"geosite","source_slug":"loyalsoldier","source_name":"Loyalsoldier"},{"full_name":"geosite:akamai","name":"akamai","type":"geosite","source_slug":"runetfreedom","source_name":"RuNet Freedom"},{"full_name":"geosite:akamai","name":"akamai","type":"geosite","source_slug":"v2fly","source_name":"v2fly (официальный)"}]},{"name":"Fastly","domain":"fastly.com","keywords":["fastly"],"categories":[{"full_name":"geoip:fastly","name":"fastly","type":"geoip","source_slug":"daniellavrushin","source_name":"DanielLavrushin (b4geoip)"},{"full_name":"geoip:fastly","name":"fastly","type":"geoip","source_slug":"loyalsoldier","source_name":"Loyalsoldier"},{"full_name":"geoip:fastly","name":"fastly","type":"geoip","source_slug":"runetfreedom","source_name":"RuNet Freedom"},{"full_name":"geosite:fastly","name":"fastly","type":"geosite","source_slug":"loyalsoldier","source_name":"Loyalsoldier"},{"full_name":"geosite:fastly","name":"fastly","type":"geosite","source_slug":"runetfreedom","source_name":"RuNet Freedom"},{"full_name":"geosite:fastly","name":"fastly","type":"geosite","source_slug":"v2fly","source_name":"v2fly (официальный)"}]},{"name":"Amazon AWS","domain":"aws.amazon.com","keywords":["amazon","amazonaws"],"categories":[{"full_name":"geoip:amazon","name":"amazon","type":"geoip","source_slug":"daniellavrushin","source_name":"DanielLavrushin (b4geoip)"},{"full_name":"geosite:amazon","name":"amazon","type":"geosite","source_slug":"loyalsoldier","source_name":"Loyalsoldier"},{"full_name":"geosite:amazon","name":"amazon","type":"geosite","source_slug":"runetfreedom","source_name":"RuNet Freedom"},{"full_name":"geosite:amazon","name":"amazon","type":"geosite","source_slug":"v2fly","source_name":"v2fly (официальный)"},{"full_name":"geosite:amazontrust","name":"amazontrust","type":"geosite","source_slug":"loyalsoldier","source_name":"Loyalsoldier"},{"full_name":"geosite:amazontrust","name":"amazontrust","type":"geosite","source_slug":"runetfreedom","source_name":"RuNet Freedom"},{"full_name":"geosite:amazontrust","name":"amazontrust","type":"geosite","source_slug":"v2fly","source_name":"v2fly (официальный)"}]},{"name":"Google Cloud","domain":"cloud.google.com","keywords":["googleusercontent","gstatic"],"categories":[]}]},{"group":"Заблокировано в РФ","icon":"🛡️","slug":"blocked_ru","services":[{"name":"Список РКН","domain":"rkn.gov.ru","keywords":["ru-blocked","rkn","russia-blocked"],"categories":[{"full_name":"geoip:ru-blocked","name":"ru-blocked","type":"geoip","source_slug":"daniellavrushin","source_name":"DanielLavrushin (b4geoip)"},{"full_name":"geoip:ru-blocked","name":"ru-blocked","type":"geoip","source_slug":"runetfreedom","source_name":"RuNet Freedom"},{"full_name":"geoip:ru-blocked-community","name":"ru-blocked-community","type":"geoip","source_slug":"daniellavrushin","source_name":"DanielLavrushin (b4geoip)"},{"full_name":"geoip:ru-blocked-community","name":"ru-blocked-community","type":"geoip","source_slug":"runetfreedom","source_name":"RuNet Freedom"},{"full_name":"geosite:category-media-ru-blocked","name":"category-media-ru-blocked","type":"geosite","source_slug":"loyalsoldier","source_name":"Loyalsoldier"},{"full_name":"geosite:category-media-ru-blocked","name":"category-media-ru-blocked","type":"geosite","source_slug":"runetfreedom","source_name":"RuNet Freedom"},{"full_name":"geosite:category-media-ru-blocked","name":"category-media-ru-blocked","type":"geosite","source_slug":"v2fly","source_name":"v2fly (официальный)"},{"full_name":"geosite:ru-blocked","name":"ru-blocked","type":"geosite","source_slug":"runetfreedom","source_name":"RuNet Freedom"},{"full_name":"geosite:ru-blocked-all","name":"ru-blocked-all","type":"geosite","source_slug":"runetfreedom","source_name":"RuNet Freedom"}]},{"name":"Антифильтр","domain":"antifilter.download","keywords":["antifilter"],"categories":[{"full_name":"geosite:antifilter-download","name":"antifilter-download","type":"geosite","source_slug":"runetfreedom","source_name":"RuNet Freedom"},{"full_name":"geosite:antifilter-download-community","name":"antifilter-download-community","type":"geosite","source_slug":"runetfreedom","source_name":"RuNet Freedom"}]},{"name":"Заблокировано","domain":"geoexport.org","keywords":["blocked"],"categories":[{"full_name":"geoip:ru-blocked","name":"ru-blocked","type":"geoip","source_slug":"daniellavrushin","source_name":"DanielLavrushin (b4geoip)"},{"full_name":"geoip:ru-blocked","name":"ru-blocked","type":"geoip","source_slug":"runetfreedom","source_name":"RuNet Freedom"},{"full_name":"geoip:ru-blocked-community","name":"ru-blocked-community","type":"geoip","source_slug":"daniellavrushin","source_name":"DanielLavrushin (b4geoip)"},{"full_name":"geoip:ru-blocked-community","name":"ru-blocked-community","type":"geoip","source_slug":"runetfreedom","source_name":"RuNet Freedom"},{"full_name":"geosite:category-media-ru-blocked","name":"category-media-ru-blocked","type":"geosite","source_slug":"loyalsoldier","source_name":"Loyalsoldier"},{"full_name":"geosite:category-media-ru-blocked","name":"category-media-ru-blocked","type":"geosite","source_slug":"runetfreedom","source_name":"RuNet Freedom"},{"full_name":"geosite:category-media-ru-blocked","name":"category-media-ru-blocked","type":"geosite","source_slug":"v2fly","source_name":"v2fly (официальный)"},{"full_name":"geosite:ru-blocked","name":"ru-blocked","type":"geosite","source_slug":"runetfreedom","source_name":"RuNet Freedom"},{"full_name":"geosite:ru-blocked-all","name":"ru-blocked-all","type":"geosite","source_slug":"runetfreedom","source_name":"RuNet Freedom"}]}]}] \ No newline at end of file diff --git a/site/data/routing-presets.json b/site/data/routing-presets.json deleted file mode 100644 index c83bde0..0000000 --- a/site/data/routing-presets.json +++ /dev/null @@ -1 +0,0 @@ -{"presets":{"direct_sites":[{"id":"ru_sites","label":"Российские сайты","description":"Домены .ru, .рф и российские сервисы — идут напрямую без VPN","value":"geosite:category-ru","checked":true},{"id":"ru_whitelist","label":"Белый список РФ","description":"Дополнительный список российских сервисов и госсайтов","value":"geosite:whitelist","checked":true},{"id":"private","label":"Локальная сеть","description":"192.168.x.x, 10.x.x.x и другие приватные адреса — всегда напрямую","value":"geosite:private","checked":true},{"id":"apple","label":"Apple","description":"iCloud, App Store, Apple Maps — для корректной работы устройства","value":"geosite:apple","checked":true},{"id":"microsoft","label":"Microsoft","description":"Windows Update, Office, Teams — экономия трафика VPN","value":"geosite:microsoft","checked":false},{"id":"steam","label":"Steam","description":"Игры и обновления Steam — напрямую для скорости","value":"geosite:steam","checked":false},{"id":"google_cn","label":"Google (доступные сервисы)","description":"Сервисы Google доступные в РФ без VPN","value":"geosite:google@cn","checked":false}],"direct_ips":[{"id":"geoip_ru","label":"IP-адреса России","description":"Все российские IP-диапазоны — напрямую","value":"geoip:ru","checked":true},{"id":"geoip_private","label":"Приватные IP","description":"10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16","value":"geoip:private","checked":true}],"proxy_sites":[{"id":"youtube","label":"YouTube","description":"YouTube заблокирован в РФ — направляем через VPN","value":"geosite:youtube","checked":true},{"id":"telegram","label":"Telegram","description":"Серверы Telegram — через VPN для надёжности","value":"geosite:telegram","checked":true},{"id":"meta","label":"Meta (Instagram, Facebook)","description":"Instagram и Facebook заблокированы в РФ","value":"geosite:facebook","checked":true},{"id":"twitter","label":"Twitter / X","description":"Twitter заблокирован в РФ","value":"geosite:twitter","checked":true},{"id":"netflix","label":"Netflix","description":"Netflix недоступен в РФ","value":"geosite:netflix","checked":false},{"id":"spotify","label":"Spotify","description":"Spotify недоступен в РФ","value":"geosite:spotify","checked":false},{"id":"github","label":"GitHub","description":"GitHub периодически блокируется в РФ","value":"geosite:github","checked":false},{"id":"geolocation_notcn","label":"Все заблокированные (универсальный)","description":"Весь иностранный трафик не из РФ/CN — через VPN. Осторожно: большой список","value":"geosite:geolocation-!cn","checked":false}],"proxy_ips":[{"id":"geoip_telegram","label":"IP Telegram","description":"IP-диапазоны серверов Telegram","value":"geoip:telegram","checked":true}],"block_sites":[{"id":"ads","label":"Реклама","description":"Рекламные домены — блокируем полностью","value":"geosite:category-ads-all","checked":true}],"block_ips":[]},"geo_sources":{"loyalsoldier":{"label":"Loyalsoldier (рекомендуется)","geoip":"https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat","geosite":"https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geosite.dat"},"runetfreedom":{"label":"RuNet Freedom (РКН + Россия)","geoip":"https://github.com/runetfreedom/russia-v2ray-rules-dat/releases/latest/download/geoip.dat","geosite":"https://github.com/runetfreedom/russia-v2ray-rules-dat/releases/latest/download/geosite.dat"},"daniellavrushin":{"label":"DanielLavrushin (расширенная)","geoip":"https://github.com/DanielLavrushin/b4geoip/releases/latest/download/geoip.dat","geosite":"https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geosite.dat"},"v2fly":{"label":"v2fly (оригинальная)","geoip":"https://github.com/v2fly/geoip/releases/latest/download/geoip.dat","geosite":"https://github.com/v2fly/domain-list-community/releases/latest/download/dlc.dat"}},"route_orders":[{"value":"block-direct-proxy","label":"Блок → Прямой → VPN (рекомендуется)"},{"value":"block-proxy-direct","label":"Блок → VPN → Прямой"},{"value":"proxy-direct-block","label":"VPN → Прямой → Блок"}],"dns_types":[{"value":"DoH","label":"DoH (DNS over HTTPS, рекомендуется)"},{"value":"DoU","label":"DoU (DNS over UDP, быстрее)"}]} \ No newline at end of file diff --git a/site/data/sources.json b/site/data/sources.json deleted file mode 100644 index a1e7667..0000000 --- a/site/data/sources.json +++ /dev/null @@ -1 +0,0 @@ -[{"slug":"runetfreedom","name":"RuNet Freedom","description":"Российские правила блокировок, оптимизировано для обхода РКН","compatible":"Xray, 3x-ui, V2Ray","geoip_url":"https://github.com/runetfreedom/russia-v2ray-rules-dat/releases/latest/download/geoip.dat","geosite_url":"https://github.com/runetfreedom/russia-v2ray-rules-dat/releases/latest/download/geosite.dat"},{"slug":"loyalsoldier","name":"Loyalsoldier","description":"Популярный набор правил, стандарт для большинства клиентов","compatible":"Xray, 3x-ui, V2Ray, Mihomo","geoip_url":"https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat","geosite_url":"https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geosite.dat"},{"slug":"daniellavrushin","name":"DanielLavrushin (b4geoip)","description":"Расширенная база с дополнительными категориями (Akamai, CDN и др.)","compatible":"Xray, 3x-ui","geoip_url":"https://github.com/DanielLavrushin/b4geoip/releases/latest/download/geoip.dat","geosite_url":null},{"slug":"v2fly","name":"v2fly (официальный)","description":"Официальные базы проекта v2fly/domain-list-community","compatible":"Xray, 3x-ui, V2Ray, Sing-box","geoip_url":"https://github.com/v2fly/geoip/releases/latest/download/geoip.dat","geosite_url":"https://github.com/v2fly/domain-list-community/releases/latest/download/dlc.dat"}] \ No newline at end of file diff --git a/site/index.html b/site/index.html deleted file mode 100644 index 45ce1dc..0000000 --- a/site/index.html +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - GeoExport - - - - -
- - diff --git a/site/package.json b/site/package.json deleted file mode 100644 index 396a369..0000000 --- a/site/package.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "name": "geoexport-clone", - "private": true, - "version": "1.0.0", - "description": "Local mirror of https://geoexport.org/", - "scripts": { - "start": "node server.mjs", - "dev": "node server.mjs" - }, - "engines": { - "node": ">=18" - } -} diff --git a/site/server.mjs b/site/server.mjs deleted file mode 100644 index 69679d1..0000000 --- a/site/server.mjs +++ /dev/null @@ -1,146 +0,0 @@ -#!/usr/bin/env node -/** - * Local GeoExport mirror: static assets + cached JSON + proxy for dynamic API. - */ -import http from "node:http"; -import https from "node:https"; -import fs from "node:fs"; -import path from "node:path"; -import { fileURLToPath } from "node:url"; - -const __dirname = path.dirname(fileURLToPath(import.meta.url)); -const PORT = Number(process.env.PORT) || 4173; -const UPSTREAM = "geoexport.org"; - -const LOCAL_API = new Set([ - "/api/presets", - "/api/sources", - "/api/last-update", - "/api/routing/presets", -]); - -const DATA_MAP = { - "/api/presets": "presets.json", - "/api/sources": "sources.json", - "/api/last-update": "last-update.json", - "/api/routing/presets": "routing-presets.json", -}; - -const MIME = { - ".html": "text/html; charset=utf-8", - ".js": "text/javascript; charset=utf-8", - ".css": "text/css; charset=utf-8", - ".json": "application/json; charset=utf-8", - ".ico": "image/x-icon", - ".svg": "image/svg+xml", - ".png": "image/png", -}; - -function send(res, status, body, headers = {}) { - res.writeHead(status, headers); - res.end(body); -} - -function serveFile(res, filePath) { - const ext = path.extname(filePath); - const type = MIME[ext] || "application/octet-stream"; - fs.readFile(filePath, (err, data) => { - if (err) { - send(res, 404, "Not found"); - return; - } - send(res, 200, data, { "Content-Type": type }); - }); -} - -function serveLocalApi(res, pathname) { - const file = DATA_MAP[pathname]; - if (!file) { - send(res, 404, JSON.stringify({ error: "unknown local api" }), { - "Content-Type": "application/json", - }); - return; - } - const full = path.join(__dirname, "data", file); - fs.readFile(full, (err, data) => { - if (err) { - send(res, 500, JSON.stringify({ error: err.message }), { - "Content-Type": "application/json", - }); - return; - } - send(res, 200, data, { - "Content-Type": "application/json; charset=utf-8", - "Cache-Control": "no-cache", - }); - }); -} - -function proxyToUpstream(req, res) { - const options = { - hostname: UPSTREAM, - port: 443, - path: req.url, - method: req.method, - headers: { - ...req.headers, - host: UPSTREAM, - }, - }; - - const proxyReq = https.request(options, (proxyRes) => { - const headers = { ...proxyRes.headers }; - delete headers["content-security-policy"]; - res.writeHead(proxyRes.statusCode || 502, headers); - proxyRes.pipe(res); - }); - - proxyReq.on("error", (err) => { - send(res, 502, JSON.stringify({ error: `Upstream error: ${err.message}` }), { - "Content-Type": "application/json", - }); - }); - - req.pipe(proxyReq); -} - -function handler(req, res) { - const url = new URL(req.url, `http://127.0.0.1:${PORT}`); - let pathname = decodeURIComponent(url.pathname); - - if (pathname.startsWith("/api/")) { - if (LOCAL_API.has(pathname)) { - serveLocalApi(res, pathname); - return; - } - proxyToUpstream(req, res); - return; - } - - if (pathname === "/") pathname = "/index.html"; - - const safe = path.normalize(pathname).replace(/^(\.\.[/\\])+/, ""); - const filePath = path.join(__dirname, safe); - - if (!filePath.startsWith(__dirname)) { - send(res, 403, "Forbidden"); - return; - } - - fs.stat(filePath, (err, stat) => { - if (err || !stat.isFile()) { - if (!path.extname(pathname)) { - serveFile(res, path.join(__dirname, "index.html")); - return; - } - send(res, 404, "Not found"); - return; - } - serveFile(res, filePath); - }); -} - -http.createServer(handler).listen(PORT, () => { - console.log(`GeoExport clone: http://127.0.0.1:${PORT}`); - console.log(`Static + local JSON; export/search/lookup proxied to ${UPSTREAM}`); -}); diff --git a/src/app/admin/page.tsx b/src/app/admin/page.tsx new file mode 100644 index 0000000..64c3c9d --- /dev/null +++ b/src/app/admin/page.tsx @@ -0,0 +1,36 @@ +import { auth } from "@/lib/auth"; +import { prisma } from "@/lib/prisma"; +import { redirect } from "next/navigation"; +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; + +export default async function AdminPage() { + const session = await auth(); + if (!session?.user || session.user.role !== "ADMIN") { + redirect("/login"); + } + + const [users, scans] = await Promise.all([ + prisma.user.count(), + prisma.scan.count(), + ]); + + return ( +
+

Admin

+
+ + + Users + + {users} + + + + Scans + + {scans} + +
+
+ ); +} diff --git a/src/app/api-docs/page.tsx b/src/app/api-docs/page.tsx new file mode 100644 index 0000000..d72b054 --- /dev/null +++ b/src/app/api-docs/page.tsx @@ -0,0 +1,57 @@ +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; + +export default function ApiDocsPage() { + return ( +
+

API

+

REST endpoints for domain scans and exports.

+ +
+ {[ + { + method: "POST", + path: "/api/scan", + body: '{ "domain": "example.com" }', + desc: "Start a scan. Returns scan id.", + }, + { + method: "GET", + path: "/api/scan/:id", + desc: "Poll scan status and result.", + }, + { + method: "GET", + path: "/api/scan/:id/stream", + desc: "SSE stream for live progress.", + }, + { + method: "GET", + path: "/api/export?id=&format=json|csv", + desc: "Download completed scan.", + }, + { + method: "GET", + path: "/api/health", + desc: "Health check.", + }, + ].map((ep) => ( + + + + {ep.method} {ep.path} + + + +

{ep.desc}

+ {ep.body && ( +
+                  {ep.body}
+                
+ )} +
+
+ ))} +
+
+ ); +} diff --git a/src/app/api/auth/[...nextauth]/route.ts b/src/app/api/auth/[...nextauth]/route.ts new file mode 100644 index 0000000..c55a45e --- /dev/null +++ b/src/app/api/auth/[...nextauth]/route.ts @@ -0,0 +1,3 @@ +import { handlers } from "@/lib/auth"; + +export const { GET, POST } = handlers; diff --git a/src/app/api/export/route.ts b/src/app/api/export/route.ts new file mode 100644 index 0000000..bdc5723 --- /dev/null +++ b/src/app/api/export/route.ts @@ -0,0 +1,42 @@ +import { NextRequest, NextResponse } from "next/server"; +import { prisma } from "@/lib/prisma"; + +export async function GET(req: NextRequest) { + const id = req.nextUrl.searchParams.get("id"); + const format = req.nextUrl.searchParams.get("format") ?? "json"; + + if (!id) { + return NextResponse.json({ error: "Missing scan id" }, { status: 400 }); + } + + const scan = await prisma.scan.findUnique({ where: { id } }); + if (!scan?.result) { + return NextResponse.json({ error: "Scan result not found" }, { status: 404 }); + } + + if (format === "csv") { + const result = scan.result as Record; + const rows = [ + ["field", "value"], + ["domain", scan.domain], + ["scanned_at", String(result.scannedAt ?? "")], + ["ssl_valid", String((result.ssl as { valid?: boolean })?.valid ?? "")], + ["security_score", String((result.security as { score?: number })?.score ?? "")], + ["security_grade", String((result.security as { grade?: string })?.grade ?? "")], + ["uptime_reachable", String((result.uptime as { reachable?: boolean })?.reachable ?? "")], + ]; + const csv = rows.map((r) => r.map((c) => `"${c.replace(/"/g, '""')}"`).join(",")).join("\n"); + return new NextResponse(csv, { + headers: { + "Content-Type": "text/csv", + "Content-Disposition": `attachment; filename="${scan.domain}-scan.csv"`, + }, + }); + } + + return NextResponse.json(scan.result, { + headers: { + "Content-Disposition": `attachment; filename="${scan.domain}-scan.json"`, + }, + }); +} diff --git a/src/app/api/health/route.ts b/src/app/api/health/route.ts new file mode 100644 index 0000000..ca5e3a5 --- /dev/null +++ b/src/app/api/health/route.ts @@ -0,0 +1,11 @@ +import { NextResponse } from "next/server"; +import { prisma } from "@/lib/prisma"; + +export async function GET() { + try { + await prisma.$queryRaw`SELECT 1`; + return NextResponse.json({ status: "ok", db: true }); + } catch { + return NextResponse.json({ status: "degraded", db: false }, { status: 503 }); + } +} diff --git a/src/app/api/monitors/route.ts b/src/app/api/monitors/route.ts new file mode 100644 index 0000000..a745b97 --- /dev/null +++ b/src/app/api/monitors/route.ts @@ -0,0 +1,51 @@ +import { NextRequest, NextResponse } from "next/server"; +import { z } from "zod"; +import { auth } from "@/lib/auth"; +import { prisma } from "@/lib/prisma"; +import { normalizeDomain, isValidDomain } from "@/lib/utils"; + +const bodySchema = z.object({ + domain: z.string(), + type: z.enum(["DNS", "SSL", "UPTIME"]), +}); + +export async function GET() { + const session = await auth(); + if (!session?.user?.id) { + return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); + } + const monitors = await prisma.monitor.findMany({ + where: { userId: session.user.id }, + orderBy: { createdAt: "desc" }, + }); + return NextResponse.json({ monitors }); +} + +export async function POST(req: NextRequest) { + const session = await auth(); + if (!session?.user?.id) { + return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); + } + + let body: z.infer; + try { + body = bodySchema.parse(await req.json()); + } catch { + return NextResponse.json({ error: "Invalid body" }, { status: 400 }); + } + + const domain = normalizeDomain(body.domain); + if (!isValidDomain(domain)) { + return NextResponse.json({ error: "Invalid domain" }, { status: 400 }); + } + + const monitor = await prisma.monitor.create({ + data: { + domain, + type: body.type, + userId: session.user.id, + }, + }); + + return NextResponse.json({ monitor }, { status: 201 }); +} diff --git a/src/app/api/scan/[id]/route.ts b/src/app/api/scan/[id]/route.ts new file mode 100644 index 0000000..58d7d95 --- /dev/null +++ b/src/app/api/scan/[id]/route.ts @@ -0,0 +1,14 @@ +import { NextRequest, NextResponse } from "next/server"; +import { prisma } from "@/lib/prisma"; + +export async function GET( + _req: NextRequest, + { params }: { params: Promise<{ id: string }> } +) { + const { id } = await params; + const scan = await prisma.scan.findUnique({ where: { id } }); + if (!scan) { + return NextResponse.json({ error: "Scan not found" }, { status: 404 }); + } + return NextResponse.json(scan); +} diff --git a/src/app/api/scan/[id]/stream/route.ts b/src/app/api/scan/[id]/stream/route.ts new file mode 100644 index 0000000..1c32a95 --- /dev/null +++ b/src/app/api/scan/[id]/stream/route.ts @@ -0,0 +1,65 @@ +import { NextRequest } from "next/server"; +import { prisma } from "@/lib/prisma"; + +export const dynamic = "force-dynamic"; + +export async function GET( + _req: NextRequest, + { params }: { params: Promise<{ id: string }> } +) { + const { id } = await params; + + const stream = new ReadableStream({ + async start(controller) { + const encoder = new TextEncoder(); + const send = (data: object) => { + controller.enqueue(encoder.encode(`data: ${JSON.stringify(data)}\n\n`)); + }; + + let attempts = 0; + const maxAttempts = 120; + + const poll = async () => { + const scan = await prisma.scan.findUnique({ where: { id } }); + if (!scan) { + send({ error: "not_found" }); + controller.close(); + return; + } + + send({ + id: scan.id, + status: scan.status, + progress: scan.progress, + domain: scan.domain, + result: scan.status === "COMPLETED" ? scan.result : undefined, + error: scan.error, + }); + + if (scan.status === "COMPLETED" || scan.status === "FAILED") { + controller.close(); + return; + } + + attempts++; + if (attempts >= maxAttempts) { + send({ error: "timeout" }); + controller.close(); + return; + } + + setTimeout(poll, 1000); + }; + + await poll(); + }, + }); + + return new Response(stream, { + headers: { + "Content-Type": "text/event-stream", + "Cache-Control": "no-cache", + Connection: "keep-alive", + }, + }); +} diff --git a/src/app/api/scan/route.ts b/src/app/api/scan/route.ts new file mode 100644 index 0000000..096453e --- /dev/null +++ b/src/app/api/scan/route.ts @@ -0,0 +1,98 @@ +import { NextRequest, NextResponse } from "next/server"; +import { z } from "zod"; +import { prisma } from "@/lib/prisma"; +import { checkRateLimit } from "@/lib/rate-limit"; +import { normalizeDomain, isValidDomain } from "@/lib/utils"; +import { runDomainScan } from "@/lib/scanner"; +import { cacheGet, cacheSet } from "@/lib/redis"; + +const bodySchema = z.object({ + domain: z.string().min(1).max(253), +}); + +export async function POST(req: NextRequest) { + const ip = req.headers.get("x-forwarded-for")?.split(",")[0]?.trim() ?? "anonymous"; + const rate = await checkRateLimit(`scan:${ip}`); + if (!rate.ok) { + return NextResponse.json({ error: "Rate limit exceeded" }, { status: 429 }); + } + + let body: z.infer; + try { + body = bodySchema.parse(await req.json()); + } catch { + return NextResponse.json({ error: "Invalid request body" }, { status: 400 }); + } + + const domain = normalizeDomain(body.domain); + if (!isValidDomain(domain)) { + return NextResponse.json({ error: "Invalid domain name" }, { status: 400 }); + } + + const cacheKey = `scan:result:${domain}`; + const cached = await cacheGet(cacheKey); + if (cached) { + const scan = await prisma.scan.create({ + data: { + domain, + status: "COMPLETED", + progress: 100, + result: cached, + ip, + userAgent: req.headers.get("user-agent") ?? undefined, + }, + }); + return NextResponse.json({ id: scan.id, domain, cached: true }); + } + + const scan = await prisma.scan.create({ + data: { + domain, + status: "RUNNING", + progress: 0, + ip, + userAgent: req.headers.get("user-agent") ?? undefined, + }, + }); + + runScanAsync(scan.id, domain, cacheKey); + + return NextResponse.json({ id: scan.id, domain }); +} + +async function runScanAsync(scanId: string, domain: string, cacheKey: string) { + try { + const result = await runDomainScan(domain, async (progress) => { + await prisma.scan.update({ where: { id: scanId }, data: { progress } }); + }); + await prisma.scan.update({ + where: { id: scanId }, + data: { status: "COMPLETED", progress: 100, result: result as object }, + }); + await cacheSet(cacheKey, result, 3600); + } catch (e) { + await prisma.scan.update({ + where: { id: scanId }, + data: { + status: "FAILED", + error: e instanceof Error ? e.message : "Scan failed", + }, + }); + } +} + +export async function GET(req: NextRequest) { + const limit = Math.min(Number(req.nextUrl.searchParams.get("limit") ?? 20), 50); + const scans = await prisma.scan.findMany({ + orderBy: { createdAt: "desc" }, + take: limit, + select: { + id: true, + domain: true, + status: true, + progress: true, + createdAt: true, + }, + }); + return NextResponse.json({ scans }); +} diff --git a/src/app/dashboard/page.tsx b/src/app/dashboard/page.tsx new file mode 100644 index 0000000..7e4578f --- /dev/null +++ b/src/app/dashboard/page.tsx @@ -0,0 +1,19 @@ +import { Suspense } from "react"; +import { ScanPanel } from "@/components/scan/scan-panel"; +import { Skeleton } from "@/components/ui/badge"; + +export const metadata = { + title: "Dashboard", +}; + +export default function DashboardPage() { + return ( +
+

Dashboard

+

Run a domain scan and export results.

+ }> + + +
+ ); +} diff --git a/src/app/domain/[domain]/not-found.tsx b/src/app/domain/[domain]/not-found.tsx new file mode 100644 index 0000000..695a67c --- /dev/null +++ b/src/app/domain/[domain]/not-found.tsx @@ -0,0 +1,11 @@ +export default function DomainNotFound() { + return ( +
+

No scan found

+

Run a scan from the dashboard first.

+ + Go to dashboard + +
+ ); +} diff --git a/src/app/domain/[domain]/page.tsx b/src/app/domain/[domain]/page.tsx new file mode 100644 index 0000000..a2e9fc2 --- /dev/null +++ b/src/app/domain/[domain]/page.tsx @@ -0,0 +1,85 @@ +import { prisma } from "@/lib/prisma"; +import { notFound } from "next/navigation"; +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; +import { Badge } from "@/components/ui/badge"; +import type { ScanResult } from "@/lib/types"; + +export async function generateMetadata({ params }: { params: Promise<{ domain: string }> }) { + const { domain } = await params; + return { title: domain }; +} + +export default async function DomainDetailPage({ + params, +}: { + params: Promise<{ domain: string }>; +}) { + const { domain: raw } = await params; + const domain = decodeURIComponent(raw); + + const scan = await prisma.scan.findFirst({ + where: { domain, status: "COMPLETED" }, + orderBy: { createdAt: "desc" }, + }); + + if (!scan?.result) { + notFound(); + } + + const result = scan.result as unknown as ScanResult; + + return ( +
+

{result.domain}

+

Full analysis report

+ +
+ + + WHOIS + + + {Object.entries(result.whois).slice(0, 30).map(([k, v]) => ( +
+ {k}: + {v} +
+ ))} +
+
+ + + + SSL certificate + + +

+ Status:{" "} + + {result.ssl.valid ? "Valid" : "Invalid"} + +

+ {result.ssl.issuer &&

Issuer: {result.ssl.issuer}

} + {result.ssl.validTo &&

Expires: {result.ssl.validTo}

} + {result.ssl.daysRemaining !== undefined && ( +

Days remaining: {result.ssl.daysRemaining}

+ )} +
+
+ + + + HTTP headers + + + {Object.entries(result.http.headers).map(([k, v]) => ( +
+ {k}: {v} +
+ ))} +
+
+
+
+ ); +} diff --git a/src/app/globals.css b/src/app/globals.css new file mode 100644 index 0000000..beabfc4 --- /dev/null +++ b/src/app/globals.css @@ -0,0 +1,47 @@ +@import "tailwindcss"; + +:root { + --background: #fafafa; + --foreground: #09090b; + --muted: #71717a; + --accent: #7c3aed; + --accent-2: #06b6d4; +} + +.dark { + --background: #030712; + --foreground: #fafafa; + --muted: #a1a1aa; +} + +@theme inline { + --color-background: var(--background); + --color-foreground: var(--foreground); + --font-sans: var(--font-geist-sans); +} + +* { + box-sizing: border-box; +} + +html { + scroll-behavior: smooth; +} + +body { + background: var(--background); + color: var(--foreground); + font-family: var(--font-geist-sans), system-ui, sans-serif; + min-height: 100vh; +} + +.glass { + @apply border border-white/10 bg-white/5 backdrop-blur-xl; +} + +.gradient-mesh { + background: + radial-gradient(ellipse 80% 50% at 50% -20%, rgba(124, 58, 237, 0.35), transparent), + radial-gradient(ellipse 60% 40% at 100% 0%, rgba(6, 182, 212, 0.15), transparent), + var(--background); +} diff --git a/src/app/layout.tsx b/src/app/layout.tsx new file mode 100644 index 0000000..c88fd39 --- /dev/null +++ b/src/app/layout.tsx @@ -0,0 +1,52 @@ +import type { Metadata } from "next"; +import { Geist, Geist_Mono } from "next/font/google"; +import "./globals.css"; +import { Providers } from "@/components/providers"; +import { Header } from "@/components/layout/header"; + +const geistSans = Geist({ + variable: "--font-geist-sans", + subsets: ["latin"], +}); + +const geistMono = Geist_Mono({ + variable: "--font-geist-mono", + subsets: ["latin"], +}); + +export const metadata: Metadata = { + title: { + default: "Domain Scanner — Domain intelligence platform", + template: "%s | Domain Scanner", + }, + description: + "Modern domain intelligence and infrastructure analysis. DNS, WHOIS, SSL, geo, security headers, and monitoring.", + keywords: [ + "domain scanner", + "dns", + "whois", + "ssl", + "cybersecurity", + "infrastructure", + ], + openGraph: { + title: "Domain Scanner", + description: "Advanced domain intelligence with premium UX", + type: "website", + }, +}; + +export default function RootLayout({ children }: { children: React.ReactNode }) { + return ( + + + +
+
+
{children}
+
+
+ + + ); +} diff --git a/src/app/login/page.tsx b/src/app/login/page.tsx new file mode 100644 index 0000000..5d3a620 --- /dev/null +++ b/src/app/login/page.tsx @@ -0,0 +1,48 @@ +"use client"; + +import { signIn } from "next-auth/react"; +import { useState } from "react"; +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { useRouter } from "next/navigation"; + +export default function LoginPage() { + const router = useRouter(); + const [email, setEmail] = useState(""); + const [password, setPassword] = useState(""); + const [error, setError] = useState(""); + + const submit = async (e: React.FormEvent) => { + e.preventDefault(); + const res = await signIn("credentials", { email, password, redirect: false }); + if (res?.error) { + setError("Invalid credentials"); + return; + } + router.push("/dashboard"); + }; + + return ( +
+

Sign in

+
+ setEmail(e.target.value)} + /> + setPassword(e.target.value)} + /> + {error &&

{error}

} + +
+
+ ); +} diff --git a/src/app/monitoring/page.tsx b/src/app/monitoring/page.tsx new file mode 100644 index 0000000..72ae1ca --- /dev/null +++ b/src/app/monitoring/page.tsx @@ -0,0 +1,79 @@ +"use client"; + +import { useState } from "react"; +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; +import { Bell, Plus } from "lucide-react"; +import { toast } from "sonner"; + +export default function MonitoringPage() { + const [domain, setDomain] = useState(""); + const [monitors, setMonitors] = useState< + { id: string; domain: string; type: string }[] + >([]); + + const addMonitor = async () => { + const res = await fetch("/api/monitors", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ domain, type: "DNS" }), + }); + if (res.status === 401) { + toast.error("Sign in to add monitors"); + return; + } + if (!res.ok) { + toast.error("Could not create monitor"); + return; + } + const data = await res.json(); + setMonitors((m) => [...m, data.monitor]); + setDomain(""); + toast.success("Monitor created (cron worker: roadmap)"); + }; + + return ( +
+

Monitoring

+

+ DNS and SSL change alerts — queue workers ship in v1.1. +

+ + + + + Add monitor + + + + setDomain(e.target.value)} + /> + + + + + {monitors.length === 0 ? ( +
+
+ +
+

No monitors yet. Add one to track DNS/SSL changes.

+
+ ) : ( +
    + {monitors.map((m) => ( +
  • + {m.domain} — {m.type} +
  • + ))} +
+ )} +
+ ); +} diff --git a/src/app/page.tsx b/src/app/page.tsx new file mode 100644 index 0000000..721e5e5 --- /dev/null +++ b/src/app/page.tsx @@ -0,0 +1,19 @@ +import { Hero } from "@/components/landing/hero"; +import { Features } from "@/components/landing/features"; + +export default function HomePage() { + return ( + <> + + +
+

Domain Scanner — self-hosted domain intelligence

+

+ + GitHub + +

+
+ + ); +} diff --git a/src/app/settings/page.tsx b/src/app/settings/page.tsx new file mode 100644 index 0000000..f730f4c --- /dev/null +++ b/src/app/settings/page.tsx @@ -0,0 +1,11 @@ +export default function SettingsPage() { + return ( +
+

Settings

+

+ Configure IPINFO_TOKEN, SCAN_RATE_LIMIT_PER_HOUR, and database URLs via environment + variables on your server. Theme toggle is in the header. +

+
+ ); +} diff --git a/src/components/landing/features.tsx b/src/components/landing/features.tsx new file mode 100644 index 0000000..d0daa69 --- /dev/null +++ b/src/components/landing/features.tsx @@ -0,0 +1,57 @@ +"use client"; + +import { motion } from "framer-motion"; +import { Database, Lock, Radio, Server } from "lucide-react"; + +const features = [ + { + icon: Database, + title: "DNS & WHOIS depth", + desc: "A/AAAA, MX, TXT, NS resolution plus registrar metadata in one pass.", + }, + { + icon: Lock, + title: "SSL & security grade", + desc: "Certificate validity, issuer chain, and security header scoring.", + }, + { + icon: Radio, + title: "Live scan stream", + desc: "SSE progress events while workers resolve each infrastructure layer.", + }, + { + icon: Server, + title: "Self-hosted stack", + desc: "PostgreSQL, Redis, Docker — your data stays on your VPS.", + }, +]; + +export function Features() { + return ( +
+

+ Built for operators +

+

+ Competitive tools scatter checks across tabs. Domain Scanner unifies infrastructure + signals with export-ready reports and monitoring hooks. +

+
+ {features.map((f, i) => ( + + +

{f.title}

+

{f.desc}

+
+ ))} +
+
+ ); +} diff --git a/src/components/landing/hero.tsx b/src/components/landing/hero.tsx new file mode 100644 index 0000000..6382f2b --- /dev/null +++ b/src/components/landing/hero.tsx @@ -0,0 +1,113 @@ +"use client"; + +import { motion } from "framer-motion"; +import { Search, Shield, Radar, Zap } from "lucide-react"; +import Link from "next/link"; +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { useRouter } from "next/navigation"; +import { useState } from "react"; +import { normalizeDomain } from "@/lib/utils"; + +export function Hero() { + const router = useRouter(); + const [domain, setDomain] = useState(""); + + const submit = (e: React.FormEvent) => { + e.preventDefault(); + const d = normalizeDomain(domain); + if (d) router.push(`/dashboard?domain=${encodeURIComponent(d)}`); + }; + + return ( +
+
+
+
+
+ + + Domain intelligence platform + + + + See every layer +
+ + of your domain + +
+ + + DNS, WHOIS, SSL, headers, geo, CDN detection, and security scoring — one scan, + exportable reports, monitoring hooks for production teams. + + + + setDomain(e.target.value)} + className="flex-1 text-left" + /> + + + + + {[ + { icon: Radar, label: "DNS & WHOIS" }, + { icon: Shield, label: "SSL & headers" }, + { icon: Zap, label: "Live progress" }, + { icon: Search, label: "Export JSON/CSV" }, + ].map(({ icon: Icon, label }) => ( +
+ +

{label}

+
+ ))} +
+ + + + +
+ ); +} diff --git a/src/components/layout/header.tsx b/src/components/layout/header.tsx new file mode 100644 index 0000000..26298ec --- /dev/null +++ b/src/components/layout/header.tsx @@ -0,0 +1,83 @@ +"use client"; + +import Link from "next/link"; +import { usePathname } from "next/navigation"; +import { motion } from "framer-motion"; +import { Globe2, Menu, X } from "lucide-react"; +import { useState } from "react"; +import { cn } from "@/lib/utils"; +import { Button } from "@/components/ui/button"; +import { ThemeToggle } from "@/components/theme-toggle"; + +const links = [ + { href: "/dashboard", label: "Dashboard" }, + { href: "/monitoring", label: "Monitoring" }, + { href: "/api-docs", label: "API" }, + { href: "/settings", label: "Settings" }, +]; + +export function Header() { + const pathname = usePathname(); + const [open, setOpen] = useState(false); + + return ( +
+
+ +
+ +
+ Domain Scanner + + + + +
+ + +
+ + +
+ + {open && ( + + {links.map((l) => ( + setOpen(false)} + > + {l.label} + + ))} + + + + + )} +
+ ); +} diff --git a/src/components/providers.tsx b/src/components/providers.tsx new file mode 100644 index 0000000..211f91d --- /dev/null +++ b/src/components/providers.tsx @@ -0,0 +1,13 @@ +"use client"; + +import { ThemeProvider } from "next-themes"; +import { Toaster } from "sonner"; + +export function Providers({ children }: { children: React.ReactNode }) { + return ( + + {children} + + + ); +} diff --git a/src/components/scan/scan-panel.tsx b/src/components/scan/scan-panel.tsx new file mode 100644 index 0000000..fcc04e2 --- /dev/null +++ b/src/components/scan/scan-panel.tsx @@ -0,0 +1,288 @@ +"use client"; + +import { useEffect, useState, useCallback } from "react"; +import { useSearchParams, useRouter } from "next/navigation"; +import { motion } from "framer-motion"; +import { Download, Loader2, Search } from "lucide-react"; +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; +import { Badge, Skeleton } from "@/components/ui/badge"; +import type { ScanResult } from "@/lib/types"; +import Link from "next/link"; + +export function ScanPanel() { + const searchParams = useSearchParams(); + const router = useRouter(); + const [domain, setDomain] = useState(searchParams.get("domain") ?? ""); + const [scanId, setScanId] = useState(null); + const [progress, setProgress] = useState(0); + const [status, setStatus] = useState("idle"); + const [result, setResult] = useState(null); + const [error, setError] = useState(null); + + const startScan = useCallback(async (d: string) => { + setError(null); + setResult(null); + setProgress(0); + setStatus("starting"); + + const res = await fetch("/api/scan", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ domain: d }), + }); + const data = await res.json(); + if (!res.ok) { + setError(data.error ?? "Scan failed"); + setStatus("failed"); + return; + } + + setScanId(data.id); + setStatus("running"); + router.replace(`/dashboard?domain=${encodeURIComponent(d)}`); + + if (data.cached) { + const full = await fetch(`/api/scan/${data.id}`); + const scan = await full.json(); + setResult(scan.result as ScanResult); + setProgress(100); + setStatus("completed"); + return; + } + + const es = new EventSource(`/api/scan/${data.id}/stream`); + es.onmessage = (ev) => { + const msg = JSON.parse(ev.data) as { + status: string; + progress: number; + result?: ScanResult; + error?: string; + }; + setProgress(msg.progress ?? 0); + if (msg.status === "COMPLETED" && msg.result) { + setResult(msg.result); + setStatus("completed"); + es.close(); + } + if (msg.status === "FAILED") { + setError(msg.error ?? "Scan failed"); + setStatus("failed"); + es.close(); + } + }; + es.onerror = () => es.close(); + }, [router]); + + useEffect(() => { + const d = searchParams.get("domain"); + if (d && status === "idle") { + setDomain(d); + startScan(d); + } + }, [searchParams, status, startScan]); + + const onSubmit = (e: React.FormEvent) => { + e.preventDefault(); + if (domain.trim()) startScan(domain.trim()); + }; + + return ( +
+
+ setDomain(e.target.value)} + disabled={status === "running"} + /> + +
+ + {error && ( +
+ {error} +
+ )} + + {status === "running" && !result && ( +
+
+ +
+
+ {[1, 2, 3, 4, 5, 6].map((i) => ( + + ))} +
+
+ )} + + {result && ( + + )} +
+ ); +} + +function ScanResults({ result, scanId }: { result: ScanResult; scanId: string | null }) { + return ( + +
+
+

{result.domain}

+

Scanned {new Date(result.scannedAt).toLocaleString()}

+
+
+ {scanId && ( + <> + + + + )} + +
+
+ +
+ + = 60} /> + + +
+ +
+ + + DNS records + + + {result.dns.records.length === 0 ? ( +

No records found

+ ) : ( +
    + {result.dns.records.map((r, i) => ( +
  • + {r.type} + {r.value} +
  • + ))} +
+ )} +
+
+ + + + Security headers + + +

{result.security.score}

+
+ {result.security.present.map((h) => ( + + {h} + + ))} + {result.security.missing.slice(0, 4).map((h) => ( + + missing: {h} + + ))} +
+
+
+ + + + Geo / IP + + + {result.geo ? ( +
+
+
IP
+
{result.geo.ip}
+
+
+
Location
+
+ {[result.geo.city, result.geo.region, result.geo.country].filter(Boolean).join(", ")} +
+
+ {result.geo.isp && ( +
+
ISP
+
{result.geo.isp}
+
+ )} +
+ ) : ( +

Geo lookup unavailable

+ )} +
+
+ + + + Tech & CDN + + +
+ {result.tech.map((t) => ( + {t.name} + ))} + {result.cdnWaf.detected.map((c) => ( + + {c} + + ))} + {result.tech.length === 0 && result.cdnWaf.detected.length === 0 && ( +

No strong signals detected

+ )} +
+
+
+
+
+ ); +} + +function MetricCard({ title, value, ok }: { title: string; value: string; ok: boolean }) { + return ( + + +

{title}

+

+ {value} +

+
+
+ ); +} diff --git a/src/components/theme-toggle.tsx b/src/components/theme-toggle.tsx new file mode 100644 index 0000000..cd04be8 --- /dev/null +++ b/src/components/theme-toggle.tsx @@ -0,0 +1,20 @@ +"use client"; + +import { useTheme } from "next-themes"; +import { Moon, Sun } from "lucide-react"; +import { Button } from "@/components/ui/button"; + +export function ThemeToggle() { + const { theme, setTheme } = useTheme(); + return ( + + ); +} diff --git a/src/components/ui/badge.tsx b/src/components/ui/badge.tsx new file mode 100644 index 0000000..b84f6ae --- /dev/null +++ b/src/components/ui/badge.tsx @@ -0,0 +1,30 @@ +import { cn } from "@/lib/utils"; + +export function Badge({ + className, + variant = "default", + ...props +}: React.HTMLAttributes & { + variant?: "default" | "success" | "warning" | "danger"; +}) { + const variants = { + default: "bg-violet-500/20 text-violet-200 border-violet-500/30", + success: "bg-emerald-500/20 text-emerald-200 border-emerald-500/30", + warning: "bg-amber-500/20 text-amber-200 border-amber-500/30", + danger: "bg-rose-500/20 text-rose-200 border-rose-500/30", + }; + return ( + + ); +} + +export function Skeleton({ className }: { className?: string }) { + return
; +} diff --git a/src/components/ui/button.tsx b/src/components/ui/button.tsx new file mode 100644 index 0000000..d9b66cd --- /dev/null +++ b/src/components/ui/button.tsx @@ -0,0 +1,44 @@ +import * as React from "react"; +import { Slot } from "@radix-ui/react-slot"; +import { cva, type VariantProps } from "class-variance-authority"; +import { cn } from "@/lib/utils"; + +const buttonVariants = cva( + "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-xl text-sm font-medium transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-violet-500/50 disabled:pointer-events-none disabled:opacity-50", + { + variants: { + variant: { + default: "bg-white text-zinc-950 hover:bg-zinc-100 shadow-lg shadow-white/10", + secondary: + "bg-white/10 text-white border border-white/20 hover:bg-white/15 backdrop-blur", + ghost: "hover:bg-white/10 text-zinc-300", + outline: "border border-zinc-700 bg-transparent hover:bg-zinc-800", + }, + size: { + default: "h-11 px-6", + sm: "h-9 px-4 text-xs", + lg: "h-13 px-8 text-base", + icon: "h-10 w-10", + }, + }, + defaultVariants: { variant: "default", size: "default" }, + } +); + +export interface ButtonProps + extends React.ButtonHTMLAttributes, + VariantProps { + asChild?: boolean; +} + +const Button = React.forwardRef( + ({ className, variant, size, asChild = false, ...props }, ref) => { + const Comp = asChild ? Slot : "button"; + return ( + + ); + } +); +Button.displayName = "Button"; + +export { Button, buttonVariants }; diff --git a/src/components/ui/card.tsx b/src/components/ui/card.tsx new file mode 100644 index 0000000..2229c8a --- /dev/null +++ b/src/components/ui/card.tsx @@ -0,0 +1,30 @@ +import * as React from "react"; +import { cn } from "@/lib/utils"; + +const Card = React.forwardRef>( + ({ className, ...props }, ref) => ( +
+ ) +); +Card.displayName = "Card"; + +const CardHeader = ({ className, ...props }: React.HTMLAttributes) => ( +
+); + +const CardTitle = ({ className, ...props }: React.HTMLAttributes) => ( +

+); + +const CardContent = ({ className, ...props }: React.HTMLAttributes) => ( +
+); + +export { Card, CardHeader, CardTitle, CardContent }; diff --git a/src/components/ui/input.tsx b/src/components/ui/input.tsx new file mode 100644 index 0000000..695b355 --- /dev/null +++ b/src/components/ui/input.tsx @@ -0,0 +1,19 @@ +import * as React from "react"; +import { cn } from "@/lib/utils"; + +const Input = React.forwardRef>( + ({ className, type, ...props }, ref) => ( + + ) +); +Input.displayName = "Input"; + +export { Input }; diff --git a/src/lib/auth.ts b/src/lib/auth.ts new file mode 100644 index 0000000..2967d15 --- /dev/null +++ b/src/lib/auth.ts @@ -0,0 +1,42 @@ +import NextAuth from "next-auth"; +import Credentials from "next-auth/providers/credentials"; +import bcrypt from "bcryptjs"; +import { prisma } from "./prisma"; + +export const { handlers, auth, signIn, signOut } = NextAuth({ + providers: [ + Credentials({ + credentials: { + email: { label: "Email", type: "email" }, + password: { label: "Password", type: "password" }, + }, + async authorize(credentials) { + if (!credentials?.email || !credentials?.password) return null; + const email = String(credentials.email).toLowerCase(); + const user = await prisma.user.findUnique({ where: { email } }); + if (!user) return null; + const ok = await bcrypt.compare(String(credentials.password), user.passwordHash); + if (!ok) return null; + return { id: user.id, email: user.email, name: user.name, role: user.role }; + }, + }), + ], + session: { strategy: "jwt" }, + pages: { signIn: "/login" }, + callbacks: { + async jwt({ token, user }) { + if (user) { + token.role = (user as { role?: string }).role; + token.id = user.id; + } + return token; + }, + async session({ session, token }) { + if (session.user) { + session.user.id = token.id as string; + session.user.role = token.role as string; + } + return session; + }, + }, +}); diff --git a/src/lib/graphql/schema.ts b/src/lib/graphql/schema.ts new file mode 100644 index 0000000..1a5f414 --- /dev/null +++ b/src/lib/graphql/schema.ts @@ -0,0 +1,20 @@ +export const typeDefs = `#graphql + type ScanResult { + domain: String! + scannedAt: String! + } + + type Query { + scan(domain: String!): ScanResult + } +`; + +export const resolvers = { + Query: { + scan: async (_: unknown, { domain }: { domain: string }) => ({ + domain, + scannedAt: new Date().toISOString(), + note: "GraphQL endpoint placeholder — use REST /api/scan for full results", + }), + }, +}; diff --git a/src/lib/prisma.ts b/src/lib/prisma.ts new file mode 100644 index 0000000..5c25a58 --- /dev/null +++ b/src/lib/prisma.ts @@ -0,0 +1,11 @@ +import { PrismaClient } from "@prisma/client"; + +const globalForPrisma = globalThis as unknown as { prisma: PrismaClient }; + +export const prisma = + globalForPrisma.prisma ?? + new PrismaClient({ + log: process.env.NODE_ENV === "development" ? ["error", "warn"] : ["error"], + }); + +if (process.env.NODE_ENV !== "production") globalForPrisma.prisma = prisma; diff --git a/src/lib/rate-limit.ts b/src/lib/rate-limit.ts new file mode 100644 index 0000000..5be8c3a --- /dev/null +++ b/src/lib/rate-limit.ts @@ -0,0 +1,31 @@ +import { prisma } from "./prisma"; + +const LIMIT = Number(process.env.SCAN_RATE_LIMIT_PER_HOUR ?? 30); +const WINDOW_MS = 60 * 60 * 1000; + +export async function checkRateLimit(key: string): Promise<{ ok: boolean; remaining: number }> { + const now = new Date(); + const windowEnd = new Date(now.getTime() + WINDOW_MS); + + try { + const row = await prisma.apiRateLimit.findUnique({ where: { key } }); + if (!row || row.windowEnd < now) { + await prisma.apiRateLimit.upsert({ + where: { key }, + create: { key, count: 1, windowEnd }, + update: { count: 1, windowEnd }, + }); + return { ok: true, remaining: LIMIT - 1 }; + } + if (row.count >= LIMIT) { + return { ok: false, remaining: 0 }; + } + await prisma.apiRateLimit.update({ + where: { key }, + data: { count: row.count + 1 }, + }); + return { ok: true, remaining: LIMIT - row.count - 1 }; + } catch { + return { ok: true, remaining: LIMIT }; + } +} diff --git a/src/lib/redis.ts b/src/lib/redis.ts new file mode 100644 index 0000000..fc1d375 --- /dev/null +++ b/src/lib/redis.ts @@ -0,0 +1,33 @@ +import Redis from "ioredis"; + +let redis: Redis | null = null; + +export function getRedis(): Redis | null { + const url = process.env.REDIS_URL; + if (!url) return null; + if (!redis) { + redis = new Redis(url, { maxRetriesPerRequest: 3, lazyConnect: true }); + } + return redis; +} + +export async function cacheGet(key: string): Promise { + const r = getRedis(); + if (!r) return null; + try { + const val = await r.get(key); + return val ? (JSON.parse(val) as T) : null; + } catch { + return null; + } +} + +export async function cacheSet(key: string, value: unknown, ttlSec = 3600) { + const r = getRedis(); + if (!r) return; + try { + await r.setex(key, ttlSec, JSON.stringify(value)); + } catch { + /* ignore */ + } +} diff --git a/src/lib/scanner/index.ts b/src/lib/scanner/index.ts new file mode 100644 index 0000000..bd94651 --- /dev/null +++ b/src/lib/scanner/index.ts @@ -0,0 +1,332 @@ +import dns from "node:dns/promises"; +import tls from "node:tls"; +import * as whois from "whois"; +import { promisify } from "node:util"; +import type { ScanResult, DnsRecord, SslInfo, SecurityHeaders, TechHint, GeoInfo } from "../types"; + +const whoisLookup = promisify(whois.lookup.bind(whois)); + +const SECURITY_HEADER_KEYS = [ + "strict-transport-security", + "content-security-policy", + "x-frame-options", + "x-content-type-options", + "referrer-policy", + "permissions-policy", + "cross-origin-opener-policy", + "cross-origin-resource-policy", +]; + +function parseWhois(raw: string): Record { + const out: Record = {}; + for (const line of raw.split("\n")) { + const idx = line.indexOf(":"); + if (idx > 0) { + const k = line.slice(0, idx).trim().toLowerCase(); + const v = line.slice(idx + 1).trim(); + if (k && v && !k.startsWith("%")) out[k] = v; + } + } + return out; +} + +async function lookupDns(domain: string) { + const records: DnsRecord[] = []; + const nameservers: string[] = []; + const mx: string[] = []; + const txt: string[] = []; + + const types: Array<["A" | "AAAA" | "CNAME" | "MX" | "TXT" | "NS", () => Promise]> = [ + ["A", () => dns.resolve4(domain)], + ["AAAA", () => dns.resolve6(domain)], + ["CNAME", () => dns.resolveCname(domain)], + ["MX", () => dns.resolveMx(domain)], + ["TXT", () => dns.resolveTxt(domain)], + ["NS", () => dns.resolveNs(domain)], + ]; + + for (const [type, fn] of types) { + try { + const res = (await fn()) as unknown; + if (type === "MX" && Array.isArray(res)) { + for (const r of res as { exchange: string; priority: number }[]) { + mx.push(`${r.priority} ${r.exchange}`); + records.push({ type: "MX", value: `${r.priority} ${r.exchange}` }); + } + } else if (type === "TXT" && Array.isArray(res)) { + for (const r of res as string[][]) { + const v = r.join(""); + txt.push(v); + records.push({ type: "TXT", value: v }); + } + } else if (type === "NS" && Array.isArray(res)) { + for (const r of res as string[]) { + nameservers.push(r); + records.push({ type: "NS", value: r }); + } + } else if (Array.isArray(res)) { + for (const r of res as string[]) { + records.push({ type, value: r }); + } + } + } catch { + /* record type may not exist */ + } + } + + return { records, nameservers, mx, txt }; +} + +async function checkSsl(domain: string): Promise { + return new Promise((resolve) => { + const socket = tls.connect( + { host: domain, port: 443, servername: domain, rejectUnauthorized: false, timeout: 10000 }, + () => { + const cert = socket.getPeerCertificate(); + socket.end(); + if (!cert || !cert.valid_to) { + resolve({ valid: false, error: "No certificate" }); + return; + } + const validTo = new Date(cert.valid_to); + const validFrom = new Date(cert.valid_from); + const daysRemaining = Math.floor((validTo.getTime() - Date.now()) / 86400000); + const issuer = cert.issuer?.O ?? cert.issuer?.CN; + const subject = cert.subject?.CN; + resolve({ + valid: daysRemaining > 0, + issuer: Array.isArray(issuer) ? issuer[0] : issuer, + subject: Array.isArray(subject) ? subject[0] : subject, + validFrom: validFrom.toISOString(), + validTo: validTo.toISOString(), + daysRemaining, + protocol: socket.getProtocol?.() ?? undefined, + }); + } + ); + socket.on("error", (e) => resolve({ valid: false, error: e.message })); + socket.on("timeout", () => { + socket.destroy(); + resolve({ valid: false, error: "Timeout" }); + }); + }); +} + +async function fetchHttp(domain: string) { + const redirectChain: string[] = []; + let url = `https://${domain}`; + let status: number | undefined; + const headers: Record = {}; + + for (let i = 0; i < 5; i++) { + const start = Date.now(); + try { + const res = await fetch(url, { + redirect: "manual", + signal: AbortSignal.timeout(15000), + headers: { "User-Agent": "DomainScanner/1.0 (+https://github.com/andrey271192/Domain_web)" }, + }); + status = res.status; + res.headers.forEach((v, k) => { + headers[k.toLowerCase()] = v; + }); + if (res.status >= 300 && res.status < 400) { + const loc = res.headers.get("location"); + if (!loc) break; + redirectChain.push(loc); + url = loc.startsWith("http") ? loc : new URL(loc, url).href; + continue; + } + return { + status, + finalUrl: url, + redirectChain, + headers, + server: headers["server"], + poweredBy: headers["x-powered-by"], + latencyMs: Date.now() - start, + }; + } catch { + try { + url = `http://${domain}`; + const res = await fetch(url, { + redirect: "follow", + signal: AbortSignal.timeout(15000), + }); + status = res.status; + res.headers.forEach((v, k) => { + headers[k.toLowerCase()] = v; + }); + return { + status, + finalUrl: res.url, + redirectChain, + headers, + server: headers["server"], + poweredBy: headers["x-powered-by"], + latencyMs: Date.now() - start, + }; + } catch { + return { redirectChain, headers, reachable: false }; + } + } + } + return { status, finalUrl: url, redirectChain, headers }; +} + +function scoreSecurityHeaders(headers: Record): SecurityHeaders { + const present: string[] = []; + const missing: string[] = []; + for (const key of SECURITY_HEADER_KEYS) { + if (headers[key]) present.push(key); + else missing.push(key); + } + const score = Math.round((present.length / SECURITY_HEADER_KEYS.length) * 100); + const grade = + score >= 90 ? "A" : score >= 75 ? "B" : score >= 60 ? "C" : score >= 40 ? "D" : "F"; + return { score, grade, present, missing, headers }; +} + +function detectTech(headers: Record): TechHint[] { + const tech: TechHint[] = []; + const server = headers["server"]?.toLowerCase() ?? ""; + const powered = headers["x-powered-by"]?.toLowerCase() ?? ""; + const via = headers["via"]?.toLowerCase() ?? ""; + const cf = headers["cf-ray"]; + + if (cf) tech.push({ name: "Cloudflare", category: "CDN", confidence: "high" }); + if (server.includes("nginx")) tech.push({ name: "nginx", category: "Web Server", confidence: "high" }); + if (server.includes("apache")) tech.push({ name: "Apache", category: "Web Server", confidence: "high" }); + if (server.includes("cloudflare")) tech.push({ name: "Cloudflare", category: "CDN", confidence: "high" }); + if (powered.includes("next")) tech.push({ name: "Next.js", category: "Framework", confidence: "medium" }); + if (powered.includes("express")) tech.push({ name: "Express", category: "Framework", confidence: "medium" }); + if (via.includes("varnish")) tech.push({ name: "Varnish", category: "Cache", confidence: "medium" }); + if (headers["x-vercel-id"]) tech.push({ name: "Vercel", category: "Hosting", confidence: "high" }); + if (headers["x-amz-cf-id"]) tech.push({ name: "AWS CloudFront", category: "CDN", confidence: "high" }); + + const cdnWaf: string[] = []; + if (cf) cdnWaf.push("Cloudflare"); + if (headers["x-served-by"]?.includes("fastly")) cdnWaf.push("Fastly"); + if (headers["server"]?.includes("Akamai")) cdnWaf.push("Akamai"); + + return tech; +} + +function detectCdnWaf(headers: Record): string[] { + const detected: string[] = []; + if (headers["cf-ray"]) detected.push("Cloudflare"); + if (headers["x-fastly-request-id"]) detected.push("Fastly"); + if (headers["x-akamai-transformed"]) detected.push("Akamai"); + if (headers["x-amz-cf-id"]) detected.push("AWS CloudFront"); + if (headers["x-sucuri-id"]) detected.push("Sucuri WAF"); + return detected; +} + +async function geoLookup(domain: string): Promise { + try { + const ips = await dns.resolve4(domain); + const ip = ips[0]; + if (!ip) return null; + + const token = process.env.IPINFO_TOKEN; + const url = token + ? `https://ipinfo.io/${ip}?token=${token}` + : `http://ip-api.com/json/${ip}?fields=status,country,regionName,city,isp,org,timezone,lat,lon,query`; + + const res = await fetch(url, { signal: AbortSignal.timeout(8000) }); + const data = (await res.json()) as Record; + + if (token) { + const loc = String(data.loc ?? "").split(","); + return { + ip, + country: data.country as string, + region: data.region as string, + city: data.city as string, + org: data.org as string, + timezone: data.timezone as string, + lat: loc[0] ? Number(loc[0]) : undefined, + lon: loc[1] ? Number(loc[1]) : undefined, + }; + } + + if (data.status === "success") { + return { + ip: String(data.query ?? ip), + country: data.country as string, + region: data.regionName as string, + city: data.city as string, + isp: data.isp as string, + org: data.org as string, + timezone: data.timezone as string, + lat: data.lat as number, + lon: data.lon as number, + }; + } + return { ip }; + } catch { + return null; + } +} + +export type ScanProgressCallback = (progress: number, stage: string) => void | Promise; + +export async function runDomainScan( + domain: string, + onProgress?: ScanProgressCallback +): Promise { + const report = async (p: number, stage: string) => { + await onProgress?.(p, stage); + }; + + await report(5, "dns"); + const dnsResult = await lookupDns(domain); + + await report(25, "whois"); + let whoisData: Record = {}; + try { + const raw = await whoisLookup(domain); + whoisData = parseWhois(String(raw)); + } catch (e) { + whoisData = { error: e instanceof Error ? e.message : "WHOIS failed" }; + } + + await report(45, "ssl"); + const ssl = await checkSsl(domain); + + await report(65, "http"); + const httpRaw = await fetchHttp(domain); + const headers = httpRaw.headers ?? {}; + const security = scoreSecurityHeaders(headers); + const tech = detectTech(headers); + const cdnWaf = { detected: detectCdnWaf(headers) }; + + await report(85, "geo"); + const geo = await geoLookup(domain); + + await report(100, "done"); + + return { + domain, + scannedAt: new Date().toISOString(), + dns: dnsResult, + whois: whoisData, + ssl, + http: { + status: httpRaw.status, + finalUrl: httpRaw.finalUrl, + redirectChain: httpRaw.redirectChain ?? [], + headers, + server: httpRaw.server, + poweredBy: httpRaw.poweredBy, + }, + geo, + security, + tech, + uptime: { + reachable: typeof httpRaw.status === "number" && httpRaw.status < 500, + latencyMs: "latencyMs" in httpRaw ? httpRaw.latencyMs : undefined, + }, + cdnWaf, + }; +} diff --git a/src/lib/types.ts b/src/lib/types.ts new file mode 100644 index 0000000..f4c93d8 --- /dev/null +++ b/src/lib/types.ts @@ -0,0 +1,73 @@ +export interface DnsRecord { + type: string; + value: string; + ttl?: number; +} + +export interface SslInfo { + valid: boolean; + issuer?: string; + subject?: string; + validFrom?: string; + validTo?: string; + daysRemaining?: number; + protocol?: string; + error?: string; +} + +export interface SecurityHeaders { + score: number; + grade: string; + present: string[]; + missing: string[]; + headers: Record; +} + +export interface GeoInfo { + ip: string; + country?: string; + region?: string; + city?: string; + isp?: string; + org?: string; + timezone?: string; + lat?: number; + lon?: number; +} + +export interface TechHint { + name: string; + category: string; + confidence: "low" | "medium" | "high"; +} + +export interface ScanResult { + domain: string; + scannedAt: string; + dns: { + records: DnsRecord[]; + nameservers: string[]; + mx: string[]; + txt: string[]; + }; + whois: Record; + ssl: SslInfo; + http: { + status?: number; + finalUrl?: string; + redirectChain: string[]; + headers: Record; + server?: string; + poweredBy?: string; + }; + geo: GeoInfo | null; + security: SecurityHeaders; + tech: TechHint[]; + uptime: { + reachable: boolean; + latencyMs?: number; + }; + cdnWaf: { + detected: string[]; + }; +} diff --git a/src/lib/utils.ts b/src/lib/utils.ts new file mode 100644 index 0000000..cf28176 --- /dev/null +++ b/src/lib/utils.ts @@ -0,0 +1,18 @@ +import { clsx, type ClassValue } from "clsx"; +import { twMerge } from "tailwind-merge"; + +export function cn(...inputs: ClassValue[]) { + return twMerge(clsx(inputs)); +} + +export function normalizeDomain(input: string): string { + let d = input.trim().toLowerCase(); + d = d.replace(/^https?:\/\//, ""); + d = d.replace(/\/.*$/, ""); + d = d.replace(/^www\./, ""); + return d; +} + +export function isValidDomain(domain: string): boolean { + return /^([a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z]{2,}$/i.test(domain); +} diff --git a/src/types/next-auth.d.ts b/src/types/next-auth.d.ts new file mode 100644 index 0000000..1fc18c8 --- /dev/null +++ b/src/types/next-auth.d.ts @@ -0,0 +1,20 @@ +import "next-auth"; +import "next-auth/jwt"; + +declare module "next-auth" { + interface Session { + user: { + id: string; + email: string; + name?: string | null; + role?: string; + }; + } +} + +declare module "next-auth/jwt" { + interface JWT { + id?: string; + role?: string; + } +} diff --git a/premium/tsconfig.json b/tsconfig.json similarity index 58% rename from premium/tsconfig.json rename to tsconfig.json index cf9c65d..fba2bf3 100644 --- a/premium/tsconfig.json +++ b/tsconfig.json @@ -11,24 +11,11 @@ "moduleResolution": "bundler", "resolveJsonModule": true, "isolatedModules": true, - "jsx": "react-jsx", + "jsx": "preserve", "incremental": true, - "plugins": [ - { - "name": "next" - } - ], - "paths": { - "@/*": ["./src/*"] - } + "plugins": [{ "name": "next" }], + "paths": { "@/*": ["./src/*"] } }, - "include": [ - "next-env.d.ts", - "**/*.ts", - "**/*.tsx", - ".next/types/**/*.ts", - ".next/dev/types/**/*.ts", - "**/*.mts" - ], + "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], "exclude": ["node_modules"] } diff --git a/uninstall.sh b/uninstall.sh index 921aaa0..a419529 100755 --- a/uninstall.sh +++ b/uninstall.sh @@ -1,48 +1,54 @@ #!/usr/bin/env bash -# GeoExport site — one-command uninstall (does not wipe the whole server) +# Domain Scanner — one-command uninstall set -euo pipefail -INSTALL_DIR="${GEOEXPORT_INSTALL_DIR:-/opt/domain_web}" -SERVICE_NAME="geoexport-site" -NGINX_SITE="geoexport-site" +INSTALL_DIR="${DOMAIN_SCANNER_INSTALL_DIR:-/opt/domain_web}" +SERVICE_NAME="domain-scanner" +NGINX_SITE="domain-scanner" +LEGACY_SERVICE="geoexport-site" +LEGACY_NGINX="geoexport-site" -log() { echo "[geoexport-uninstall] $*"; } +log() { echo "[domain-scanner-uninstall] $*"; } need_root() { if [[ "${EUID:-$(id -u)}" -ne 0 ]]; then - echo "Запустите от root: sudo bash uninstall.sh" >&2 + echo "Run as root: sudo bash uninstall.sh" >&2 exit 1 fi } -stop_services() { - log "Остановка ${SERVICE_NAME}..." - systemctl stop "${SERVICE_NAME}.service" 2>/dev/null || true - systemctl disable "${SERVICE_NAME}.service" 2>/dev/null || true - rm -f "/etc/systemd/system/${SERVICE_NAME}.service" +stop_docker() { + log "Stopping Docker stack..." + if [[ -f "${INSTALL_DIR}/docker-compose.yml" ]]; then + docker compose -f "${INSTALL_DIR}/docker-compose.yml" down -v 2>/dev/null || true + fi +} + +stop_legacy() { + systemctl stop "${LEGACY_SERVICE}.service" 2>/dev/null || true + systemctl disable "${LEGACY_SERVICE}.service" 2>/dev/null || true + rm -f "/etc/systemd/system/${LEGACY_SERVICE}.service" systemctl daemon-reload } remove_nginx() { - log "Удаление конфигурации nginx..." - rm -f "/etc/nginx/sites-enabled/${NGINX_SITE}" - rm -f "/etc/nginx/sites-available/${NGINX_SITE}" + log "Removing nginx site..." + for site in "${NGINX_SITE}" "${LEGACY_NGINX}"; do + rm -f "/etc/nginx/sites-enabled/${site}" + rm -f "/etc/nginx/sites-available/${site}" + done if command -v nginx >/dev/null 2>&1; then - if [[ -d /etc/nginx/sites-available ]] && [[ ! -e /etc/nginx/sites-enabled/default ]]; then - if [[ -f /etc/nginx/sites-available/default ]]; then - ln -sf /etc/nginx/sites-available/default /etc/nginx/sites-enabled/default - fi - fi nginx -t 2>/dev/null && systemctl reload nginx 2>/dev/null || true fi } remove_files() { - log "Удаление файлов в ${INSTALL_DIR}..." + log "Removing ${INSTALL_DIR}..." rm -rf "${INSTALL_DIR}" } need_root -stop_services +stop_docker +stop_legacy remove_nginx remove_files -log "Сайт GeoExport удалён. nginx/node/git на сервере не удалялись." +log "Domain Scanner removed."